diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts index c9aa11457b7..12db58b8b4b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts @@ -34,9 +34,8 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; -import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module'; -import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module'; import { ViewModule } from 'src/modules/view/view.module'; +import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module'; import { FieldMetadataEntity } from './field-metadata.entity'; @@ -64,8 +63,7 @@ import { FieldMetadataService } from './services/field-metadata.service'; ViewModule, PermissionsModule, WorkspaceMetadataCacheModule, - WorkspaceMigrationBuilderV2Module, - WorkspaceMigrationRunnerV2Module, + WorkspaceMigrationV2Module, FlatFieldMetadataModule, IndexMetadataModule, ], diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts index 335e7f76d71..bb56ddc01b1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts @@ -35,8 +35,7 @@ import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules import { getSubFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps.util'; import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; -import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; -import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service'; @Injectable() export class FieldMetadataServiceV2 { @@ -44,9 +43,8 @@ export class FieldMetadataServiceV2 { @InjectRepository(FieldMetadataEntity, 'core') private readonly fieldMetadataRepository: Repository, private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, - private readonly workspaceMigrationBuilderV2: WorkspaceMigrationBuilderV2Service, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, private readonly flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService, - private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service, ) {} async createOne({ @@ -104,46 +102,42 @@ export class FieldMetadataServiceV2 { if (validationErrors.length > 0) { throw new MultipleMetadataValidationErrors( validationErrors, - validationErrors.length > 1 - ? 'Multiple validation errors occurred while deleting field' - : 'A validation error occurred while deleting field', + 'Multiple validation errors occurred while deleting field', ); } - try { - const flatObjectMetadataMapsWithImpactedObject = - getSubFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - objectMetadataIds: flatFieldMetadatasToDelete.map( - (flatFieldMetadataToDelete) => - flatFieldMetadataToDelete.objectMetadataId, - ), - }); - - const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete.reduce( - (flatObjectMetadataMaps, flatFieldMetadataToDelete) => - deleteFieldFromFlatObjectMetadataMapsOrThrow({ - fieldMetadataId: flatFieldMetadataToDelete.id, - flatObjectMetadataMaps, - objectMetadataId: flatFieldMetadataToDelete.objectMetadataId, - }), - flatObjectMetadataMapsWithImpactedObject, - ); - - const workspaceMigration = this.workspaceMigrationBuilderV2.build({ - fromFlatObjectMetadataMaps: flatObjectMetadataMapsWithImpactedObject, - toFlatObjectMetadataMaps, - inferDeletionFromMissingObjectFieldIndex: true, - workspaceId, + const flatObjectMetadataMapsWithImpactedObject = + getSubFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataIds: flatFieldMetadatasToDelete.map( + (flatFieldMetadataToDelete) => + flatFieldMetadataToDelete.objectMetadataId, + ), }); - await this.workspaceMigrationRunnerV2Service.run(workspaceMigration); - } catch { - throw new FieldMetadataException( - 'Workspace migration failed to run', - FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, - ); - } + const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete.reduce( + (flatObjectMetadataMaps, flatFieldMetadataToDelete) => + deleteFieldFromFlatObjectMetadataMapsOrThrow({ + fieldMetadataId: flatFieldMetadataToDelete.id, + flatObjectMetadataMaps, + objectMetadataId: flatFieldMetadataToDelete.objectMetadataId, + }), + flatObjectMetadataMapsWithImpactedObject, + ); + + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingObjectFieldIndex: true, + }, + fromFlatObjectMetadataMaps: flatObjectMetadataMapsWithImpactedObject, + toFlatObjectMetadataMaps, + workspaceId, + errorMessage: + 'Multiple validation errors occurred while deleting field', + }, + ); return fromFlatFieldMetadataToFieldMetadataDto( flatFieldMetadatasToDelete[0], @@ -241,39 +235,38 @@ export class FieldMetadataServiceV2 { ); } - try { - const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - objectMetadataIds: [ - optimisticiallyUpdatedFlatFieldMetadata.objectMetadataId, - ], + const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataIds: [ + optimisticiallyUpdatedFlatFieldMetadata.objectMetadataId, + ], + }); + const toFlatObjectMetadataMaps = + replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: fromFlatObjectMetadataMaps, + flatFieldMetadata: optimisticiallyUpdatedFlatFieldMetadata, }); - const toFlatObjectMetadataMaps = - replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: fromFlatObjectMetadataMaps, - flatFieldMetadata: optimisticiallyUpdatedFlatFieldMetadata, - }); - const workspaceMigration = this.workspaceMigrationBuilderV2.build({ + + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps, - inferDeletionFromMissingObjectFieldIndex: false, - workspaceId, - }); - - await this.workspaceMigrationRunnerV2Service.run(workspaceMigration); - - return this.fieldMetadataRepository.findOneOrFail({ - where: { - id: optimisticiallyUpdatedFlatFieldMetadata.id, - workspaceId, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingObjectFieldIndex: false, }, - }); - } catch { - throw new FieldMetadataException( - 'Workspace migration failed to run', - FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, - ); - } + workspaceId, + errorMessage: + 'Multiple validation errors occurred while updating field', + }, + ); + + return this.fieldMetadataRepository.findOneOrFail({ + where: { + id: optimisticiallyUpdatedFlatFieldMetadata.id, + workspaceId, + }, + }); } async createMany({ @@ -371,43 +364,41 @@ export class FieldMetadataServiceV2 { ), ); - try { - const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - objectMetadataIds: impactedObjectMetadataIds, - }); - const toFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, - objectMetadataIds: impactedObjectMetadataIds, - }); - const workspaceMigration = this.workspaceMigrationBuilderV2.build({ + const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataIds: impactedObjectMetadataIds, + }); + const toFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + objectMetadataIds: impactedObjectMetadataIds, + }); + + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps, - inferDeletionFromMissingObjectFieldIndex: false, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingObjectFieldIndex: false, + }, workspaceId, - }); + errorMessage: + 'Multiple validation errors occurred while creating fields', + }, + ); - await this.workspaceMigrationRunnerV2Service.run(workspaceMigration); - - // In the best of the world could consume runner returned value instead of searching in db here - return this.fieldMetadataRepository.find({ - where: { - name: In( - fieldMetadataInputs.map((flatFieldMetadata) => - trimAndRemoveDuplicatedWhitespacesFromString( - flatFieldMetadata.name, - ), + // In the best of the world could consume runner returned value instead of searching in db here + return this.fieldMetadataRepository.find({ + where: { + name: In( + fieldMetadataInputs.map((flatFieldMetadata) => + trimAndRemoveDuplicatedWhitespacesFromString( + flatFieldMetadata.name, ), ), - workspaceId, - }, - }); - } catch { - // TODO prastoin We should pass the internal error here - throw new FieldMetadataException( - 'Workspace migration failed to run', - FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, - ); - } + ), + workspaceId, + }, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-flat-fields.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-flat-fields.mock.ts new file mode 100644 index 00000000000..795b7520c7e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-flat-fields.mock.ts @@ -0,0 +1,864 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { DateDisplayFormat } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; + +export const FAVORITE_FLAT_FIELDS_MOCK = { + position: getFlatFieldMetadataMock({ + id: '64dbf3ef-a352-44bb-9055-f95822f18d0a', + standardId: '20202020-dd26-42c6-8c3c-2a7598c204f6', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.NUMBER, + name: 'position', + label: 'Position', + defaultValue: 0, + description: 'Favorite position', + icon: 'IconList', + standardOverrides: null, + options: null, + settings: null, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-dd26-42c6-8c3c-2a7598c204f6', + flatRelationTargetFieldMetadata: null, + }), + id: getFlatFieldMetadataMock({ + id: '49f58497-4e7c-49e6-b3bc-c91d4b8a8ddc', + standardId: '20202020-eda0-4cee-9577-3eb357e3c22b', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.UUID, + name: 'id', + label: 'Id', + defaultValue: 'uuid', + description: 'Id', + icon: 'Icon123', + standardOverrides: null, + options: null, + settings: null, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-eda0-4cee-9577-3eb357e3c22b', + flatRelationTargetFieldMetadata: null, + }), + createdAt: getFlatFieldMetadataMock({ + id: '0606a76c-8a72-48ca-90c6-526c729c25eb', + standardId: '20202020-66ac-4502-9975-e4d959c50311', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.DATE_TIME, + name: 'createdAt', + label: 'Creation date', + defaultValue: 'now', + description: 'Creation date', + icon: 'IconCalendar', + standardOverrides: null, + options: null, + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-66ac-4502-9975-e4d959c50311', + flatRelationTargetFieldMetadata: null, + }), + updatedAt: getFlatFieldMetadataMock({ + id: 'fb202982-0c8e-4917-8dda-acb8248be749', + standardId: '20202020-d767-4622-bdcf-d8a084834d86', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.DATE_TIME, + name: 'updatedAt', + label: 'Last update', + defaultValue: 'now', + description: 'Last time the record was changed', + icon: 'IconCalendarClock', + standardOverrides: null, + options: null, + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-d767-4622-bdcf-d8a084834d86', + flatRelationTargetFieldMetadata: null, + }), + deletedAt: getFlatFieldMetadataMock({ + id: 'ef52e151-a974-470f-bbe7-f345fd4eca65', + standardId: '20202020-b9a7-48d8-8387-b9a3090a50ec', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.DATE_TIME, + name: 'deletedAt', + label: 'Deleted at', + defaultValue: null, + description: 'Date when the record was deleted', + icon: 'IconCalendarMinus', + standardOverrides: null, + options: null, + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-b9a7-48d8-8387-b9a3090a50ec', + flatRelationTargetFieldMetadata: null, + }), + forWorkspaceMember: getFlatFieldMetadataMock({ + id: '07f2c3ba-63d1-4e44-bac4-bc4591e51bac', + standardId: '20202020-ce63-49cb-9676-fdc0c45892cd', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'forWorkspaceMember', + label: 'Workspace Member', + defaultValue: null, + description: 'Favorite workspace member', + icon: 'IconCircleUser', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'forWorkspaceMemberId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: 'a15a4b41-1849-4dea-aa43-f53caad016be', + relationTargetObjectMetadataId: '7d8264db-2b55-4b74-81ff-b7064ed43840', + uniqueIdentifier: '20202020-ce63-49cb-9676-fdc0c45892cd', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: 'a15a4b41-1849-4dea-aa43-f53caad016be', + standardId: '20202020-f3c1-4faf-b343-cf7681038757', + objectMetadataId: '7d8264db-2b55-4b74-81ff-b7064ed43840', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the workspace member', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '07f2c3ba-63d1-4e44-bac4-bc4591e51bac', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-f3c1-4faf-b343-cf7681038757', + }), + }), + person: getFlatFieldMetadataMock({ + id: '013cec20-e9cc-42db-b9b9-ff6bd0f52abd', + standardId: '20202020-c428-4f40-b6f3-86091511c41c', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'person', + label: 'Person', + defaultValue: null, + description: 'Favorite person', + icon: 'IconUser', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'personId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'a1a72bee-1c15-4598-ba23-67ba8e3e505d', + relationTargetObjectMetadataId: '843e0b67-9619-4628-91c4-2fa62256a611', + uniqueIdentifier: '20202020-c428-4f40-b6f3-86091511c41c', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: 'a1a72bee-1c15-4598-ba23-67ba8e3e505d', + standardId: '20202020-4073-4117-9cf1-203bcdc91cbd', + objectMetadataId: '843e0b67-9619-4628-91c4-2fa62256a611', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the contact', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '013cec20-e9cc-42db-b9b9-ff6bd0f52abd', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-4073-4117-9cf1-203bcdc91cbd', + }), + }), + company: getFlatFieldMetadataMock({ + id: '9a11532c-6839-46ed-a2f5-ed03f88b86f6', + standardId: '20202020-cff5-4682-8bf9-069169e08279', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'company', + label: 'Company', + defaultValue: null, + description: 'Favorite company', + icon: 'IconBuildingSkyscraper', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'companyId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '72415216-fed4-450b-aa00-691e37718b01', + relationTargetObjectMetadataId: '7f5c2c7a-bb23-46fb-b59d-9b7a52a8d1cc', + uniqueIdentifier: '20202020-cff5-4682-8bf9-069169e08279', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '72415216-fed4-450b-aa00-691e37718b01', + standardId: '20202020-4d1d-41ac-b13b-621631298d55', + objectMetadataId: '7f5c2c7a-bb23-46fb-b59d-9b7a52a8d1cc', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the company', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '9a11532c-6839-46ed-a2f5-ed03f88b86f6', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-4d1d-41ac-b13b-621631298d55', + }), + }), + favoriteFolder: getFlatFieldMetadataMock({ + id: 'fabbe669-b8fc-49fe-8010-f13d01eb61f1', + standardId: '20202020-f658-4d12-8b4d-248356aa4bd9', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'favoriteFolder', + label: 'Favorite Folder', + defaultValue: null, + description: 'The folder this favorite belongs to', + icon: 'IconFolder', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.SET_NULL, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'favoriteFolderId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '71b95076-6b8d-45c9-9a7c-ab981b81bd1b', + relationTargetObjectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + uniqueIdentifier: '20202020-f658-4d12-8b4d-248356aa4bd9', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '71b95076-6b8d-45c9-9a7c-ab981b81bd1b', + standardId: '20202020-b5e3-4b42-8af2-03cd4fd2e4d2', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites in this folder', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'fabbe669-b8fc-49fe-8010-f13d01eb61f1', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-b5e3-4b42-8af2-03cd4fd2e4d2', + }), + }), + opportunity: getFlatFieldMetadataMock({ + id: '6380cf8e-4327-4d1e-ad21-1826ce4ecf05', + standardId: '20202020-dabc-48e1-8318-2781a2b32aa2', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'opportunity', + label: 'Opportunity', + defaultValue: null, + description: 'Favorite opportunity', + icon: 'IconTargetArrow', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'opportunityId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '87bb9924-a1cc-4357-9c8a-52141dabf266', + relationTargetObjectMetadataId: 'e6996bbf-dd41-423a-9324-8546f5b22fa7', + uniqueIdentifier: '20202020-dabc-48e1-8318-2781a2b32aa2', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '87bb9924-a1cc-4357-9c8a-52141dabf266', + standardId: '20202020-a1c2-4500-aaae-83ba8a0e827a', + objectMetadataId: 'e6996bbf-dd41-423a-9324-8546f5b22fa7', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the opportunity', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '6380cf8e-4327-4d1e-ad21-1826ce4ecf05', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-a1c2-4500-aaae-83ba8a0e827a', + }), + }), + workflow: getFlatFieldMetadataMock({ + id: '12c20527-9b1d-4922-9650-00cc31b26387', + standardId: '20202020-b11b-4dc8-999a-6bd0a947b463', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'workflow', + label: 'Workflow', + defaultValue: null, + description: 'Favorite workflow', + icon: 'IconSettingsAutomation', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'workflowId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'b848abe8-276b-46c9-9eab-baf34d2a0583', + relationTargetObjectMetadataId: '664c19a5-0693-45d4-a4e5-76c3717db83c', + uniqueIdentifier: '20202020-b11b-4dc8-999a-6bd0a947b463', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: 'b848abe8-276b-46c9-9eab-baf34d2a0583', + standardId: '20202020-c554-4c41-be7a-cf9cd4b0d512', + objectMetadataId: '664c19a5-0693-45d4-a4e5-76c3717db83c', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the workflow', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '12c20527-9b1d-4922-9650-00cc31b26387', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-c554-4c41-be7a-cf9cd4b0d512', + }), + }), + workflowVersion: getFlatFieldMetadataMock({ + id: '5cb92f25-c067-4e19-afb3-c7cc038ed1a4', + standardId: '20202020-e1b8-4caf-b55a-3ab4d4cbcd21', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'workflowVersion', + label: 'Workflow', + defaultValue: null, + description: 'Favorite workflow version', + icon: 'IconSettingsAutomation', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'workflowVersionId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: 'ea6b6bda-6ba7-4549-b81a-1f3ced0d4b25', + relationTargetObjectMetadataId: '99cf5f27-b41d-4f25-a23b-9e47d27dae9b', + uniqueIdentifier: '20202020-e1b8-4caf-b55a-3ab4d4cbcd21', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: 'ea6b6bda-6ba7-4549-b81a-1f3ced0d4b25', + standardId: '20202020-b8e0-4e57-928d-b51671cc71f2', + objectMetadataId: '99cf5f27-b41d-4f25-a23b-9e47d27dae9b', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the workflow version', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '5cb92f25-c067-4e19-afb3-c7cc038ed1a4', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-b8e0-4e57-928d-b51671cc71f2', + }), + }), + workflowRun: getFlatFieldMetadataMock({ + id: 'a4bdd0d8-c36f-4519-812b-be4800ab14b0', + standardId: '20202020-db5a-4fe4-9a13-9afa22b1e762', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'workflowRun', + label: 'Workflow', + defaultValue: null, + description: 'Favorite workflow run', + icon: 'IconSettingsAutomation', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'workflowRunId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: '444a6e9e-443d-4f04-a60d-32f76f17049c', + relationTargetObjectMetadataId: '06d95e96-b050-443f-946e-dd276d291a2f', + uniqueIdentifier: '20202020-db5a-4fe4-9a13-9afa22b1e762', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '444a6e9e-443d-4f04-a60d-32f76f17049c', + standardId: '20202020-4baf-4604-b899-2f7fcfbbf90d', + objectMetadataId: '06d95e96-b050-443f-946e-dd276d291a2f', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the workflow run', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'a4bdd0d8-c36f-4519-812b-be4800ab14b0', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-4baf-4604-b899-2f7fcfbbf90d', + }), + }), + task: getFlatFieldMetadataMock({ + id: 'db16ab78-ea3f-4b67-93e3-6ad9ab1b7273', + standardId: '20202020-1b1b-4b3b-8b1b-7f8d6a1d7d5c', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'task', + label: 'Task', + defaultValue: null, + description: 'Favorite task', + icon: 'IconCheckbox', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'taskId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '33e4eff0-ff0f-4530-802a-cd630481ee84', + relationTargetObjectMetadataId: '3186920d-2a15-4b5f-96c7-2bf6567024b0', + uniqueIdentifier: '20202020-1b1b-4b3b-8b1b-7f8d6a1d7d5c', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '33e4eff0-ff0f-4530-802a-cd630481ee84', + standardId: '20202020-4d1d-41ac-b13b-621631298d65', + objectMetadataId: '3186920d-2a15-4b5f-96c7-2bf6567024b0', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the task', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'db16ab78-ea3f-4b67-93e3-6ad9ab1b7273', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-4d1d-41ac-b13b-621631298d65', + }), + }), + note: getFlatFieldMetadataMock({ + id: 'a61baf0f-2d43-4fda-9baf-efb000c17ecd', + standardId: '20202020-1f25-43fe-8b00-af212fdde824', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'note', + label: 'Note', + defaultValue: null, + description: 'Favorite note', + icon: 'IconNotes', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'noteId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '065a1ede-f773-48ca-84bf-70f61d838396', + relationTargetObjectMetadataId: '1253e3e5-5b00-4a34-93b8-02f3dc6e2b7c', + uniqueIdentifier: '20202020-1f25-43fe-8b00-af212fdde824', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '065a1ede-f773-48ca-84bf-70f61d838396', + standardId: '20202020-4d1d-41ac-b13b-621631298d67', + objectMetadataId: '1253e3e5-5b00-4a34-93b8-02f3dc6e2b7c', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the note', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'a61baf0f-2d43-4fda-9baf-efb000c17ecd', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-4d1d-41ac-b13b-621631298d67', + }), + }), + view: getFlatFieldMetadataMock({ + id: 'accd216c-790e-4bd2-83bb-0700e06d0e5c', + standardId: '20202020-5a93-4fa9-acce-e73481a0bbdf', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'view', + label: 'View', + defaultValue: null, + description: 'Favorite view', + icon: 'IconLayoutCollage', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'viewId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '90a103ec-7dce-4bd4-aed4-e2c9d46ecf32', + relationTargetObjectMetadataId: '8377fc0d-ec1c-4fb5-b6ae-9381d9b22a95', + uniqueIdentifier: '20202020-5a93-4fa9-acce-e73481a0bbdf', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '90a103ec-7dce-4bd4-aed4-e2c9d46ecf32', + standardId: '20202020-c818-4a86-8284-9ec0ef0a59a5', + objectMetadataId: '8377fc0d-ec1c-4fb5-b6ae-9381d9b22a95', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites linked to the view', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'accd216c-790e-4bd2-83bb-0700e06d0e5c', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-c818-4a86-8284-9ec0ef0a59a5', + }), + }), + rocket: getFlatFieldMetadataMock({ + id: '9448627d-725d-499e-a071-90fd5cfcf0ea', + standardId: '20202020-78f7-4d7d-86e9-7562ced0b381', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'rocket', + label: 'Rocket', + defaultValue: null, + description: 'Favorites Rocket', + icon: 'IconBuildingSkyscraper', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'rocketId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: '14010360-f3c2-442e-b05f-43c30158f246', + relationTargetObjectMetadataId: 'd78ec657-74a4-4652-a350-1f44ff62970a', + uniqueIdentifier: '20202020-78f7-4d7d-86e9-7562ced0b381', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '14010360-f3c2-442e-b05f-43c30158f246', + standardId: '20202020-a4a7-4686-b296-1c6c3482ee21', + objectMetadataId: 'd78ec657-74a4-4652-a350-1f44ff62970a', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites tied to the Rocket', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: '9448627d-725d-499e-a071-90fd5cfcf0ea', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-a4a7-4686-b296-1c6c3482ee21', + }), + }), + pet: getFlatFieldMetadataMock({ + id: 'd34b4048-bf0c-4c61-8ffe-59f1838a2cc4', + standardId: '20202020-1bd7-4d10-80dc-968b9f5d1a8a', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'pet', + label: 'Pet', + defaultValue: null, + description: 'Favorites Pet', + icon: 'IconBuildingSkyscraper', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'petId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: '80bc4e7a-6bd1-4f2b-98b1-6333c631cc1a', + relationTargetObjectMetadataId: 'd34e0f07-1b8c-4de0-938e-599cf05e1f7f', + uniqueIdentifier: '20202020-1bd7-4d10-80dc-968b9f5d1a8a', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '80bc4e7a-6bd1-4f2b-98b1-6333c631cc1a', + standardId: '20202020-a4a7-4686-b296-1c6c3482ee21', + objectMetadataId: 'd34e0f07-1b8c-4de0-938e-599cf05e1f7f', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites tied to the Pet', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: 'd34b4048-bf0c-4c61-8ffe-59f1838a2cc4', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-a4a7-4686-b296-1c6c3482ee21', + }), + }), + surveyResult: getFlatFieldMetadataMock({ + id: '9cdfb5a1-bb9c-4a59-811d-f5a1034a8f8b', + standardId: '20202020-d049-4912-80ee-43b09fe3b0ac', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'surveyResult', + label: 'Survey result', + defaultValue: null, + description: 'Favorites Survey result', + icon: 'IconBuildingSkyscraper', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.CASCADE, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'surveyResultId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: '8e11aced-3a20-4551-baa1-b38b98a74e1c', + relationTargetObjectMetadataId: '713da753-0340-49b5-b1fa-add34d2dc9a8', + uniqueIdentifier: '20202020-d049-4912-80ee-43b09fe3b0ac', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: '8e11aced-3a20-4551-baa1-b38b98a74e1c', + standardId: '20202020-a4a7-4686-b296-1c6c3482ee21', + objectMetadataId: '713da753-0340-49b5-b1fa-add34d2dc9a8', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites tied to the Survey result', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: '9cdfb5a1-bb9c-4a59-811d-f5a1034a8f8b', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-a4a7-4686-b296-1c6c3482ee21', + }), + }), +} as const satisfies Record; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-folder-flat-fields.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-folder-flat-fields.mock.ts new file mode 100644 index 00000000000..c4682843637 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-folder-flat-fields.mock.ts @@ -0,0 +1,213 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { DateDisplayFormat } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; + +export const FAVORITE_FOLDER_FLAT_FIELDS_MOCK = { + position: getFlatFieldMetadataMock({ + id: '5894ab56-99b7-49e8-98be-dd6524250df5', + standardId: '20202020-5278-4bde-8909-2cec74d43744', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.NUMBER, + name: 'position', + label: 'Position', + defaultValue: 0, + description: 'Favorite folder position', + icon: 'IconList', + standardOverrides: null, + options: null, + settings: null, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-5278-4bde-8909-2cec74d43744', + flatRelationTargetFieldMetadata: null, + }), + name: getFlatFieldMetadataMock({ + id: '1502abe0-b2f8-43a7-ae55-91c826b34ffe', + standardId: '20202020-82a3-4537-8ff0-dbce7eec35d6', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.TEXT, + name: 'name', + label: 'Name', + defaultValue: "''", + description: 'Name of the favorite folder', + icon: 'IconText', + standardOverrides: null, + options: null, + settings: null, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-82a3-4537-8ff0-dbce7eec35d6', + flatRelationTargetFieldMetadata: null, + }), + id: getFlatFieldMetadataMock({ + id: '85ab1864-0a92-4682-8583-82e8b696410b', + standardId: '20202020-eda0-4cee-9577-3eb357e3c22b', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.UUID, + name: 'id', + label: 'Id', + defaultValue: 'uuid', + description: 'Id', + icon: 'Icon123', + standardOverrides: null, + options: null, + settings: null, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-eda0-4cee-9577-3eb357e3c22b', + flatRelationTargetFieldMetadata: null, + }), + createdAt: getFlatFieldMetadataMock({ + id: 'a92b4b66-c2de-483e-8973-6f12bf36ac1a', + standardId: '20202020-66ac-4502-9975-e4d959c50311', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.DATE_TIME, + name: 'createdAt', + label: 'Creation date', + defaultValue: 'now', + description: 'Creation date', + icon: 'IconCalendar', + standardOverrides: null, + options: null, + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-66ac-4502-9975-e4d959c50311', + flatRelationTargetFieldMetadata: null, + }), + updatedAt: getFlatFieldMetadataMock({ + id: '3907fc24-3bf0-4f0b-88be-f9635e473145', + standardId: '20202020-d767-4622-bdcf-d8a084834d86', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.DATE_TIME, + name: 'updatedAt', + label: 'Last update', + defaultValue: 'now', + description: 'Last time the record was changed', + icon: 'IconCalendarClock', + standardOverrides: null, + options: null, + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: false, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: false, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-d767-4622-bdcf-d8a084834d86', + flatRelationTargetFieldMetadata: null, + }), + deletedAt: getFlatFieldMetadataMock({ + id: '52dcb335-5ce1-4165-a30b-4a61e84af21f', + standardId: '20202020-b9a7-48d8-8387-b9a3090a50ec', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.DATE_TIME, + name: 'deletedAt', + label: 'Deleted at', + defaultValue: null, + description: 'Date when the record was deleted', + icon: 'IconCalendarMinus', + standardOverrides: null, + options: null, + settings: { displayFormat: DateDisplayFormat.RELATIVE }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + uniqueIdentifier: '20202020-b9a7-48d8-8387-b9a3090a50ec', + flatRelationTargetFieldMetadata: null, + }), + favorites: getFlatFieldMetadataMock({ + id: '71b95076-6b8d-45c9-9a7c-ab981b81bd1b', + standardId: '20202020-b5e3-4b42-8af2-03cd4fd2e4d2', + objectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + defaultValue: null, + description: 'Favorites in this folder', + icon: 'IconHeart', + standardOverrides: null, + options: null, + settings: { relationType: RelationType.ONE_TO_MANY }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: 'fabbe669-b8fc-49fe-8010-f13d01eb61f1', + relationTargetObjectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + uniqueIdentifier: '20202020-b5e3-4b42-8af2-03cd4fd2e4d2', + flatRelationTargetFieldMetadata: getFlatFieldMetadataMock({ + id: 'fabbe669-b8fc-49fe-8010-f13d01eb61f1', + standardId: '20202020-f658-4d12-8b4d-248356aa4bd9', + objectMetadataId: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + type: FieldMetadataType.RELATION, + name: 'favoriteFolder', + label: 'Favorite Folder', + defaultValue: null, + description: 'The folder this favorite belongs to', + icon: 'IconFolder', + standardOverrides: null, + options: null, + settings: { + onDelete: RelationOnDeleteAction.SET_NULL, + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'favoriteFolderId', + }, + isCustom: false, + isActive: true, + isSystem: true, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '71b95076-6b8d-45c9-9a7c-ab981b81bd1b', + relationTargetObjectMetadataId: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + uniqueIdentifier: '20202020-f658-4d12-8b4d-248356aa4bd9', + }), + }), +} as const satisfies Record; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts index 29f5eadb0ef..21764bfb5d5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts @@ -13,7 +13,7 @@ type FlatFieldMetadataOverrides< export const getFlatFieldMetadataMock = ( overrides: FlatFieldMetadataOverrides, ): FlatFieldMetadata => { - const createdAt = new Date(); + const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date; return { createdAt, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts index af9bf519673..c4d6c70dbea 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts @@ -266,6 +266,19 @@ export class FlatFieldMetadataValidatorService { ), ); } else { + if ( + isDefined( + parentFlatObjectMetadata.fieldsById[flatFieldMetadataToValidate.id], + ) + ) { + errors.push( + new ObjectMetadataException( + 'Field with same id already exists in object', + ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED, + ), + ); + } + if (parentFlatObjectMetadata.isRemote === true) { errors.push( new ObjectMetadataException( diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-relation-flat-field-metadatas-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util.ts similarity index 78% rename from packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-relation-flat-field-metadatas-or-throw.util.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util.ts index f519860170a..911c71f7e9d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-relation-flat-field-metadatas-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util.ts @@ -8,22 +8,22 @@ import { import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; -type GetRelationFlatFieldMetadatasUtilArgs = { - existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; +export type GetRelationFlatFieldMetadatasUtilArgs = { + flatObjectMetadataMaps: FlatObjectMetadataMaps; flatFieldMetadata: FlatFieldMetadata< FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >; }; // TODO prastoin handle morph relation -export const getRelationFlatFieldMetadatasOrThrow = ({ - existingFlatObjectMetadataMaps, +export const findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow = ({ + flatObjectMetadataMaps, flatFieldMetadata, -}: GetRelationFlatFieldMetadatasUtilArgs): FlatFieldMetadata[] => { +}: GetRelationFlatFieldMetadatasUtilArgs): FlatFieldMetadata => { const { relationTargetFieldMetadataId, relationTargetObjectMetadataId } = flatFieldMetadata; const relatedFlatObjectMetadata = - existingFlatObjectMetadataMaps.byId[relationTargetObjectMetadataId]; + flatObjectMetadataMaps.byId[relationTargetObjectMetadataId]; if (!isDefined(relatedFlatObjectMetadata)) { throw new FieldMetadataException( @@ -41,5 +41,5 @@ export const getRelationFlatFieldMetadatasOrThrow = ({ ); } - return [flatFieldMetadata, relatedFlatFieldMetadata]; + return relatedFlatFieldMetadata; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata.util.ts new file mode 100644 index 00000000000..6d60f203d17 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata.util.ts @@ -0,0 +1,15 @@ +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { + findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow, + type GetRelationFlatFieldMetadatasUtilArgs, +} from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util'; + +export const findRelationFlatFieldMetadataTargetFlatFieldMetadata = ( + args: GetRelationFlatFieldMetadatasUtilArgs, +): FlatFieldMetadata | undefined => { + try { + return findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow(args); + } catch { + return undefined; + } +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts index d06aabfe546..0c37a0a7430 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts @@ -10,7 +10,7 @@ import { FieldMetadataExceptionCode, } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { getRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-relation-flat-field-metadatas-or-throw.util'; +import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util'; import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util'; @@ -52,10 +52,13 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({ FieldMetadataType.MORPH_RELATION, ) ) { - return getRelationFlatFieldMetadatasOrThrow({ - existingFlatObjectMetadataMaps, - flatFieldMetadata: flatFieldMetadataToDelete, - }); + const relationTargetFlatFieldMetadata = + findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + flatFieldMetadata: flatFieldMetadataToDelete, + }); + + return [flatFieldMetadataToDelete, relationTargetFlatFieldMetadata]; } return [flatFieldMetadataToDelete]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-field-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-field-metadata.mock.ts index 05bc5461892..0cd6cfd5865 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-field-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-field-metadata.mock.ts @@ -12,7 +12,7 @@ type FlatIndexFieldMetadataOverrides = Required< export const getFlatIndexFieldMetadataMock = ( overrides: FlatIndexFieldMetadataOverrides, ): FlatIndexFieldMetadata => { - const createdAt = faker.date.anytime(); + const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date; return { createdAt, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock.ts index 6bcf338cb2a..d0a358e6341 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock.ts @@ -1,4 +1,4 @@ -import { ALL_FLAT_OBJECT_METADATA_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object.mock'; +import { ALL_FLAT_OBJECT_METADATA_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object-metadatas.mock'; import { fromFlatObjectMetadatasToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadatas-to-flat-object-metadata-maps.util'; export const FLAT_OBJECT_METADATA_MAPS_MOCKS = diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/__snapshots__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/__snapshots__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.spec.ts.snap new file mode 100644 index 00000000000..396b1c60e62 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/__snapshots__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow should throw when object metadata id is not found 1`] = `"getSubFlatObjectMetadataMapsOfSpecificFieldsOrThrow object metadata not found"`; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.spec.ts new file mode 100644 index 00000000000..01ce810cdcb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.spec.ts @@ -0,0 +1,119 @@ +import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test'; +import { + eachTestingContextFilter, + type EachTestingContext, +} from 'twenty-shared/testing'; +import { FieldMetadataType } from 'twenty-shared/types'; + +import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; +import { PET_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/pet-flat-fields.mock'; +import { ROCKET_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/rocket-flat-fields.mock'; +import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock'; +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { + type GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs, + getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow, +} from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.util'; +import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock'; +import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock'; +import { fromFlatObjectMetadatasToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadatas-to-flat-object-metadata-maps.util'; + +type GetSubObjectMetadataMapsOrThrowTestCase = { + input: GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs; + shouldThrow?: true; + expected?: FlatObjectMetadataMaps; +}; + +describe('getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow', () => { + const testCases: EachTestingContext[] = + [ + { + title: 'should throw when object metadata id is not found', + context: { + input: { + flatFieldMetadatas: [ + getFlatFieldMetadataMock({ + objectMetadataId: 'non-existent', + type: FieldMetadataType.TEXT, + uniqueIdentifier: 'unique-identifier-1', + }), + ], + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + }, + shouldThrow: true, + }, + }, + { + title: 'should extract single object metadata from maps', + context: { + input: { + flatFieldMetadatas: [PET_FLAT_FIELDS_MOCK.species], + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + }, + expected: fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + { + ...PET_FLAT_OBJECT_MOCK, + flatFieldMetadatas: [PET_FLAT_FIELDS_MOCK.species], + }, + ]), + }, + }, + { + title: 'should extract object metadata from maps with provided fields', + context: { + input: { + flatFieldMetadatas: [ + PET_FLAT_FIELDS_MOCK.species, + PET_FLAT_FIELDS_MOCK.name, + ROCKET_FLAT_FIELDS_MOCK.attachments, + ], + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + }, + expected: fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + { + ...PET_FLAT_OBJECT_MOCK, + flatFieldMetadatas: [ + PET_FLAT_FIELDS_MOCK.name, + PET_FLAT_FIELDS_MOCK.species, + ], + }, + { + ...ROCKET_FLAT_OBJECT_MOCK, + flatFieldMetadatas: [ROCKET_FLAT_FIELDS_MOCK.attachments], + }, + ]), + }, + }, + ]; + + const filteredTestCases = eachTestingContextFilter(testCases); + + it.each(filteredTestCases)( + '$title', + ({ + context: { + input: { flatFieldMetadatas, flatObjectMetadataMaps }, + shouldThrow = false, + expected, + }, + }) => { + if (shouldThrow) { + expect(() => + getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow({ + flatFieldMetadatas, + flatObjectMetadataMaps, + }), + ).toThrowErrorMatchingSnapshot(); + } else { + jestExpectToBeDefined(expected); + const result = + getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow({ + flatFieldMetadatas, + flatObjectMetadataMaps, + }); + + expect(result).toEqual(expected); + } + }, + ); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.spec.ts new file mode 100644 index 00000000000..29c82cab307 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/__tests__/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.spec.ts @@ -0,0 +1,53 @@ +import { + eachTestingContextFilter, + type EachTestingContext, +} from 'twenty-shared/testing'; +import { FieldMetadataType } from 'twenty-shared/types'; + +import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock'; +import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock'; +import { type GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.util'; +import { getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.util'; + +type GetSubFlatObjectMetadataMapsTestCase = { + input: GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs; +}; + +describe('getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatas', () => { + const testCases: EachTestingContext[] = + [ + { + title: 'return undefined when object metadata id is not found', + context: { + input: { + flatFieldMetadatas: [ + getFlatFieldMetadataMock({ + objectMetadataId: 'non-existent', + type: FieldMetadataType.TEXT, + uniqueIdentifier: 'unique-identifier-1', + }), + ], + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + }, + }, + }, + ]; + + const filteredTestCases = eachTestingContextFilter(testCases); + + it.each(filteredTestCases)( + '$title', + ({ + context: { + input: { flatFieldMetadatas, flatObjectMetadataMaps }, + }, + }) => { + const result = getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatas({ + flatFieldMetadatas, + flatObjectMetadataMaps, + }); + + expect(result).toBeUndefined(); + }, + ); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.util.ts new file mode 100644 index 00000000000..b64ee277288 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.util.ts @@ -0,0 +1,66 @@ +import { fromArrayToValuesByKeyRecord, isDefined } from 'twenty-shared/utils'; + +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant'; +import { + FlatObjectMetadataMapsException, + FlatObjectMetadataMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception'; +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-with-field-maps-to-flat-object-metadata-maps-or-throw.util'; +import { deleteFieldFromFlatObjectMetadataWithFlatFieldMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-with-flat-field-maps-or-throw.util'; + +export type GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs = { + flatFieldMetadatas: Pick[]; + flatObjectMetadataMaps: FlatObjectMetadataMaps; +}; +export const getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow = ({ + flatObjectMetadataMaps: sourceFlatObjectMetadataMaps, + flatFieldMetadatas, +}: GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs): FlatObjectMetadataMaps => { + const fieldIdPerObjectId = fromArrayToValuesByKeyRecord({ + array: flatFieldMetadatas, + key: 'objectMetadataId', + }); + + return Object.keys(fieldIdPerObjectId).reduce( + (flatObjectMetadataMaps, objectMetadataId) => { + let flatObjectMetadataWithFlatFieldMaps = structuredClone( + sourceFlatObjectMetadataMaps.byId[objectMetadataId], + ); + + if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) { + throw new FlatObjectMetadataMapsException( + 'getSubFlatObjectMetadataMapsOfSpecificFieldsOrThrow object metadata not found', + FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_NOT_FOUND, + ); + } + + const fieldIds = + fieldIdPerObjectId[objectMetadataId]?.map(({ id }) => id) ?? []; + + flatObjectMetadataWithFlatFieldMaps = + flatObjectMetadataWithFlatFieldMaps.flatFieldMetadatas.reduce( + (flatObjectMetadataWithFlatFieldMaps, flatFieldMetadata) => { + if (fieldIds.includes(flatFieldMetadata.id)) { + return flatObjectMetadataWithFlatFieldMaps; + } + + return deleteFieldFromFlatObjectMetadataWithFlatFieldMapsOrThrow({ + fieldMetadataId: flatFieldMetadata.id, + flatObjectMetadataWithFlatFieldMaps, + }); + }, + flatObjectMetadataWithFlatFieldMaps, + ); + + return addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrow( + { + flatObjectMetadataWithFlatFieldMaps, + flatObjectMetadataMaps, + }, + ); + }, + EMPTY_FLAT_OBJECT_METADATA_MAPS, + ); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.util.ts new file mode 100644 index 00000000000..f3d0ad97f04 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.util.ts @@ -0,0 +1,15 @@ +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { + type GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs, + getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow, +} from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas-or-throw.util'; + +export const getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatas = ( + args: GetSubFlatObjectMetadataMapsOfSpecificFieldsOrThrowArgs, +): FlatObjectMetadataMaps | undefined => { + try { + return getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow(args); + } catch { + return undefined; + } +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object-metadatas.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object-metadatas.mock.ts index 9bd5adc8604..c3ecd3cd78d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object-metadatas.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object-metadatas.mock.ts @@ -1,25 +1,29 @@ import { ATTACHMENT_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/attachment-flat-object.mock'; import { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock'; +import { FAVORITE_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-flat-object.mock'; +import { FAVORITE_FOLDER_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-folder-flat-object.mock'; import { NOTE_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/note-flat-object.mock'; -import { NOTETARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/notetarget-flat-object.mock'; +import { NOTE_TARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/note-target-flat-object.mock'; import { OPPORTUNITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/opportunity-flat-object.mock'; import { PERSON_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/person-flat-object.mock'; import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock'; import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock'; import { TASK_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/task-flat-object.mock'; -import { TASKTARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/tasktarget-flat-object.mock'; -import { TIMELINEACTIVITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/timelineactivity-flat-object.mock'; +import { TASK_TARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/task-target-flat-object.mock'; +import { TIMELINE_ACTIVITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/timeline-activity-flat-object.mock'; export const ALL_FLAT_OBJECT_METADATA_MOCKS = [ - TIMELINEACTIVITY_FLAT_OBJECT_MOCK, - NOTETARGET_FLAT_OBJECT_MOCK, + TIMELINE_ACTIVITY_FLAT_OBJECT_MOCK, + NOTE_TARGET_FLAT_OBJECT_MOCK, OPPORTUNITY_FLAT_OBJECT_MOCK, PERSON_FLAT_OBJECT_MOCK, TASK_FLAT_OBJECT_MOCK, - TASKTARGET_FLAT_OBJECT_MOCK, + TASK_TARGET_FLAT_OBJECT_MOCK, PET_FLAT_OBJECT_MOCK, ROCKET_FLAT_OBJECT_MOCK, NOTE_FLAT_OBJECT_MOCK, COMPANY_FLAT_OBJECT_MOCK, ATTACHMENT_FLAT_OBJECT_MOCK, + FAVORITE_FLAT_OBJECT_MOCK, + FAVORITE_FOLDER_FLAT_OBJECT_MOCK, ] as const; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object.mock.ts deleted file mode 100644 index 9bd5adc8604..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object.mock.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ATTACHMENT_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/attachment-flat-object.mock'; -import { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock'; -import { NOTE_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/note-flat-object.mock'; -import { NOTETARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/notetarget-flat-object.mock'; -import { OPPORTUNITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/opportunity-flat-object.mock'; -import { PERSON_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/person-flat-object.mock'; -import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock'; -import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock'; -import { TASK_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/task-flat-object.mock'; -import { TASKTARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/tasktarget-flat-object.mock'; -import { TIMELINEACTIVITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/timelineactivity-flat-object.mock'; - -export const ALL_FLAT_OBJECT_METADATA_MOCKS = [ - TIMELINEACTIVITY_FLAT_OBJECT_MOCK, - NOTETARGET_FLAT_OBJECT_MOCK, - OPPORTUNITY_FLAT_OBJECT_MOCK, - PERSON_FLAT_OBJECT_MOCK, - TASK_FLAT_OBJECT_MOCK, - TASKTARGET_FLAT_OBJECT_MOCK, - PET_FLAT_OBJECT_MOCK, - ROCKET_FLAT_OBJECT_MOCK, - NOTE_FLAT_OBJECT_MOCK, - COMPANY_FLAT_OBJECT_MOCK, - ATTACHMENT_FLAT_OBJECT_MOCK, -] as const; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-flat-object.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-flat-object.mock.ts new file mode 100644 index 00000000000..39c4bfa0171 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-flat-object.mock.ts @@ -0,0 +1,29 @@ +import { FAVORITE_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-flat-fields.mock'; +import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; + +export const FAVORITE_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ + id: 'f4749ffb-dde8-44ff-8b01-d3fc82df0ba2', + standardId: '20202020-ab56-4e05-92a3-e2414a499860', + nameSingular: 'favorite', + namePlural: 'favorites', + labelSingular: 'Favorite', + labelPlural: 'Favorites', + description: 'A favorite that can be accessed from the left menu', + icon: 'IconHeart', + standardOverrides: null, + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: true, + isAuditLogged: true, + isSearchable: false, + shortcut: null, + labelIdentifierFieldMetadataId: '49f58497-4e7c-49e6-b3bc-c91d4b8a8ddc', + imageIdentifierFieldMetadataId: null, + isLabelSyncedWithName: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + uniqueIdentifier: '20202020-ab56-4e05-92a3-e2414a499860', + flatIndexMetadatas: [], + flatFieldMetadatas: Object.values(FAVORITE_FLAT_FIELDS_MOCK), +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-folder-flat-object.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-folder-flat-object.mock.ts new file mode 100644 index 00000000000..11817c2233a --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-folder-flat-object.mock.ts @@ -0,0 +1,29 @@ +import { FAVORITE_FOLDER_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/favorite-folder-flat-fields.mock'; +import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; + +export const FAVORITE_FOLDER_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ + id: '35763b74-1abe-4c4b-9eab-27693f0ee06d', + standardId: '20202020-7cf8-401f-8211-a9587d27fd2d', + nameSingular: 'favoriteFolder', + namePlural: 'favoriteFolders', + labelSingular: 'Favorite Folder', + labelPlural: 'Favorite Folders', + description: 'A Folder of favorites', + icon: 'IconFolder', + standardOverrides: null, + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: true, + isAuditLogged: true, + isSearchable: false, + shortcut: null, + labelIdentifierFieldMetadataId: '85ab1864-0a92-4682-8583-82e8b696410b', + imageIdentifierFieldMetadataId: null, + isLabelSyncedWithName: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + uniqueIdentifier: '20202020-7cf8-401f-8211-a9587d27fd2d', + flatIndexMetadatas: [], + flatFieldMetadatas: Object.values(FAVORITE_FOLDER_FLAT_FIELDS_MOCK), +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts index 465b397a4be..8c98243d99e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock.ts @@ -9,7 +9,7 @@ type FlatObjectMetadataOverrides = Required< export const getFlatObjectMetadataMock = ( overrides: FlatObjectMetadataOverrides, ): FlatObjectMetadata => { - const createdAt = new Date(); + const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date; return { flatFieldMetadatas: [], diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/notetarget-flat-object.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/note-target-flat-object.mock.ts similarity index 93% rename from packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/notetarget-flat-object.mock.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/note-target-flat-object.mock.ts index 3069255087c..915d6806b1b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/notetarget-flat-object.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/note-target-flat-object.mock.ts @@ -1,7 +1,7 @@ import { NOTETARGET_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/notetarget-flat-fields.mock'; import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; -export const NOTETARGET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ +export const NOTE_TARGET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ id: '12e3cb51-c3de-4192-b0d5-965d48d001c0', standardId: '20202020-fff0-4b44-be82-bda313884400', nameSingular: 'noteTarget', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/standard-relation-target-flat-object-metadata.mocks.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/standard-relation-target-flat-object-metadata.mocks.ts new file mode 100644 index 00000000000..5b5da763b14 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/standard-relation-target-flat-object-metadata.mocks.ts @@ -0,0 +1,17 @@ +import { ATTACHMENT_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/attachment-flat-object.mock'; +import { FAVORITE_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-flat-object.mock'; +import { FAVORITE_FOLDER_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/favorite-folder-flat-object.mock'; +import { NOTE_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/note-flat-object.mock'; +import { NOTE_TARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/note-target-flat-object.mock'; +import { TASK_TARGET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/task-target-flat-object.mock'; +import { TIMELINE_ACTIVITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/timeline-activity-flat-object.mock'; + +export const STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS = [ + NOTE_FLAT_OBJECT_MOCK, + TIMELINE_ACTIVITY_FLAT_OBJECT_MOCK, + NOTE_TARGET_FLAT_OBJECT_MOCK, + FAVORITE_FLAT_OBJECT_MOCK, + ATTACHMENT_FLAT_OBJECT_MOCK, + TASK_TARGET_FLAT_OBJECT_MOCK, + FAVORITE_FOLDER_FLAT_OBJECT_MOCK, +] as const; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/tasktarget-flat-object.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/task-target-flat-object.mock.ts similarity index 93% rename from packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/tasktarget-flat-object.mock.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/task-target-flat-object.mock.ts index 7bd58c99a9c..f8d1d282f67 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/tasktarget-flat-object.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/task-target-flat-object.mock.ts @@ -1,7 +1,7 @@ import { TASKTARGET_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/tasktarget-flat-fields.mock'; import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; -export const TASKTARGET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ +export const TASK_TARGET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ id: 'bb2b29b8-7f46-4106-a8ae-3a32df9c9166', standardId: '20202020-5a9a-44e8-95df-771cd06d0fb1', nameSingular: 'taskTarget', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/timelineactivity-flat-object.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/timeline-activity-flat-object.mock.ts similarity index 93% rename from packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/timelineactivity-flat-object.mock.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/timeline-activity-flat-object.mock.ts index 7ea9ba5b66b..7a814f75134 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/timelineactivity-flat-object.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/__mocks__/timeline-activity-flat-object.mock.ts @@ -1,7 +1,7 @@ import { TIMELINEACTIVITY_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/timelineactivity-flat-fields.mock'; import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; -export const TIMELINEACTIVITY_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ +export const TIMELINE_ACTIVITY_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({ id: '3090f830-c4b1-41a1-8e18-815760830bec', standardId: '20202020-6736-4337-b5c4-8b39fae325a5', nameSingular: 'timelineActivity', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts index 2303574678e..bbf74f40ba5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service.ts @@ -1,13 +1,19 @@ import { Injectable } from '@nestjs/common'; import { t } from '@lingui/core/macro'; +import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service'; import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type'; +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { findRelationFlatFieldMetadataTargetFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata.util'; +import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util'; +import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util'; +import { getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-out-of-flat-field-metadatas.util'; import { FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { areFlatObjectMetadataNamesSyncedWithLabels } from 'src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util'; @@ -18,13 +24,44 @@ import { ObjectMetadataException, ObjectMetadataExceptionCode, } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; +import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util'; import { doesOtherObjectWithSameNameExists } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util'; +import { WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; -export type ValidateOneFlatObjectMetadataArgs = { - existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; - flatObjectMetadataToValidate: FlatObjectMetadata; - otherFlatObjectMetadataMapsToValidate?: FlatObjectMetadataMaps; - workspaceId: string; +const computeRelationTargetFlatObjectMetadataMapsForValidation = ({ + flatFieldMetadata, + flatObjectMetadataMaps, +}: { + flatFieldMetadata: FlatFieldMetadata; + flatObjectMetadataMaps: FlatObjectMetadataMaps; +}): FlatObjectMetadataMaps | undefined => { + if ( + !isFlatFieldMetadataEntityOfType( + flatFieldMetadata, + FieldMetadataType.RELATION, + ) && + !isFlatFieldMetadataEntityOfType( + flatFieldMetadata, + FieldMetadataType.MORPH_RELATION, + ) + ) { + return undefined; + } + + const relationTargetFlatFieldMetadata = + findRelationFlatFieldMetadataTargetFlatFieldMetadata({ + flatFieldMetadata, + flatObjectMetadataMaps, + }); + + if (!isDefined(relationTargetFlatFieldMetadata)) { + return undefined; + } + + return getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatas({ + flatObjectMetadataMaps, + flatFieldMetadatas: [relationTargetFlatFieldMetadata], + }); }; @Injectable() @@ -70,9 +107,11 @@ export class FlatObjectMetadataValidatorService { public validateFlatObjectMetadataDeletion({ existingFlatObjectMetadataMaps, objectMetadataToDeleteId, + buildOptions, }: { existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; objectMetadataToDeleteId: string; + buildOptions: WorkspaceMigrationV2BuilderOptions; }) { const errors: FailedFlatObjectMetadataValidationExceptions[] = []; @@ -97,8 +136,8 @@ export class FlatObjectMetadataValidatorService { } if ( - flatObjectMetadataToDelete.standardId !== null && - !flatObjectMetadataToDelete.isCustom + !buildOptions.isSystemBuild && + isStandardMetadata(flatObjectMetadataToDelete) ) { errors.push( new ObjectMetadataException( @@ -108,7 +147,7 @@ export class FlatObjectMetadataValidatorService { ); } - if (flatObjectMetadataToDelete.isActive) { + if (!buildOptions.isSystemBuild && flatObjectMetadataToDelete.isActive) { errors.push( new ObjectMetadataException( t`Active objects cannot be deleted`, @@ -124,10 +163,30 @@ export class FlatObjectMetadataValidatorService { public async validateFlatObjectMetadataCreation({ existingFlatObjectMetadataMaps, flatObjectMetadataToValidate, - workspaceId, - }: ValidateOneFlatObjectMetadataArgs) { + otherFlatObjectMetadataMapsToValidate, + }: { + existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; + flatObjectMetadataToValidate: FlatObjectMetadata; + otherFlatObjectMetadataMapsToValidate?: FlatObjectMetadataMaps; + }) { const errors: FailedFlatObjectMetadataValidationExceptions[] = []; + if ( + isDefined( + findFlatObjectMetadataInFlatObjectMetadataMaps({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataId: flatObjectMetadataToValidate.id, + }), + ) + ) { + errors.push( + new ObjectMetadataException( + t`Object with same id already exists`, + ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, + ), + ); + } + if (flatObjectMetadataToValidate.isRemote) { errors.push( new ObjectMetadataException( @@ -146,7 +205,7 @@ export class FlatObjectMetadataValidatorService { const allFlatFieldMetadatasValidationErrors: FailedFlatFieldMetadataValidationExceptions[] = []; - let existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields = + let optimisticFlatObjectMetadataMaps = addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ flatObjectMetadata: { ...flatObjectMetadataToValidate, @@ -156,16 +215,20 @@ export class FlatObjectMetadataValidatorService { }); for (const flatFieldMetadataToValidate of flatObjectMetadataToValidate.flatFieldMetadatas) { - const otherFlatObjectMetadataMapsToValidate = undefined; // TODO prastoin when implementing import - const flatFieldValidatorErrors = await this.flatFieldMetadataValidatorService.validateFlatFieldMetadataCreation( { - existingFlatObjectMetadataMaps: - existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields, + existingFlatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, flatFieldMetadataToValidate, - workspaceId, - otherFlatObjectMetadataMapsToValidate, + workspaceId: flatObjectMetadataToValidate.workspaceId, + otherFlatObjectMetadataMapsToValidate: isDefined( + otherFlatObjectMetadataMapsToValidate, + ) + ? computeRelationTargetFlatObjectMetadataMapsForValidation({ + flatFieldMetadata: flatFieldMetadataToValidate, + flatObjectMetadataMaps: otherFlatObjectMetadataMapsToValidate, + }) + : undefined, }, ); @@ -174,11 +237,10 @@ export class FlatObjectMetadataValidatorService { continue; } - existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields = + optimisticFlatObjectMetadataMaps = addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ flatFieldMetadata: flatFieldMetadataToValidate, - flatObjectMetadataMaps: - existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields, + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts index 99a01258b92..c96aeafd5c9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util.ts @@ -5,7 +5,7 @@ import { } from 'twenty-shared/utils'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { getRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-relation-flat-field-metadatas-or-throw.util'; +import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util'; import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util'; @@ -58,10 +58,13 @@ export const fromDeleteObjectInputToFlatFieldMetadatasToDelete = ({ FieldMetadataType.MORPH_RELATION, ) ) { - return getRelationFlatFieldMetadatasOrThrow({ - existingFlatObjectMetadataMaps, - flatFieldMetadata, - }); + const relationTargetFlatFieldMetadata = + findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + flatFieldMetadata, + }); + + return [flatFieldMetadata, relationTargetFlatFieldMetadata]; } return flatFieldMetadata; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts index f28474f2043..a507da091c1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util.ts @@ -19,12 +19,12 @@ export const validateFlatObjectMetadataIdentifiers = ( flatObjectMetadata; if (isDefined(labelIdentifierFieldMetadataId)) { - const relatedFlatFieldMetadata = flatObjectMetadata.flatFieldMetadatas.find( + const flatFieldMetadata = flatObjectMetadata.flatFieldMetadatas.find( (flatFieldMetadata) => flatFieldMetadata.id === labelIdentifierFieldMetadataId, ); - if (!isDefined(relatedFlatFieldMetadata)) { + if (!isDefined(flatFieldMetadata)) { errors.push( new ObjectMetadataException( 'labelIdentifierFieldMetadataId validation failed: related field metadata not found', @@ -34,9 +34,7 @@ export const validateFlatObjectMetadataIdentifiers = ( }, ), ); - } else if ( - !isLabelIdentifierFieldMetadataTypes(relatedFlatFieldMetadata.type) - ) { + } else if (!isLabelIdentifierFieldMetadataTypes(flatFieldMetadata.type)) { errors.push( new ObjectMetadataException( 'labelIdentifierFieldMetadataId validation failed: field type not compatible', diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts index 92c1831aaea..d05b7be7422 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts @@ -2,14 +2,13 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; -import { MultipleMetadataValidationErrors } from 'src/engine/core-modules/error/multiple-metadata-validation-errors'; import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant'; import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util'; import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util'; import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util'; import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util'; +import { getSubFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps.util'; import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; -import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { fromCreateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util'; import { fromDeleteObjectInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util'; @@ -24,16 +23,13 @@ import { ObjectMetadataExceptionCode, } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; -import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; -import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service'; @Injectable() export class ObjectMetadataServiceV2 { constructor( private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, - private readonly workspaceMigrationBuilderV2: WorkspaceMigrationBuilderV2Service, - private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service, - private readonly flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, ) {} async updateOne({ @@ -56,43 +52,29 @@ export class ObjectMetadataServiceV2 { updateObjectInput, }); - const validationErrors = - this.flatObjectMetadataValidatorService.validateFlatObjectMetadataUpdate({ - existingFlatObjectMetadataMaps, - updatedFlatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata, + const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataIds: [optimisticallyUpdatedFlatObjectMetadata.id], + }); + const toFlatObjectMetadataMaps = + replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata, + flatObjectMetadataMaps: fromFlatObjectMetadataMaps, }); - if (validationErrors.length > 0) { - throw new MultipleMetadataValidationErrors( - validationErrors, - 'Multiple validation errors occurred while updating object', - ); - } - - try { - const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - objectMetadataIds: [optimisticallyUpdatedFlatObjectMetadata.id], - }); - const toFlatObjectMetadataMaps = - replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata, - flatObjectMetadataMaps: fromFlatObjectMetadataMaps, - }); - const workspaceMigration = this.workspaceMigrationBuilderV2.build({ + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps, - inferDeletionFromMissingObjectFieldIndex: false, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingObjectFieldIndex: false, + }, workspaceId, - }); - - await this.workspaceMigrationRunnerV2Service.run(workspaceMigration); - } catch { - throw new ObjectMetadataException( - 'Workspace migration failed to run', - ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR, - ); - } + errorMessage: + 'Multiple validation errors occurred while updating object', + }, + ); const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } = await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( @@ -137,67 +119,51 @@ export class ObjectMetadataServiceV2 { }); const { id: objectMetadataToDeleteId } = flatObjectMetadataToDelete; - const flatObjectDeleteValidationErrors = - this.flatObjectMetadataValidatorService.validateFlatObjectMetadataDeletion( - { - existingFlatObjectMetadataMaps, - objectMetadataToDeleteId, - }, - ); - - if (flatObjectDeleteValidationErrors.length > 0) { - throw new MultipleMetadataValidationErrors( - flatObjectDeleteValidationErrors, - 'Multiple validation errors occurred while deleting object', - ); - } - - try { - const impactedObjectMetadataIds = Array.from( - new Set( - flatFieldMetadatasToDelete.map( - (flatFieldMetadata) => flatFieldMetadata.objectMetadataId, - ), + const impactedObjectMetadataIds = Array.from( + new Set( + flatFieldMetadatasToDelete.map( + (flatFieldMetadata) => flatFieldMetadata.objectMetadataId, ), + ), + ); + + const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataIds: impactedObjectMetadataIds, + }); + + const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete + .filter( + (flatFieldMetadataToDelete) => + flatFieldMetadataToDelete.objectMetadataId !== + objectMetadataToDeleteId, + ) + .reduce( + (flatObjectMetadataMaps, flatFieldMetadata) => + deleteFieldFromFlatObjectMetadataMapsOrThrow({ + fieldMetadataId: flatFieldMetadata.id, + objectMetadataId: flatFieldMetadata.objectMetadataId, + flatObjectMetadataMaps, + }), + deleteObjectFromFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: fromFlatObjectMetadataMaps, + objectMetadataId: objectMetadataToDeleteId, + }), ); - const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - objectMetadataIds: impactedObjectMetadataIds, - }); - - const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete - .filter( - (flatFieldMetadataToDelete) => - flatFieldMetadataToDelete.objectMetadataId !== - objectMetadataToDeleteId, - ) - .reduce( - (flatObjectMetadataMaps, flatFieldMetadata) => - deleteFieldFromFlatObjectMetadataMapsOrThrow({ - fieldMetadataId: flatFieldMetadata.id, - objectMetadataId: flatFieldMetadata.objectMetadataId, - flatObjectMetadataMaps, - }), - deleteObjectFromFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: fromFlatObjectMetadataMaps, - objectMetadataId: objectMetadataToDeleteId, - }), - ); - const workspaceMigration = this.workspaceMigrationBuilderV2.build({ + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps, - inferDeletionFromMissingObjectFieldIndex: true, + buildOptions: { + inferDeletionFromMissingObjectFieldIndex: true, + isSystemBuild: false, + }, workspaceId, - }); - - await this.workspaceMigrationRunnerV2Service.run(workspaceMigration); - } catch { - throw new ObjectMetadataException( - 'Workspace migration failed to run', - ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR, - ); - } + errorMessage: + 'Multiple validation errors occurred while deleting object', + }, + ); return fromFlatObjectMetadataToObjectMetadataDto( flatObjectMetadataToDelete, @@ -224,41 +190,29 @@ export class ObjectMetadataServiceV2 { workspaceId, }); - const validationErrors = - await this.flatObjectMetadataValidatorService.validateFlatObjectMetadataCreation( - { - existingFlatObjectMetadataMaps, - flatObjectMetadataToValidate: flatObjectMetadataToCreate, - workspaceId, - }, - ); + const fromFlatObjectMetadataMaps = + getSubFlatObjectMetadataMaps({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataIds: [flatObjectMetadataToCreate.id], + }) ?? EMPTY_FLAT_OBJECT_METADATA_MAPS; - if (validationErrors.length > 0) { - throw new MultipleMetadataValidationErrors( - validationErrors, - 'Multiple validation errors occurred while creating object', - ); - } - - try { - const workspaceMigration = this.workspaceMigrationBuilderV2.build({ - fromFlatObjectMetadataMaps: EMPTY_FLAT_OBJECT_METADATA_MAPS, + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps: addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ flatObjectMetadataMaps: EMPTY_FLAT_OBJECT_METADATA_MAPS, flatObjectMetadata: flatObjectMetadataToCreate, }), - inferDeletionFromMissingObjectFieldIndex: false, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingObjectFieldIndex: false, + }, workspaceId, - }); - - await this.workspaceMigrationRunnerV2Service.run(workspaceMigration); - } catch { - throw new ObjectMetadataException( - 'Workspace migration failed to run', - ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR, - ); - } + errorMessage: + 'Multiple validation errors occurred while creating object', + }, + ); const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } = await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts index ba7b5af6505..9adfe234158 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts @@ -43,8 +43,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; -import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module'; -import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module'; +import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module'; @Module({ imports: [ @@ -69,8 +68,7 @@ import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/w WorkspaceMetadataCacheModule, WorkspaceDataSourceModule, FeatureFlagModule, - WorkspaceMigrationBuilderV2Module, - WorkspaceMigrationRunnerV2Module, + WorkspaceMigrationV2Module, ], services: [ ObjectMetadataService, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap index 66338744b36..6fd068990a1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/__snapshots__/workspace-migration-builder.spec.ts.snap @@ -16,7 +16,7 @@ exports[`Workspace migration builder field actions test suite It should build an [ { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", "flatRelationTargetFieldMetadata": null, @@ -40,7 +40,7 @@ exports[`Workspace migration builder field actions test suite It should build an "standardOverrides": null, "type": "TEXT", "uniqueIdentifier": "field-metadata-unique-identifier-1", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", @@ -52,11 +52,11 @@ exports[`Workspace migration builder field actions test suite It should build an [ { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "default flat field metadata description", "flatRelationTargetFieldMetadata": null, @@ -80,11 +80,11 @@ exports[`Workspace migration builder field actions test suite It should build an "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "field-metadata-unique-identifier-2", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": { - "createdAt": Any, + "createdAt": Any, "description": "default flat object metadata description", "duplicateCriteria": null, "flatFieldMetadatas": [], @@ -109,7 +109,7 @@ exports[`Workspace migration builder field actions test suite It should build an "standardOverrides": null, "targetTableName": "", "uniqueIdentifier": "object-metadata-unique-identifier-2", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "icon": "icon", @@ -131,7 +131,7 @@ exports[`Workspace migration builder field actions test suite It should build an "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "field-metadata-unique-identifier-1", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", @@ -267,13 +267,13 @@ exports[`Workspace migration builder index actions test suite It should build an exports[`Workspace migration builder index actions test suite It should not infer any actions as from and to indexes are identical 1`] = `[]`; -exports[`Workspace migration builder object actions test suite It should build a create_object action 1`] = ` +exports[`Workspace migration builder object actions test suite It should build a create_object action with custom object 1`] = ` [ { "createFieldActions": [ { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "uuid", "description": "Id", "flatRelationTargetFieldMetadata": null, @@ -297,14 +297,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "UUID", "uniqueIdentifier": "20202020-eda0-4cee-9577-3eb357e3c22b", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "'Untitled'", "description": "Name", "flatRelationTargetFieldMetadata": null, @@ -328,14 +328,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "TEXT", "uniqueIdentifier": "20202020-ba07-4ffd-ba63-009491f5749c", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "now", "description": "Creation date", "flatRelationTargetFieldMetadata": null, @@ -359,14 +359,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "DATE_TIME", "uniqueIdentifier": "20202020-66ac-4502-9975-e4d959c50311", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "now", "description": "Last time the record was changed", "flatRelationTargetFieldMetadata": null, @@ -390,14 +390,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "DATE_TIME", "uniqueIdentifier": "20202020-d767-4622-bdcf-d8a084834d86", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Deletion date", "flatRelationTargetFieldMetadata": null, @@ -421,14 +421,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "DATE_TIME", "uniqueIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": { "name": "''", "source": "'MANUAL'", @@ -455,14 +455,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "ACTOR", "uniqueIdentifier": "20202020-be0e-4971-865b-32ca87cbb315", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": 0, "description": "Position", "flatRelationTargetFieldMetadata": null, @@ -486,18 +486,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "POSITION", "uniqueIdentifier": "20202020-c2bd-4e16-bb9a-c8b0411bf49d", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TimelineActivities tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TimelineActivities Rocket", "flatRelationTargetFieldMetadata": null, @@ -525,7 +525,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-0be6-444e-88f0-12e0c64ac27d", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -550,18 +550,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-f1ef-4ba4-8f33-1a4577afa477", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Favorites tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Favorites Rocket", "flatRelationTargetFieldMetadata": null, @@ -589,7 +589,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-78f7-4d7d-86e9-7562ced0b381", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -614,18 +614,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-a4a7-4686-b296-1c6c3482ee21", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Attachments tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Attachments Rocket", "flatRelationTargetFieldMetadata": null, @@ -653,7 +653,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-ca2d-47c0-8253-9d0b662fb01a", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -678,18 +678,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-8d59-46ca-b7b2-73d167712134", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "NoteTargets tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "NoteTargets Rocket", "flatRelationTargetFieldMetadata": null, @@ -717,7 +717,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-52b6-4d5e-8f21-ed8b7c09c4a9", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -742,18 +742,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-01fd-4f37-99dc-9427a444018a", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TaskTargets tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TaskTargets Rocket", "flatRelationTargetFieldMetadata": null, @@ -781,7 +781,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-b98f-473d-85fb-8cebc40b9a50", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -806,14 +806,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-0860-4566-b865-bff3c626c303", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Field used for full-text search", "flatRelationTargetFieldMetadata": null, @@ -837,14 +837,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "TS_VECTOR", "uniqueIdentifier": "70e56537-18ef-4811-b1c7-0a444006b815", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, ], "flatObjectMetadataWithoutFields": { - "createdAt": Any, + "createdAt": Any, "description": "A rocket", "duplicateCriteria": null, "icon": "IconRocket", @@ -867,7 +867,1255 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "targetTableName": "DEPRECATED", "uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a", - "updatedAt": Any, + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_object", + }, +] +`; + +exports[`Workspace migration builder object actions test suite It should build a create_object action with standard object 1`] = ` +[ + { + "createFieldActions": [ + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "NoteTargets tied to the Pet", + "flatRelationTargetFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "NoteTargets Pet", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconBuildingSkyscraper", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Pet", + "name": "pet", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "joinColumnName": "petId", + "onDelete": "CASCADE", + "relationType": "MANY_TO_ONE", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-c54e-4804-8b0d-8795e5166432", + "updatedAt": Any, + "workspaceId": Any, + }, + "flatRelationTargetObjectMetadata": null, + "icon": "IconCheckbox", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "NoteTargets", + "name": "noteTargets", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "relationType": "ONE_TO_MANY", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-01fd-4f37-99dc-9427a444018a", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": "uuid", + "description": "Id", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "Icon123", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": false, + "isSystem": true, + "isUnique": false, + "label": "Id", + "name": "id", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "UUID", + "uniqueIdentifier": "20202020-eda0-4cee-9577-3eb357e3c22b", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": "'Untitled'", + "description": "Name", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconAbc", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": false, + "isSystem": false, + "isUnique": false, + "label": "Name", + "name": "name", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "TEXT", + "uniqueIdentifier": "20202020-ba07-4ffd-ba63-009491f5749c", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": "now", + "description": "Creation date", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconCalendar", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": false, + "isSystem": false, + "isUnique": false, + "label": "Creation date", + "name": "createdAt", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "DATE_TIME", + "uniqueIdentifier": "20202020-66ac-4502-9975-e4d959c50311", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": "now", + "description": "Last time the record was changed", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconCalendarClock", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": false, + "isSystem": false, + "isUnique": false, + "label": "Last update", + "name": "updatedAt", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "DATE_TIME", + "uniqueIdentifier": "20202020-d767-4622-bdcf-d8a084834d86", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "Deletion date", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconCalendarClock", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Deleted at", + "name": "deletedAt", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "DATE_TIME", + "uniqueIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": { + "name": "''", + "source": "'MANUAL'", + }, + "description": "The creator of the record", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconCreativeCommonsSa", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": false, + "isSystem": false, + "isUnique": false, + "label": "Created by", + "name": "createdBy", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "ACTOR", + "uniqueIdentifier": "20202020-be0e-4971-865b-32ca87cbb315", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": 0, + "description": "Position", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconHierarchy2", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": false, + "isSystem": true, + "isUnique": false, + "label": "Position", + "name": "position", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "POSITION", + "uniqueIdentifier": "20202020-c2bd-4e16-bb9a-c8b0411bf49d", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "TimelineActivities tied to the Pet", + "flatRelationTargetFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "TimelineActivities Pet", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconBuildingSkyscraper", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Pet", + "name": "pet", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "joinColumnName": "petId", + "onDelete": "CASCADE", + "relationType": "MANY_TO_ONE", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-d88f-4020-8c85-d6c600903762", + "updatedAt": Any, + "workspaceId": Any, + }, + "flatRelationTargetObjectMetadata": null, + "icon": "IconTimelineEvent", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "TimelineActivities", + "name": "timelineActivities", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "relationType": "ONE_TO_MANY", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-f1ef-4ba4-8f33-1a4577afa477", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "Favorites tied to the Pet", + "flatRelationTargetFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "Favorites Pet", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconBuildingSkyscraper", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Pet", + "name": "pet", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "joinColumnName": "petId", + "onDelete": "CASCADE", + "relationType": "MANY_TO_ONE", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-1bd7-4d10-80dc-968b9f5d1a8a", + "updatedAt": Any, + "workspaceId": Any, + }, + "flatRelationTargetObjectMetadata": null, + "icon": "IconHeart", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Favorites", + "name": "favorites", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "relationType": "ONE_TO_MANY", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-a4a7-4686-b296-1c6c3482ee21", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "Attachments tied to the Pet", + "flatRelationTargetFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "Attachments Pet", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconBuildingSkyscraper", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Pet", + "name": "pet", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "joinColumnName": "petId", + "onDelete": "CASCADE", + "relationType": "MANY_TO_ONE", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-b715-4658-8b6a-5359824dbddd", + "updatedAt": Any, + "workspaceId": Any, + }, + "flatRelationTargetObjectMetadata": null, + "icon": "IconFileImport", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Attachments", + "name": "attachments", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "relationType": "ONE_TO_MANY", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-8d59-46ca-b7b2-73d167712134", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "TaskTargets tied to the Pet", + "flatRelationTargetFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "TaskTargets Pet", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": "IconBuildingSkyscraper", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Pet", + "name": "pet", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "joinColumnName": "petId", + "onDelete": "CASCADE", + "relationType": "MANY_TO_ONE", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-07d9-4d04-8b73-a39ab65943f1", + "updatedAt": Any, + "workspaceId": Any, + }, + "flatRelationTargetObjectMetadata": null, + "icon": "IconCheckbox", + "id": Any, + "isActive": true, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "TaskTargets", + "name": "taskTargets", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": Any, + "relationTargetObjectMetadataId": Any, + "settings": { + "relationType": "ONE_TO_MANY", + }, + "standardId": Any, + "standardOverrides": null, + "type": "RELATION", + "uniqueIdentifier": "20202020-0860-4566-b865-bff3c626c303", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": "Field used for full-text search", + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": false, + "isCustom": false, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": true, + "isUnique": false, + "label": "Search vector", + "name": "searchVector", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": Any, + "standardOverrides": null, + "type": "TS_VECTOR", + "uniqueIdentifier": "70e56537-18ef-4811-b1c7-0a444006b815", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Species", + "name": "species", + "objectMetadataId": Any, + "options": [ + { + "color": "blue", + "id": Any, + "label": "Dog", + "position": 0, + "value": "DOG", + }, + { + "color": "red", + "id": Any, + "label": "Cat", + "position": 1, + "value": "CAT", + }, + { + "color": "green", + "id": Any, + "label": "Bird", + "position": 2, + "value": "BIRD", + }, + { + "color": "yellow", + "id": Any, + "label": "Fish", + "position": 3, + "value": "FISH", + }, + { + "color": "purple", + "id": Any, + "label": "Rabbit", + "position": 4, + "value": "RABBIT", + }, + { + "color": "orange", + "id": Any, + "label": "Hamster", + "position": 5, + "value": "HAMSTER", + }, + ], + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "SELECT", + "uniqueIdentifier": "0292cc51-246f-40b8-99e8-3345dac0e688", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Traits", + "name": "traits", + "objectMetadataId": Any, + "options": [ + { + "color": "blue", + "id": Any, + "label": "Playful", + "position": 0, + "value": "PLAYFUL", + }, + { + "color": "red", + "id": Any, + "label": "Friendly", + "position": 1, + "value": "FRIENDLY", + }, + { + "color": "green", + "id": Any, + "label": "Protective", + "position": 2, + "value": "PROTECTIVE", + }, + { + "color": "yellow", + "id": Any, + "label": "Shy", + "position": 3, + "value": "SHY", + }, + { + "color": "purple", + "id": Any, + "label": "Brave", + "position": 4, + "value": "BRAVE", + }, + { + "color": "orange", + "id": Any, + "label": "Curious", + "position": 5, + "value": "CURIOUS", + }, + ], + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "MULTI_SELECT", + "uniqueIdentifier": "5aaf6be0-b9f5-4222-9810-941d5ab58527", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": "''", + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": false, + "isSystem": false, + "isUnique": false, + "label": "Comments", + "name": "comments", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "TEXT", + "uniqueIdentifier": "2e726631-d80a-4594-9a5a-3cca0c2a50a9", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Age", + "name": "age", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "NUMBER", + "uniqueIdentifier": "ed1573cf-b29e-4d4f-a8e3-19d472d28c0d", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": { + "addressCity": "''", + "addressCountry": "''", + "addressLat": null, + "addressLng": null, + "addressPostcode": "''", + "addressState": "''", + "addressStreet1": "''", + "addressStreet2": "''", + }, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Location", + "name": "location", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "ADDRESS", + "uniqueIdentifier": "7b05e127-d0ea-422a-8f98-ef48f41d4388", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": { + "additionalPhones": null, + "primaryPhoneCallingCode": "''", + "primaryPhoneCountryCode": "''", + "primaryPhoneNumber": "''", + }, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Vet phone", + "name": "vetPhone", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "PHONES", + "uniqueIdentifier": "92c856ef-5b4b-4202-8c23-b2f22f959eda", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": { + "additionalEmails": null, + "primaryEmail": "''", + }, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Vet email", + "name": "vetEmail", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "EMAILS", + "uniqueIdentifier": "2c89697d-925a-485c-a0ed-adf2f1228e0f", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Birthday", + "name": "birthday", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "DATE", + "uniqueIdentifier": "13c5b4ed-5746-494f-856b-081bdbb4a8ec", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Is good with kids", + "name": "isGoodWithKids", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "BOOLEAN", + "uniqueIdentifier": "ec3bf27e-70e6-4d4a-932e-6ef026013603", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": { + "primaryLinkLabel": "''", + "primaryLinkUrl": "''", + "secondaryLinks": [], + }, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Pictures", + "name": "pictures", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "LINKS", + "uniqueIdentifier": "8aa0a700-c308-4d8b-8fbd-f545daa8980a", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": { + "amountMicros": null, + "currencyCode": "''", + }, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Average cost of kibble per month", + "name": "averageCostOfKibblePerMonth", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "CURRENCY", + "uniqueIdentifier": "37421848-b64d-4827-be33-da4e337acbb6", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": { + "firstName": "''", + "lastName": "''", + }, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Makes its owner think of", + "name": "makesOwnerThinkOf", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "FULL_NAME", + "uniqueIdentifier": "84b874dc-983d-47eb-9c67-54f557a99f60", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Sound swag (bark style, meow style, etc.)", + "name": "soundSwag", + "objectMetadataId": Any, + "options": [ + { + "id": Any, + "label": "1", + "position": 0, + "value": "RATING_1", + }, + { + "id": Any, + "label": "2", + "position": 1, + "value": "RATING_2", + }, + { + "id": Any, + "label": "3", + "position": 2, + "value": "RATING_3", + }, + { + "id": Any, + "label": "4", + "position": 3, + "value": "RATING_4", + }, + { + "id": Any, + "label": "5", + "position": 4, + "value": "RATING_5", + }, + ], + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "RATING", + "uniqueIdentifier": "42e6c0d2-6d00-4c77-b179-890af8c56236", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Bio", + "name": "bio", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "RICH_TEXT", + "uniqueIdentifier": "fa0ec409-0f29-4b19-b304-31d1018a2344", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Interesting facts", + "name": "interestingFacts", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "ARRAY", + "uniqueIdentifier": "2b806a74-e7a5-4ec7-b902-67cf811e8bda", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + { + "flatFieldMetadata": { + "createdAt": Any, + "defaultValue": null, + "description": null, + "flatRelationTargetFieldMetadata": null, + "flatRelationTargetObjectMetadata": null, + "icon": null, + "id": Any, + "isActive": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isNullable": true, + "isSystem": false, + "isUnique": false, + "label": "Extra data", + "name": "extraData", + "objectMetadataId": Any, + "options": null, + "relationTargetFieldMetadataId": null, + "relationTargetObjectMetadataId": null, + "settings": null, + "standardId": null, + "standardOverrides": null, + "type": "RAW_JSON", + "uniqueIdentifier": "a79bd7c8-c7ce-416a-9d12-a05335c9c596", + "updatedAt": Any, + "workspaceId": Any, + }, + "type": "create_field", + }, + ], + "flatObjectMetadataWithoutFields": { + "createdAt": Any, + "description": null, + "duplicateCriteria": null, + "icon": "IconCat", + "id": Any, + "imageIdentifierFieldMetadataId": null, + "isActive": true, + "isAuditLogged": true, + "isCustom": true, + "isLabelSyncedWithName": false, + "isRemote": false, + "isSearchable": true, + "isSystem": false, + "labelIdentifierFieldMetadataId": Any, + "labelPlural": "Pets", + "labelSingular": "Pet", + "namePlural": "pets", + "nameSingular": "pet", + "shortcut": null, + "standardId": null, + "standardOverrides": null, + "targetTableName": "DEPRECATED", + "uniqueIdentifier": "d34e0f07-1b8c-4de0-938e-599cf05e1f7f", + "updatedAt": Any, "workspaceId": Any, }, "type": "create_object", @@ -881,7 +2129,7 @@ exports[`Workspace migration builder object actions test suite It should build a "createFieldActions": [ { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "uuid", "description": "Id", "flatRelationTargetFieldMetadata": null, @@ -905,14 +2153,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "UUID", "uniqueIdentifier": "20202020-eda0-4cee-9577-3eb357e3c22b", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "'Untitled'", "description": "Name", "flatRelationTargetFieldMetadata": null, @@ -936,14 +2184,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "TEXT", "uniqueIdentifier": "20202020-ba07-4ffd-ba63-009491f5749c", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "now", "description": "Creation date", "flatRelationTargetFieldMetadata": null, @@ -967,14 +2215,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "DATE_TIME", "uniqueIdentifier": "20202020-66ac-4502-9975-e4d959c50311", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": "now", "description": "Last time the record was changed", "flatRelationTargetFieldMetadata": null, @@ -998,14 +2246,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "DATE_TIME", "uniqueIdentifier": "20202020-d767-4622-bdcf-d8a084834d86", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Deletion date", "flatRelationTargetFieldMetadata": null, @@ -1029,14 +2277,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "DATE_TIME", "uniqueIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": { "name": "''", "source": "'MANUAL'", @@ -1063,14 +2311,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "ACTOR", "uniqueIdentifier": "20202020-be0e-4971-865b-32ca87cbb315", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": 0, "description": "Position", "flatRelationTargetFieldMetadata": null, @@ -1094,18 +2342,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "POSITION", "uniqueIdentifier": "20202020-c2bd-4e16-bb9a-c8b0411bf49d", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TimelineActivities tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TimelineActivities Rocket", "flatRelationTargetFieldMetadata": null, @@ -1133,7 +2381,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-0be6-444e-88f0-12e0c64ac27d", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -1158,18 +2406,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-f1ef-4ba4-8f33-1a4577afa477", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Favorites tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Favorites Rocket", "flatRelationTargetFieldMetadata": null, @@ -1197,7 +2445,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-78f7-4d7d-86e9-7562ced0b381", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -1222,18 +2470,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-a4a7-4686-b296-1c6c3482ee21", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Attachments tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Attachments Rocket", "flatRelationTargetFieldMetadata": null, @@ -1261,7 +2509,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-ca2d-47c0-8253-9d0b662fb01a", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -1286,18 +2534,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-8d59-46ca-b7b2-73d167712134", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "NoteTargets tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "NoteTargets Rocket", "flatRelationTargetFieldMetadata": null, @@ -1325,7 +2573,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-52b6-4d5e-8f21-ed8b7c09c4a9", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -1350,18 +2598,18 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-01fd-4f37-99dc-9427a444018a", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TaskTargets tied to the Rocket", "flatRelationTargetFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "TaskTargets Rocket", "flatRelationTargetFieldMetadata": null, @@ -1389,7 +2637,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-b98f-473d-85fb-8cebc40b9a50", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "flatRelationTargetObjectMetadata": null, @@ -1414,14 +2662,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "RELATION", "uniqueIdentifier": "20202020-0860-4566-b865-bff3c626c303", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, { "flatFieldMetadata": { - "createdAt": Any, + "createdAt": Any, "defaultValue": null, "description": "Field used for full-text search", "flatRelationTargetFieldMetadata": null, @@ -1445,14 +2693,14 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "type": "TS_VECTOR", "uniqueIdentifier": "70e56537-18ef-4811-b1c7-0a444006b815", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_field", }, ], "flatObjectMetadataWithoutFields": { - "createdAt": Any, + "createdAt": Any, "description": "A rocket", "duplicateCriteria": null, "icon": "IconRocket", @@ -1475,7 +2723,7 @@ exports[`Workspace migration builder object actions test suite It should build a "standardOverrides": null, "targetTableName": "DEPRECATED", "uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a", - "updatedAt": Any, + "updatedAt": Any, "workspaceId": Any, }, "type": "create_object", @@ -1500,7 +2748,81 @@ exports[`Workspace migration builder object actions test suite It should build a ] `; -exports[`Workspace migration builder object actions test suite It should build a delete_object action 1`] = ` +exports[`Workspace migration builder object actions test suite It should build a delete_object action with custom deactivated object 1`] = ` +[ + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "fieldMetadataId": Any, + "objectMetadataId": Any, + "type": "delete_field", + }, + { + "objectMetadataId": Any, + "type": "delete_object", + }, +] +`; + +exports[`Workspace migration builder object actions test suite It should build a delete_object action with standard activated object 1`] = ` [ { "fieldMetadataId": Any, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts index 6a86e04f5f5..52480d3a3d9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case.ts @@ -1,5 +1,4 @@ import { getFlatIndexMetadataMock } from 'src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock'; -import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant'; import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock'; import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util'; import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; @@ -8,116 +7,187 @@ import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-obje import { OPPORTUNITY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/opportunity-flat-object.mock'; import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock'; import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock'; +import { STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/standard-relation-target-flat-object-metadata.mocks'; import { fromFlatObjectMetadatasToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadatas-to-flat-object-metadata-maps.util'; import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type'; +// TODO prastoin implement validation exception tests cases coverage too +const DELETE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [ + { + title: + 'It should build a delete_object action with custom deactivated object', + context: { + input: { + fromFlatObjectMetadataMaps: + replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: getFlatObjectMetadataMock({ + ...ROCKET_FLAT_OBJECT_MOCK, + isActive: false, + }), + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + }), + toFlatObjectMetadataMaps: deleteObjectFromFlatObjectMetadataMapsOrThrow( + { + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id, + }, + ), + }, + expectedActionsTypeCounter: { + deleteObject: 1, + deleteField: ROCKET_FLAT_OBJECT_MOCK.flatFieldMetadatas.length, + }, + }, + }, + { + title: + 'It should build a delete_object action with standard activated object', + context: { + input: { + buildOptions: { + inferDeletionFromMissingObjectFieldIndex: true, + isSystemBuild: true, + }, + fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + toFlatObjectMetadataMaps: deleteObjectFromFlatObjectMetadataMapsOrThrow( + { + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + objectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id, + }, + ), + }, + expectedActionsTypeCounter: { + deleteObject: 1, + deleteField: COMPANY_FLAT_OBJECT_MOCK.flatFieldMetadatas.length, + }, + }, + }, +]; + +const CREATE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [ + { + title: 'It should build a create_object action with custom object', + context: { + input: { + fromFlatObjectMetadataMaps: + fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + PET_FLAT_OBJECT_MOCK, + ...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS, + ]), + toFlatObjectMetadataMaps: + fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + PET_FLAT_OBJECT_MOCK, + ROCKET_FLAT_OBJECT_MOCK, + ...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS, + ]), + }, + expectedActionsTypeCounter: { + createObject: 1, + }, + }, + }, + { + title: 'It should build a create_object action with standard object', + context: { + input: { + fromFlatObjectMetadataMaps: + fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + ROCKET_FLAT_OBJECT_MOCK, + ...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS, + ]), + toFlatObjectMetadataMaps: + fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + PET_FLAT_OBJECT_MOCK, + ROCKET_FLAT_OBJECT_MOCK, + ...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS, + ]), + }, + expectedActionsTypeCounter: { + createObject: 1, + }, + }, + }, + { + title: + 'It should build a create_object and create_index actions for each of this fieldMetadata', + context: { + expectedActionsTypeCounter: { + createIndex: 1, + createObject: 1, + }, + input: { + fromFlatObjectMetadataMaps: + fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + ...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS, + ]), + toFlatObjectMetadataMaps: + fromFlatObjectMetadatasToFlatObjectMetadataMaps([ + ...STANDARD_RELATION_TARGET_FLAT_OBJECT_METADATA_MOCKS, + { + ...ROCKET_FLAT_OBJECT_MOCK, + flatIndexMetadatas: [ + getFlatIndexMetadataMock({ + objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id, + uniqueIdentifier: 'field-metadata-unique-identifier-1', + }), + ], + }, + ]), + }, + }, + }, +]; + +const UPDATE_OBJECT_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [ + { + title: 'It should build an update_object for custom object', + context: { + input: { + fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + toFlatObjectMetadataMaps: + replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatObjectMetadata: getFlatObjectMetadataMock({ + ...PET_FLAT_OBJECT_MOCK, + nameSingular: 'prastouin', + namePlural: 'prastoins', + isLabelSyncedWithName: false, + }), + }), + }, + expectedActionsTypeCounter: { + updateObject: 1, + }, + }, + }, + { + title: 'It should build an update_object for standard object', + context: { + input: { + fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + toFlatObjectMetadataMaps: + replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, + flatObjectMetadata: getFlatObjectMetadataMock({ + ...OPPORTUNITY_FLAT_OBJECT_MOCK, + standardOverrides: { + description: 'Updated description', + }, + }), + }), + }, + expectedActionsTypeCounter: { + updateObject: 1, + }, + }, + }, +]; + export const WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES: WorkspaceMigrationBuilderTestCase[] = [ - { - title: 'It should build an update_object for custom object', - context: { - input: { - fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, - toFlatObjectMetadataMaps: - replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, - flatObjectMetadata: getFlatObjectMetadataMock({ - ...PET_FLAT_OBJECT_MOCK, - nameSingular: 'prastouin', - namePlural: 'prastoins', - isLabelSyncedWithName: false, - }), - }), - }, - expectedActionsTypeCounter: { - updateObject: 1, - }, - }, - }, - { - title: 'It should build an update_object for standard object', - context: { - input: { - fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, - toFlatObjectMetadataMaps: - replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, - flatObjectMetadata: getFlatObjectMetadataMock({ - ...OPPORTUNITY_FLAT_OBJECT_MOCK, - standardOverrides: { - description: 'Updated description', - }, - }), - }), - }, - expectedActionsTypeCounter: { - updateObject: 1, - }, - }, - }, - { - title: 'It should build a create_object action', - context: { - input: { - fromFlatObjectMetadataMaps: - fromFlatObjectMetadatasToFlatObjectMetadataMaps([ - PET_FLAT_OBJECT_MOCK, - ]), - toFlatObjectMetadataMaps: - fromFlatObjectMetadatasToFlatObjectMetadataMaps([ - PET_FLAT_OBJECT_MOCK, - ROCKET_FLAT_OBJECT_MOCK, - ]), - }, - expectedActionsTypeCounter: { - createObject: 1, - }, - }, - }, - { - title: - 'It should build a create_object and create_index actions for each of this fieldMetadata', - context: { - input: { - fromFlatObjectMetadataMaps: EMPTY_FLAT_OBJECT_METADATA_MAPS, - toFlatObjectMetadataMaps: - fromFlatObjectMetadatasToFlatObjectMetadataMaps([ - { - ...ROCKET_FLAT_OBJECT_MOCK, - flatIndexMetadatas: [ - getFlatIndexMetadataMock({ - objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id, - uniqueIdentifier: 'field-metadata-unique-identifier-1', - }), - ], - }, - ]), - }, - - expectedActionsTypeCounter: { - createObject: 1, - createField: 0, - createIndex: 1, - }, - }, - }, - { - title: 'It should build a delete_object action', - context: { - input: { - fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, - toFlatObjectMetadataMaps: - deleteObjectFromFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS, - objectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id, - }), - }, - expectedActionsTypeCounter: { - deleteObject: 1, - deleteField: COMPANY_FLAT_OBJECT_MOCK.flatFieldMetadatas.length, - }, - }, - }, + ...DELETE_OBJECT_TEST_CASES, + ...CREATE_OBJECT_TEST_CASES, + ...UPDATE_OBJECT_TEST_CASES, { title: 'It should not infer any actions as from and to are identical', context: { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type.ts index 517e8d61825..26f28105714 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type.ts @@ -11,9 +11,15 @@ export type ExpectedActionCounters = Partial< Record >; +type TestWorkspaceMigrationBuildArgs = Omit< + WorkspaceMigrationBuildArgs, + 'workspaceId' | 'buildOptions' +> & + Partial>; + export type WorkspaceMigrationBuilderTestCase = EachTestingContext<{ input: - | Omit - | (() => Omit); + | TestWorkspaceMigrationBuildArgs + | (() => TestWorkspaceMigrationBuildArgs); expectedActionsTypeCounter?: ExpectedActionCounters; }>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-builder.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-builder.spec.ts index 020b7998f21..e21921a0643 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-builder.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/workspace-migration-builder.spec.ts @@ -2,6 +2,11 @@ import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-i import { eachTestingContextFilter } from 'twenty-shared/testing'; import { capitalize } from 'twenty-shared/utils'; +import { type FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service'; +import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service'; +import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service'; +import { WorkspaceMigrationV2ObjectActionsBuilder } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder'; import { WORKSPACE_MIGRATION_FIELD_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-field-test-case'; import { WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-index-test-case'; import { WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case'; @@ -74,31 +79,63 @@ describe.each(allWorkspaceBuilderTestCases)( 'Workspace migration builder $label actions test suite', ({ testCases }) => { let service: WorkspaceMigrationBuilderV2Service; + let featureFlagService: FeatureFlagService; + let flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService; + let flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService; + let flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService; + let objectActionsBuilder: WorkspaceMigrationV2ObjectActionsBuilder; beforeEach(() => { - service = new WorkspaceMigrationBuilderV2Service(); + featureFlagService = { + isFeatureEnabled: jest.fn().mockResolvedValue(true), + } as any; + + flatFieldMetadataTypeValidatorService = + new FlatFieldMetadataTypeValidatorService(featureFlagService); + + flatFieldMetadataValidatorService = new FlatFieldMetadataValidatorService( + flatFieldMetadataTypeValidatorService, + ); + + flatObjectMetadataValidatorService = + new FlatObjectMetadataValidatorService( + flatFieldMetadataValidatorService, + ); + + objectActionsBuilder = new WorkspaceMigrationV2ObjectActionsBuilder( + flatObjectMetadataValidatorService, + ); + service = new WorkspaceMigrationBuilderV2Service(objectActionsBuilder); }); it.each(eachTestingContextFilter(testCases))( '$title', - ({ context: { input, expectedActionsTypeCounter } }) => { + async ({ context: { input, expectedActionsTypeCounter } }) => { const { fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps, - inferDeletionFromMissingObjectFieldIndex, + buildOptions = { + inferDeletionFromMissingObjectFieldIndex: true, + isSystemBuild: false, + }, } = typeof input === 'function' ? input() : input; - const workspaceMigration = service.build({ + const validateAndBuildResult = await service.validateAndBuild({ + buildOptions, fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps, - inferDeletionFromMissingObjectFieldIndex, workspaceId: '20202020-52cc-4c64-ad63-76c26fc3a1e1', }); + expect(validateAndBuildResult.status).toBe('success'); + if (validateAndBuildResult.status !== 'success') { + throw new Error('Should never occur'); + } + expectedActionsTypeCounterChecker({ expectedActionsTypeCounter, - workspaceMigration, + workspaceMigration: validateAndBuildResult.workspaceMigration, }); - const { actions } = workspaceMigration; + const { actions } = validateAndBuildResult.workspaceMigration; expect(actions).toMatchSnapshot( actions.map(extractRecordIdsAndDatesAsExpectAny), diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/constants/empty-failed-and-successful-metadata-build-record.constant.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/constants/empty-failed-and-successful-metadata-build-record.constant.ts new file mode 100644 index 00000000000..eaf83b73f20 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/constants/empty-failed-and-successful-metadata-build-record.constant.ts @@ -0,0 +1,6 @@ +import { type FailedAndSuccessfulMetadataValidateAndBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type'; + +export const EMPTY_FAILED_AND_SUCCESSFUL_METADATA_BUILD_RECORD = { + failed: [], + successful: [], +} as const satisfies FailedAndSuccessfulMetadataValidateAndBuildRecord; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type.ts new file mode 100644 index 00000000000..db3ee5bcdf2 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type.ts @@ -0,0 +1,10 @@ +import { type FailedMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type'; +import { type SuccessfulMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/successful-metadata-validate-and-build.type'; +import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; + +export type FailedAndSuccessfulMetadataValidateAndBuildRecord< + T extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2, +> = { + failed: FailedMetadataValidateAndBuild[]; + successful: SuccessfulMetadataValidateAndBuild[]; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type.ts new file mode 100644 index 00000000000..5ab45d44a78 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type.ts @@ -0,0 +1,10 @@ +import { type FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type'; +import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type'; + +export type FailedMetadataValidateAndBuild = { + status: 'fail'; + errors: ( + | FailedFlatFieldMetadataValidationExceptions + | FailedFlatObjectMetadataValidationExceptions + )[]; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-action-validate-and-build-result.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-action-validate-and-build-result.type.ts new file mode 100644 index 00000000000..f1e94da068f --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-action-validate-and-build-result.type.ts @@ -0,0 +1,10 @@ +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { type FailedAndSuccessfulMetadataValidateAndBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type'; +import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; + +export type MetadataActionValidateAndBuildResult< + T extends WorkspaceMigrationActionV2, +> = { + results: FailedAndSuccessfulMetadataValidateAndBuildRecord; + optimisticFlatObjectMetadataMaps: FlatObjectMetadataMaps; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-validate-and-build-result.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-validate-and-build-result.type.ts new file mode 100644 index 00000000000..28398a99f28 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-validate-and-build-result.type.ts @@ -0,0 +1,7 @@ +import { type FailedMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type'; +import { type SuccessfulMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/successful-metadata-validate-and-build.type'; +import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; + +export type MetadataValidateAndBuildResult< + T extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2, +> = FailedMetadataValidateAndBuild | SuccessfulMetadataValidateAndBuild; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/successful-metadata-validate-and-build.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/successful-metadata-validate-and-build.type.ts new file mode 100644 index 00000000000..2aa6833e636 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/successful-metadata-validate-and-build.type.ts @@ -0,0 +1,8 @@ +import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; + +export type SuccessfulMetadataValidateAndBuild< + T extends WorkspaceMigrationActionV2 = WorkspaceMigrationActionV2, +> = { + status: 'success'; + result: T; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts index 82a9ff9b9e6..6ffe98e6e93 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util.ts @@ -9,7 +9,7 @@ import { export type UpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = { flatObjectMetadata: FlatObjectMetadata; -} & CustomDeletedCreatedUpdatedMatrix<'fieldMetadata', FlatFieldMetadata>; +} & CustomDeletedCreatedUpdatedMatrix<'flatFieldMetadata', FlatFieldMetadata>; export const computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = ( updatedObjectMetadata: FromTo[], @@ -25,9 +25,9 @@ export const computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix = ( matrixAccumulator.push({ flatObjectMetadata: to, - createdFieldMetadata: fieldMetadataMatrix.created, - deletedFieldMetadata: fieldMetadataMatrix.deleted, - updatedFieldMetadata: fieldMetadataMatrix.updated, + createdFlatFieldMetadata: fieldMetadataMatrix.created, + deletedFlatFieldMetadata: fieldMetadataMatrix.deleted, + updatedFlatFieldMetadata: fieldMetadataMatrix.updated, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-metadata-build-results-to-failed-and-successful-metadata-build-record.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-metadata-build-results-to-failed-and-successful-metadata-build-record.util.ts new file mode 100644 index 00000000000..b49e0ef36cc --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-metadata-build-results-to-failed-and-successful-metadata-build-record.util.ts @@ -0,0 +1,19 @@ +import { EMPTY_FAILED_AND_SUCCESSFUL_METADATA_BUILD_RECORD } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/constants/empty-failed-and-successful-metadata-build-record.constant'; +import { type FailedAndSuccessfulMetadataValidateAndBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-and-successful-metadata-validate-and-build-record.type'; +import { type MetadataValidateAndBuildResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-validate-and-build-result.type'; +import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; + +export const fromMetadataBuildResultsToFailedAndSuccessfulMetadataBuildRecord = + ( + metadataBuildResults: MetadataValidateAndBuildResult[], + ): FailedAndSuccessfulMetadataValidateAndBuildRecord => + metadataBuildResults.reduce( + (acc, buildResult) => + buildResult.status === 'fail' + ? { + ...acc, + failed: [...acc.failed, buildResult], + } + : { ...acc, successful: [...acc.successful, buildResult] }, + EMPTY_FAILED_AND_SUCCESSFUL_METADATA_BUILD_RECORD, + ); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts index c98f4bf947a..f94bcb12481 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts @@ -1,11 +1,21 @@ import { Module } from '@nestjs/common'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service'; +import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service'; +import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service'; import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; +import { WorkspaceMigrationV2ObjectActionsBuilder } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder'; @Module({ imports: [FeatureFlagModule], - providers: [WorkspaceMigrationBuilderV2Service], + providers: [ + WorkspaceMigrationBuilderV2Service, + FlatFieldMetadataValidatorService, + FlatFieldMetadataTypeValidatorService, + WorkspaceMigrationV2ObjectActionsBuilder, + FlatObjectMetadataValidatorService, + ], exports: [WorkspaceMigrationBuilderV2Service], }) export class WorkspaceMigrationBuilderV2Module {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts index 4066244b0a4..dd52edabfc6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service.ts @@ -5,28 +5,44 @@ import { type FromTo } from 'twenty-shared/types'; import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; import { fromFlatObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-maps-to-flat-object-metadatas.util'; import { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util'; -import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; +import { FailedMetadataValidateAndBuild } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/failed-metadata-validate-and-build.type'; +import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; import { computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util'; import { computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util'; import { getWorkspaceMigrationV2FieldDeleteAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions'; import { getWorkspaceMigrationV2CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions'; import { buildWorkspaceMigrationV2FieldActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder'; import { buildWorkspaceMigrationIndexActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-index-actions-builder'; -import { buildWorkspaceMigrationV2ObjectActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder'; +import { WorkspaceMigrationV2ObjectActionsBuilder } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder'; + +export type WorkspaceMigrationV2BuilderOptions = { + inferDeletionFromMissingObjectFieldIndex: boolean; + isSystemBuild: boolean; +}; + export type WorkspaceMigrationBuildArgs = { workspaceId: string; - inferDeletionFromMissingObjectFieldIndex?: boolean; + buildOptions: WorkspaceMigrationV2BuilderOptions; + // Could from be optional ? we still have a race condition when if we recompute cache it could have been different than when we calculated the to } & FromTo; @Injectable() export class WorkspaceMigrationBuilderV2Service { - constructor() {} + constructor( + private readonly workspaceMigrationV2ObjectActionsBuilder: WorkspaceMigrationV2ObjectActionsBuilder, + ) {} - build({ + public async validateAndBuild({ fromFlatObjectMetadataMaps, toFlatObjectMetadataMaps, workspaceId, - inferDeletionFromMissingObjectFieldIndex = true, - }: WorkspaceMigrationBuildArgs): WorkspaceMigrationV2 { + buildOptions, + }: WorkspaceMigrationBuildArgs): Promise< + | FailedMetadataValidateAndBuild + | { + status: 'success'; + workspaceMigration: WorkspaceMigrationV2; + } + > { const fromFlatObjectMetadatas = fromFlatObjectMetadataMapsToFlatObjectMetadatas( fromFlatObjectMetadataMaps, @@ -34,33 +50,50 @@ export class WorkspaceMigrationBuilderV2Service { const toFlatObjectMetadatas = fromFlatObjectMetadataMapsToFlatObjectMetadatas(toFlatObjectMetadataMaps); + // Dispatch const { - created: createdObjectMetadata, - deleted: deletedObjectMetadata, - updated: updatedObjectMetadata, + created: createdFlatObjectMetadata, + deleted: deletedFlatObjectMetadata, + updated: updatedFlatObjectMetadata, } = deletedCreatedUpdatedMatrixDispatcher({ from: fromFlatObjectMetadatas, to: toFlatObjectMetadatas, }); + const objectMetadataDeletedCreatedUpdatedIndex = + computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix( + updatedFlatObjectMetadata, + ); + + const objectMetadataDeletedCreatedUpdatedFields = + computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix( + updatedFlatObjectMetadata, + ); + /// + + // Validate and build const objectWorkspaceMigrationActions = - buildWorkspaceMigrationV2ObjectActions({ - createdObjectMetadata, - deletedObjectMetadata, - updatedObjectMetadata, - inferDeletionFromMissingObjectFieldIndex, - }); + await this.workspaceMigrationV2ObjectActionsBuilder.validateAndBuildObjectActions( + { + fromFlatObjectMetadataMaps, + createdFlatObjectMetadata, + deletedFlatObjectMetadata, + toFlatObjectMetadataMaps, + updatedFlatObjectMetadata, + buildOptions, + }, + ); const createdObjectMetadataCreateIndexActions = - createdObjectMetadata.flatMap((objectMetadata) => + createdFlatObjectMetadata.flatMap((objectMetadata) => objectMetadata.flatIndexMetadatas.map( getWorkspaceMigrationV2CreateIndexAction, ), ); const deletedObjectWorkspaceMigrationDeleteFieldActions = - inferDeletionFromMissingObjectFieldIndex - ? deletedObjectMetadata.flatMap((flatObjectMetadata) => + buildOptions.inferDeletionFromMissingObjectFieldIndex + ? deletedFlatObjectMetadata.flatMap((flatObjectMetadata) => flatObjectMetadata.flatFieldMetadatas.map((flatFieldMetadata) => getWorkspaceMigrationV2FieldDeleteAction({ flatFieldMetadata, @@ -70,35 +103,43 @@ export class WorkspaceMigrationBuilderV2Service { ) : []; - const objectMetadataDeletedCreatedUpdatedFields = - computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix( - updatedObjectMetadata, - ); - const fieldWorkspaceMigrationActions = buildWorkspaceMigrationV2FieldActions({ - inferDeletionFromMissingObjectFieldIndex, + inferDeletionFromMissingObjectFieldIndex: + buildOptions.inferDeletionFromMissingObjectFieldIndex, objectMetadataDeletedCreatedUpdatedFields, }); - const objectMetadataDeletedCreatedUpdatedIndex = - computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix( - updatedObjectMetadata, - ); const indexWorkspaceMigrationActions = buildWorkspaceMigrationIndexActions({ objectMetadataDeletedCreatedUpdatedIndex, - inferDeletionFromMissingObjectFieldIndex, + inferDeletionFromMissingObjectFieldIndex: + buildOptions.inferDeletionFromMissingObjectFieldIndex, }); + /// + + if (objectWorkspaceMigrationActions.results.failed.length > 0) { + return { + status: 'fail', + errors: objectWorkspaceMigrationActions.results.failed.flatMap( + (el) => el.errors, + ), + }; + } return { - workspaceId, - actions: [ - ...deletedObjectWorkspaceMigrationDeleteFieldActions, - ...objectWorkspaceMigrationActions, - ...createdObjectMetadataCreateIndexActions, - ...fieldWorkspaceMigrationActions, - ...indexWorkspaceMigrationActions, - ], + status: 'success', + workspaceMigration: { + workspaceId, + actions: [ + ...deletedObjectWorkspaceMigrationDeleteFieldActions, + ...objectWorkspaceMigrationActions.results.successful.map( + (el) => el.result, + ), // TMP create util + ...createdObjectMetadataCreateIndexActions, + ...fieldWorkspaceMigrationActions, + ...indexWorkspaceMigrationActions, + ], + }, }; } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts index ed57adf8f46..816fc7f1766 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-field-actions-builder.ts @@ -21,13 +21,13 @@ export const buildWorkspaceMigrationV2FieldActions = ({ []; for (const { - createdFieldMetadata, - deletedFieldMetadata, - updatedFieldMetadata, + createdFlatFieldMetadata, + deletedFlatFieldMetadata, + updatedFlatFieldMetadata, flatObjectMetadata, } of objectMetadataDeletedCreatedUpdatedFields) { - const updateFieldActions = updatedFieldMetadata.flatMap( - ({ from, to }) => { + const updateFieldActions = + updatedFlatFieldMetadata.flatMap(({ from, to }) => { const updates = compareTwoFlatFieldMetadata({ from, to, @@ -44,18 +44,18 @@ export const buildWorkspaceMigrationV2FieldActions = ({ workspaceId: to.workspaceId, updates, }; - }, - ); + }); - const createFieldAction = createdFieldMetadata.map((flatFieldMetadata) => - getWorkspaceMigrationV2FieldCreateAction({ - flatFieldMetadata, - flatObjectMetadata, - }), + const createFieldAction = createdFlatFieldMetadata.map( + (flatFieldMetadata) => + getWorkspaceMigrationV2FieldCreateAction({ + flatFieldMetadata, + flatObjectMetadata, + }), ); const deleteFieldAction = inferDeletionFromMissingObjectFieldIndex - ? deletedFieldMetadata.map((flatFieldMetadata) => + ? deletedFlatFieldMetadata.map((flatFieldMetadata) => getWorkspaceMigrationV2FieldDeleteAction({ flatFieldMetadata, flatObjectMetadata, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts index aeb4fc19e4c..020ba1e10c2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-v2-object-actions-builder.ts @@ -1,68 +1,202 @@ +import { Injectable } from '@nestjs/common'; + +import { FromTo } from 'twenty-shared/types'; + +import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util'; +import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util'; +import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { compareTwoFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/compare-two-flat-object-metadata.util'; import { type CustomDeletedCreatedUpdatedMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util'; +import { MetadataActionValidateAndBuildResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-action-validate-and-build-result.type'; +import { MetadataValidateAndBuildResult } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/metadata-validate-and-build-result.type'; import { - type UpdateObjectAction, - type WorkspaceMigrationObjectActionV2, + UpdateObjectAction, + WorkspaceMigrationObjectActionV2, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; +import { fromMetadataBuildResultsToFailedAndSuccessfulMetadataBuildRecord } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-metadata-build-results-to-failed-and-successful-metadata-build-record.util'; import { getWorkspaceMigrationV2FieldCreateAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-field-actions'; import { getWorkspaceMigrationV2ObjectCreateAction, getWorkspaceMigrationV2ObjectDeleteAction, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions'; +import { WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; -export type CreatedDeletedUpdatedObjectMetadataInputMatrix = - CustomDeletedCreatedUpdatedMatrix<'objectMetadata', FlatObjectMetadata> & { - inferDeletionFromMissingObjectFieldIndex: boolean; +export type CreatedDeletedUpdatedObjectMetadataInputMatrix = FromTo< + FlatObjectMetadataMaps, + 'FlatObjectMetadataMaps' +> & + CustomDeletedCreatedUpdatedMatrix< + 'flatObjectMetadata', + FlatObjectMetadata + > & { + buildOptions: WorkspaceMigrationV2BuilderOptions; + fromFlatObjectMetadataMaps: FlatObjectMetadataMaps; // should be renamed to from }; -export const buildWorkspaceMigrationV2ObjectActions = ({ - createdObjectMetadata, - deletedObjectMetadata, - updatedObjectMetadata, - inferDeletionFromMissingObjectFieldIndex, -}: CreatedDeletedUpdatedObjectMetadataInputMatrix): WorkspaceMigrationObjectActionV2[] => { - const createdObjectActions = createdObjectMetadata.map( - (flatObjectMetadata) => { - const createFieldActions = flatObjectMetadata.flatFieldMetadatas.map( - (flatFieldMetadata) => - getWorkspaceMigrationV2FieldCreateAction({ - flatFieldMetadata, - flatObjectMetadata, - }), - ); - return getWorkspaceMigrationV2ObjectCreateAction({ - flatObjectMetadata, - createFieldActions, - }); - }, - ); +@Injectable() +export class WorkspaceMigrationV2ObjectActionsBuilder { + constructor( + private readonly flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService, + ) {} - const deletedObjectActions = inferDeletionFromMissingObjectFieldIndex - ? deletedObjectMetadata.map(getWorkspaceMigrationV2ObjectDeleteAction) - : []; + public async validateAndBuildObjectActions({ + createdFlatObjectMetadata, + deletedFlatObjectMetadata, + updatedFlatObjectMetadata, + buildOptions, + fromFlatObjectMetadataMaps, + toFlatObjectMetadataMaps, + }: CreatedDeletedUpdatedObjectMetadataInputMatrix): Promise< + MetadataActionValidateAndBuildResult + > { + let optimisticFlatObjectMetadataMaps = structuredClone( + fromFlatObjectMetadataMaps, + ); + const createdObjectBuildResults = await Promise.all( + createdFlatObjectMetadata.map< + Promise< + MetadataValidateAndBuildResult + > + >(async (flatObjectMetadata) => { + const validationErrors = + await this.flatObjectMetadataValidatorService.validateFlatObjectMetadataCreation( + { + existingFlatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + flatObjectMetadataToValidate: flatObjectMetadata, + otherFlatObjectMetadataMapsToValidate: toFlatObjectMetadataMaps, + }, + ); - const updatedObjectActions = - updatedObjectMetadata.flatMap(({ from, to }) => { + if (validationErrors.length > 0) { + return { + status: 'fail', + errors: validationErrors, + }; + } + + optimisticFlatObjectMetadataMaps = + addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: flatObjectMetadata, + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + }); + + const createFieldActions = flatObjectMetadata.flatFieldMetadatas.map( + (flatFieldMetadata) => + getWorkspaceMigrationV2FieldCreateAction({ + flatFieldMetadata, + flatObjectMetadata, + }), + ); + + const createObjectAction = getWorkspaceMigrationV2ObjectCreateAction({ + flatObjectMetadata, + createFieldActions, + }); + + return { + status: 'success', + result: createObjectAction, + }; + }), + ); + + const deletedObjectBuildResults = + buildOptions.inferDeletionFromMissingObjectFieldIndex + ? deletedFlatObjectMetadata.map< + MetadataValidateAndBuildResult + >((flatObjectMetadataToDelete) => { + const validationErrors = + this.flatObjectMetadataValidatorService.validateFlatObjectMetadataDeletion( + { + buildOptions, + existingFlatObjectMetadataMaps: + optimisticFlatObjectMetadataMaps, + objectMetadataToDeleteId: flatObjectMetadataToDelete.id, + }, + ); + + if (validationErrors.length > 0) { + return { + status: 'fail', + errors: validationErrors, + }; + } + + optimisticFlatObjectMetadataMaps = + deleteObjectFromFlatObjectMetadataMapsOrThrow({ + objectMetadataId: flatObjectMetadataToDelete.id, + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + }); + + const deleteObjectAction = + getWorkspaceMigrationV2ObjectDeleteAction( + flatObjectMetadataToDelete, + ); + + return { + status: 'success', + result: deleteObjectAction, + }; + }) + : []; + + const updatedObjectBuildResults = updatedFlatObjectMetadata.flatMap< + MetadataValidateAndBuildResult + >(({ from: fromFlatObjectMetadata, to: toFlatObjectMetadata }) => { const objectUpdatedProperties = compareTwoFlatObjectMetadata({ - from, - to, + from: fromFlatObjectMetadata, + to: toFlatObjectMetadata, }); if (objectUpdatedProperties.length === 0) { return []; } - return { + const validationErrors = + this.flatObjectMetadataValidatorService.validateFlatObjectMetadataUpdate( + { + existingFlatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + updatedFlatObjectMetadata: toFlatObjectMetadata, + }, + ); + + if (validationErrors.length > 0) { + return { + status: 'fail', + errors: validationErrors, + }; + } + + optimisticFlatObjectMetadataMaps = + replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: toFlatObjectMetadata, + flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, + }); + + const updateObjectAction: UpdateObjectAction = { type: 'update_object', - objectMetadataId: to.id, + objectMetadataId: toFlatObjectMetadata.id, updates: objectUpdatedProperties, }; + + return { + status: 'success', + result: updateObjectAction, + }; }); - return [ - ...createdObjectActions, - ...deletedObjectActions, - ...updatedObjectActions, - ]; -}; + return { + optimisticFlatObjectMetadataMaps, + results: fromMetadataBuildResultsToFailedAndSuccessfulMetadataBuildRecord( + [ + ...createdObjectBuildResults, + ...deletedObjectBuildResults, + ...updatedObjectBuildResults, + ], + ), + }; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts index 2fa51b30872..02dedd30461 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts @@ -46,6 +46,8 @@ import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-m WorkspaceSchemaObjectActionRunnerService, WorkspaceSchemaIndexActionRunnerService, WorkspaceSchemaFieldActionRunnerService, + WorkspaceMetadataMigrationRunnerService, + WorkspaceSchemaMigrationRunnerService, ], }) export class WorkspaceMigrationRunnerV2Module {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts index 6f3b4770c74..54c3f5a533c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts @@ -2,13 +2,18 @@ import { Module } from '@nestjs/common'; import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module'; import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service'; @Module({ imports: [ WorkspaceMigrationBuilderV2Module, WorkspaceMigrationRunnerV2Module, ], - providers: [], - exports: [WorkspaceMigrationRunnerV2Module], + providers: [WorkspaceMigrationValidateBuildAndRunService], + exports: [ + WorkspaceMigrationRunnerV2Module, + WorkspaceMigrationBuilderV2Module, + WorkspaceMigrationValidateBuildAndRunService, + ], }) export class WorkspaceMigrationV2Module {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service.ts new file mode 100644 index 00000000000..1890d7a9844 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; + +import { MultipleMetadataValidationErrors } from 'src/engine/core-modules/error/multiple-metadata-validation-errors'; +import { + WorkspaceMigrationBuildArgs, + WorkspaceMigrationBuilderV2Service, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; +import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; +import { + WorkspaceMigrationV2Exception, + WorkspaceMigrationV2ExceptionCode, +} from 'src/engine/workspace-manager/workspace-migration.exception'; + +@Injectable() +export class WorkspaceMigrationValidateBuildAndRunService { + constructor( + private readonly workspaceMigrationBuilderV2Service: WorkspaceMigrationBuilderV2Service, + private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service, + ) {} + + public async validateBuildAndRunWorkspaceMigration({ + errorMessage, + ...builderArgs + }: WorkspaceMigrationBuildArgs & { errorMessage: string }) { + const validateAndBuildResult = await this.workspaceMigrationBuilderV2Service + .validateAndBuild(builderArgs) + .catch((error) => { + throw new WorkspaceMigrationV2Exception( + WorkspaceMigrationV2ExceptionCode.BUILDER_INTERNAL_SERVER_ERROR, + error.message, + ); + }); + + if (validateAndBuildResult.status === 'fail') { + throw new MultipleMetadataValidationErrors( + validateAndBuildResult.errors, + errorMessage, + ); + } + + await this.workspaceMigrationRunnerV2Service + .run(validateAndBuildResult.workspaceMigration) + .catch((error) => { + throw new WorkspaceMigrationV2Exception( + WorkspaceMigrationV2ExceptionCode.RUNNER_INTERNAL_SERVER_ERROR, + error.message, + ); + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration.exception.ts new file mode 100644 index 00000000000..2d24e26b27d --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration.exception.ts @@ -0,0 +1,8 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class WorkspaceMigrationV2Exception extends CustomException {} + +export enum WorkspaceMigrationV2ExceptionCode { + BUILDER_INTERNAL_SERVER_ERROR = 'BUILDER_INTERNAL_SERVER_ERROR', + RUNNER_INTERNAL_SERVER_ERROR = 'RUNNER_INTERNAL_SERVER_ERROR', +} diff --git a/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts b/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts index b82ae77cacc..24e31807cef 100644 --- a/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts +++ b/packages/twenty-server/test/utils/extract-record-ids-and-dates-as-expect-any.ts @@ -18,13 +18,6 @@ export const extractRecordIdsAndDatesAsExpectAny = ( return acc; } - if (key.endsWith('Id') || key === 'id') { - return { - ...acc, - [key]: expect.any(String), - }; - } - if (value instanceof Date) { return { ...acc, @@ -32,6 +25,19 @@ export const extractRecordIdsAndDatesAsExpectAny = ( }; } + if ( + key.endsWith('Id') || + key === 'id' || + key === 'updatedAt' || + key === 'deletedAt' || + key === 'createdAt' + ) { + return { + ...acc, + [key]: expect.any(String), + }; + } + if (typeof value === 'object' || Array.isArray(value)) { return { ...acc,