Skip to content

Commit

Permalink
Add stripe connection option (twentyhq#5372)
Browse files Browse the repository at this point in the history
- 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>
  • Loading branch information
thomtrp and Thomas Trompette committed May 13, 2024
1 parent b9154f3 commit de438b0
Show file tree
Hide file tree
Showing 19 changed files with 252 additions and 101 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useCallback } from 'react';
import { ApolloClient, useApolloClient, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';

import { SYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/syncRemoteTable';
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
Expand Down Expand Up @@ -39,7 +38,16 @@ export const useSyncRemoteTable = () => {
input,
},
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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useCallback } from 'react';
import { ApolloClient, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';

import { UNSYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/unsyncRemoteTable';
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
Expand Down Expand Up @@ -31,7 +30,16 @@ export const useUnsyncRemoteTable = () => {
input,
},
awaitRefetchQueries: true,
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
refetchQueries: [
{
query: GET_MANY_REMOTE_TABLES,
variables: {
input: {
id: input.remoteServerId,
},
},
},
],
});

await refetchObjectMetadataItems();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const getForeignDataWrapperType = (databaseKey: string) => {
switch (databaseKey) {
case 'postgresql':
return 'postgres_fdw';
case 'stripe':
return 'stripe_fdw';
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export const MOCK_REMOTE_DATABASES = [
name: 'postgresql',
isActive: true,
},
{
name: 'stripe',
isActive: true,
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ type SettingsIntegrationPostgreSQLConnectionFormValues = z.infer<
typeof settingsIntegrationPostgreSQLConnectionFormSchema
>;

export const settingsIntegrationStripeConnectionFormSchema = z.object({
api_key: z.string().min(1),
});

type SettingsIntegrationStripeConnectionFormValues = z.infer<
typeof settingsIntegrationStripeConnectionFormSchema
>;

const StyledInputsContainer = styled.div`
display: grid;
gap: ${({ theme }) => theme.spacing(2, 4)};
Expand All @@ -31,19 +39,35 @@ const StyledInputsContainer = styled.div`
}
`;

type SettingsIntegrationPostgreSQLConnectionFormProps = {
type SettingsIntegrationDatabaseConnectionFormProps = {
databaseKey: string;
disabled?: boolean;
};

export const SettingsIntegrationPostgreSQLConnectionForm = ({
disabled,
}: SettingsIntegrationPostgreSQLConnectionFormProps) => {
const { control } =
useFormContext<SettingsIntegrationPostgreSQLConnectionFormValues>();
type SettingsIntegrationConnectionFormValues =
| SettingsIntegrationPostgreSQLConnectionFormValues
| SettingsIntegrationStripeConnectionFormValues;

return (
<StyledInputsContainer>
{[
const getFormFields = (
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,
label: 'Database Name',
Expand All @@ -63,15 +87,36 @@ export const SettingsIntegrationPostgreSQLConnectionForm = ({
placeholder: '••••••',
},
{ 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
key={name}
name={name}
control={control}
render={({ field: { onChange, value } }) => {
return (
<TextInput
autoComplete="new-password"
autoComplete="new-password" // Disable autocomplete
label={label}
value={value}
onChange={onChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { z } from 'zod';
import { useUpdateOneDatabaseConnection } from '@/databases/hooks/useUpdateOneDatabaseConnection';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
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 {
formatValuesForUpdate,
getEditionSchemaForForm,
Expand Down Expand Up @@ -132,18 +132,17 @@ export const SettingsIntegrationEditDatabaseConnectionContent = ({
accent={'blue'}
/>
)}
{databaseKey === 'postgresql' ? (
<Section>
<H2Title
title="Edit PostgreSQL Connection"
description="Edit the information to connect your PostgreSQL database"
/>
<Section>
<H2Title
title="Edit Connection"
description="Edit the information to connect your database"
/>

<SettingsIntegrationPostgreSQLConnectionForm
disabled={hasSyncedTables}
/>
</Section>
) : null}
<SettingsIntegrationDatabaseConnectionForm
databaseKey={databaseKey}
disabled={hasSyncedTables}
/>
</Section>
</FormProvider>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useNavigate, useParams } from 'react-router-dom';

import { useGetDatabaseConnection } from '@/databases/hooks/useGetDatabaseConnection';
import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables';
import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled';
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
import { AppPath } from '@/types/AppPath';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';

export const useDatabaseConnection = () => {
const { databaseKey = '', connectionId = '' } = useParams();
Expand All @@ -16,16 +16,9 @@ export const useDatabaseConnection = () => {
({ from: { key } }) => key === databaseKey,
);

const isAirtableIntegrationEnabled = useIsFeatureEnabled(
'IS_AIRTABLE_INTEGRATION_ENABLED',
);
const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
'IS_POSTGRESQL_INTEGRATION_ENABLED',
);
const isIntegrationAvailable =
!!integration &&
((databaseKey === 'airtable' && isAirtableIntegrationEnabled) ||
(databaseKey === 'postgresql' && isPostgresqlIntegrationEnabled));
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);

const isIntegrationAvailable = !!integration && isIntegrationEnabled;

const { connection, loading } = useGetDatabaseConnection({
databaseKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import isEmpty from 'lodash.isempty';
import pickBy from 'lodash.pickby';
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';

export const getEditionSchemaForForm = (databaseKey: string) => {
Expand All @@ -12,6 +15,8 @@ export const getEditionSchemaForForm = (databaseKey: string) => {
return settingsIntegrationPostgreSQLConnectionFormSchema.extend({
password: z.string().optional(),
});
case 'stripe':
return settingsIntegrationStripeConnectionFormSchema;
default:
throw new Error(`No schema found for database key: ${databaseKey}`);
}
Expand All @@ -34,6 +39,10 @@ export const getFormDefaultValuesFromConnection = ({
schema: connection.schema || undefined,
password: '',
};
case 'stripe':
return {
api_key: connection.foreignDataWrapperOptions.api_key,
};
default:
throw new Error(
`No default form values for database key: ${databaseKey}`,
Expand Down Expand Up @@ -71,6 +80,12 @@ export const formatValuesForUpdate = ({

return pickBy(formattedValues, (obj) => !isEmpty(obj));
}
case 'stripe':
return {
foreignDataWrapperOptions: {
api_key: formValues.api_key,
},
};
default:
throw new Error(`Cannot format values for database key: ${databaseKey}`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,29 @@ export const useSettingsIntegrationCategories =
const isAirtableIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
({ name }) => name === 'airtable',
)?.isActive;

const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
'IS_POSTGRESQL_INTEGRATION_ENABLED',
);
const isPostgresqlIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
({ name }) => name === 'postgresql',
)?.isActive;

const isStripeIntegrationEnabled = useIsFeatureEnabled(
'IS_STRIPE_INTEGRATION_ENABLED',
);
const isStripeIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
({ name }) => name === 'stripe',
)?.isActive;

return [
getSettingsIntegrationAll({
isAirtableIntegrationEnabled,
isAirtableIntegrationActive,
isPostgresqlIntegrationEnabled,
isPostgresqlIntegrationActive,
isStripeIntegrationEnabled,
isStripeIntegrationActive,
}),
SETTINGS_INTEGRATION_ZAPIER_CATEGORY,
SETTINGS_INTEGRATION_WINDMILL_CATEGORY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ type GetConnectionDbNameParams = {
export const getConnectionDbName = ({
integration,
connection,
}: GetConnectionDbNameParams) =>
integration.from.key === 'postgresql'
? connection.foreignDataWrapperOptions?.dbname
: '';
}: GetConnectionDbNameParams) => {
switch (integration.from.key) {
case 'postgresql':
return connection.foreignDataWrapperOptions?.dbname;
case 'stripe':
return connection.id;
default:
return '';
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ export const getSettingsIntegrationAll = ({
isAirtableIntegrationActive,
isPostgresqlIntegrationEnabled,
isPostgresqlIntegrationActive,
isStripeIntegrationEnabled,
isStripeIntegrationActive,
}: {
isAirtableIntegrationEnabled: boolean;
isAirtableIntegrationActive: boolean;
isPostgresqlIntegrationEnabled: boolean;
isPostgresqlIntegrationActive: boolean;
isStripeIntegrationEnabled: boolean;
isStripeIntegrationActive: boolean;
}): SettingsIntegrationCategory => ({
key: 'all',
title: 'All',
Expand Down Expand Up @@ -40,5 +44,18 @@ export const getSettingsIntegrationAll = ({
text: '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',
},
],
});
Loading

0 comments on commit de438b0

Please sign in to comment.