From 1b2ed80c1c1df73972a8086d030fd8d6def2af16 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Tue, 30 Apr 2024 17:46:30 +0200 Subject: [PATCH] [feat][Remote objects] Edit a connection (for pg) (#5210) ## Context #4774 ## How was it tested Locally ## In further PRs - Update connection status upon page change - Adapt Info banner to dark mode - placeholders for form --- packages/twenty-front/src/App.tsx | 9 +- .../src/generated-metadata/gql.ts | 9 +- .../src/generated-metadata/graphql.ts | 52 ++++-- .../twenty-front/src/generated/graphql.tsx | 6 + .../fragments/databaseConnectionFragment.ts | 4 + .../mutations/updateOneDatabaseConnection.ts | 12 ++ .../hooks/useUpdateOneDatabaseConnection.ts | 34 ++++ ...tingsIntegrationDatabaseConnectionForm.tsx | 37 +++-- ...grationDatabaseConnectionShowContainer.tsx | 80 ++++++++++ ...ntegrationDatabaseConnectionSyncStatus.tsx | 0 ...IntegrationDatabaseConnectionsListCard.tsx | 2 +- ...tingsIntegrationDatabaseTablesListCard.tsx | 0 ...grationEditDatabaseConnectionContainer.tsx | 18 +++ ...tegrationEditDatabaseConnectionContent.tsx | 151 ++++++++++++++++++ .../hooks/useDatabaseConnection.ts | 55 +++++++ .../utils/editDatabaseConnection.ts | 75 +++++++++ .../src/modules/types/SettingsPath.ts | 1 + .../ui/display/info/components/Info.tsx | 18 ++- .../ui/input/components/TextInputV2.tsx | 3 +- .../bread-crumb/components/Breadcrumb.tsx | 2 +- .../SettingsIntegrationDatabase.tsx | 2 +- .../SettingsIntegrationDatabaseConnection.tsx | 124 -------------- ...tegrationDatabaseConnectionSummaryCard.tsx | 11 +- ...tingsIntegrationEditDatabaseConnection.tsx | 17 ++ ...ttingsIntegrationNewDatabaseConnection.tsx | 18 ++- ...tingsIntegrationShowDatabaseConnection.tsx | 15 ++ ...egrationEditDatabaseConnection.stories.tsx | 40 +++++ ...grationShowDatabaseConnection.stories.tsx} | 9 +- .../twenty-front/src/testing/graphqlMocks.ts | 18 ++- .../src/testing/mock-data/remote-servers.ts | 20 +++ .../src/testing/mock-data/remote-tables.ts | 16 ++ .../foreign-data-wrapper-query.factory.ts | 8 +- .../dtos/create-remote-server.input.ts | 5 +- .../remote-server/dtos/remote-server.dto.ts | 9 ++ .../dtos/update-remote-server.input.ts | 9 +- .../remote-server/remote-server.entity.ts | 7 +- .../remote-server/remote-server.service.ts | 7 +- ...ld-update-remote-server-raw-query.utils.ts | 10 +- ...utils.ts => user-mapping-options.utils.ts} | 9 +- 39 files changed, 727 insertions(+), 195 deletions(-) create mode 100644 packages/twenty-front/src/modules/databases/graphql/mutations/updateOneDatabaseConnection.ts create mode 100644 packages/twenty-front/src/modules/databases/hooks/useUpdateOneDatabaseConnection.ts rename packages/twenty-front/src/modules/settings/integrations/{ => database-connection}/components/SettingsIntegrationDatabaseConnectionForm.tsx (65%) create mode 100644 packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx rename packages/twenty-front/src/modules/settings/integrations/{ => database-connection}/components/SettingsIntegrationDatabaseConnectionSyncStatus.tsx (100%) rename packages/twenty-front/src/modules/settings/integrations/{ => database-connection}/components/SettingsIntegrationDatabaseConnectionsListCard.tsx (95%) rename packages/twenty-front/src/modules/settings/integrations/{ => database-connection}/components/SettingsIntegrationDatabaseTablesListCard.tsx (100%) create mode 100644 packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer.tsx create mode 100644 packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx create mode 100644 packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts create mode 100644 packages/twenty-front/src/modules/settings/integrations/database-connection/utils/editDatabaseConnection.ts delete mode 100644 packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnection.tsx create mode 100644 packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx create mode 100644 packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx create mode 100644 packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationEditDatabaseConnection.stories.tsx rename packages/twenty-front/src/pages/settings/integrations/__stories__/{SettingsIntegrationDatabaseConnection.stories.tsx => SettingsIntegrationShowDatabaseConnection.stories.tsx} (73%) create mode 100644 packages/twenty-front/src/testing/mock-data/remote-servers.ts create mode 100644 packages/twenty-front/src/testing/mock-data/remote-tables.ts rename packages/twenty-server/src/engine/metadata-modules/remote-server/utils/{user-mapping-options-input.utils.ts => user-mapping-options.utils.ts} (56%) diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx index 8ec2994b3..a984504d1 100644 --- a/packages/twenty-front/src/App.tsx +++ b/packages/twenty-front/src/App.tsx @@ -41,9 +41,10 @@ import { SettingsDevelopers } from '~/pages/settings/developers/SettingsDevelope import { SettingsDevelopersWebhooksDetail } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail'; import { SettingsDevelopersWebhooksNew } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew'; import { SettingsIntegrationDatabase } from '~/pages/settings/integrations/SettingsIntegrationDatabase'; -import { SettingsIntegrationDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationDatabaseConnection'; +import { SettingsIntegrationEditDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection'; import { SettingsIntegrationNewDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection'; import { SettingsIntegrations } from '~/pages/settings/integrations/SettingsIntegrations'; +import { SettingsIntegrationShowDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection'; import { SettingsAppearance } from '~/pages/settings/SettingsAppearance'; import { SettingsBilling } from '~/pages/settings/SettingsBilling.tsx'; import { SettingsProfile } from '~/pages/settings/SettingsProfile'; @@ -186,9 +187,13 @@ export const App = () => { path={SettingsPath.IntegrationNewDatabaseConnection} element={} /> + } + /> } + element={} /> ; + schema?: InputMaybe; + userMappingOptions?: InputMaybe; }; export type CursorPaging = { @@ -331,6 +332,11 @@ export type FullName = { lastName: Scalars['String']['output']; }; +export type GetUserMappingOptions = { + __typename?: 'GetUserMappingOptions'; + username?: Maybe; +}; + export type InvalidatePassword = { __typename?: 'InvalidatePassword'; /** Boolean that confirms query was dispatched */ @@ -379,6 +385,7 @@ export type Mutation = { updateBillingSubscription: UpdateBillingEntity; updateOneField: Field; updateOneObject: Object; + updateOneRemoteServer: RemoteServer; updatePasswordViaResetToken: InvalidatePassword; updateWorkspace: Workspace; uploadFile: Scalars['String']['output']; @@ -526,6 +533,11 @@ export type MutationUpdateOneObjectArgs = { }; +export type MutationUpdateOneRemoteServerArgs = { + input: UpdateRemoteServerInput; +}; + + export type MutationUpdatePasswordViaResetTokenArgs = { newPassword: Scalars['String']['input']; passwordResetToken: Scalars['String']['input']; @@ -788,7 +800,9 @@ export type RemoteServer = { foreignDataWrapperOptions?: Maybe; foreignDataWrapperType: Scalars['String']['output']; id: Scalars['ID']['output']; + schema?: Maybe; updatedAt: Scalars['DateTime']['output']; + userMappingOptions?: Maybe; }; export type RemoteServerIdInput = { @@ -993,6 +1007,13 @@ export type UpdateOneObjectInput = { update: UpdateObjectInput; }; +export type UpdateRemoteServerInput = { + foreignDataWrapperOptions?: InputMaybe; + id: Scalars['String']['input']; + schema?: InputMaybe; + userMappingOptions?: InputMaybe; +}; + export type UpdateWorkspaceInput = { allowImpersonation?: InputMaybe; displayName?: InputMaybe; @@ -1037,6 +1058,11 @@ export type UserExists = { exists: Scalars['Boolean']['output']; }; +export type UserMappingOptionsInput = { + password?: InputMaybe; + username?: InputMaybe; +}; + export type UserWorkspace = { __typename?: 'UserWorkspace'; createdAt: Scalars['DateTime']['output']; @@ -1220,7 +1246,7 @@ export type RelationEdge = { node: Relation; }; -export type RemoteServerFieldsFragment = { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any }; +export type RemoteServerFieldsFragment = { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any, schema?: string | null, userMappingOptions?: { __typename?: 'GetUserMappingOptions', username?: string | null } | null }; export type RemoteTableFieldsFragment = { __typename?: 'RemoteTable', id?: any | null, name: string, schema: string, status: RemoteTableStatus }; @@ -1229,7 +1255,7 @@ export type CreateServerMutationVariables = Exact<{ }>; -export type CreateServerMutation = { __typename?: 'Mutation', createOneRemoteServer: { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any } }; +export type CreateServerMutation = { __typename?: 'Mutation', createOneRemoteServer: { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any, schema?: string | null, userMappingOptions?: { __typename?: 'GetUserMappingOptions', username?: string | null } | null } }; export type DeleteServerMutationVariables = Exact<{ input: RemoteServerIdInput; @@ -1252,12 +1278,19 @@ export type UnsyncRemoteTableMutationVariables = Exact<{ export type UnsyncRemoteTableMutation = { __typename?: 'Mutation', unsyncRemoteTable: { __typename?: 'RemoteTable', id?: any | null, name: string, schema: string, status: RemoteTableStatus } }; +export type UpdateServerMutationVariables = Exact<{ + input: UpdateRemoteServerInput; +}>; + + +export type UpdateServerMutation = { __typename?: 'Mutation', updateOneRemoteServer: { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any, schema?: string | null, userMappingOptions?: { __typename?: 'GetUserMappingOptions', username?: string | null } | null } }; + export type GetManyDatabaseConnectionsQueryVariables = Exact<{ input: RemoteServerTypeInput; }>; -export type GetManyDatabaseConnectionsQuery = { __typename?: 'Query', findManyRemoteServersByType: Array<{ __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any }> }; +export type GetManyDatabaseConnectionsQuery = { __typename?: 'Query', findManyRemoteServersByType: Array<{ __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any, schema?: string | null, userMappingOptions?: { __typename?: 'GetUserMappingOptions', username?: string | null } | null }> }; export type GetManyRemoteTablesQueryVariables = Exact<{ input: RemoteServerIdInput; @@ -1271,7 +1304,7 @@ export type GetOneDatabaseConnectionQueryVariables = Exact<{ }>; -export type GetOneDatabaseConnectionQuery = { __typename?: 'Query', findOneRemoteServerById: { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any } }; +export type GetOneDatabaseConnectionQuery = { __typename?: 'Query', findOneRemoteServerById: { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any, schema?: string | null, userMappingOptions?: { __typename?: 'GetUserMappingOptions', username?: string | null } | null } }; export type CreateOneObjectMetadataItemMutationVariables = Exact<{ input: CreateOneObjectInput; @@ -1332,15 +1365,16 @@ export type ObjectMetadataItemsQueryVariables = Exact<{ export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'objectEdge', node: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, createdAt: any, updatedAt: any, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, fields: { __typename?: 'ObjectFieldsConnection', edges: Array<{ __typename?: 'fieldEdge', node: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, defaultValue?: any | null, options?: any | null, fromRelationMetadata?: { __typename?: 'relation', id: any, relationType: RelationMetadataType, toFieldMetadataId: string, toObjectMetadata: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, isSystem: boolean, isRemote: boolean } } | null, toRelationMetadata?: { __typename?: 'relation', id: any, relationType: RelationMetadataType, fromFieldMetadataId: string, fromObjectMetadata: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, isSystem: boolean, isRemote: boolean } } | null, relationDefinition?: { __typename?: 'RelationDefinition', direction: RelationDefinitionType, sourceObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'field', id: any, name: string }, targetObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, targetFieldMetadata: { __typename?: 'field', id: any, name: string } } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; -export const RemoteServerFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const RemoteServerFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}}]}}]} as unknown as DocumentNode; export const RemoteTableFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteTableFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTable"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]} as unknown as DocumentNode; -export const CreateServerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createServer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateRemoteServerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneRemoteServer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const CreateServerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"createServer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateRemoteServerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneRemoteServer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}}]}}]} as unknown as DocumentNode; export const DeleteServerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"deleteServer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneRemoteServer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const SyncRemoteTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"syncRemoteTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"syncRemoteTable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteTableFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteTableFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTable"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]} as unknown as DocumentNode; export const UnsyncRemoteTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"unsyncRemoteTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unsyncRemoteTable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteTableFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteTableFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTable"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]} as unknown as DocumentNode; -export const GetManyDatabaseConnectionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyDatabaseConnections"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerTypeInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyRemoteServersByType"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const UpdateServerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateServer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateRemoteServerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneRemoteServer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}}]}}]} as unknown as DocumentNode; +export const GetManyDatabaseConnectionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyDatabaseConnections"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerTypeInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyRemoteServersByType"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}}]}}]} as unknown as DocumentNode; export const GetManyRemoteTablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyRemoteTables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAvailableRemoteTablesByServerId"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteTableFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteTableFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteTable"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]} as unknown as DocumentNode; -export const GetOneDatabaseConnectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOneDatabaseConnection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneRemoteServerById"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const GetOneDatabaseConnectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOneDatabaseConnection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServerIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneRemoteServerById"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteServerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteServerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteServer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperId"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperOptions"}},{"kind":"Field","name":{"kind":"Name","value":"foreignDataWrapperType"}},{"kind":"Field","name":{"kind":"Name","value":"userMappingOptions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"schema"}}]}}]} as unknown as DocumentNode; export const CreateOneObjectMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneObjectMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneObjectInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneObject"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"dataSourceId"}},{"kind":"Field","name":{"kind":"Name","value":"nameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"namePlural"}},{"kind":"Field","name":{"kind":"Name","value":"labelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"labelPlural"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifierFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifierFieldMetadataId"}}]}}]}}]} as unknown as DocumentNode; export const CreateOneFieldMetadataItemDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneFieldMetadataItem"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneFieldMetadataInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneField"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"isNullable"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"defaultValue"}},{"kind":"Field","name":{"kind":"Name","value":"options"}}]}}]}}]} as unknown as DocumentNode; export const CreateOneRelationMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneRelationMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateOneRelationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneRelation"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"relationType"}},{"kind":"Field","name":{"kind":"Name","value":"fromObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"toObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"fromFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"toFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 83b226c71..95fd509a8 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -239,6 +239,11 @@ export type FullName = { lastName: Scalars['String']; }; +export type GetUserMappingOptions = { + __typename?: 'GetUserMappingOptions'; + username?: Maybe; +}; + export type InvalidatePassword = { __typename?: 'InvalidatePassword'; /** Boolean that confirms query was dispatched */ @@ -574,6 +579,7 @@ export type RemoteServer = { foreignDataWrapperType: Scalars['String']; id: Scalars['ID']; updatedAt: Scalars['DateTime']; + userMappingOptions?: Maybe; }; export type RemoteTable = { diff --git a/packages/twenty-front/src/modules/databases/graphql/fragments/databaseConnectionFragment.ts b/packages/twenty-front/src/modules/databases/graphql/fragments/databaseConnectionFragment.ts index 73b3c0c32..053aab769 100644 --- a/packages/twenty-front/src/modules/databases/graphql/fragments/databaseConnectionFragment.ts +++ b/packages/twenty-front/src/modules/databases/graphql/fragments/databaseConnectionFragment.ts @@ -7,6 +7,10 @@ export const DATABASE_CONNECTION_FRAGMENT = gql` foreignDataWrapperId foreignDataWrapperOptions foreignDataWrapperType + userMappingOptions { + username + } updatedAt + schema } `; diff --git a/packages/twenty-front/src/modules/databases/graphql/mutations/updateOneDatabaseConnection.ts b/packages/twenty-front/src/modules/databases/graphql/mutations/updateOneDatabaseConnection.ts new file mode 100644 index 000000000..54a84723e --- /dev/null +++ b/packages/twenty-front/src/modules/databases/graphql/mutations/updateOneDatabaseConnection.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { DATABASE_CONNECTION_FRAGMENT } from '@/databases/graphql/fragments/databaseConnectionFragment'; + +export const UPDATE_ONE_DATABASE_CONNECTION = gql` + ${DATABASE_CONNECTION_FRAGMENT} + mutation updateServer($input: UpdateRemoteServerInput!) { + updateOneRemoteServer(input: $input) { + ...RemoteServerFields + } + } +`; diff --git a/packages/twenty-front/src/modules/databases/hooks/useUpdateOneDatabaseConnection.ts b/packages/twenty-front/src/modules/databases/hooks/useUpdateOneDatabaseConnection.ts new file mode 100644 index 000000000..5cffbf2a3 --- /dev/null +++ b/packages/twenty-front/src/modules/databases/hooks/useUpdateOneDatabaseConnection.ts @@ -0,0 +1,34 @@ +import { ApolloClient, useMutation } from '@apollo/client'; + +import { UPDATE_ONE_DATABASE_CONNECTION } from '@/databases/graphql/mutations/updateOneDatabaseConnection'; +import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient'; +import { + UpdateRemoteServerInput, + UpdateServerMutation, + UpdateServerMutationVariables, +} from '~/generated-metadata/graphql'; + +export const useUpdateOneDatabaseConnection = () => { + const apolloMetadataClient = useApolloMetadataClient(); + + const [mutate] = useMutation< + UpdateServerMutation, + UpdateServerMutationVariables + >(UPDATE_ONE_DATABASE_CONNECTION, { + client: apolloMetadataClient ?? ({} as ApolloClient), + }); + + const updateOneDatabaseConnection = async ( + input: UpdateRemoteServerInput, + ) => { + return await mutate({ + variables: { + input, + }, + }); + }; + + return { + updateOneDatabaseConnection, + }; +}; diff --git a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionForm.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm.tsx similarity index 65% rename from packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionForm.tsx rename to packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm.tsx index 8bde7c851..08099468b 100644 --- a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionForm.tsx +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm.tsx @@ -31,7 +31,15 @@ const StyledInputsContainer = styled.div` } `; -export const SettingsIntegrationPostgreSQLConnectionForm = () => { +type SettingsIntegrationPostgreSQLConnectionFormProps = { + disabled?: boolean; + passwordPlaceholder?: string; +}; + +export const SettingsIntegrationPostgreSQLConnectionForm = ({ + disabled, + passwordPlaceholder, +}: SettingsIntegrationPostgreSQLConnectionFormProps) => { const { control } = useFormContext(); @@ -49,15 +57,24 @@ export const SettingsIntegrationPostgreSQLConnectionForm = () => { key={name} name={name} control={control} - render={({ field: { onChange, value } }) => ( - - )} + render={({ field: { onChange, value } }) => { + return ( + + ); + }} /> ))} diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx new file mode 100644 index 000000000..8e1edfd4d --- /dev/null +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx @@ -0,0 +1,80 @@ +import { useNavigate } from 'react-router-dom'; +import { Section } from '@react-email/components'; + +import { useDeleteOneDatabaseConnection } from '@/databases/hooks/useDeleteOneDatabaseConnection'; +import { SettingsIntegrationDatabaseTablesListCard } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseTablesListCard'; +import { useDatabaseConnection } from '@/settings/integrations/database-connection/hooks/useDatabaseConnection'; +import { getConnectionDbName } from '@/settings/integrations/utils/getConnectionDbName'; +import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; +import { SettingsPath } from '@/types/SettingsPath'; +import { H2Title } from '@/ui/display/typography/components/H2Title'; +import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; +import { SettingsIntegrationDatabaseConnectionSummaryCard } from '~/pages/settings/integrations/SettingsIntegrationDatabaseConnectionSummaryCard'; + +export const SettingsIntegrationDatabaseConnectionShowContainer = () => { + const navigate = useNavigate(); + const { connection, integration, databaseKey, tables } = + useDatabaseConnection(); + + const { deleteOneDatabaseConnection } = useDeleteOneDatabaseConnection(); + + if (!connection || !integration) { + return null; + } + + const deleteConnection = async () => { + await deleteOneDatabaseConnection({ id: connection.id }); + + navigate(`${settingsIntegrationsPagePath}/${databaseKey}`); + }; + + const onEdit = () => { + navigate('./edit'); + }; + + const settingsIntegrationsPagePath = getSettingsPagePath( + SettingsPath.Integrations, + ); + + const connectionName = getConnectionDbName({ integration, connection }); + + return ( + <> + +
+ + +
+
+ + {!!tables?.length && ( + + )} +
+ + ); +}; diff --git a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionSyncStatus.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSyncStatus.tsx similarity index 100% rename from packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionSyncStatus.tsx rename to packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSyncStatus.tsx diff --git a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionsListCard.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard.tsx similarity index 95% rename from packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionsListCard.tsx rename to packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard.tsx index ecb425cd0..298f6aadc 100644 --- a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseConnectionsListCard.tsx +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard.tsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled'; import { IconChevronRight } from 'twenty-ui'; import { SettingsListCard } from '@/settings/components/SettingsListCard'; -import { SettingsIntegrationDatabaseConnectionSyncStatus } from '@/settings/integrations/components/SettingsIntegrationDatabaseConnectionSyncStatus'; +import { SettingsIntegrationDatabaseConnectionSyncStatus } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSyncStatus'; import { SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration'; import { getConnectionDbName } from '@/settings/integrations/utils/getConnectionDbName'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; diff --git a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseTablesListCard.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseTablesListCard.tsx similarity index 100% rename from packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseTablesListCard.tsx rename to packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseTablesListCard.tsx diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer.tsx new file mode 100644 index 000000000..fffde050e --- /dev/null +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer.tsx @@ -0,0 +1,18 @@ +import { SettingsIntegrationEditDatabaseConnectionContent } from '@/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent'; +import { useDatabaseConnection } from '@/settings/integrations/database-connection/hooks/useDatabaseConnection'; + +export const SettingsIntegrationEditDatabaseConnectionContainer = () => { + const { connection, integration, databaseKey, tables } = + useDatabaseConnection(); + + if (!connection || !integration) return null; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx new file mode 100644 index 000000000..9907ecd9d --- /dev/null +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx @@ -0,0 +1,151 @@ +import { FormProvider, useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Section } from '@react-email/components'; +import pick from 'lodash.pick'; +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 { + formatValuesForUpdate, + getEditionSchemaForForm, + getFormDefaultValuesFromConnection, +} from '@/settings/integrations/database-connection/utils/editDatabaseConnection'; +import { SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration'; +import { getConnectionDbName } from '@/settings/integrations/utils/getConnectionDbName'; +import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; +import { SettingsPath } from '@/types/SettingsPath'; +import { Info } from '@/ui/display/info/components/Info'; +import { H2Title } from '@/ui/display/typography/components/H2Title'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; +import { + RemoteServer, + RemoteTable, + RemoteTableStatus, +} from '~/generated-metadata/graphql'; + +export const SettingsIntegrationEditDatabaseConnectionContent = ({ + connection, + integration, + databaseKey, + tables, +}: { + connection: RemoteServer; + integration: SettingsIntegration; + databaseKey: string; + tables: RemoteTable[]; +}) => { + const { enqueueSnackBar } = useSnackBar(); + const navigate = useNavigate(); + + const editConnectionSchema = getEditionSchemaForForm(databaseKey); + type SettingsIntegrationEditConnectionFormValues = z.infer< + typeof editConnectionSchema + >; + + const formConfig = useForm({ + mode: 'onTouched', + resolver: zodResolver(editConnectionSchema), + defaultValues: getFormDefaultValuesFromConnection({ + databaseKey, + connection, + }), + }); + + const { updateOneDatabaseConnection } = useUpdateOneDatabaseConnection(); + + const settingsIntegrationsPagePath = getSettingsPagePath( + SettingsPath.Integrations, + ); + + const hasSyncedTables = tables?.some( + (table) => table?.status === RemoteTableStatus.Synced, + ); + + const connectionName = getConnectionDbName({ integration, connection }); + + const { isDirty, isValid } = formConfig.formState; + const canSave = !hasSyncedTables && isDirty && isValid; + + const handleSave = async () => { + const formValues = formConfig.getValues(); + const dirtyFieldKeys = Object.keys( + formConfig.formState.dirtyFields, + ) as (keyof SettingsIntegrationEditConnectionFormValues)[]; + + try { + await updateOneDatabaseConnection({ + ...formatValuesForUpdate({ + databaseKey, + formValues: pick(formValues, dirtyFieldKeys), + }), + id: connection?.id ?? '', + }); + + navigate( + `${settingsIntegrationsPagePath}/${databaseKey}/${connection?.id}`, + ); + } catch (error) { + enqueueSnackBar((error as Error).message, { + variant: 'error', + }); + } + }; + + return ( + <> + + + + + navigate(`${settingsIntegrationsPagePath}/${databaseKey}`) + } + onSave={handleSave} + /> + + {hasSyncedTables && ( + + )} + {databaseKey === 'postgresql' ? ( +
+ + + +
+ ) : null} +
+ + ); +}; diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts b/packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts new file mode 100644 index 000000000..aa94c3ffe --- /dev/null +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts @@ -0,0 +1,55 @@ +import { useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +import { useGetDatabaseConnection } from '@/databases/hooks/useGetDatabaseConnection'; +import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables'; +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(); + const navigate = useNavigate(); + + const [integrationCategoryAll] = useSettingsIntegrationCategories(); + const integration = integrationCategoryAll.integrations.find( + ({ 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 { connection, loading } = useGetDatabaseConnection({ + databaseKey, + connectionId, + skip: !isIntegrationAvailable, + }); + + useEffect(() => { + if (!isIntegrationAvailable || (!loading && !connection)) { + navigate(AppPath.NotFound); + } + }, [ + integration, + databaseKey, + navigate, + isIntegrationAvailable, + connection, + loading, + ]); + + const { tables } = useGetDatabaseConnectionTables({ + connectionId, + skip: !connection, + }); + + return { connection, integration, databaseKey, tables }; +}; diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/utils/editDatabaseConnection.ts b/packages/twenty-front/src/modules/settings/integrations/database-connection/utils/editDatabaseConnection.ts new file mode 100644 index 000000000..d79c65d53 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/utils/editDatabaseConnection.ts @@ -0,0 +1,75 @@ +import { identity, isEmpty, pickBy } from 'lodash'; +import { z } from 'zod'; + +import { settingsIntegrationPostgreSQLConnectionFormSchema } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm'; +import { RemoteServer } from '~/generated-metadata/graphql'; + +export const getEditionSchemaForForm = (databaseKey: string) => { + switch (databaseKey) { + case 'postgresql': + return settingsIntegrationPostgreSQLConnectionFormSchema.extend({ + password: z.string().optional(), + }); + default: + throw new Error(`No schema found for database key: ${databaseKey}`); + } +}; + +export const getFormDefaultValuesFromConnection = ({ + databaseKey, + connection, +}: { + databaseKey: string; + connection: RemoteServer; +}) => { + switch (databaseKey) { + case 'postgresql': + return { + dbname: connection.foreignDataWrapperOptions.dbname, + host: connection.foreignDataWrapperOptions.host, + port: connection.foreignDataWrapperOptions.port, + username: connection.userMappingOptions?.username || undefined, + schema: connection.schema || undefined, + password: '', + }; + default: + throw new Error( + `No default form values for database key: ${databaseKey}`, + ); + } +}; + +export const formatValuesForUpdate = ({ + databaseKey, + formValues, +}: { + databaseKey: string; + formValues: any; +}) => { + switch (databaseKey) { + case 'postgresql': { + const formattedValues = { + userMappingOptions: pickBy( + { + username: formValues.username, + password: formValues.password, + }, + identity, + ), + foreignDataWrapperOptions: pickBy( + { + dbname: formValues.dbname, + host: formValues.host, + port: formValues.port, + }, + identity, + ), + schema: formValues.schema, + }; + + return pickBy(formattedValues, (obj) => !isEmpty(obj)); + } + default: + throw new Error(`Cannot format values for database key: ${databaseKey}`); + } +}; diff --git a/packages/twenty-front/src/modules/types/SettingsPath.ts b/packages/twenty-front/src/modules/types/SettingsPath.ts index d0f453757..36ca5d42a 100644 --- a/packages/twenty-front/src/modules/types/SettingsPath.ts +++ b/packages/twenty-front/src/modules/types/SettingsPath.ts @@ -23,6 +23,7 @@ export enum SettingsPath { Integrations = 'integrations', IntegrationDatabase = 'integrations/:databaseKey', IntegrationDatabaseConnection = 'integrations/:databaseKey/:connectionId', + IntegrationEditDatabaseConnection = 'integrations/:databaseKey/:connectionId/edit', IntegrationNewDatabaseConnection = 'integrations/:databaseKey/new', DevelopersNewWebhook = 'webhooks/new', DevelopersNewWebhookDetail = 'webhooks/:webhookId', diff --git a/packages/twenty-front/src/modules/ui/display/info/components/Info.tsx b/packages/twenty-front/src/modules/ui/display/info/components/Info.tsx index e65a17312..66c3e961d 100644 --- a/packages/twenty-front/src/modules/ui/display/info/components/Info.tsx +++ b/packages/twenty-front/src/modules/ui/display/info/components/Info.tsx @@ -9,8 +9,8 @@ export type InfoAccent = 'blue' | 'danger'; export type InfoProps = { accent?: InfoAccent; text: string; - buttonTitle: string; - onClick: (event: React.MouseEvent) => void; + buttonTitle?: string; + onClick?: (event: React.MouseEvent) => void; }; const StyledTextContainer = styled.div` @@ -54,12 +54,14 @@ export const Info = ({ {text} -