Add stripe connection option (#5372)
- Refactor creation and edition form so it handles stripe integration and not only postgres - Add a hook `useIsSettingsIntegrationEnabled` to avoid checking feature flags everywhere - Add zod schema for stripe <img width="250" alt="Capture d’écran 2024-05-13 à 12 41 52" src="https://github.com/twentyhq/twenty/assets/22936103/a77e7278-5d79-4f95-bddb-ae9ddd1426eb"> <img width="250" alt="Capture d’écran 2024-05-13 à 12 41 59" src="https://github.com/twentyhq/twenty/assets/22936103/d617dc6a-31a4-43c8-8192-dbfb7157de1c"> <img width="250" alt="Capture d’écran 2024-05-13 à 12 42 08" src="https://github.com/twentyhq/twenty/assets/22936103/c4e2d0e4-f826-436d-89be-4d1679a27861"> --------- Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
BIN
packages/twenty-front/public/images/integrations/stripe-logo.png
Normal file
BIN
packages/twenty-front/public/images/integrations/stripe-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
@ -1,6 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { ApolloClient, useApolloClient, useMutation } from '@apollo/client';
|
import { ApolloClient, useApolloClient, useMutation } from '@apollo/client';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
|
||||||
|
|
||||||
import { SYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/syncRemoteTable';
|
import { SYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/syncRemoteTable';
|
||||||
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
||||||
@ -39,7 +38,16 @@ export const useSyncRemoteTable = () => {
|
|||||||
input,
|
input,
|
||||||
},
|
},
|
||||||
awaitRefetchQueries: true,
|
awaitRefetchQueries: true,
|
||||||
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
refetchQueries: [
|
||||||
|
{
|
||||||
|
query: GET_MANY_REMOTE_TABLES,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: input.remoteServerId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: we should return the tables with the columns and store in cache instead of refetching
|
// TODO: we should return the tables with the columns and store in cache instead of refetching
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { ApolloClient, useMutation } from '@apollo/client';
|
import { ApolloClient, useMutation } from '@apollo/client';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
|
||||||
|
|
||||||
import { UNSYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/unsyncRemoteTable';
|
import { UNSYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/unsyncRemoteTable';
|
||||||
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
||||||
@ -31,7 +30,16 @@ export const useUnsyncRemoteTable = () => {
|
|||||||
input,
|
input,
|
||||||
},
|
},
|
||||||
awaitRefetchQueries: true,
|
awaitRefetchQueries: true,
|
||||||
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
refetchQueries: [
|
||||||
|
{
|
||||||
|
query: GET_MANY_REMOTE_TABLES,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: input.remoteServerId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await refetchObjectMetadataItems();
|
await refetchObjectMetadataItems();
|
||||||
|
|||||||
@ -2,6 +2,8 @@ export const getForeignDataWrapperType = (databaseKey: string) => {
|
|||||||
switch (databaseKey) {
|
switch (databaseKey) {
|
||||||
case 'postgresql':
|
case 'postgresql':
|
||||||
return 'postgres_fdw';
|
return 'postgres_fdw';
|
||||||
|
case 'stripe':
|
||||||
|
return 'stripe_fdw';
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,4 +7,8 @@ export const MOCK_REMOTE_DATABASES = [
|
|||||||
name: 'postgresql',
|
name: 'postgresql',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'stripe',
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -17,6 +17,14 @@ type SettingsIntegrationPostgreSQLConnectionFormValues = z.infer<
|
|||||||
typeof settingsIntegrationPostgreSQLConnectionFormSchema
|
typeof settingsIntegrationPostgreSQLConnectionFormSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export const settingsIntegrationStripeConnectionFormSchema = z.object({
|
||||||
|
api_key: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
type SettingsIntegrationStripeConnectionFormValues = z.infer<
|
||||||
|
typeof settingsIntegrationStripeConnectionFormSchema
|
||||||
|
>;
|
||||||
|
|
||||||
const StyledInputsContainer = styled.div`
|
const StyledInputsContainer = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: ${({ theme }) => theme.spacing(2, 4)};
|
gap: ${({ theme }) => theme.spacing(2, 4)};
|
||||||
@ -31,19 +39,35 @@ const StyledInputsContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type SettingsIntegrationPostgreSQLConnectionFormProps = {
|
type SettingsIntegrationDatabaseConnectionFormProps = {
|
||||||
|
databaseKey: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsIntegrationPostgreSQLConnectionForm = ({
|
type SettingsIntegrationConnectionFormValues =
|
||||||
disabled,
|
| SettingsIntegrationPostgreSQLConnectionFormValues
|
||||||
}: SettingsIntegrationPostgreSQLConnectionFormProps) => {
|
| SettingsIntegrationStripeConnectionFormValues;
|
||||||
const { control } =
|
|
||||||
useFormContext<SettingsIntegrationPostgreSQLConnectionFormValues>();
|
|
||||||
|
|
||||||
return (
|
const getFormFields = (
|
||||||
<StyledInputsContainer>
|
databaseKey: string,
|
||||||
{[
|
):
|
||||||
|
| {
|
||||||
|
name:
|
||||||
|
| 'dbname'
|
||||||
|
| 'host'
|
||||||
|
| 'port'
|
||||||
|
| 'user'
|
||||||
|
| 'password'
|
||||||
|
| 'schema'
|
||||||
|
| 'api_key';
|
||||||
|
label: string;
|
||||||
|
type?: string;
|
||||||
|
placeholder: string;
|
||||||
|
}[]
|
||||||
|
| null => {
|
||||||
|
switch (databaseKey) {
|
||||||
|
case 'postgresql':
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
name: 'dbname' as const,
|
name: 'dbname' as const,
|
||||||
label: 'Database Name',
|
label: 'Database Name',
|
||||||
@ -63,7 +87,28 @@ export const SettingsIntegrationPostgreSQLConnectionForm = ({
|
|||||||
placeholder: '••••••',
|
placeholder: '••••••',
|
||||||
},
|
},
|
||||||
{ name: 'schema' as const, label: 'Schema', placeholder: 'public' },
|
{ name: 'schema' as const, label: 'Schema', placeholder: 'public' },
|
||||||
].map(({ name, label, type, placeholder }) => (
|
];
|
||||||
|
case 'stripe':
|
||||||
|
return [
|
||||||
|
{ name: 'api_key' as const, label: 'API Key', placeholder: 'API key' },
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsIntegrationDatabaseConnectionForm = ({
|
||||||
|
databaseKey,
|
||||||
|
disabled,
|
||||||
|
}: SettingsIntegrationDatabaseConnectionFormProps) => {
|
||||||
|
const { control } = useFormContext<SettingsIntegrationConnectionFormValues>();
|
||||||
|
const formFields = getFormFields(databaseKey);
|
||||||
|
|
||||||
|
if (!formFields) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledInputsContainer>
|
||||||
|
{formFields.map(({ name, label, type, placeholder }) => (
|
||||||
<Controller
|
<Controller
|
||||||
key={name}
|
key={name}
|
||||||
name={name}
|
name={name}
|
||||||
@ -71,7 +116,7 @@ export const SettingsIntegrationPostgreSQLConnectionForm = ({
|
|||||||
render={({ field: { onChange, value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
autoComplete="new-password"
|
autoComplete="new-password" // Disable autocomplete
|
||||||
label={label}
|
label={label}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { z } from 'zod';
|
|||||||
import { useUpdateOneDatabaseConnection } from '@/databases/hooks/useUpdateOneDatabaseConnection';
|
import { useUpdateOneDatabaseConnection } from '@/databases/hooks/useUpdateOneDatabaseConnection';
|
||||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||||
import { SettingsIntegrationPostgreSQLConnectionForm } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
|
import { SettingsIntegrationDatabaseConnectionForm } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
|
||||||
import {
|
import {
|
||||||
formatValuesForUpdate,
|
formatValuesForUpdate,
|
||||||
getEditionSchemaForForm,
|
getEditionSchemaForForm,
|
||||||
@ -132,18 +132,17 @@ export const SettingsIntegrationEditDatabaseConnectionContent = ({
|
|||||||
accent={'blue'}
|
accent={'blue'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{databaseKey === 'postgresql' ? (
|
<Section>
|
||||||
<Section>
|
<H2Title
|
||||||
<H2Title
|
title="Edit Connection"
|
||||||
title="Edit PostgreSQL Connection"
|
description="Edit the information to connect your database"
|
||||||
description="Edit the information to connect your PostgreSQL database"
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<SettingsIntegrationPostgreSQLConnectionForm
|
<SettingsIntegrationDatabaseConnectionForm
|
||||||
disabled={hasSyncedTables}
|
databaseKey={databaseKey}
|
||||||
/>
|
disabled={hasSyncedTables}
|
||||||
</Section>
|
/>
|
||||||
) : null}
|
</Section>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { useGetDatabaseConnection } from '@/databases/hooks/useGetDatabaseConnection';
|
import { useGetDatabaseConnection } from '@/databases/hooks/useGetDatabaseConnection';
|
||||||
import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables';
|
import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables';
|
||||||
|
import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled';
|
||||||
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
|
||||||
|
|
||||||
export const useDatabaseConnection = () => {
|
export const useDatabaseConnection = () => {
|
||||||
const { databaseKey = '', connectionId = '' } = useParams();
|
const { databaseKey = '', connectionId = '' } = useParams();
|
||||||
@ -16,16 +16,9 @@ export const useDatabaseConnection = () => {
|
|||||||
({ from: { key } }) => key === databaseKey,
|
({ from: { key } }) => key === databaseKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isAirtableIntegrationEnabled = useIsFeatureEnabled(
|
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);
|
||||||
'IS_AIRTABLE_INTEGRATION_ENABLED',
|
|
||||||
);
|
const isIntegrationAvailable = !!integration && isIntegrationEnabled;
|
||||||
const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
|
|
||||||
'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
|
||||||
);
|
|
||||||
const isIntegrationAvailable =
|
|
||||||
!!integration &&
|
|
||||||
((databaseKey === 'airtable' && isAirtableIntegrationEnabled) ||
|
|
||||||
(databaseKey === 'postgresql' && isPostgresqlIntegrationEnabled));
|
|
||||||
|
|
||||||
const { connection, loading } = useGetDatabaseConnection({
|
const { connection, loading } = useGetDatabaseConnection({
|
||||||
databaseKey,
|
databaseKey,
|
||||||
|
|||||||
@ -3,7 +3,10 @@ import isEmpty from 'lodash.isempty';
|
|||||||
import pickBy from 'lodash.pickby';
|
import pickBy from 'lodash.pickby';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { settingsIntegrationPostgreSQLConnectionFormSchema } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
|
import {
|
||||||
|
settingsIntegrationPostgreSQLConnectionFormSchema,
|
||||||
|
settingsIntegrationStripeConnectionFormSchema,
|
||||||
|
} from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
|
||||||
import { RemoteServer } from '~/generated-metadata/graphql';
|
import { RemoteServer } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const getEditionSchemaForForm = (databaseKey: string) => {
|
export const getEditionSchemaForForm = (databaseKey: string) => {
|
||||||
@ -12,6 +15,8 @@ export const getEditionSchemaForForm = (databaseKey: string) => {
|
|||||||
return settingsIntegrationPostgreSQLConnectionFormSchema.extend({
|
return settingsIntegrationPostgreSQLConnectionFormSchema.extend({
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
case 'stripe':
|
||||||
|
return settingsIntegrationStripeConnectionFormSchema;
|
||||||
default:
|
default:
|
||||||
throw new Error(`No schema found for database key: ${databaseKey}`);
|
throw new Error(`No schema found for database key: ${databaseKey}`);
|
||||||
}
|
}
|
||||||
@ -34,6 +39,10 @@ export const getFormDefaultValuesFromConnection = ({
|
|||||||
schema: connection.schema || undefined,
|
schema: connection.schema || undefined,
|
||||||
password: '',
|
password: '',
|
||||||
};
|
};
|
||||||
|
case 'stripe':
|
||||||
|
return {
|
||||||
|
api_key: connection.foreignDataWrapperOptions.api_key,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`No default form values for database key: ${databaseKey}`,
|
`No default form values for database key: ${databaseKey}`,
|
||||||
@ -71,6 +80,12 @@ export const formatValuesForUpdate = ({
|
|||||||
|
|
||||||
return pickBy(formattedValues, (obj) => !isEmpty(obj));
|
return pickBy(formattedValues, (obj) => !isEmpty(obj));
|
||||||
}
|
}
|
||||||
|
case 'stripe':
|
||||||
|
return {
|
||||||
|
foreignDataWrapperOptions: {
|
||||||
|
api_key: formValues.api_key,
|
||||||
|
},
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
throw new Error(`Cannot format values for database key: ${databaseKey}`);
|
throw new Error(`Cannot format values for database key: ${databaseKey}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
|
||||||
|
const getFeatureKey = (databaseKey: string) => {
|
||||||
|
switch (databaseKey) {
|
||||||
|
case 'airtable':
|
||||||
|
return 'IS_AIRTABLE_INTEGRATION_ENABLED';
|
||||||
|
case 'postgresql':
|
||||||
|
return 'IS_POSTGRESQL_INTEGRATION_ENABLED';
|
||||||
|
case 'stripe':
|
||||||
|
return 'IS_STRIPE_INTEGRATION_ENABLED';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useIsSettingsIntegrationEnabled = (
|
||||||
|
databaseKey: string,
|
||||||
|
): boolean => {
|
||||||
|
const featureKey = getFeatureKey(databaseKey);
|
||||||
|
return useIsFeatureEnabled(featureKey);
|
||||||
|
};
|
||||||
@ -14,6 +14,7 @@ export const useSettingsIntegrationCategories =
|
|||||||
const isAirtableIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
|
const isAirtableIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
|
||||||
({ name }) => name === 'airtable',
|
({ name }) => name === 'airtable',
|
||||||
)?.isActive;
|
)?.isActive;
|
||||||
|
|
||||||
const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
|
const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
|
||||||
'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
||||||
);
|
);
|
||||||
@ -21,12 +22,21 @@ export const useSettingsIntegrationCategories =
|
|||||||
({ name }) => name === 'postgresql',
|
({ name }) => name === 'postgresql',
|
||||||
)?.isActive;
|
)?.isActive;
|
||||||
|
|
||||||
|
const isStripeIntegrationEnabled = useIsFeatureEnabled(
|
||||||
|
'IS_STRIPE_INTEGRATION_ENABLED',
|
||||||
|
);
|
||||||
|
const isStripeIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
|
||||||
|
({ name }) => name === 'stripe',
|
||||||
|
)?.isActive;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
getSettingsIntegrationAll({
|
getSettingsIntegrationAll({
|
||||||
isAirtableIntegrationEnabled,
|
isAirtableIntegrationEnabled,
|
||||||
isAirtableIntegrationActive,
|
isAirtableIntegrationActive,
|
||||||
isPostgresqlIntegrationEnabled,
|
isPostgresqlIntegrationEnabled,
|
||||||
isPostgresqlIntegrationActive,
|
isPostgresqlIntegrationActive,
|
||||||
|
isStripeIntegrationEnabled,
|
||||||
|
isStripeIntegrationActive,
|
||||||
}),
|
}),
|
||||||
SETTINGS_INTEGRATION_ZAPIER_CATEGORY,
|
SETTINGS_INTEGRATION_ZAPIER_CATEGORY,
|
||||||
SETTINGS_INTEGRATION_WINDMILL_CATEGORY,
|
SETTINGS_INTEGRATION_WINDMILL_CATEGORY,
|
||||||
|
|||||||
@ -9,7 +9,13 @@ type GetConnectionDbNameParams = {
|
|||||||
export const getConnectionDbName = ({
|
export const getConnectionDbName = ({
|
||||||
integration,
|
integration,
|
||||||
connection,
|
connection,
|
||||||
}: GetConnectionDbNameParams) =>
|
}: GetConnectionDbNameParams) => {
|
||||||
integration.from.key === 'postgresql'
|
switch (integration.from.key) {
|
||||||
? connection.foreignDataWrapperOptions?.dbname
|
case 'postgresql':
|
||||||
: '';
|
return connection.foreignDataWrapperOptions?.dbname;
|
||||||
|
case 'stripe':
|
||||||
|
return connection.id;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -5,11 +5,15 @@ export const getSettingsIntegrationAll = ({
|
|||||||
isAirtableIntegrationActive,
|
isAirtableIntegrationActive,
|
||||||
isPostgresqlIntegrationEnabled,
|
isPostgresqlIntegrationEnabled,
|
||||||
isPostgresqlIntegrationActive,
|
isPostgresqlIntegrationActive,
|
||||||
|
isStripeIntegrationEnabled,
|
||||||
|
isStripeIntegrationActive,
|
||||||
}: {
|
}: {
|
||||||
isAirtableIntegrationEnabled: boolean;
|
isAirtableIntegrationEnabled: boolean;
|
||||||
isAirtableIntegrationActive: boolean;
|
isAirtableIntegrationActive: boolean;
|
||||||
isPostgresqlIntegrationEnabled: boolean;
|
isPostgresqlIntegrationEnabled: boolean;
|
||||||
isPostgresqlIntegrationActive: boolean;
|
isPostgresqlIntegrationActive: boolean;
|
||||||
|
isStripeIntegrationEnabled: boolean;
|
||||||
|
isStripeIntegrationActive: boolean;
|
||||||
}): SettingsIntegrationCategory => ({
|
}): SettingsIntegrationCategory => ({
|
||||||
key: 'all',
|
key: 'all',
|
||||||
title: 'All',
|
title: 'All',
|
||||||
@ -40,5 +44,18 @@ export const getSettingsIntegrationAll = ({
|
|||||||
text: 'PostgreSQL',
|
text: 'PostgreSQL',
|
||||||
link: '/settings/integrations/postgresql',
|
link: '/settings/integrations/postgresql',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
key: 'stripe',
|
||||||
|
image: '/images/integrations/stripe-logo.png',
|
||||||
|
},
|
||||||
|
type: !isStripeIntegrationEnabled
|
||||||
|
? 'Soon'
|
||||||
|
: isStripeIntegrationActive
|
||||||
|
? 'Active'
|
||||||
|
: 'Add',
|
||||||
|
text: 'Stripe',
|
||||||
|
link: '/settings/integrations/stripe',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,9 +3,13 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { FeatureFlagKey } from '@/workspace/types/FeatureFlagKey';
|
import { FeatureFlagKey } from '@/workspace/types/FeatureFlagKey';
|
||||||
|
|
||||||
export const useIsFeatureEnabled = (featureKey: FeatureFlagKey) => {
|
export const useIsFeatureEnabled = (featureKey: FeatureFlagKey | null) => {
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
|
if (!featureKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const featureFlag = currentWorkspace?.featureFlags?.find(
|
const featureFlag = currentWorkspace?.featureFlags?.find(
|
||||||
(flag) => flag.key === featureKey,
|
(flag) => flag.key === featureKey,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,4 +3,5 @@ export type FeatureFlagKey =
|
|||||||
| 'IS_QUICK_ACTIONS_ENABLED'
|
| 'IS_QUICK_ACTIONS_ENABLED'
|
||||||
| 'IS_EVENT_OBJECT_ENABLED'
|
| 'IS_EVENT_OBJECT_ENABLED'
|
||||||
| 'IS_AIRTABLE_INTEGRATION_ENABLED'
|
| 'IS_AIRTABLE_INTEGRATION_ENABLED'
|
||||||
| 'IS_POSTGRESQL_INTEGRATION_ENABLED';
|
| 'IS_POSTGRESQL_INTEGRATION_ENABLED'
|
||||||
|
| 'IS_STRIPE_INTEGRATION_ENABLED';
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useGetDatabaseConnections } from '@/databases/hooks/useGetDatabaseConne
|
|||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { SettingsIntegrationPreview } from '@/settings/integrations/components/SettingsIntegrationPreview';
|
import { SettingsIntegrationPreview } from '@/settings/integrations/components/SettingsIntegrationPreview';
|
||||||
import { SettingsIntegrationDatabaseConnectionsListCard } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard';
|
import { SettingsIntegrationDatabaseConnectionsListCard } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard';
|
||||||
|
import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled';
|
||||||
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
@ -14,7 +15,6 @@ import { H2Title } from '@/ui/display/typography/components/H2Title';
|
|||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
|
||||||
|
|
||||||
export const SettingsIntegrationDatabase = () => {
|
export const SettingsIntegrationDatabase = () => {
|
||||||
const { databaseKey = '' } = useParams();
|
const { databaseKey = '' } = useParams();
|
||||||
@ -25,16 +25,9 @@ export const SettingsIntegrationDatabase = () => {
|
|||||||
({ from: { key } }) => key === databaseKey,
|
({ from: { key } }) => key === databaseKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isAirtableIntegrationEnabled = useIsFeatureEnabled(
|
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);
|
||||||
'IS_AIRTABLE_INTEGRATION_ENABLED',
|
|
||||||
);
|
const isIntegrationAvailable = !!integration && isIntegrationEnabled;
|
||||||
const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
|
|
||||||
'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
|
||||||
);
|
|
||||||
const isIntegrationAvailable =
|
|
||||||
!!integration &&
|
|
||||||
((databaseKey === 'airtable' && isAirtableIntegrationEnabled) ||
|
|
||||||
(databaseKey === 'postgresql' && isPostgresqlIntegrationEnabled));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isIntegrationAvailable) {
|
if (!isIntegrationAvailable) {
|
||||||
|
|||||||
@ -11,9 +11,11 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons
|
|||||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import {
|
import {
|
||||||
SettingsIntegrationPostgreSQLConnectionForm,
|
SettingsIntegrationDatabaseConnectionForm,
|
||||||
settingsIntegrationPostgreSQLConnectionFormSchema,
|
settingsIntegrationPostgreSQLConnectionFormSchema,
|
||||||
|
settingsIntegrationStripeConnectionFormSchema,
|
||||||
} from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
|
} from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
|
||||||
|
import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled';
|
||||||
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
||||||
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
@ -23,33 +25,47 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
|
||||||
import { CreateRemoteServerInput } from '~/generated-metadata/graphql';
|
import { CreateRemoteServerInput } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const newConnectionSchema = settingsIntegrationPostgreSQLConnectionFormSchema;
|
const createRemoteServerInputPostgresSchema =
|
||||||
|
settingsIntegrationPostgreSQLConnectionFormSchema.transform<CreateRemoteServerInput>(
|
||||||
|
(values) => ({
|
||||||
|
foreignDataWrapperType: 'postgres_fdw',
|
||||||
|
foreignDataWrapperOptions: {
|
||||||
|
dbname: values.dbname,
|
||||||
|
host: values.host,
|
||||||
|
port: values.port,
|
||||||
|
},
|
||||||
|
userMappingOptions: {
|
||||||
|
password: values.password,
|
||||||
|
user: values.user,
|
||||||
|
},
|
||||||
|
schema: values.schema,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const createRemoteServerInputSchema = newConnectionSchema
|
type SettingsIntegrationNewConnectionPostgresFormValues = z.infer<
|
||||||
.extend({
|
typeof createRemoteServerInputPostgresSchema
|
||||||
foreignDataWrapperType: z.string().min(1),
|
|
||||||
})
|
|
||||||
.transform<CreateRemoteServerInput>((values) => ({
|
|
||||||
foreignDataWrapperType: values.foreignDataWrapperType,
|
|
||||||
foreignDataWrapperOptions: {
|
|
||||||
dbname: values.dbname,
|
|
||||||
host: values.host,
|
|
||||||
port: values.port,
|
|
||||||
},
|
|
||||||
userMappingOptions: {
|
|
||||||
password: values.password,
|
|
||||||
user: values.user,
|
|
||||||
},
|
|
||||||
schema: values.schema,
|
|
||||||
}));
|
|
||||||
|
|
||||||
type SettingsIntegrationNewConnectionFormValues = z.infer<
|
|
||||||
typeof newConnectionSchema
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const createRemoteServerInputStripeSchema =
|
||||||
|
settingsIntegrationStripeConnectionFormSchema.transform<CreateRemoteServerInput>(
|
||||||
|
(values) => ({
|
||||||
|
foreignDataWrapperType: 'stripe_fdw',
|
||||||
|
foreignDataWrapperOptions: {
|
||||||
|
api_key: values.api_key,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
type SettingsIntegrationNewConnectionStripeFormValues = z.infer<
|
||||||
|
typeof createRemoteServerInputStripeSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
type SettingsIntegrationNewConnectionFormValues =
|
||||||
|
| SettingsIntegrationNewConnectionPostgresFormValues
|
||||||
|
| SettingsIntegrationNewConnectionStripeFormValues;
|
||||||
|
|
||||||
export const SettingsIntegrationNewDatabaseConnection = () => {
|
export const SettingsIntegrationNewDatabaseConnection = () => {
|
||||||
const { databaseKey = '' } = useParams();
|
const { databaseKey = '' } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -62,16 +78,9 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
|
|||||||
const { createOneDatabaseConnection } = useCreateOneDatabaseConnection();
|
const { createOneDatabaseConnection } = useCreateOneDatabaseConnection();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const isAirtableIntegrationEnabled = useIsFeatureEnabled(
|
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);
|
||||||
'IS_AIRTABLE_INTEGRATION_ENABLED',
|
|
||||||
);
|
const isIntegrationAvailable = !!integration && isIntegrationEnabled;
|
||||||
const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
|
|
||||||
'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
|
||||||
);
|
|
||||||
const isIntegrationAvailable =
|
|
||||||
!!integration &&
|
|
||||||
((databaseKey === 'airtable' && isAirtableIntegrationEnabled) ||
|
|
||||||
(databaseKey === 'postgresql' && isPostgresqlIntegrationEnabled));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isIntegrationAvailable) {
|
if (!isIntegrationAvailable) {
|
||||||
@ -79,6 +88,11 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
|
|||||||
}
|
}
|
||||||
}, [integration, databaseKey, navigate, isIntegrationAvailable]);
|
}, [integration, databaseKey, navigate, isIntegrationAvailable]);
|
||||||
|
|
||||||
|
const newConnectionSchema =
|
||||||
|
databaseKey === 'postgresql'
|
||||||
|
? createRemoteServerInputPostgresSchema
|
||||||
|
: createRemoteServerInputStripeSchema;
|
||||||
|
|
||||||
const formConfig = useForm<SettingsIntegrationNewConnectionFormValues>({
|
const formConfig = useForm<SettingsIntegrationNewConnectionFormValues>({
|
||||||
mode: 'onTouched',
|
mode: 'onTouched',
|
||||||
resolver: zodResolver(newConnectionSchema),
|
resolver: zodResolver(newConnectionSchema),
|
||||||
@ -97,7 +111,7 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const createdConnection = await createOneDatabaseConnection(
|
const createdConnection = await createOneDatabaseConnection(
|
||||||
createRemoteServerInputSchema.parse({
|
newConnectionSchema.parse({
|
||||||
...formValues,
|
...formValues,
|
||||||
foreignDataWrapperType: getForeignDataWrapperType(databaseKey),
|
foreignDataWrapperType: getForeignDataWrapperType(databaseKey),
|
||||||
}),
|
}),
|
||||||
@ -144,15 +158,15 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
|
|||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
</SettingsHeaderContainer>
|
</SettingsHeaderContainer>
|
||||||
{databaseKey === 'postgresql' ? (
|
<Section>
|
||||||
<Section>
|
<H2Title
|
||||||
<H2Title
|
title="Connect a new database"
|
||||||
title="Connect a new database"
|
description="Provide the information to connect your database"
|
||||||
description="Provide the information to connect your PostgreSQL database"
|
/>
|
||||||
/>
|
<SettingsIntegrationDatabaseConnectionForm
|
||||||
<SettingsIntegrationPostgreSQLConnectionForm />
|
databaseKey={databaseKey}
|
||||||
</Section>
|
/>
|
||||||
) : null}
|
</Section>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
|
|||||||
@ -35,6 +35,6 @@ export const Default: Story = {
|
|||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
sleep(100);
|
sleep(100);
|
||||||
|
|
||||||
await canvas.findByText('Edit PostgreSQL Connection');
|
await canvas.findByText('Edit Connection');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,4 +17,15 @@ export const mockedRemoteServers = [
|
|||||||
updatedAt: '2024-04-30T13:41:25.858Z',
|
updatedAt: '2024-04-30T13:41:25.858Z',
|
||||||
schema: 'public',
|
schema: 'public',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
__typename: 'RemoteServer',
|
||||||
|
id: 'ddc3b641-2142-4b4e-8fba-976afbc3b2bc',
|
||||||
|
createdAt: '2024-04-30T13:41:25.584Z',
|
||||||
|
foreignDataWrapperId: 'dqfdsqf-2142-4b4e-8fba-976afbc3b2bc',
|
||||||
|
foreignDataWrapperOptions: {
|
||||||
|
api_key: 'sk_test_51',
|
||||||
|
},
|
||||||
|
foreignDataWrapperType: 'stripe_fdw',
|
||||||
|
updatedAt: '2024-04-30T13:41:25.858Z',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user