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}
-
+ {buttonTitle && onClick && (
+
+ )}
);
};
diff --git a/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx b/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx
index 395058083..e75b2c865 100644
--- a/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx
+++ b/packages/twenty-front/src/modules/ui/input/components/TextInputV2.tsx
@@ -123,6 +123,7 @@ const TextInputV2Component = (
disabled,
tabIndex,
RightIcon,
+ autoComplete,
}: TextInputV2ComponentProps,
// eslint-disable-next-line @nx/workspace-component-props-naming
ref: ForwardedRef,
@@ -143,7 +144,7 @@ const TextInputV2Component = (
{label && {label + (required ? '*' : '')}}
theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
gap: ${({ theme }) => theme.spacing(2)};
- line-height: ${({ theme }) => theme.text.lineHeight.md};
+ line-height: ${({ theme }) => theme.text.lineHeight.lg};
`;
const StyledLink = styled(Link)`
diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx
index efcf298c3..65c241036 100644
--- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx
+++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx
@@ -4,8 +4,8 @@ import { IconSettings } from 'twenty-ui';
import { useGetDatabaseConnections } from '@/databases/hooks/useGetDatabaseConnections';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
-import { SettingsIntegrationDatabaseConnectionsListCard } from '@/settings/integrations/components/SettingsIntegrationDatabaseConnectionsListCard';
import { SettingsIntegrationPreview } from '@/settings/integrations/components/SettingsIntegrationPreview';
+import { SettingsIntegrationDatabaseConnectionsListCard } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard';
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { AppPath } from '@/types/AppPath';
diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnection.tsx
deleted file mode 100644
index df61c9f70..000000000
--- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnection.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { useEffect } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
-import { IconSettings } from 'twenty-ui';
-
-import { useDeleteOneDatabaseConnection } from '@/databases/hooks/useDeleteOneDatabaseConnection';
-import { useGetDatabaseConnection } from '@/databases/hooks/useGetDatabaseConnection';
-import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables';
-import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
-import { SettingsIntegrationDatabaseTablesListCard } from '@/settings/integrations/components/SettingsIntegrationDatabaseTablesListCard';
-import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
-import { getConnectionDbName } from '@/settings/integrations/utils/getConnectionDbName';
-import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
-import { AppPath } from '@/types/AppPath';
-import { SettingsPath } from '@/types/SettingsPath';
-import { H2Title } from '@/ui/display/typography/components/H2Title';
-import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
-import { Section } from '@/ui/layout/section/components/Section';
-import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { SettingsIntegrationDatabaseConnectionSummaryCard } from '~/pages/settings/integrations/SettingsIntegrationDatabaseConnectionSummaryCard';
-
-export const SettingsIntegrationDatabaseConnection = () => {
- 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,
- });
-
- const { deleteOneDatabaseConnection } = useDeleteOneDatabaseConnection();
-
- const deleteConnection = async () => {
- if (!connection) return;
-
- await deleteOneDatabaseConnection({ id: connection.id });
-
- navigate(`${settingsIntegrationsPagePath}/${databaseKey}`);
- };
-
- useEffect(() => {
- if (!isIntegrationAvailable || (!loading && !connection)) {
- navigate(AppPath.NotFound);
- }
- }, [
- integration,
- databaseKey,
- navigate,
- isIntegrationAvailable,
- connection,
- loading,
- ]);
-
- const { tables } = useGetDatabaseConnectionTables({
- connectionId,
- skip: !isIntegrationAvailable || !connection,
- });
-
- if (!isIntegrationAvailable || !connection) return null;
-
- const settingsIntegrationsPagePath = getSettingsPagePath(
- SettingsPath.Integrations,
- );
-
- const connectionName = getConnectionDbName({ integration, connection });
-
- return (
-
-
-
-
-
-
- {!!tables.length && (
-
- )}
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnectionSummaryCard.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnectionSummaryCard.tsx
index 5c3bddb24..4fe124377 100644
--- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnectionSummaryCard.tsx
+++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabaseConnectionSummaryCard.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
-import { IconDotsVertical, IconTrash } from 'twenty-ui';
+import { IconDotsVertical, IconPencil, IconTrash } from 'twenty-ui';
import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
-import { SettingsIntegrationDatabaseConnectionSyncStatus } from '@/settings/integrations/components/SettingsIntegrationDatabaseConnectionSyncStatus';
+import { SettingsIntegrationDatabaseConnectionSyncStatus } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSyncStatus';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@@ -14,6 +14,7 @@ type SettingsIntegrationDatabaseConnectionSummaryCardProps = {
connectionId: string;
connectionName: string;
onRemove: () => void;
+ onEdit: () => void;
};
const StyledDatabaseLogoContainer = styled.div`
@@ -33,6 +34,7 @@ export const SettingsIntegrationDatabaseConnectionSummaryCard = ({
connectionId,
connectionName,
onRemove,
+ onEdit,
}: SettingsIntegrationDatabaseConnectionSummaryCardProps) => {
const dropdownId =
'settings-integration-database-connection-summary-card-dropdown';
@@ -66,6 +68,11 @@ export const SettingsIntegrationDatabaseConnectionSummaryCard = ({
text="Remove"
onClick={onRemove}
/>
+
}
diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx
new file mode 100644
index 000000000..05d3789ff
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx
@@ -0,0 +1,17 @@
+import { IconSettings } from 'twenty-ui';
+
+import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
+import { SettingsIntegrationEditDatabaseConnectionContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer';
+import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
+
+export const SettingsIntegrationEditDatabaseConnection = () => {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx
index 2dbe707b9..9a68e502a 100644
--- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx
+++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx
@@ -13,7 +13,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import {
SettingsIntegrationPostgreSQLConnectionForm,
settingsIntegrationPostgreSQLConnectionFormSchema,
-} from '@/settings/integrations/components/SettingsIntegrationDatabaseConnectionForm';
+} from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm';
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { AppPath } from '@/types/AppPath';
@@ -116,10 +116,12 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
};
return (
- // eslint-disable-next-line react/jsx-props-no-spreading
-
-
-
+
+
+
{
) : null}
-
-
-
+
+
+
);
};
diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx
new file mode 100644
index 000000000..297c6fc76
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx
@@ -0,0 +1,15 @@
+import { IconSettings } from 'twenty-ui';
+
+import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
+import { SettingsIntegrationDatabaseConnectionShowContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer';
+import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
+
+export const SettingsIntegrationShowDatabaseConnection = () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationEditDatabaseConnection.stories.tsx b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationEditDatabaseConnection.stories.tsx
new file mode 100644
index 000000000..010d4c357
--- /dev/null
+++ b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationEditDatabaseConnection.stories.tsx
@@ -0,0 +1,40 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { within } from '@storybook/test';
+
+import { SettingsIntegrationEditDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection';
+import {
+ PageDecorator,
+ PageDecoratorArgs,
+} from '~/testing/decorators/PageDecorator';
+import { graphqlMocks } from '~/testing/graphqlMocks';
+import { sleep } from '~/testing/sleep';
+
+const meta: Meta = {
+ title:
+ 'Pages/Settings/Integrations/SettingsIntegrationEditDatabaseConnection',
+ component: SettingsIntegrationEditDatabaseConnection,
+ decorators: [PageDecorator],
+ args: {
+ routePath: '/settings/integrations/:databaseKey/edit',
+ routeParams: {
+ ':databaseKey': 'postgresql',
+ ':connectionId': '67cbfd35-8dd4-4591-b9d4-c1906281a5da',
+ },
+ },
+ parameters: {
+ msw: graphqlMocks,
+ },
+};
+
+export default meta;
+
+export type Story = StoryObj;
+
+export const Default: Story = {
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ sleep(100);
+
+ await canvas.findByText('Edit PostgreSQL Connection');
+ },
+};
diff --git a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabaseConnection.stories.tsx b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationShowDatabaseConnection.stories.tsx
similarity index 73%
rename from packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabaseConnection.stories.tsx
rename to packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationShowDatabaseConnection.stories.tsx
index f72710d7c..b81e24610 100644
--- a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabaseConnection.stories.tsx
+++ b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationShowDatabaseConnection.stories.tsx
@@ -3,7 +3,7 @@ import { within } from '@storybook/test';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
-import { SettingsIntegrationDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationDatabaseConnection';
+import { SettingsIntegrationShowDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection';
import {
PageDecorator,
PageDecoratorArgs,
@@ -12,8 +12,9 @@ import { graphqlMocks } from '~/testing/graphqlMocks';
import { sleep } from '~/testing/sleep';
const meta: Meta = {
- title: 'Pages/Settings/Integrations/SettingsIntegrationDatabaseConnection',
- component: SettingsIntegrationDatabaseConnection,
+ title:
+ 'Pages/Settings/Integrations/SettingsIntegrationShowDatabaseConnection',
+ component: SettingsIntegrationShowDatabaseConnection,
decorators: [PageDecorator],
args: {
routePath: getSettingsPagePath(SettingsPath.IntegrationDatabaseConnection),
@@ -29,7 +30,7 @@ const meta: Meta = {
export default meta;
-export type Story = StoryObj;
+export type Story = StoryObj;
export const Default: Story = {
play: async ({ canvasElement }) => {
diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts
index 7610d3daf..334a553cf 100644
--- a/packages/twenty-front/src/testing/graphqlMocks.ts
+++ b/packages/twenty-front/src/testing/graphqlMocks.ts
@@ -13,12 +13,14 @@ import {
} from '~/testing/mock-data/companies';
import { mockedClientConfig } from '~/testing/mock-data/config';
import { mockedObjectMetadataItemsQueryResult } from '~/testing/mock-data/metadata';
+import { mockedRemoteTables } from '~/testing/mock-data/remote-tables';
import { mockedUsersData } from '~/testing/mock-data/users';
+import { mockedViewsData } from '~/testing/mock-data/views';
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
import { mockedPeopleData } from './mock-data/people';
+import { mockedRemoteServers } from './mock-data/remote-servers';
import { mockedViewFieldsData } from './mock-data/view-fields';
-import { mockedViewsData } from './mock-data/views';
const metadataGraphql = graphql.link(`${REACT_APP_SERVER_BASE_URL}/metadata`);
@@ -315,6 +317,20 @@ export const graphqlMocks = {
},
});
}),
+ graphql.query('GetOneDatabaseConnection', () => {
+ return HttpResponse.json({
+ data: {
+ findOneRemoteServerById: mockedRemoteServers[0],
+ },
+ });
+ }),
+ graphql.query('GetManyRemoteTables', () => {
+ return HttpResponse.json({
+ data: {
+ findAvailableRemoteTablesByServerId: mockedRemoteTables,
+ },
+ });
+ }),
http.get('https://chat-assets.frontapp.com/v1/chat.bundle.js', () => {
return HttpResponse.text(
`
diff --git a/packages/twenty-front/src/testing/mock-data/remote-servers.ts b/packages/twenty-front/src/testing/mock-data/remote-servers.ts
new file mode 100644
index 000000000..f7953d856
--- /dev/null
+++ b/packages/twenty-front/src/testing/mock-data/remote-servers.ts
@@ -0,0 +1,20 @@
+export const mockedRemoteServers = [
+ {
+ __typename: 'RemoteServer',
+ id: '67cbfd35-8dd4-4591-b9d4-c1906281a5da',
+ createdAt: '2024-04-30T13:41:25.584Z',
+ foreignDataWrapperId: 'b306b641-2142-4b4e-8fba-976afbc3b2bc',
+ foreignDataWrapperOptions: {
+ host: 'localhost',
+ port: 5432,
+ dbname: 'dbname_test',
+ },
+ foreignDataWrapperType: 'postgres_fdw',
+ userMappingOptions: {
+ __typename: 'GetUserMappingOptions',
+ username: 'twenty',
+ },
+ updatedAt: '2024-04-30T13:41:25.858Z',
+ schema: 'public',
+ },
+];
diff --git a/packages/twenty-front/src/testing/mock-data/remote-tables.ts b/packages/twenty-front/src/testing/mock-data/remote-tables.ts
new file mode 100644
index 000000000..85f885d22
--- /dev/null
+++ b/packages/twenty-front/src/testing/mock-data/remote-tables.ts
@@ -0,0 +1,16 @@
+export const mockedRemoteTables = [
+ {
+ __typename: 'RemoteTable',
+ id: 'bb06a39e-a792-424e-ac29-c54a395fdb4f',
+ name: 'Cars',
+ schema: 'public',
+ status: 'SYNCED',
+ },
+ {
+ __typename: 'RemoteTable',
+ id: '994b68c7-148d-4ee0-b557-c688c3f53f73',
+ name: 'Flowers',
+ schema: 'public',
+ status: 'NOT_SYNCED',
+ },
+];
diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/foreign-data-wrapper-query.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/foreign-data-wrapper-query.factory.ts
index ed0be33d9..c0aa98358 100644
--- a/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/foreign-data-wrapper-query.factory.ts
+++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-builder/factories/foreign-data-wrapper-query.factory.ts
@@ -5,8 +5,8 @@ import { isDefined } from 'class-validator';
import {
ForeignDataWrapperOptions,
RemoteServerType,
- UserMappingOptions,
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
+import { UserMappingOptionsInput } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils';
@Injectable()
export class ForeignDataWrapperQueryFactory {
@@ -39,7 +39,7 @@ export class ForeignDataWrapperQueryFactory {
createUserMapping(
foreignDataWrapperId: string,
- userMappingOptions: UserMappingOptions,
+ userMappingOptions: UserMappingOptionsInput,
) {
// CURRENT_USER works for now since we are using only one user. But if we switch to a user per workspace, we need to change this.
return `CREATE USER MAPPING IF NOT EXISTS FOR CURRENT_USER SERVER "${foreignDataWrapperId}" OPTIONS (user '${userMappingOptions.username}', password '${userMappingOptions.password}')`;
@@ -47,7 +47,7 @@ export class ForeignDataWrapperQueryFactory {
updateUserMapping(
foreignDataWrapperId: string,
- userMappingOptions: Partial,
+ userMappingOptions: Partial,
) {
const options = this.buildUpdateUserMappingOptions(userMappingOptions);
@@ -82,7 +82,7 @@ export class ForeignDataWrapperQueryFactory {
}
private buildUpdateUserMappingOptions(
- userMappingOptions?: Partial,
+ userMappingOptions?: Partial,
) {
const setStatements: string[] = [];
diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/create-remote-server.input.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/create-remote-server.input.ts
index eaf2a8d14..684f48bc4 100644
--- a/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/create-remote-server.input.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/create-remote-server.input.ts
@@ -6,9 +6,8 @@ import GraphQLJSON from 'graphql-type-json';
import {
ForeignDataWrapperOptions,
RemoteServerType,
- UserMappingOptions,
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
-import { UserMappingOptionsInput } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options-input.utils';
+import { UserMappingOptionsInput } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils';
@InputType()
export class CreateRemoteServerInput {
@@ -20,7 +19,7 @@ export class CreateRemoteServerInput {
@IsOptional()
@Field(() => UserMappingOptionsInput, { nullable: true })
- userMappingOptions?: UserMappingOptions;
+ userMappingOptions?: UserMappingOptionsInput;
@IsOptional()
@Field(() => String, { nullable: true })
diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/remote-server.dto.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/remote-server.dto.ts
index 7773e948b..cba0a8d4b 100644
--- a/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/remote-server.dto.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/remote-server.dto.ts
@@ -7,6 +7,7 @@ import {
ForeignDataWrapperOptions,
RemoteServerType,
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
+import { GetUserMappingOptions } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils';
@ObjectType('RemoteServer')
export class RemoteServerDTO {
@@ -23,6 +24,14 @@ export class RemoteServerDTO {
@Field(() => GraphQLJSON, { nullable: true })
foreignDataWrapperOptions?: ForeignDataWrapperOptions;
+ @IsOptional()
+ @Field(() => GetUserMappingOptions, { nullable: true })
+ userMappingOptions?: GetUserMappingOptions;
+
+ @IsOptional()
+ @Field(() => String, { nullable: true })
+ schema?: string;
+
@HideField()
workspaceId: string;
diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/update-remote-server.input.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/update-remote-server.input.ts
index 0ee527b92..7bb220bb7 100644
--- a/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/update-remote-server.input.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/dtos/update-remote-server.input.ts
@@ -6,9 +6,8 @@ import GraphQLJSON from 'graphql-type-json';
import {
ForeignDataWrapperOptions,
RemoteServerType,
- UserMappingOptions,
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
-import { UserMappingOptionsInput } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options-input.utils';
+import { UserMappingOptionsInput } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils';
@InputType()
export class UpdateRemoteServerInput {
@@ -21,5 +20,9 @@ export class UpdateRemoteServerInput {
@IsOptional()
@Field(() => UserMappingOptionsInput, { nullable: true })
- userMappingOptions?: Partial;
+ userMappingOptions?: Partial;
+
+ @IsOptional()
+ @Field(() => String, { nullable: true })
+ schema?: string;
}
diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.entity.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.entity.ts
index 116c479df..beb993c9c 100644
--- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.entity.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.entity.ts
@@ -10,6 +10,7 @@ import {
} from 'typeorm';
import { RemoteTableEntity } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.entity';
+import { UserMappingOptionsInput as UserMappingOptions } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils';
import { DistantTables } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/types/distant-table';
export enum RemoteServerType {
@@ -26,12 +27,6 @@ export type ForeignDataWrapperOptions =
T extends RemoteServerType.POSTGRES_FDW
? PostgresForeignDataWrapperOptions
: never;
-
-export type UserMappingOptions = {
- username: string;
- password: string;
-};
-
@Entity('remoteServer')
export class RemoteServerEntity {
@PrimaryGeneratedColumn('uuid')
diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts
index b3cbbd8dc..07d05aa17 100644
--- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts
@@ -5,6 +5,7 @@ import {
} from '@nestjs/common';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
+import isEmpty from 'lodash.isempty';
import { v4 } from 'uuid';
import { DataSource, EntityManager, Repository } from 'typeorm';
@@ -162,7 +163,9 @@ export class RemoteServerService {
partialRemoteServerWithUpdates,
);
- if (partialRemoteServerWithUpdates.foreignDataWrapperOptions) {
+ if (
+ !isEmpty(partialRemoteServerWithUpdates.foreignDataWrapperOptions)
+ ) {
const foreignDataWrapperQuery =
this.foreignDataWrapperQueryFactory.updateForeignDataWrapper({
foreignDataWrapperId,
@@ -173,7 +176,7 @@ export class RemoteServerService {
await entityManager.query(foreignDataWrapperQuery);
}
- if (partialRemoteServerWithUpdates.userMappingOptions) {
+ if (!isEmpty(partialRemoteServerWithUpdates.userMappingOptions)) {
const userMappingQuery =
this.foreignDataWrapperQueryFactory.updateUserMapping(
foreignDataWrapperId,
diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts
index 435751d5e..86d683d18 100644
--- a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts
@@ -1,10 +1,12 @@
+import { BadRequestException } from '@nestjs/common';
+
import { isDefined } from 'class-validator';
import {
RemoteServerEntity,
RemoteServerType,
- UserMappingOptions,
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
+import { UserMappingOptionsInput } from 'src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils';
export type DeepPartial = {
[P in keyof T]?: DeepPartial;
@@ -13,7 +15,7 @@ export type DeepPartial = {
const buildUserMappingOptionsQuery = (
parameters: any[],
parametersPositions: object,
- userMappingOptions: DeepPartial,
+ userMappingOptions: DeepPartial,
): string | null => {
const shouldUpdateUserMappingOptionsPassword = isDefined(
userMappingOptions?.password,
@@ -130,6 +132,10 @@ export const updateRemoteServerRawQuery = (
options.push(fwdOptionsQuery);
}
+ if (options.length < 1) {
+ throw new BadRequestException('No fields to update');
+ }
+
const rawQuery = `UPDATE metadata."remoteServer" SET ${options.join(
', ',
)} WHERE "id"= $1 RETURNING *`;
diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/user-mapping-options-input.utils.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils.ts
similarity index 56%
rename from packages/twenty-server/src/engine/metadata-modules/remote-server/utils/user-mapping-options-input.utils.ts
rename to packages/twenty-server/src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils.ts
index e6f245877..61e31f035 100644
--- a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/user-mapping-options-input.utils.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/user-mapping-options.utils.ts
@@ -1,4 +1,4 @@
-import { InputType, Field } from '@nestjs/graphql';
+import { InputType, Field, ObjectType } from '@nestjs/graphql';
import { IsOptional } from 'class-validator';
@@ -12,3 +12,10 @@ export class UserMappingOptionsInput {
@Field(() => String, { nullable: true })
password: string;
}
+
+@ObjectType()
+export class GetUserMappingOptions {
+ @IsOptional()
+ @Field(() => String, { nullable: true })
+ username: string;
+}