[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
This commit is contained in:
Marie
2024-04-30 17:46:30 +02:00
committed by GitHub
parent 3bf9045990
commit 1b2ed80c1c
39 changed files with 727 additions and 195 deletions

View File

@ -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={<SettingsIntegrationNewDatabaseConnection />}
/>
<Route
path={SettingsPath.IntegrationEditDatabaseConnection}
element={<SettingsIntegrationEditDatabaseConnection />}
/>
<Route
path={SettingsPath.IntegrationDatabaseConnection}
element={<SettingsIntegrationDatabaseConnection />}
element={<SettingsIntegrationShowDatabaseConnection />}
/>
<Route
path={SettingsPath.ObjectNewFieldStep1}

View File

@ -13,12 +13,13 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
* Therefore it is highly recommended to use the babel or swc plugin for production.
*/
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 RemoteServerFields on RemoteServer {\n id\n createdAt\n foreignDataWrapperId\n foreignDataWrapperOptions\n foreignDataWrapperType\n userMappingOptions {\n username\n }\n updatedAt\n schema\n }\n": types.RemoteServerFieldsFragmentDoc,
"\n fragment RemoteTableFields on RemoteTable {\n id\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 \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 mutation updateServer($input: UpdateRemoteServerInput!) {\n updateOneRemoteServer(input: $input) {\n ...RemoteServerFields\n }\n }\n": types.UpdateServerDocument,
"\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,
@ -49,7 +50,7 @@ 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"];
export function graphql(source: "\n fragment RemoteServerFields on RemoteServer {\n id\n createdAt\n foreignDataWrapperId\n foreignDataWrapperOptions\n foreignDataWrapperType\n userMappingOptions {\n username\n }\n updatedAt\n schema\n }\n"): (typeof documents)["\n fragment RemoteServerFields on RemoteServer {\n id\n createdAt\n foreignDataWrapperId\n foreignDataWrapperOptions\n foreignDataWrapperType\n userMappingOptions {\n username\n }\n updatedAt\n schema\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@ -70,6 +71,10 @@ export function graphql(source: "\n \n mutation syncRemoteTable($input: Remote
* 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 mutation updateServer($input: UpdateRemoteServerInput!) {\n updateOneRemoteServer(input: $input) {\n ...RemoteServerFields\n }\n }\n"): (typeof documents)["\n \n mutation updateServer($input: UpdateRemoteServerInput!) {\n updateOneRemoteServer(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.
*/

View File

@ -220,7 +220,8 @@ export type CreateRelationInput = {
export type CreateRemoteServerInput = {
foreignDataWrapperOptions: Scalars['JSON']['input'];
foreignDataWrapperType: Scalars['String']['input'];
userMappingOptions?: InputMaybe<Scalars['JSON']['input']>;
schema?: InputMaybe<Scalars['String']['input']>;
userMappingOptions?: InputMaybe<UserMappingOptionsInput>;
};
export type CursorPaging = {
@ -331,6 +332,11 @@ export type FullName = {
lastName: Scalars['String']['output'];
};
export type GetUserMappingOptions = {
__typename?: 'GetUserMappingOptions';
username?: Maybe<Scalars['String']['output']>;
};
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<Scalars['JSON']['output']>;
foreignDataWrapperType: Scalars['String']['output'];
id: Scalars['ID']['output'];
schema?: Maybe<Scalars['String']['output']>;
updatedAt: Scalars['DateTime']['output'];
userMappingOptions?: Maybe<GetUserMappingOptions>;
};
export type RemoteServerIdInput = {
@ -993,6 +1007,13 @@ export type UpdateOneObjectInput = {
update: UpdateObjectInput;
};
export type UpdateRemoteServerInput = {
foreignDataWrapperOptions?: InputMaybe<Scalars['JSON']['input']>;
id: Scalars['String']['input'];
schema?: InputMaybe<Scalars['String']['input']>;
userMappingOptions?: InputMaybe<UserMappingOptionsInput>;
};
export type UpdateWorkspaceInput = {
allowImpersonation?: InputMaybe<Scalars['Boolean']['input']>;
displayName?: InputMaybe<Scalars['String']['input']>;
@ -1037,6 +1058,11 @@ export type UserExists = {
exists: Scalars['Boolean']['output'];
};
export type UserMappingOptionsInput = {
password?: InputMaybe<Scalars['String']['input']>;
username?: InputMaybe<Scalars['String']['input']>;
};
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<RemoteServerFieldsFragment, unknown>;
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<RemoteServerFieldsFragment, unknown>;
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<RemoteTableFieldsFragment, unknown>;
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<CreateServerMutation, CreateServerMutationVariables>;
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<CreateServerMutation, CreateServerMutationVariables>;
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<DeleteServerMutation, DeleteServerMutationVariables>;
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<SyncRemoteTableMutation, SyncRemoteTableMutationVariables>;
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<UnsyncRemoteTableMutation, UnsyncRemoteTableMutationVariables>;
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<GetManyDatabaseConnectionsQuery, GetManyDatabaseConnectionsQueryVariables>;
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<UpdateServerMutation, UpdateServerMutationVariables>;
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<GetManyDatabaseConnectionsQuery, GetManyDatabaseConnectionsQueryVariables>;
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<GetManyRemoteTablesQuery, GetManyRemoteTablesQueryVariables>;
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<GetOneDatabaseConnectionQuery, GetOneDatabaseConnectionQueryVariables>;
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<GetOneDatabaseConnectionQuery, GetOneDatabaseConnectionQueryVariables>;
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<CreateOneObjectMetadataItemMutation, CreateOneObjectMetadataItemMutationVariables>;
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<CreateOneFieldMetadataItemMutation, CreateOneFieldMetadataItemMutationVariables>;
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<CreateOneRelationMetadataMutation, CreateOneRelationMetadataMutationVariables>;

View File

@ -239,6 +239,11 @@ export type FullName = {
lastName: Scalars['String'];
};
export type GetUserMappingOptions = {
__typename?: 'GetUserMappingOptions';
username?: Maybe<Scalars['String']>;
};
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<GetUserMappingOptions>;
};
export type RemoteTable = {

View File

@ -7,6 +7,10 @@ export const DATABASE_CONNECTION_FRAGMENT = gql`
foreignDataWrapperId
foreignDataWrapperOptions
foreignDataWrapperType
userMappingOptions {
username
}
updatedAt
schema
}
`;

View File

@ -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
}
}
`;

View File

@ -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<any>),
});
const updateOneDatabaseConnection = async (
input: UpdateRemoteServerInput,
) => {
return await mutate({
variables: {
input,
},
});
};
return {
updateOneDatabaseConnection,
};
};

View File

@ -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<SettingsIntegrationPostgreSQLConnectionFormValues>();
@ -49,15 +57,24 @@ export const SettingsIntegrationPostgreSQLConnectionForm = () => {
key={name}
name={name}
control={control}
render={({ field: { onChange, value } }) => (
<TextInput
label={label}
value={value}
onChange={onChange}
fullWidth
type={type}
/>
)}
render={({ field: { onChange, value } }) => {
return (
<TextInput
autoComplete="new-password"
label={label}
value={value}
onChange={onChange}
fullWidth
type={type}
disabled={disabled}
placeholder={
passwordPlaceholder && name === 'password'
? passwordPlaceholder
: ''
}
/>
);
}}
/>
))}
</StyledInputsContainer>

View File

@ -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 (
<>
<Breadcrumb
links={[
{
children: 'Integrations',
href: settingsIntegrationsPagePath,
},
{
children: integration.text,
href: `${settingsIntegrationsPagePath}/${databaseKey}`,
},
{ children: connectionName },
]}
/>
<Section>
<H2Title title="About" description="About this remote object" />
<SettingsIntegrationDatabaseConnectionSummaryCard
databaseLogoUrl={integration.from.image}
connectionId={connection.id}
connectionName={connectionName}
onRemove={deleteConnection}
onEdit={onEdit}
/>
</Section>
<Section>
<H2Title
title="Tables"
description="Select the tables that should be tracked"
/>
{!!tables?.length && (
<SettingsIntegrationDatabaseTablesListCard
connectionId={connection.id}
tables={tables}
/>
)}
</Section>
</>
);
};

View File

@ -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';

View File

@ -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 (
<SettingsIntegrationEditDatabaseConnectionContent
connection={connection}
integration={integration}
databaseKey={databaseKey}
tables={tables}
/>
);
};

View File

@ -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<SettingsIntegrationEditConnectionFormValues>({
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 (
<>
<FormProvider
// eslint-disable-next-line react/jsx-props-no-spreading
{...formConfig}
>
<SettingsHeaderContainer>
<Breadcrumb
links={[
{
children: 'Integrations',
href: settingsIntegrationsPagePath,
},
{
children: integration.text,
href: `${settingsIntegrationsPagePath}/${databaseKey}`,
},
{ children: connectionName },
]}
/>
<SaveAndCancelButtons
isSaveDisabled={!canSave}
onCancel={() =>
navigate(`${settingsIntegrationsPagePath}/${databaseKey}`)
}
onSave={handleSave}
/>
</SettingsHeaderContainer>
{hasSyncedTables && (
<Info
text={
'You cannot edit this connection because it has tracked tables.\nIf you need to make changes, please create a new connection or unsync the tables first.'
}
accent={'blue'}
/>
)}
{databaseKey === 'postgresql' ? (
<Section>
<H2Title
title="Edit PostgreSQL Connection"
description="Edit the information to connect your PostgreSQL database"
/>
<SettingsIntegrationPostgreSQLConnectionForm
disabled={hasSyncedTables}
passwordPlaceholder="••••••"
/>
</Section>
) : null}
</FormProvider>
</>
);
};

View File

@ -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 };
};

View File

@ -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}`);
}
};

View File

@ -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',

View File

@ -9,8 +9,8 @@ export type InfoAccent = 'blue' | 'danger';
export type InfoProps = {
accent?: InfoAccent;
text: string;
buttonTitle: string;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
buttonTitle?: string;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
};
const StyledTextContainer = styled.div`
@ -54,12 +54,14 @@ export const Info = ({
<IconInfoCircle size={theme.icon.size.md} />
{text}
</StyledTextContainer>
<Button
title={buttonTitle}
onClick={onClick}
size={'small'}
variant={'secondary'}
/>
{buttonTitle && onClick && (
<Button
title={buttonTitle}
onClick={onClick}
size={'small'}
variant={'secondary'}
/>
)}
</StyledInfo>
);
};

View File

@ -123,6 +123,7 @@ const TextInputV2Component = (
disabled,
tabIndex,
RightIcon,
autoComplete,
}: TextInputV2ComponentProps,
// eslint-disable-next-line @nx/workspace-component-props-naming
ref: ForwardedRef<HTMLInputElement>,
@ -143,7 +144,7 @@ const TextInputV2Component = (
{label && <StyledLabel>{label + (required ? '*' : '')}</StyledLabel>}
<StyledInputContainer>
<StyledInput
autoComplete="off"
autoComplete={autoComplete || 'off'}
ref={combinedRef}
tabIndex={tabIndex ?? 0}
onFocus={onFocus}

View File

@ -14,7 +14,7 @@ const StyledWrapper = styled.nav`
font-size: ${({ theme }) => 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)`

View File

@ -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';

View File

@ -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 (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<Breadcrumb
links={[
{
children: 'Integrations',
href: settingsIntegrationsPagePath,
},
{
children: integration.text,
href: `${settingsIntegrationsPagePath}/${databaseKey}`,
},
{ children: connectionName },
]}
/>
<Section>
<H2Title title="About" description="About this remote object" />
<SettingsIntegrationDatabaseConnectionSummaryCard
databaseLogoUrl={integration.from.image}
connectionId={connectionId}
connectionName={connectionName}
onRemove={deleteConnection}
/>
</Section>
<Section>
<H2Title
title="Tables"
description="Select the tables that should be tracked"
/>
{!!tables.length && (
<SettingsIntegrationDatabaseTablesListCard
connectionId={connectionId}
tables={tables}
/>
)}
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};

View File

@ -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}
/>
<MenuItem
LeftIcon={IconPencil}
text="Edit"
onClick={onEdit}
/>
</DropdownMenuItemsContainer>
</DropdownMenu>
}

View File

@ -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 (
<>
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<SettingsIntegrationEditDatabaseConnectionContainer />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</>
);
};

View File

@ -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
<FormProvider {...formConfig}>
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<FormProvider
// eslint-disable-next-line react/jsx-props-no-spreading
{...formConfig}
>
<SettingsHeaderContainer>
<Breadcrumb
links={[
@ -151,8 +153,8 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
<SettingsIntegrationPostgreSQLConnectionForm />
</Section>
) : null}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</FormProvider>
</FormProvider>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};

View File

@ -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 (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<SettingsIntegrationDatabaseConnectionShowContainer />
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};

View File

@ -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<PageDecoratorArgs> = {
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<typeof SettingsIntegrationEditDatabaseConnection>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
sleep(100);
await canvas.findByText('Edit PostgreSQL Connection');
},
};

View File

@ -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<PageDecoratorArgs> = {
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<PageDecoratorArgs> = {
export default meta;
export type Story = StoryObj<typeof SettingsIntegrationDatabaseConnection>;
export type Story = StoryObj<typeof SettingsIntegrationShowDatabaseConnection>;
export const Default: Story = {
play: async ({ canvasElement }) => {

View File

@ -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(
`

View File

@ -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',
},
];

View File

@ -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',
},
];

View File

@ -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>,
userMappingOptions: Partial<UserMappingOptionsInput>,
) {
const options = this.buildUpdateUserMappingOptions(userMappingOptions);
@ -82,7 +82,7 @@ export class ForeignDataWrapperQueryFactory {
}
private buildUpdateUserMappingOptions(
userMappingOptions?: Partial<UserMappingOptions>,
userMappingOptions?: Partial<UserMappingOptionsInput>,
) {
const setStatements: string[] = [];

View File

@ -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<T extends RemoteServerType> {
@ -20,7 +19,7 @@ export class CreateRemoteServerInput<T extends RemoteServerType> {
@IsOptional()
@Field(() => UserMappingOptionsInput, { nullable: true })
userMappingOptions?: UserMappingOptions;
userMappingOptions?: UserMappingOptionsInput;
@IsOptional()
@Field(() => String, { nullable: true })

View File

@ -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<T extends RemoteServerType> {
@ -23,6 +24,14 @@ export class RemoteServerDTO<T extends RemoteServerType> {
@Field(() => GraphQLJSON, { nullable: true })
foreignDataWrapperOptions?: ForeignDataWrapperOptions<T>;
@IsOptional()
@Field(() => GetUserMappingOptions, { nullable: true })
userMappingOptions?: GetUserMappingOptions;
@IsOptional()
@Field(() => String, { nullable: true })
schema?: string;
@HideField()
workspaceId: string;

View File

@ -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<T extends RemoteServerType> {
@ -21,5 +20,9 @@ export class UpdateRemoteServerInput<T extends RemoteServerType> {
@IsOptional()
@Field(() => UserMappingOptionsInput, { nullable: true })
userMappingOptions?: Partial<UserMappingOptions>;
userMappingOptions?: Partial<UserMappingOptionsInput>;
@IsOptional()
@Field(() => String, { nullable: true })
schema?: string;
}

View File

@ -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> =
T extends RemoteServerType.POSTGRES_FDW
? PostgresForeignDataWrapperOptions
: never;
export type UserMappingOptions = {
username: string;
password: string;
};
@Entity('remoteServer')
export class RemoteServerEntity<T extends RemoteServerType> {
@PrimaryGeneratedColumn('uuid')

View File

@ -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<T extends RemoteServerType> {
partialRemoteServerWithUpdates,
);
if (partialRemoteServerWithUpdates.foreignDataWrapperOptions) {
if (
!isEmpty(partialRemoteServerWithUpdates.foreignDataWrapperOptions)
) {
const foreignDataWrapperQuery =
this.foreignDataWrapperQueryFactory.updateForeignDataWrapper({
foreignDataWrapperId,
@ -173,7 +176,7 @@ export class RemoteServerService<T extends RemoteServerType> {
await entityManager.query(foreignDataWrapperQuery);
}
if (partialRemoteServerWithUpdates.userMappingOptions) {
if (!isEmpty(partialRemoteServerWithUpdates.userMappingOptions)) {
const userMappingQuery =
this.foreignDataWrapperQueryFactory.updateUserMapping(
foreignDataWrapperId,

View File

@ -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<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
@ -13,7 +15,7 @@ export type DeepPartial<T> = {
const buildUserMappingOptionsQuery = (
parameters: any[],
parametersPositions: object,
userMappingOptions: DeepPartial<UserMappingOptions>,
userMappingOptions: DeepPartial<UserMappingOptionsInput>,
): 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 *`;

View File

@ -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;
}