From 4d479ee8ea5956acd360085610e26fee8f3762f4 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Mon, 20 May 2024 16:37:35 +0200 Subject: [PATCH] Remove relations for remotes (#5455) For remotes, we will only create the foreign key, without the relation metadata. Expected behavior will be: - possible to create an activity. But the remote object will not be displayed in the relations of the activity - the remote objects should not be available in the search for relations Also switched the number settings to an enum, since we now have to handle `BigInt` case. --------- Co-authored-by: Thomas Trompette --- package.json | 1 + ...archMatchesSearchFilterAndToSelectQuery.ts | 14 +- .../input/big-int-filter.input-type.ts | 22 +- .../services/type-mapper.service.ts | 34 +- .../utils/get-number-filter-type.util.ts | 24 + .../utils/get-number-scalar-type.util.ts | 19 + .../field-metadata-settings.interface.ts | 8 +- .../object-metadata.service.ts | 665 +++++++++--------- ...ations-for-custom-object-relations.util.ts | 8 +- ...ations-for-remote-object-relations.util.ts | 124 +--- .../utils/udt-name-mapper.util.ts | 16 +- yarn.lock | 12 + 12 files changed, 461 insertions(+), 486 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util.ts diff --git a/package.json b/package.json index dd802a7df8..55920ec070 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "graphql-fields": "^2.0.3", "graphql-middleware": "^6.1.35", "graphql-rate-limit": "^3.3.0", + "graphql-scalars": "^1.23.0", "graphql-subscriptions": "2.0.0", "graphql-tag": "^2.12.6", "graphql-type-json": "^0.3.2", diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts index cf753a4df7..8d3b4e0761 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts @@ -29,19 +29,19 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ }) => { const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const nonSystemObjectMetadataItems = objectMetadataItems.filter( - ({ isSystem }) => !isSystem, + const selectableObjectMetadataItems = objectMetadataItems.filter( + ({ isSystem, isRemote }) => !isSystem && !isRemote, ); const { searchFilterPerMetadataItemNameSingular } = useSearchFilterPerMetadataItem({ - objectMetadataItems: nonSystemObjectMetadataItems, + objectMetadataItems: selectableObjectMetadataItems, searchFilterValue, }); const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem = Object.fromEntries( - nonSystemObjectMetadataItems + selectableObjectMetadataItems .map(({ nameSingular }) => { const selectedIds = selectedObjectRecordIds .filter( @@ -74,16 +74,16 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ ); const { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({ - objectMetadataItems: nonSystemObjectMetadataItems, + objectMetadataItems: selectableObjectMetadataItems, }); const { limitPerMetadataItem } = useLimitPerMetadataItem({ - objectMetadataItems: nonSystemObjectMetadataItems, + objectMetadataItems: selectableObjectMetadataItems, limit, }); const multiSelectQuery = useGenerateCombinedFindManyRecordsQuery({ - operationSignatures: nonSystemObjectMetadataItems.map( + operationSignatures: selectableObjectMetadataItems.map( (objectMetadataItem) => ({ objectNameSingular: objectMetadataItem.nameSingular, variables: {}, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/big-int-filter.input-type.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/big-int-filter.input-type.ts index fed62dc16f..98cdbae1db 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/big-int-filter.input-type.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/big-int-filter.input-type.ts @@ -1,22 +1,18 @@ -import { - GraphQLInputObjectType, - GraphQLList, - GraphQLNonNull, - GraphQLInt, -} from 'graphql'; +import { GraphQLInputObjectType, GraphQLList, GraphQLNonNull } from 'graphql'; +import { GraphQLBigInt } from 'graphql-scalars'; import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type'; export const BigIntFilterType = new GraphQLInputObjectType({ name: 'BigIntFilter', fields: { - eq: { type: GraphQLInt }, - gt: { type: GraphQLInt }, - gte: { type: GraphQLInt }, - in: { type: new GraphQLList(new GraphQLNonNull(GraphQLInt)) }, - lt: { type: GraphQLInt }, - lte: { type: GraphQLInt }, - neq: { type: GraphQLInt }, + eq: { type: GraphQLBigInt }, + gt: { type: GraphQLBigInt }, + gte: { type: GraphQLBigInt }, + in: { type: new GraphQLList(new GraphQLNonNull(GraphQLBigInt)) }, + lt: { type: GraphQLBigInt }, + lte: { type: GraphQLBigInt }, + neq: { type: GraphQLBigInt }, is: { type: FilterIs }, }, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index 7ab60a4fd5..cb04e53002 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -8,7 +8,6 @@ import { GraphQLID, GraphQLInputObjectType, GraphQLInputType, - GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLScalarType, @@ -26,7 +25,6 @@ import { BooleanFilterType, BigFloatFilterType, RawJsonFilterType, - IntFilterType, } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input'; import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum'; import { @@ -36,6 +34,8 @@ import { import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar'; import { RawJSONScalar } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/raw-json.scalar'; import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type'; +import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util'; +import { getNumberScalarType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util'; export interface TypeOptions { nullable?: boolean; @@ -57,13 +57,6 @@ export class TypeMapperService { return GraphQLID; } - const numberScalar = - fieldMetadataType === FieldMetadataType.NUMBER && - (settings as FieldMetadataSettings) - ?.precision === 0 - ? GraphQLInt - : GraphQLFloat; - const typeScalarMapping = new Map([ [FieldMetadataType.UUID, UUIDScalarType], [FieldMetadataType.TEXT, GraphQLString], @@ -72,7 +65,13 @@ export class TypeMapperService { [FieldMetadataType.DATE_TIME, GraphQLISODateTime], [FieldMetadataType.DATE, GraphQLISODateTime], [FieldMetadataType.BOOLEAN, GraphQLBoolean], - [FieldMetadataType.NUMBER, numberScalar], + [ + FieldMetadataType.NUMBER, + getNumberScalarType( + (settings as FieldMetadataSettings) + ?.dataType, + ), + ], [FieldMetadataType.NUMERIC, BigFloatScalarType], [FieldMetadataType.PROBABILITY, GraphQLFloat], [FieldMetadataType.POSITION, PositionScalarType], @@ -91,13 +90,6 @@ export class TypeMapperService { return IDFilterType; } - const numberScalar = - fieldMetadataType === FieldMetadataType.NUMBER && - (settings as FieldMetadataSettings) - ?.precision === 0 - ? IntFilterType - : FloatFilterType; - const typeFilterMapping = new Map< FieldMetadataType, GraphQLInputObjectType | GraphQLScalarType @@ -109,7 +101,13 @@ export class TypeMapperService { [FieldMetadataType.DATE_TIME, DateFilterType], [FieldMetadataType.DATE, DateFilterType], [FieldMetadataType.BOOLEAN, BooleanFilterType], - [FieldMetadataType.NUMBER, numberScalar], + [ + FieldMetadataType.NUMBER, + getNumberFilterType( + (settings as FieldMetadataSettings) + ?.dataType, + ), + ], [FieldMetadataType.NUMERIC, BigFloatFilterType], [FieldMetadataType.PROBABILITY, FloatFilterType], [FieldMetadataType.POSITION, FloatFilterType], diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util.ts new file mode 100644 index 0000000000..8aa0d36553 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util.ts @@ -0,0 +1,24 @@ +import { GraphQLInputObjectType } from 'graphql'; + +import { NumberDataType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; + +import { + BigIntFilterType, + FloatFilterType, + IntFilterType, +} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input'; + +export const getNumberFilterType = ( + subType: NumberDataType | undefined, +): GraphQLInputObjectType => { + switch (subType) { + case NumberDataType.FLOAT: + return FloatFilterType; + case NumberDataType.BIGINT: + return BigIntFilterType; + case NumberDataType.INT: + return IntFilterType; + default: + return FloatFilterType; + } +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util.ts new file mode 100644 index 0000000000..cc58c54777 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util.ts @@ -0,0 +1,19 @@ +import { GraphQLInt, GraphQLFloat, GraphQLScalarType } from 'graphql'; +import { GraphQLBigInt } from 'graphql-scalars'; + +import { NumberDataType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; + +export const getNumberScalarType = ( + dataType: NumberDataType, +): GraphQLScalarType => { + switch (dataType) { + case NumberDataType.FLOAT: + return GraphQLFloat; + case NumberDataType.BIGINT: + return GraphQLBigInt; + case NumberDataType.INT: + return GraphQLInt; + default: + return GraphQLFloat; + } +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts index 0108118356..c79f330473 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts @@ -1,11 +1,17 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +export enum NumberDataType { + FLOAT = 'float', + INT = 'int', + BIGINT = 'bigint', +} + type FieldMetadataDefaultSettings = { isForeignKey?: boolean; }; type FieldMetadataNumberSettings = { - precision: number; + dataType: NumberDataType; }; type FieldMetadataSettingsMapping = { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 71556e8dbe..af23476fd5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -8,12 +8,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import console from 'console'; -import { - DataSource, - FindManyOptions, - FindOneOptions, - Repository, -} from 'typeorm'; +import { FindManyOptions, FindOneOptions, Repository } from 'typeorm'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { Query, QueryOptions } from '@ptc-org/nestjs-query-core'; @@ -57,7 +52,6 @@ import { import { createWorkspaceMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util'; import { createWorkspaceMigrationsForRemoteObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util'; import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; -import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { validateObjectMetadataInputOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; @@ -380,9 +374,6 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, + isRemoteObject: boolean, ) { const activityTargetObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -591,88 +584,93 @@ export class ObjectMetadataService extends TypeOrmQueryService { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ { - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets, - objectMetadataId: createdObjectMetadata.id, workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'activityTargets', - label: 'Activities', - description: `Activities tied to the ${createdObjectMetadata.labelSingular}`, - icon: 'IconCheckbox', - isNullable: true, - }, - // TO - { - standardId: createRelationDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: activityTargetObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: createdObjectMetadata.nameSingular, - label: createdObjectMetadata.labelSingular, - description: `ActivityTarget ${createdObjectMetadata.labelSingular}`, - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - // Foreign key - { - standardId: createForeignKeyDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: activityTargetObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: objectPrimaryKeyType, - name: `${createdObjectMetadata.nameSingular}Id`, - label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, - description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`, - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: activityTargetObjectMetadata.id, + fromFieldMetadataId: + activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + activityTargetRelationFieldMetadataMap[ + activityTargetObjectMetadata.id + ].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, }, ]); - - const activityTargetRelationFieldMetadataMap = - activityTargetRelationFieldMetadata.reduce( - (acc, fieldMetadata: FieldMetadataEntity) => { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ - { - workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: activityTargetObjectMetadata.id, - fromFieldMetadataId: - activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - activityTargetRelationFieldMetadataMap[ - activityTargetObjectMetadata.id - ].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, - }, - ]); + } return { activityTargetObjectMetadata }; } @@ -684,6 +682,7 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, + isRemoteObject: boolean, ) { const attachmentObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -691,86 +690,91 @@ export class ObjectMetadataService extends TypeOrmQueryService { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ { - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.attachments, - objectMetadataId: createdObjectMetadata.id, workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'attachments', - label: 'Attachments', - description: `Attachments tied to the ${createdObjectMetadata.labelSingular}`, - icon: 'IconFileImport', - isNullable: true, - }, - // TO - { - standardId: createRelationDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: attachmentObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: createdObjectMetadata.nameSingular, - label: createdObjectMetadata.labelSingular, - description: `Attachment ${createdObjectMetadata.labelSingular}`, - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - // Foreign key - { - standardId: createForeignKeyDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: ATTACHMENT_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: attachmentObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: objectPrimaryKeyType, - name: `${createdObjectMetadata.nameSingular}Id`, - label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, - description: `Attachment ${createdObjectMetadata.labelSingular} id foreign key`, - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: attachmentObjectMetadata.id, + fromFieldMetadataId: + attachmentRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + attachmentRelationFieldMetadataMap[attachmentObjectMetadata.id].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, }, ]); - - const attachmentRelationFieldMetadataMap = - attachmentRelationFieldMetadata.reduce( - (acc, fieldMetadata: FieldMetadataEntity) => { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ - { - workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: attachmentObjectMetadata.id, - fromFieldMetadataId: - attachmentRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - attachmentRelationFieldMetadataMap[attachmentObjectMetadata.id].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, - }, - ]); + } return { attachmentObjectMetadata }; } @@ -782,6 +786,7 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, + isRemoteObject: boolean, ) { const timelineActivityObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -789,88 +794,94 @@ export class ObjectMetadataService extends TypeOrmQueryService { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ { - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.timelineActivities, - objectMetadataId: createdObjectMetadata.id, workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'timelineActivities', - label: 'Timeline Activities', - description: `Timeline Activities tied to the ${createdObjectMetadata.labelSingular}`, - icon: 'IconTimeline', - isNullable: true, - }, - // TO - { - standardId: createRelationDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: timelineActivityObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: createdObjectMetadata.nameSingular, - label: createdObjectMetadata.labelSingular, - description: `Timeline Activity ${createdObjectMetadata.labelSingular}`, - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - // Foreign key - { - standardId: createForeignKeyDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: timelineActivityObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: objectPrimaryKeyType, - name: `${createdObjectMetadata.nameSingular}Id`, - label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, - description: `Timeline Activity ${createdObjectMetadata.labelSingular} id foreign key`, - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: timelineActivityObjectMetadata.id, + fromFieldMetadataId: + timelineActivityRelationFieldMetadataMap[createdObjectMetadata.id] + .id, + toFieldMetadataId: + timelineActivityRelationFieldMetadataMap[ + timelineActivityObjectMetadata.id + ].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, }, ]); - - const timelineActivityRelationFieldMetadataMap = - timelineActivityRelationFieldMetadata.reduce( - (acc, fieldMetadata: FieldMetadataEntity) => { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ - { - workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: timelineActivityObjectMetadata.id, - fromFieldMetadataId: - timelineActivityRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - timelineActivityRelationFieldMetadataMap[ - timelineActivityObjectMetadata.id - ].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, - }, - ]); + } return { timelineActivityObjectMetadata }; } @@ -882,6 +893,7 @@ export class ObjectMetadataService extends TypeOrmQueryService | undefined, + isRemoteObject: boolean, ) { const favoriteObjectMetadata = await this.objectMetadataRepository.findOneByOrFail({ @@ -889,87 +901,92 @@ export class ObjectMetadataService extends TypeOrmQueryService { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ { - standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.favorites, - objectMetadataId: createdObjectMetadata.id, workspaceId: workspaceId, - isCustom: false, - isActive: true, - isSystem: true, - type: FieldMetadataType.RELATION, - name: 'favorites', - label: 'Favorites', - description: `Favorites tied to the ${createdObjectMetadata.labelSingular}`, - icon: 'IconHeart', - isNullable: true, - }, - // TO - { - standardId: createRelationDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: FAVORITE_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: favoriteObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: createdObjectMetadata.nameSingular, - label: createdObjectMetadata.labelSingular, - description: `Favorite ${createdObjectMetadata.labelSingular}`, - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - // Foreign key - { - standardId: createForeignKeyDeterministicUuid({ - objectId: createdObjectMetadata.id, - standardId: FAVORITE_STANDARD_FIELD_IDS.custom, - }), - objectMetadataId: favoriteObjectMetadata.id, - workspaceId: workspaceId, - isCustom: false, - isActive: true, - type: objectPrimaryKeyType, - name: `${createdObjectMetadata.nameSingular}Id`, - label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, - description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`, - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: favoriteObjectMetadata.id, + fromFieldMetadataId: + favoriteRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + favoriteRelationFieldMetadataMap[favoriteObjectMetadata.id].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, }, ]); - - const favoriteRelationFieldMetadataMap = - favoriteRelationFieldMetadata.reduce( - (acc, fieldMetadata: FieldMetadataEntity) => { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, - ); - - await this.relationMetadataRepository.save([ - { - workspaceId: workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: favoriteObjectMetadata.id, - fromFieldMetadataId: - favoriteRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - favoriteRelationFieldMetadataMap[favoriteObjectMetadata.id].id, - onDeleteAction: RelationOnDeleteAction.CASCADE, - }, - ]); + } return { favoriteObjectMetadata }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util.ts index eb2b71c5bf..8c05bcfe43 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util.ts @@ -13,7 +13,7 @@ export const createWorkspaceMigrationsForCustomObjectRelations = ( createdObjectMetadata: ObjectMetadataEntity, activityTargetObjectMetadata: ObjectMetadataEntity, attachmentObjectMetadata: ObjectMetadataEntity, - eventObjectMetadata: ObjectMetadataEntity, + timelineActivityObjectMetadata: ObjectMetadataEntity, favoriteObjectMetadata: ObjectMetadataEntity, ): WorkspaceMigrationTableAction[] => [ { @@ -80,9 +80,9 @@ export const createWorkspaceMigrationsForCustomObjectRelations = ( }, ], }, - // Add event relation + // Add timeline activity relation { - name: computeObjectTargetTable(eventObjectMetadata), + name: computeObjectTargetTable(timelineActivityObjectMetadata), action: WorkspaceMigrationTableActionType.ALTER, columns: [ { @@ -96,7 +96,7 @@ export const createWorkspaceMigrationsForCustomObjectRelations = ( ], }, { - name: computeObjectTargetTable(eventObjectMetadata), + name: computeObjectTargetTable(timelineActivityObjectMetadata), action: WorkspaceMigrationTableActionType.ALTER, columns: [ { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util.ts index 7daf6728ab..d817fc5435 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util.ts @@ -1,5 +1,3 @@ -import { DataSource } from 'typeorm'; - import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { @@ -10,55 +8,14 @@ import { } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -const buildCommentForRemoteObjectForeignKey = async ( - localObjectMetadataName: string, - remoteObjectMetadataName: string, - schema: string, - workspaceDataSource: DataSource | undefined, -): Promise => { - const existingComment = await workspaceDataSource?.query( - `SELECT col_description('${schema}."${localObjectMetadataName}"'::regclass, 0)`, - ); - - if (!existingComment[0]?.col_description) { - return `@graphql({"totalCount":{"enabled": true},"foreign_keys":[{"local_name":"${localObjectMetadataName}Collection","local_columns":["${remoteObjectMetadataName}Id"],"foreign_name":"${remoteObjectMetadataName}","foreign_schema":"${schema}","foreign_table":"${remoteObjectMetadataName}","foreign_columns":["id"]}]})`; - } - - const commentWithoutGraphQL = existingComment[0].col_description - .replace('@graphql(', '') - .replace(')', ''); - const parsedComment = JSON.parse(commentWithoutGraphQL); - - const foreignKey = { - local_name: `${localObjectMetadataName}Collection`, - local_columns: [`${remoteObjectMetadataName}Id`], - foreign_name: `${remoteObjectMetadataName}`, - foreign_schema: schema, - foreign_table: remoteObjectMetadataName, - foreign_columns: ['id'], - }; - - if (parsedComment.foreign_keys) { - parsedComment.foreign_keys.push(foreignKey); - } else { - parsedComment.foreign_keys = [foreignKey]; - } - - return `@graphql(${JSON.stringify(parsedComment)})`; -}; - export const createWorkspaceMigrationsForRemoteObjectRelations = async ( createdObjectMetadata: ObjectMetadataEntity, activityTargetObjectMetadata: ObjectMetadataEntity, attachmentObjectMetadata: ObjectMetadataEntity, - eventObjectMetadata: ObjectMetadataEntity, + timelineActivityObjectMetadata: ObjectMetadataEntity, favoriteObjectMetadata: ObjectMetadataEntity, - schema: string, primaryKeyColumnType: string, - workspaceDataSource: DataSource | undefined, ): Promise => { - const createdObjectName = createdObjectMetadata.nameSingular; - return [ { name: computeObjectTargetTable(activityTargetObjectMetadata), @@ -74,22 +31,6 @@ export const createWorkspaceMigrationsForRemoteObjectRelations = async ( } satisfies WorkspaceMigrationColumnCreate, ], }, - { - name: computeObjectTargetTable(activityTargetObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, - comment: await buildCommentForRemoteObjectForeignKey( - activityTargetObjectMetadata.nameSingular, - createdObjectName, - schema, - workspaceDataSource, - ), - }, - ], - }, - // Add attachment relation { name: computeObjectTargetTable(attachmentObjectMetadata), action: WorkspaceMigrationTableActionType.ALTER, @@ -105,53 +46,7 @@ export const createWorkspaceMigrationsForRemoteObjectRelations = async ( ], }, { - name: computeObjectTargetTable(attachmentObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, - comment: await buildCommentForRemoteObjectForeignKey( - attachmentObjectMetadata.nameSingular, - createdObjectName, - schema, - workspaceDataSource, - ), - }, - ], - }, - // Add event relation - { - name: computeObjectTargetTable(eventObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE, - columnName: computeColumnName(createdObjectMetadata.nameSingular, { - isForeignKey: true, - }), - columnType: primaryKeyColumnType, - isNullable: true, - } satisfies WorkspaceMigrationColumnCreate, - ], - }, - { - name: computeObjectTargetTable(eventObjectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: [ - { - action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, - comment: await buildCommentForRemoteObjectForeignKey( - eventObjectMetadata.nameSingular, - createdObjectName, - schema, - workspaceDataSource, - ), - }, - ], - }, - // Add favorite relation - { - name: computeObjectTargetTable(favoriteObjectMetadata), + name: computeObjectTargetTable(timelineActivityObjectMetadata), action: WorkspaceMigrationTableActionType.ALTER, columns: [ { @@ -169,14 +64,13 @@ export const createWorkspaceMigrationsForRemoteObjectRelations = async ( action: WorkspaceMigrationTableActionType.ALTER, columns: [ { - action: WorkspaceMigrationColumnActionType.CREATE_COMMENT, - comment: await buildCommentForRemoteObjectForeignKey( - favoriteObjectMetadata.nameSingular, - createdObjectName, - schema, - workspaceDataSource, - ), - }, + action: WorkspaceMigrationColumnActionType.CREATE, + columnName: computeColumnName(createdObjectMetadata.nameSingular, { + isForeignKey: true, + }), + columnType: primaryKeyColumnType, + isNullable: true, + } satisfies WorkspaceMigrationColumnCreate, ], }, ]; diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util.ts index cdf4034907..5678f2ac5a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util.ts @@ -1,4 +1,7 @@ -import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { + FieldMetadataSettings, + NumberDataType, +} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -8,8 +11,6 @@ export const mapUdtNameToFieldType = (udtName: string): FieldMetadataType => { return FieldMetadataType.UUID; case 'varchar': case 'text': - case 'bigint': - case 'int8': return FieldMetadataType.TEXT; case 'bool': return FieldMetadataType.BOOLEAN; @@ -19,6 +20,8 @@ export const mapUdtNameToFieldType = (udtName: string): FieldMetadataType => { case 'integer': case 'int2': case 'int4': + case 'int8': + case 'bigint': return FieldMetadataType.NUMBER; default: return FieldMetadataType.TEXT; @@ -33,7 +36,12 @@ export const mapUdtNameToFieldSettings = ( case 'int2': case 'int4': return { - precision: 0, + dataType: NumberDataType.INT, + } satisfies FieldMetadataSettings; + case 'int8': + case 'bigint': + return { + dataType: NumberDataType.BIGINT, } satisfies FieldMetadataSettings; default: return undefined; diff --git a/yarn.lock b/yarn.lock index d582d78930..d1b9cb520c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28910,6 +28910,17 @@ __metadata: languageName: node linkType: hard +"graphql-scalars@npm:^1.23.0": + version: 1.23.0 + resolution: "graphql-scalars@npm:1.23.0" + dependencies: + tslib: "npm:^2.5.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 7666c305b8367528c4fbb583a2731d93d52f36043d71f4957ecc1d2db71d710d268e25535243beb1b2497db921daa94d3cfa83daaf4a3101a667f358ddbf3436 + languageName: node + linkType: hard + "graphql-shield@npm:^7.5.0": version: 7.6.5 resolution: "graphql-shield@npm:7.6.5" @@ -46585,6 +46596,7 @@ __metadata: graphql-fields: "npm:^2.0.3" graphql-middleware: "npm:^6.1.35" graphql-rate-limit: "npm:^3.3.0" + graphql-scalars: "npm:^1.23.0" graphql-subscriptions: "npm:2.0.0" graphql-tag: "npm:^2.12.6" graphql-type-json: "npm:^0.3.2"