From aecf8783a056a898b686c810f306861df5e641b5 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Thu, 11 Apr 2024 11:51:49 +0200 Subject: [PATCH] Sync table from frontend (#4894) This PR: - separates the existing updateSyncStatus endpoint into 2 endpoints - creates mutations and hooks that will call those endpoints - trigger the hook on toggle - removes form logic and add a separated component for toggling --------- Co-authored-by: Thomas Trompette --- .../src/generated-metadata/gql.ts | 19 ++++- .../src/generated-metadata/graphql.ts | 46 ++++++++--- .../graphql/fragments/remoteTableFragment.ts | 9 +++ .../graphql/mutations/syncRemoteTable.ts | 12 +++ .../graphql/mutations/unsyncRemoteTable.ts | 12 +++ .../findManyDatabaseConnectionTables.ts | 11 --- .../graphql/queries/findManyRemoteTables.ts | 12 +++ .../hooks/useGetDatabaseConnectionTables.ts | 12 +-- .../databases/hooks/useSyncRemoteTable.ts | 40 +++++++++ .../databases/hooks/useUnsyncRemoteTable.ts | 40 +++++++++ ...tingsIntegrationDatabaseTablesListCard.tsx | 76 ++++++++++++----- ...IntegrationRemoteTableSyncStatusToggle.tsx | 29 +++++++ .../modules/ui/input/components/Toggle.tsx | 6 ++ .../SettingsIntegrationDatabaseConnection.tsx | 81 +++++++++---------- .../remote-server/remote-server.service.ts | 3 +- .../remote-table/dtos/remote-table-input.ts | 8 -- .../remote-table/dtos/remote-table.dto.ts | 2 +- .../remote-postgres-table.service.ts | 24 +++--- .../remote-table/remote-table.resolver.ts | 17 ++-- .../remote-table/remote-table.service.ts | 62 ++++++++------ 20 files changed, 372 insertions(+), 149 deletions(-) create mode 100644 packages/twenty-front/src/modules/databases/graphql/fragments/remoteTableFragment.ts create mode 100644 packages/twenty-front/src/modules/databases/graphql/mutations/syncRemoteTable.ts create mode 100644 packages/twenty-front/src/modules/databases/graphql/mutations/unsyncRemoteTable.ts delete mode 100644 packages/twenty-front/src/modules/databases/graphql/queries/findManyDatabaseConnectionTables.ts create mode 100644 packages/twenty-front/src/modules/databases/graphql/queries/findManyRemoteTables.ts create mode 100644 packages/twenty-front/src/modules/databases/hooks/useSyncRemoteTable.ts create mode 100644 packages/twenty-front/src/modules/databases/hooks/useUnsyncRemoteTable.ts create mode 100644 packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationRemoteTableSyncStatusToggle.tsx diff --git a/packages/twenty-front/src/generated-metadata/gql.ts b/packages/twenty-front/src/generated-metadata/gql.ts index c0e090507..ecde7f3ca 100644 --- a/packages/twenty-front/src/generated-metadata/gql.ts +++ b/packages/twenty-front/src/generated-metadata/gql.ts @@ -14,10 +14,13 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ */ const documents = { "\n fragment RemoteServerFields on RemoteServer {\n id\n createdAt\n foreignDataWrapperId\n foreignDataWrapperOptions\n foreignDataWrapperType\n updatedAt\n }\n": types.RemoteServerFieldsFragmentDoc, + "\n fragment RemoteTableFields on RemoteTable {\n name\n schema\n status\n }\n": types.RemoteTableFieldsFragmentDoc, "\n \n mutation createServer($input: CreateRemoteServerInput!) {\n createOneRemoteServer(input: $input) {\n ...RemoteServerFields\n }\n }\n": types.CreateServerDocument, "\n mutation deleteServer($input: RemoteServerIdInput!) {\n deleteOneRemoteServer(input: $input) {\n id\n }\n }\n": types.DeleteServerDocument, - "\n query GetManyDatabaseConnectionTables($input: RemoteServerIdInput!) {\n findAvailableRemoteTablesByServerId(input: $input) {\n name\n schema\n status\n }\n }\n": types.GetManyDatabaseConnectionTablesDocument, + "\n \n mutation syncRemoteTable($input: RemoteTableInput!) {\n syncRemoteTable(input: $input) {\n ...RemoteTableFields\n }\n }\n": types.SyncRemoteTableDocument, + "\n \n mutation unsyncRemoteTable($input: RemoteTableInput!) {\n unsyncRemoteTable(input: $input) {\n ...RemoteTableFields\n }\n }\n": types.UnsyncRemoteTableDocument, "\n \n query GetManyDatabaseConnections($input: RemoteServerTypeInput!) {\n findManyRemoteServersByType(input: $input) {\n ...RemoteServerFields\n }\n }\n": types.GetManyDatabaseConnectionsDocument, + "\n \n query GetManyRemoteTables($input: RemoteServerIdInput!) {\n findAvailableRemoteTablesByServerId(input: $input) {\n ...RemoteTableFields\n }\n }\n": types.GetManyRemoteTablesDocument, "\n \n query GetOneDatabaseConnection($input: RemoteServerIdInput!) {\n findOneRemoteServerById(input: $input) {\n ...RemoteServerFields\n }\n }\n": types.GetOneDatabaseConnectionDocument, "\n mutation CreateOneObjectMetadataItem($input: CreateOneObjectInput!) {\n createOneObject(input: $input) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.CreateOneObjectMetadataItemDocument, "\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n }\n }\n": types.CreateOneFieldMetadataItemDocument, @@ -47,6 +50,10 @@ export function graphql(source: string): unknown; * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n fragment RemoteServerFields on RemoteServer {\n id\n createdAt\n foreignDataWrapperId\n foreignDataWrapperOptions\n foreignDataWrapperType\n updatedAt\n }\n"): (typeof documents)["\n fragment RemoteServerFields on RemoteServer {\n id\n createdAt\n foreignDataWrapperId\n foreignDataWrapperOptions\n foreignDataWrapperType\n updatedAt\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n fragment RemoteTableFields on RemoteTable {\n name\n schema\n status\n }\n"): (typeof documents)["\n fragment RemoteTableFields on RemoteTable {\n name\n schema\n status\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -58,11 +65,19 @@ export function graphql(source: "\n mutation deleteServer($input: RemoteServerI /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query GetManyDatabaseConnectionTables($input: RemoteServerIdInput!) {\n findAvailableRemoteTablesByServerId(input: $input) {\n name\n schema\n status\n }\n }\n"): (typeof documents)["\n query GetManyDatabaseConnectionTables($input: RemoteServerIdInput!) {\n findAvailableRemoteTablesByServerId(input: $input) {\n name\n schema\n status\n }\n }\n"]; +export function graphql(source: "\n \n mutation syncRemoteTable($input: RemoteTableInput!) {\n syncRemoteTable(input: $input) {\n ...RemoteTableFields\n }\n }\n"): (typeof documents)["\n \n mutation syncRemoteTable($input: RemoteTableInput!) {\n syncRemoteTable(input: $input) {\n ...RemoteTableFields\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n \n mutation unsyncRemoteTable($input: RemoteTableInput!) {\n unsyncRemoteTable(input: $input) {\n ...RemoteTableFields\n }\n }\n"): (typeof documents)["\n \n mutation unsyncRemoteTable($input: RemoteTableInput!) {\n unsyncRemoteTable(input: $input) {\n ...RemoteTableFields\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n \n query GetManyDatabaseConnections($input: RemoteServerTypeInput!) {\n findManyRemoteServersByType(input: $input) {\n ...RemoteServerFields\n }\n }\n"): (typeof documents)["\n \n query GetManyDatabaseConnections($input: RemoteServerTypeInput!) {\n findManyRemoteServersByType(input: $input) {\n ...RemoteServerFields\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n \n query GetManyRemoteTables($input: RemoteServerIdInput!) {\n findAvailableRemoteTablesByServerId(input: $input) {\n ...RemoteTableFields\n }\n }\n"): (typeof documents)["\n \n query GetManyRemoteTables($input: RemoteServerIdInput!) {\n findAvailableRemoteTablesByServerId(input: $input) {\n ...RemoteTableFields\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 891b9aebe..1ad5b50b4 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -356,12 +356,13 @@ export type Mutation = { impersonate: Verify; renewToken: AuthTokens; signUp: LoginToken; + syncRemoteTable: RemoteTable; track: Analytics; + unsyncRemoteTable: RemoteTable; updateBillingSubscription: UpdateBillingEntity; updateOneField: Field; updateOneObject: Object; updatePasswordViaResetToken: InvalidatePassword; - updateRemoteTableSyncStatus: RemoteTable; updateWorkspace: Workspace; uploadFile: Scalars['String']['output']; uploadImage: Scalars['String']['output']; @@ -473,12 +474,22 @@ export type MutationSignUpArgs = { }; +export type MutationSyncRemoteTableArgs = { + input: RemoteTableInput; +}; + + export type MutationTrackArgs = { data: Scalars['JSON']['input']; type: Scalars['String']['input']; }; +export type MutationUnsyncRemoteTableArgs = { + input: RemoteTableInput; +}; + + export type MutationUpdateOneFieldArgs = { input: UpdateOneFieldMetadataInput; }; @@ -495,11 +506,6 @@ export type MutationUpdatePasswordViaResetTokenArgs = { }; -export type MutationUpdateRemoteTableSyncStatusArgs = { - input: RemoteTableInput; -}; - - export type MutationUpdateWorkspaceArgs = { data: UpdateWorkspaceInput; }; @@ -786,7 +792,6 @@ export type RemoteTableInput = { name: Scalars['String']['input']; remoteServerId: Scalars['ID']['input']; schema: Scalars['String']['input']; - status: RemoteTableStatus; }; /** Status of the table */ @@ -1194,6 +1199,8 @@ export type RelationEdge = { export type RemoteServerFieldsFragment = { __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any }; +export type RemoteTableFieldsFragment = { __typename?: 'RemoteTable', name: string, schema: string, status: RemoteTableStatus }; + export type CreateServerMutationVariables = Exact<{ input: CreateRemoteServerInput; }>; @@ -1208,12 +1215,19 @@ export type DeleteServerMutationVariables = Exact<{ export type DeleteServerMutation = { __typename?: 'Mutation', deleteOneRemoteServer: { __typename?: 'RemoteServer', id: string } }; -export type GetManyDatabaseConnectionTablesQueryVariables = Exact<{ - input: RemoteServerIdInput; +export type SyncRemoteTableMutationVariables = Exact<{ + input: RemoteTableInput; }>; -export type GetManyDatabaseConnectionTablesQuery = { __typename?: 'Query', findAvailableRemoteTablesByServerId: Array<{ __typename?: 'RemoteTable', name: string, schema: string, status: RemoteTableStatus }> }; +export type SyncRemoteTableMutation = { __typename?: 'Mutation', syncRemoteTable: { __typename?: 'RemoteTable', name: string, schema: string, status: RemoteTableStatus } }; + +export type UnsyncRemoteTableMutationVariables = Exact<{ + input: RemoteTableInput; +}>; + + +export type UnsyncRemoteTableMutation = { __typename?: 'Mutation', unsyncRemoteTable: { __typename?: 'RemoteTable', name: string, schema: string, status: RemoteTableStatus } }; export type GetManyDatabaseConnectionsQueryVariables = Exact<{ input: RemoteServerTypeInput; @@ -1222,6 +1236,13 @@ export type GetManyDatabaseConnectionsQueryVariables = Exact<{ export type GetManyDatabaseConnectionsQuery = { __typename?: 'Query', findManyRemoteServersByType: Array<{ __typename?: 'RemoteServer', id: string, createdAt: any, foreignDataWrapperId: string, foreignDataWrapperOptions?: any | null, foreignDataWrapperType: string, updatedAt: any }> }; +export type GetManyRemoteTablesQueryVariables = Exact<{ + input: RemoteServerIdInput; +}>; + + +export type GetManyRemoteTablesQuery = { __typename?: 'Query', findAvailableRemoteTablesByServerId: Array<{ __typename?: 'RemoteTable', name: string, schema: string, status: RemoteTableStatus }> }; + export type GetOneDatabaseConnectionQueryVariables = Exact<{ input: RemoteServerIdInput; }>; @@ -1289,10 +1310,13 @@ 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 } } | null, toRelationMetadata?: { __typename?: 'relation', id: any, relationType: RelationMetadataType, fromFieldMetadataId: string, fromObjectMetadata: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, isSystem: 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 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":"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 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 GetManyDatabaseConnectionTablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyDatabaseConnectionTables"},"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":"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 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":"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":"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 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":"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 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; diff --git a/packages/twenty-front/src/modules/databases/graphql/fragments/remoteTableFragment.ts b/packages/twenty-front/src/modules/databases/graphql/fragments/remoteTableFragment.ts new file mode 100644 index 000000000..a14cdfa38 --- /dev/null +++ b/packages/twenty-front/src/modules/databases/graphql/fragments/remoteTableFragment.ts @@ -0,0 +1,9 @@ +import { gql } from '@apollo/client'; + +export const REMOTE_TABLE_FRAGMENT = gql` + fragment RemoteTableFields on RemoteTable { + name + schema + status + } +`; diff --git a/packages/twenty-front/src/modules/databases/graphql/mutations/syncRemoteTable.ts b/packages/twenty-front/src/modules/databases/graphql/mutations/syncRemoteTable.ts new file mode 100644 index 000000000..acb457467 --- /dev/null +++ b/packages/twenty-front/src/modules/databases/graphql/mutations/syncRemoteTable.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { REMOTE_TABLE_FRAGMENT } from '@/databases/graphql/fragments/remoteTableFragment'; + +export const SYNC_REMOTE_TABLE = gql` + ${REMOTE_TABLE_FRAGMENT} + mutation syncRemoteTable($input: RemoteTableInput!) { + syncRemoteTable(input: $input) { + ...RemoteTableFields + } + } +`; diff --git a/packages/twenty-front/src/modules/databases/graphql/mutations/unsyncRemoteTable.ts b/packages/twenty-front/src/modules/databases/graphql/mutations/unsyncRemoteTable.ts new file mode 100644 index 000000000..300696154 --- /dev/null +++ b/packages/twenty-front/src/modules/databases/graphql/mutations/unsyncRemoteTable.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { REMOTE_TABLE_FRAGMENT } from '@/databases/graphql/fragments/remoteTableFragment'; + +export const UNSYNC_REMOTE_TABLE = gql` + ${REMOTE_TABLE_FRAGMENT} + mutation unsyncRemoteTable($input: RemoteTableInput!) { + unsyncRemoteTable(input: $input) { + ...RemoteTableFields + } + } +`; diff --git a/packages/twenty-front/src/modules/databases/graphql/queries/findManyDatabaseConnectionTables.ts b/packages/twenty-front/src/modules/databases/graphql/queries/findManyDatabaseConnectionTables.ts deleted file mode 100644 index 3d8342129..000000000 --- a/packages/twenty-front/src/modules/databases/graphql/queries/findManyDatabaseConnectionTables.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { gql } from '@apollo/client'; - -export const GET_MANY_DATABASE_CONNECTION_TABLES = gql` - query GetManyDatabaseConnectionTables($input: RemoteServerIdInput!) { - findAvailableRemoteTablesByServerId(input: $input) { - name - schema - status - } - } -`; diff --git a/packages/twenty-front/src/modules/databases/graphql/queries/findManyRemoteTables.ts b/packages/twenty-front/src/modules/databases/graphql/queries/findManyRemoteTables.ts new file mode 100644 index 000000000..69eca2bf8 --- /dev/null +++ b/packages/twenty-front/src/modules/databases/graphql/queries/findManyRemoteTables.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { REMOTE_TABLE_FRAGMENT } from '@/databases/graphql/fragments/remoteTableFragment'; + +export const GET_MANY_REMOTE_TABLES = gql` + ${REMOTE_TABLE_FRAGMENT} + query GetManyRemoteTables($input: RemoteServerIdInput!) { + findAvailableRemoteTablesByServerId(input: $input) { + ...RemoteTableFields + } + } +`; diff --git a/packages/twenty-front/src/modules/databases/hooks/useGetDatabaseConnectionTables.ts b/packages/twenty-front/src/modules/databases/hooks/useGetDatabaseConnectionTables.ts index 94171c16d..6548222e6 100644 --- a/packages/twenty-front/src/modules/databases/hooks/useGetDatabaseConnectionTables.ts +++ b/packages/twenty-front/src/modules/databases/hooks/useGetDatabaseConnectionTables.ts @@ -1,10 +1,10 @@ import { useQuery } from '@apollo/client'; -import { GET_MANY_DATABASE_CONNECTION_TABLES } from '@/databases/graphql/queries/findManyDatabaseConnectionTables'; +import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables'; import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient'; import { - GetManyDatabaseConnectionTablesQuery, - GetManyDatabaseConnectionTablesQueryVariables, + GetManyRemoteTablesQuery, + GetManyRemoteTablesQueryVariables, } from '~/generated-metadata/graphql'; type UseGetDatabaseConnectionTablesParams = { @@ -19,9 +19,9 @@ export const useGetDatabaseConnectionTables = ({ const apolloMetadataClient = useApolloMetadataClient(); const { data } = useQuery< - GetManyDatabaseConnectionTablesQuery, - GetManyDatabaseConnectionTablesQueryVariables - >(GET_MANY_DATABASE_CONNECTION_TABLES, { + GetManyRemoteTablesQuery, + GetManyRemoteTablesQueryVariables + >(GET_MANY_REMOTE_TABLES, { client: apolloMetadataClient ?? undefined, skip: skip || !apolloMetadataClient, variables: { diff --git a/packages/twenty-front/src/modules/databases/hooks/useSyncRemoteTable.ts b/packages/twenty-front/src/modules/databases/hooks/useSyncRemoteTable.ts new file mode 100644 index 000000000..ee891a4fb --- /dev/null +++ b/packages/twenty-front/src/modules/databases/hooks/useSyncRemoteTable.ts @@ -0,0 +1,40 @@ +import { useCallback } from 'react'; +import { ApolloClient, useMutation } from '@apollo/client'; +import { getOperationName } from '@apollo/client/utilities'; + +import { SYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/syncRemoteTable'; +import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables'; +import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient'; +import { + RemoteTableInput, + SyncRemoteTableMutation, + SyncRemoteTableMutationVariables, +} from '~/generated-metadata/graphql'; + +export const useSyncRemoteTable = () => { + const apolloMetadataClient = useApolloMetadataClient(); + + const [mutate] = useMutation< + SyncRemoteTableMutation, + SyncRemoteTableMutationVariables + >(SYNC_REMOTE_TABLE, { + client: apolloMetadataClient ?? ({} as ApolloClient), + }); + + const syncRemoteTable = useCallback( + async (input: RemoteTableInput) => { + return await mutate({ + variables: { + input, + }, + awaitRefetchQueries: true, + refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''], + }); + }, + [mutate], + ); + + return { + syncRemoteTable, + }; +}; diff --git a/packages/twenty-front/src/modules/databases/hooks/useUnsyncRemoteTable.ts b/packages/twenty-front/src/modules/databases/hooks/useUnsyncRemoteTable.ts new file mode 100644 index 000000000..97fd3a363 --- /dev/null +++ b/packages/twenty-front/src/modules/databases/hooks/useUnsyncRemoteTable.ts @@ -0,0 +1,40 @@ +import { useCallback } from 'react'; +import { ApolloClient, useMutation } from '@apollo/client'; +import { getOperationName } from '@apollo/client/utilities'; + +import { UNSYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/unsyncRemoteTable'; +import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables'; +import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient'; +import { + RemoteTableInput, + UnsyncRemoteTableMutation, + UnsyncRemoteTableMutationVariables, +} from '~/generated-metadata/graphql'; + +export const useUnsyncRemoteTable = () => { + const apolloMetadataClient = useApolloMetadataClient(); + + const [mutate] = useMutation< + UnsyncRemoteTableMutation, + UnsyncRemoteTableMutationVariables + >(UNSYNC_REMOTE_TABLE, { + client: apolloMetadataClient ?? ({} as ApolloClient), + }); + + const unsyncRemoteTable = useCallback( + async (input: RemoteTableInput) => { + return await mutate({ + variables: { + input, + }, + awaitRefetchQueries: true, + refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''], + }); + }, + [mutate], + ); + + return { + unsyncRemoteTable, + }; +}; diff --git a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseTablesListCard.tsx b/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseTablesListCard.tsx index 1f17036f4..a35427d88 100644 --- a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseTablesListCard.tsx +++ b/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationDatabaseTablesListCard.tsx @@ -1,10 +1,13 @@ -import { Controller, useFormContext } from 'react-hook-form'; +import { useCallback, useState } from 'react'; import styled from '@emotion/styled'; import { z } from 'zod'; +import { useSyncRemoteTable } from '@/databases/hooks/useSyncRemoteTable'; +import { useUnsyncRemoteTable } from '@/databases/hooks/useUnsyncRemoteTable'; import { SettingsListCard } from '@/settings/components/SettingsListCard'; -import { Toggle } from '@/ui/input/components/Toggle'; +import { SettingsIntegrationRemoteTableSyncStatusToggle } from '@/settings/integrations/components/SettingsIntegrationRemoteTableSyncStatusToggle'; import { RemoteTable, RemoteTableStatus } from '~/generated-metadata/graphql'; +import { isDefined } from '~/utils/isDefined'; export const settingsIntegrationsDatabaseTablesSchema = z.object({ syncedTablesByName: z.record(z.boolean()), @@ -15,6 +18,7 @@ export type SettingsIntegrationsDatabaseTablesFormValues = z.infer< >; type SettingsIntegrationDatabaseTablesListCardProps = { + connectionId: string; tables: RemoteTable[]; }; @@ -25,27 +29,63 @@ const StyledRowRightContainer = styled.div` `; export const SettingsIntegrationDatabaseTablesListCard = ({ + connectionId, tables, }: SettingsIntegrationDatabaseTablesListCardProps) => { - const { control } = - useFormContext(); + const { syncRemoteTable } = useSyncRemoteTable(); + const { unsyncRemoteTable } = useUnsyncRemoteTable(); + // We need to use a state because the table status update re-render the whole list of toggles + const [items] = useState( + tables.map((table) => ({ + id: table.name, + ...table, + })), + ); + + const onSyncUpdate = useCallback( + async (isSynced: boolean, tableName: string) => { + const table = items.find((table) => table.name === tableName); + + if (!isDefined(table)) return; + + if (isSynced) { + await syncRemoteTable({ + remoteServerId: connectionId, + name: tableName, + schema: table.schema, + }); + } else { + await unsyncRemoteTable({ + remoteServerId: connectionId, + name: tableName, + schema: table.schema, + }); + } + }, + [connectionId, syncRemoteTable, items, unsyncRemoteTable], + ); + + const rowRightComponent = useCallback( + ({ + item, + }: { + item: { id: string; name: string; status: RemoteTableStatus }; + }) => ( + + + + ), + [onSyncUpdate], + ); return ( ({ id: table.name, ...table }))} - RowRightComponent={({ item: table }) => ( - - ( - - )} - /> - - )} - getItemLabel={(table) => table.name} + items={items} + RowRightComponent={rowRightComponent} + getItemLabel={(table) => table.id} /> ); }; diff --git a/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationRemoteTableSyncStatusToggle.tsx b/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationRemoteTableSyncStatusToggle.tsx new file mode 100644 index 000000000..3b4e403ce --- /dev/null +++ b/packages/twenty-front/src/modules/settings/integrations/components/SettingsIntegrationRemoteTableSyncStatusToggle.tsx @@ -0,0 +1,29 @@ +import { useState } from 'react'; + +import { Toggle } from '@/ui/input/components/Toggle'; +import { RemoteTableStatus } from '~/generated-metadata/graphql'; + +export const SettingsIntegrationRemoteTableSyncStatusToggle = ({ + table, + onSyncUpdate, +}: { + table: { id: string; name: string; status: RemoteTableStatus }; + onSyncUpdate: (value: boolean, tableName: string) => Promise; +}) => { + const [isToggleLoading, setIsToggleLoading] = useState(false); + + const onChange = async (newValue: boolean) => { + if (isToggleLoading) return; + setIsToggleLoading(true); + await onSyncUpdate(newValue, table.name); + setIsToggleLoading(false); + }; + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx index a4a636cf9..2efd7b06e 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Toggle.tsx @@ -10,6 +10,7 @@ type ContainerProps = { isOn: boolean; color?: string; toggleSize: ToggleSize; + disabled?: boolean; }; const StyledContainer = styled.div` @@ -22,6 +23,8 @@ const StyledContainer = styled.div` height: ${({ toggleSize }) => (toggleSize === 'small' ? 16 : 20)}px; transition: background-color 0.3s ease; width: ${({ toggleSize }) => (toggleSize === 'small' ? 24 : 32)}px; + opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; + pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')}; `; const StyledCircle = styled(motion.div)<{ @@ -39,6 +42,7 @@ export type ToggleProps = { color?: string; toggleSize?: ToggleSize; className?: string; + disabled?: boolean; }; export const Toggle = ({ @@ -47,6 +51,7 @@ export const Toggle = ({ color, toggleSize = 'medium', className, + disabled, }: ToggleProps) => { const [isOn, setIsOn] = useState(value ?? false); @@ -77,6 +82,7 @@ export const Toggle = ({ color={color} toggleSize={toggleSize} className={className} + disabled={disabled} > { skip: !isIntegrationAvailable || !connection, }); - const formConfig = useForm({ - mode: 'onTouched', - resolver: zodResolver(settingsIntegrationsDatabaseTablesSchema), - }); - if (!isIntegrationAvailable || !connection) return null; const settingsIntegrationsPagePath = getSettingsPagePath( @@ -93,41 +82,43 @@ export const SettingsIntegrationDatabaseConnection = () => { const connectionName = getConnectionDbName({ integration, connection }); return ( - // eslint-disable-next-line react/jsx-props-no-spreading - - - - + + +
+ + -
- - +
+ + {!!tables.length && ( + -
-
- - -
- - - + )} +
+ + ); }; 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 637ca3105..7caa28a3e 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 @@ -126,11 +126,10 @@ export class RemoteServerService { if (remoteTablesToRemove.length) { for (const remoteTable of remoteTablesToRemove) { - await this.remoteTableService.updateRemoteTableSyncStatus( + await this.remoteTableService.unsyncRemoteTable( { remoteServerId: id, name: remoteTable.name, - status: RemoteTableStatus.NOT_SYNCED, }, workspaceId, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input.ts index 32116d4d9..178e5ad80 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input.ts @@ -1,9 +1,5 @@ import { InputType, Field, ID } from '@nestjs/graphql'; -import { IsEnum } from 'class-validator'; - -import { RemoteTableStatus } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto'; - @InputType() export class RemoteTableInput { @Field(() => ID) @@ -12,10 +8,6 @@ export class RemoteTableInput { @Field(() => String) name: string; - @IsEnum(RemoteTableStatus) - @Field(() => RemoteTableStatus) - status: RemoteTableStatus; - @Field(() => String) schema?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto.ts index 2997f3532..e44b2acc1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto.ts @@ -22,5 +22,5 @@ export class RemoteTableDTO { status: RemoteTableStatus; @Field(() => String) - schema: string; + schema?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/remote-postgres-table.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/remote-postgres-table.service.ts index 1814090c7..46249a202 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/remote-postgres-table.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-postgres-table/remote-postgres-table.service.ts @@ -40,14 +40,11 @@ export class RemotePostgresTableService { await dataSource.destroy(); - return columns.map( - (column) => - ({ - columnName: column.column_name, - dataType: column.data_type, - udtName: column.udt_name, - }) as RemoteTableColumn, - ); + return columns.map((column) => ({ + columnName: column.column_name, + dataType: column.data_type, + udtName: column.udt_name, + })); } public async fetchTablesFromRemotePostgresSchema( @@ -78,12 +75,9 @@ export class RemotePostgresTableService { await dataSource.destroy(); - return remotePostgresTables.map( - (table) => - ({ - tableName: table.table_name, - tableSchema: table.table_schema, - }) as RemoteTable, - ); + return remotePostgresTables.map((table) => ({ + tableName: table.table_name, + tableSchema: table.table_schema, + })); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts index 433dd5fd9..5278c58c9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts @@ -10,7 +10,7 @@ import { RemoteTableDTO } from 'src/engine/metadata-modules/remote-server/remote import { RemoteTableService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.service'; @UseGuards(JwtAuthGuard) -@Resolver(() => RemoteTableDTO) +@Resolver() export class RemoteTableResolver { constructor(private readonly remoteTableService: RemoteTableService) {} @@ -26,13 +26,18 @@ export class RemoteTableResolver { } @Mutation(() => RemoteTableDTO) - async updateRemoteTableSyncStatus( + async syncRemoteTable( @Args('input') input: RemoteTableInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteTableService.updateRemoteTableSyncStatus( - input, - workspaceId, - ); + return this.remoteTableService.syncRemoteTable(input, workspaceId); + } + + @Mutation(() => RemoteTableDTO) + async unsyncRemoteTable( + @Args('input') input: RemoteTableInput, + @AuthWorkspace() { id: workspaceId }: Workspace, + ) { + return this.remoteTableService.unsyncRemoteTable(input, workspaceId); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts index 18ad1f14e..257aaf5d1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts @@ -7,7 +7,10 @@ import { RemoteServerType, RemoteServerEntity, } from 'src/engine/metadata-modules/remote-server/remote-server.entity'; -import { RemoteTableStatus } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto'; +import { + RemoteTableDTO, + RemoteTableStatus, +} from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto'; import { isPostgreSQLIntegrationEnabled, mapUdtNameToFieldType, @@ -86,10 +89,7 @@ export class RemoteTableService { })); } - public async updateRemoteTableSyncStatus( - input: RemoteTableInput, - workspaceId: string, - ) { + public async syncRemoteTable(input: RemoteTableInput, workspaceId: string) { const remoteServer = await this.remoteServerRepository.findOne({ where: { id: input.remoteServerId, @@ -101,31 +101,33 @@ export class RemoteTableService { throw new NotFoundException('Remote server does not exist'); } - switch (input.status) { - case RemoteTableStatus.SYNCED: - await this.createForeignTableAndMetadata( - input, - remoteServer, - workspaceId, - ); - break; - case RemoteTableStatus.NOT_SYNCED: - await this.removeForeignTableAndMetadata(input, workspaceId); - break; - default: - throw new Error('Unsupported remote table status'); - } + const remoteTable = await this.createForeignTableAndMetadata( + input, + remoteServer, + workspaceId, + ); await this.workspaceCacheVersionService.incrementVersion(workspaceId); - return input; + return remoteTable; + } + + public async unsyncRemoteTable(input: RemoteTableInput, workspaceId: string) { + const remoteTable = await this.removeForeignTableAndMetadata( + input, + workspaceId, + ); + + await this.workspaceCacheVersionService.incrementVersion(workspaceId); + + return remoteTable; } private async createForeignTableAndMetadata( input: RemoteTableInput, remoteServer: RemoteServerEntity, workspaceId: string, - ) { + ): Promise { if (!input.schema) { throw new Error('Schema is required for syncing remote table'); } @@ -198,7 +200,7 @@ export class RemoteTableService { icon: 'IconUser', isRemote: true, remoteTablePrimaryKeyColumnType: remoteTableIdColumn.udtName, - } as CreateObjectInput); + } satisfies CreateObjectInput); for (const column of remoteTableColumns) { const field = await this.fieldMetadataService.createOne({ @@ -212,7 +214,7 @@ export class RemoteTableService { isRemoteCreation: true, isNullable: true, icon: 'IconUser', - } as CreateFieldInput); + } satisfies CreateFieldInput); if (column.columnName === 'id') { await this.objectMetadataService.updateOne(objectMetadata.id, { @@ -220,12 +222,18 @@ export class RemoteTableService { }); } } + + return { + name: input.name, + schema: input.schema, + status: RemoteTableStatus.SYNCED, + }; } private async removeForeignTableAndMetadata( input: RemoteTableInput, workspaceId: string, - ) { + ): Promise { const remoteTableName = getRemoteTableName(input.name); const currentForeignTableNames = @@ -261,6 +269,12 @@ export class RemoteTableService { await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( workspaceId, ); + + return { + name: input.name, + schema: input.schema, + status: RemoteTableStatus.NOT_SYNCED, + }; } private async fetchTableColumnsSchema(