From 1a45576990611777118582cf1079da8046b72c71 Mon Sep 17 00:00:00 2001 From: Guillim Date: Thu, 27 Nov 2025 12:10:13 +0100 Subject: [PATCH] Morph-add-new-object-destination (#16027) Add new object target to an existing morph relation (backend only) Fixes https://github.com/twentyhq/core-team-issues/issues/1898 --------- Co-authored-by: prastoin --- .../src/generated-metadata/graphql.ts | 1 + .../twenty-front/src/generated/graphql.ts | 1 + .../field-metadata/dtos/update-field.input.ts | 13 +- .../services/field-metadata.service-v2.ts | 11 +- ...t-field-metadata-type-validator.service.ts | 20 +- ...flat-field-metadata-type-validator.type.ts | 4 +- ...from-morph-relation-update-payload.util.ts | 117 +++++++ ...field-input-to-flat-field-metadata.util.ts | 28 ++ ...-relation-flat-field-metadata-pair.util.ts | 8 +- ...ph-or-relation-flat-field-metadata.util.ts | 6 +- ...morph-relation-flat-field-metadata.util.ts | 60 ++++ ...ce-migration-build-orchestrator.service.ts | 140 ++++----- ...-v2-cron-trigger-action-builder.service.ts | 32 +- ...e-event-trigger-actions-builder.service.ts | 32 +- ...ration-v2-field-actions-builder.service.ts | 38 +-- ...ration-v2-index-actions-builder.service.ts | 21 +- ...ation-v2-object-actions-builder.service.ts | 34 +- ...2-route-trigger-actions-builder.service.ts | 32 +- ...erless-function-actions-builder.service.ts | 40 +-- ...n-v2-view-field-actions-builder.service.ts | 30 +- ...-v2-view-filter-actions-builder.service.ts | 30 +- ...n-v2-view-group-actions-builder.service.ts | 30 +- ...gration-v2-view-actions-builder.service.ts | 20 +- ...ace-entity-migration-builder-v2.service.ts | 16 +- .../flat-cron-trigger-validator.service.ts | 8 +- ...atabase-event-trigger-validator.service.ts | 4 +- .../flat-field-metadata-validator.service.ts | 18 +- .../flat-object-metadata-validator.service.ts | 4 +- .../flat-route-trigger-validator.service.ts | 8 +- ...t-serverless-function-validator.service.ts | 4 +- .../services/flat-view-validator.service.ts | 8 +- ...morph-relation-v2.integration-spec.ts.snap | 189 +++++++++++ ...data-morph-relation-v2.integration-spec.ts | 238 ++++++++++++++ ...data-morph-relation-v2.integration-spec.ts | 296 ++++++++++++++++++ .../src/types/RelationUpdatePayload.ts | 3 + packages/twenty-shared/src/types/index.ts | 1 + 36 files changed, 1208 insertions(+), 337 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-flat-field-metadata.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts create mode 100644 packages/twenty-shared/src/types/RelationUpdatePayload.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index dfcf69a632..f46d1266be 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4220,6 +4220,7 @@ export type UpdateFieldInput = { isUIReadOnly?: InputMaybe; isUnique?: InputMaybe; label?: InputMaybe; + morphRelationsUpdatePayload?: InputMaybe>; name?: InputMaybe; options?: InputMaybe; settings?: InputMaybe; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 2af4bec455..27f7972201 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -4057,6 +4057,7 @@ export type UpdateFieldInput = { isUIReadOnly?: InputMaybe; isUnique?: InputMaybe; label?: InputMaybe; + morphRelationsUpdatePayload?: InputMaybe>; name?: InputMaybe; options?: InputMaybe; settings?: InputMaybe; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/update-field.input.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/update-field.input.ts index 3592f6cfcf..43679b99c6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/update-field.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/update-field.input.ts @@ -7,7 +7,14 @@ import { } from '@nestjs/graphql'; import { Type } from 'class-transformer'; -import { IsNotEmpty, IsUUID, ValidateNested } from 'class-validator'; +import { + IsNotEmpty, + IsOptional, + IsUUID, + ValidateNested, +} from 'class-validator'; +import GraphQLJSON from 'graphql-type-json'; +import { RelationUpdatePayload } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; @@ -29,6 +36,10 @@ export class UpdateFieldInput extends OmitType( @HideField() workspaceId: string; + + @IsOptional() + @Field(() => [GraphQLJSON], { nullable: true }) + morphRelationsUpdatePayload?: RelationUpdatePayload[]; } @InputType() 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 91f1b702b8..4a3c153ce1 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 @@ -175,6 +175,13 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService => []; +const DEFAULT_NO_VALIDATION = (): FlatFieldMetadataValidationError[] => []; export type GenericValidateFlatFieldMetadataTypeSpecificitiesArgs = FlatEntityValidationArgs<'fieldMetadata'> & { @@ -26,9 +25,9 @@ const rejectUserCreation = ( message: string, userFriendlyMessage: ReturnType, ) => { - return async ( + return ( args: GenericValidateFlatFieldMetadataTypeSpecificitiesArgs, - ): Promise => { + ): FlatFieldMetadataValidationError[] => { const isCreation = !isDefined(args.updates); const isCustomField = args.flatEntityToValidate.isCustom; @@ -85,19 +84,16 @@ export class FlatFieldMetadataTypeValidatorService { msg`Field type TS_VECTOR is a system type and cannot be created manually.`, ), UUID: DEFAULT_NO_VALIDATION, - - MORPH_RELATION: async (args) => { - return validateMorphOrRelationFlatFieldMetadata(args); - }, + MORPH_RELATION: validateMorphRelationFlatFieldMetadata, MULTI_SELECT: validateEnumSelectFlatFieldMetadata, RATING: validateEnumSelectFlatFieldMetadata, RELATION: validateMorphOrRelationFlatFieldMetadata, SELECT: validateEnumSelectFlatFieldMetadata, }; - public async validateFlatFieldMetadataTypeSpecificities( + public validateFlatFieldMetadataTypeSpecificities( args: GenericValidateFlatFieldMetadataTypeSpecificitiesArgs, - ): Promise { + ): FlatFieldMetadataValidationError[] { const { flatEntityToValidate } = args; const fieldType = flatEntityToValidate.type; const fieldMetadataTypeValidator = @@ -114,7 +110,7 @@ export class FlatFieldMetadataTypeValidatorService { ]; } - return await fieldMetadataTypeValidator( + return fieldMetadataTypeValidator( // @ts-expect-error TODO could be improved args, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type.ts index a13712e808..dc0ef53a71 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type.ts @@ -15,7 +15,5 @@ export type FlatFieldMetadataTypeValidationArgs = export type FlatFieldMetadataTypeValidator = { [T in FieldMetadataType]: ( args: FlatFieldMetadataTypeValidationArgs, - ) => - | FlatFieldMetadataValidationError[] - | Promise; + ) => FlatFieldMetadataValidationError[]; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts new file mode 100644 index 0000000000..b30e23196a --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts @@ -0,0 +1,117 @@ +import { + FieldMetadataType, + type RelationUpdatePayload, +} from 'twenty-shared/types'; +import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; + +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util'; +import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util'; + +type ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs = { + workspaceCustomApplicationId: string; + flatFieldMetadataMaps: FlatEntityMaps; + morphRelationsUpdatePayload?: RelationUpdatePayload[]; + fieldMetadataToUpdate: FlatFieldMetadata; + flatObjectMetadataMaps: FlatEntityMaps; +}; + +export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({ + workspaceCustomApplicationId, + flatFieldMetadataMaps, + morphRelationsUpdatePayload, + fieldMetadataToUpdate, + flatObjectMetadataMaps, +}: ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs): { + flatFieldMetadatasToCreate: FlatFieldMetadata[]; + flatIndexMetadatasToCreate: FlatIndexMetadata[]; +} => { + const flatFieldMetadatasToCreate: FlatFieldMetadata[] = []; + const flatIndexMetadatasToCreate: FlatIndexMetadata[] = []; + + if (!isDefined(morphRelationsUpdatePayload)) { + return { flatFieldMetadatasToCreate, flatIndexMetadatasToCreate }; + } + + const sourceObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: fieldMetadataToUpdate.objectMetadataId, + flatEntityMaps: flatObjectMetadataMaps, + }); + + const morphRelationsCommonLabel = fieldMetadataToUpdate.label; + + const initialFlatFieldMetadataTargetObjectMetadata = + findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: fieldMetadataToUpdate.relationTargetObjectMetadataId, + flatEntityMaps: flatObjectMetadataMaps, + }); + + const morphNameWithoutObjectName = getMorphNameFromMorphFieldMetadataName({ + morphRelationFlatFieldMetadata: { + name: fieldMetadataToUpdate.name, + settings: fieldMetadataToUpdate.settings, + }, + nameSingular: initialFlatFieldMetadataTargetObjectMetadata.nameSingular, + namePlural: initialFlatFieldMetadataTargetObjectMetadata.namePlural, + }); + + const initialTargetFieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: fieldMetadataToUpdate.relationTargetFieldMetadataId, + flatEntityMaps: flatFieldMetadataMaps, + }); + const commonTargetFieldLabel = initialTargetFieldMetadata.label; + const commonTargetFieldName = initialTargetFieldMetadata.name; + const commonObjectMetadataId = initialTargetFieldMetadata.objectMetadataId; + + morphRelationsUpdatePayload.forEach((morphRelationUpdatePayload) => { + const { targetObjectMetadataId } = morphRelationUpdatePayload; + + const newTargetObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: targetObjectMetadataId, + flatEntityMaps: flatObjectMetadataMaps, + }); + + const computedMorphName = computeMorphRelationFieldName({ + fieldName: morphNameWithoutObjectName, + relationType: fieldMetadataToUpdate.settings.relationType, + targetObjectMetadataNameSingular: newTargetObjectMetadata.nameSingular, + targetObjectMetadataNamePlural: newTargetObjectMetadata.namePlural, + }); + + const { flatFieldMetadatas, indexMetadatas } = + generateMorphOrRelationFlatFieldMetadataPair({ + createFieldInput: { + type: FieldMetadataType.MORPH_RELATION, + name: computedMorphName, + label: morphRelationsCommonLabel, + objectMetadataId: commonObjectMetadataId, + relationCreationPayload: { + type: fieldMetadataToUpdate.settings.relationType, + targetObjectMetadataId, + targetFieldLabel: commonTargetFieldLabel, + targetFieldIcon: fieldMetadataToUpdate.icon ?? 'Icon123', + }, + }, + sourceFlatObjectMetadata: sourceObjectMetadata, + targetFlatObjectMetadata: newTargetObjectMetadata, + workspaceId: fieldMetadataToUpdate.workspaceId, + workspaceCustomApplicationId, + sourceFlatObjectMetadataJoinColumnName: + computeMorphOrRelationFieldJoinColumnName({ + name: computedMorphName, + }), + morphId: fieldMetadataToUpdate.morphId, + targetFieldName: commonTargetFieldName, + }); + + flatFieldMetadatasToCreate.push(...flatFieldMetadatas); + flatIndexMetadatasToCreate.push(...indexMetadatas); + }); + + return { flatFieldMetadatasToCreate, flatIndexMetadatasToCreate }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts index de5f60c798..79e2c695f3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util.ts @@ -1,4 +1,5 @@ import { msg } from '@lingui/core/macro'; +import { FieldMetadataType } from 'twenty-shared/types'; import { extractAndSanitizeObjectStringFields, isDefined, @@ -14,14 +15,17 @@ import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/ import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { computeFlatFieldToUpdateAndRelatedFlatFieldToUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-and-related-flat-field-to-update.util'; +import { computeFlatFieldToUpdateFromMorphRelationUpdatePayload } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util'; import { FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS, type FlatFieldMetadataUpdateSideEffects, handleFlatFieldMetadataUpdateSideEffect, } from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-flat-field-metadata-update-side-effect.util'; +import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; type FromUpdateFieldInputToFlatFieldMetadataArgs = { updateFieldInput: UpdateFieldInput; + workspaceCustomApplicationId: string; } & Pick< AllFlatEntityMaps, | 'flatObjectMetadataMaps' @@ -35,8 +39,10 @@ type FromUpdateFieldInputToFlatFieldMetadataArgs = { type FlatFieldMetadataAndIndexToUpdate = { flatFieldMetadatasToUpdate: FlatFieldMetadata[]; + flatFieldMetadatasToCreate: FlatFieldMetadata[]; } & FlatFieldMetadataUpdateSideEffects; export const fromUpdateFieldInputToFlatFieldMetadata = ({ + workspaceCustomApplicationId, flatIndexMaps, flatObjectMetadataMaps: existingFlatObjectMetadataMaps, flatFieldMetadataMaps, @@ -87,9 +93,30 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({ fromFlatFieldMetadata: existingFlatFieldMetadataToUpdate, rawUpdateFieldInput, }); + + const { flatFieldMetadatasToCreate, flatIndexMetadatasToCreate } = + isFlatFieldMetadataOfType( + flatFieldMetadataFromTo.toFlatFieldMetadata, + FieldMetadataType.MORPH_RELATION, + ) + ? computeFlatFieldToUpdateFromMorphRelationUpdatePayload({ + workspaceCustomApplicationId, + morphRelationsUpdatePayload: + rawUpdateFieldInput?.morphRelationsUpdatePayload, + flatFieldMetadataMaps: flatFieldMetadataMaps, + fieldMetadataToUpdate: flatFieldMetadataFromTo.toFlatFieldMetadata, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + }) + : { + flatFieldMetadatasToCreate: [], + flatIndexMetadatasToCreate: [], + }; + const initialAccumulator: FlatFieldMetadataAndIndexToUpdate = { ...structuredClone(FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS), flatFieldMetadatasToUpdate: [], + flatFieldMetadatasToCreate: flatFieldMetadatasToCreate, + flatIndexMetadatasToCreate: flatIndexMetadatasToCreate, }; const optimisticiallyUpdatedFlatFieldMetadatas = [ @@ -130,6 +157,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({ ...accumulator.flatIndexMetadatasToUpdate, ...flatIndexMetadatasToUpdate, ], + flatFieldMetadatasToCreate: [...accumulator.flatFieldMetadatasToCreate], flatViewFiltersToDelete: [ ...accumulator.flatViewFiltersToDelete, ...flatViewFiltersToDelete, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts index 1167e01656..f32e91d90d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts @@ -47,6 +47,7 @@ type GenerateMorphOrRelationFlatFieldMetadataPairArgs = { workspaceId: string; morphId?: string | null; workspaceCustomApplicationId: string; + targetFieldName?: string; }; export type SourceTargetMorphOrRelationFlatFieldAndFlatIndex = { @@ -62,6 +63,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ workspaceCustomApplicationId, sourceFlatObjectMetadataJoinColumnName, morphId = null, + targetFieldName, }: GenerateMorphOrRelationFlatFieldMetadataPairArgs): SourceTargetMorphOrRelationFlatFieldAndFlatIndex => { const { relationCreationPayload } = createFieldInput; @@ -96,9 +98,9 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ const targetCreateFieldInput: CreateFieldInput = { icon: relationCreationPayload.targetFieldIcon ?? 'Icon123', label: relationCreationPayload.targetFieldLabel, - name: computeMetadataNameFromLabel( - relationCreationPayload.targetFieldLabel, - ), + name: + targetFieldName ?? + computeMetadataNameFromLabel(relationCreationPayload.targetFieldLabel), objectMetadataId: targetFlatObjectMetadata.id, type: FieldMetadataType.RELATION, workspaceId, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts index 40eadffe2b..8e38ca121d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util.ts @@ -10,7 +10,7 @@ import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/ import { validateMorphOrRelationFlatFieldJoinColumName } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-join-column-name.util'; import { findFlatEntityPropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/utils/find-flat-entity-property-update.util'; -export const validateMorphOrRelationFlatFieldMetadata = async ({ +export const validateMorphOrRelationFlatFieldMetadata = ({ flatEntityToValidate: flatFieldMetadataToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatFieldMetadataMaps, @@ -18,9 +18,7 @@ export const validateMorphOrRelationFlatFieldMetadata = async ({ }, updates, remainingFlatEntityMapsToValidate, -}: FlatFieldMetadataTypeValidationArgs): Promise< - FlatFieldMetadataValidationError[] -> => { +}: FlatFieldMetadataTypeValidationArgs): FlatFieldMetadataValidationError[] => { const { relationTargetFieldMetadataId, relationTargetObjectMetadataId } = flatFieldMetadataToValidate; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-flat-field-metadata.util.ts new file mode 100644 index 0000000000..4bb1695aaf --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-flat-field-metadata.util.ts @@ -0,0 +1,60 @@ +import { msg } from '@lingui/core/macro'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { type FlatFieldMetadataTypeValidationArgs } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type'; +import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type'; +import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; +import { validateMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util'; + +export const validateMorphRelationFlatFieldMetadata = ( + args: FlatFieldMetadataTypeValidationArgs, +): FlatFieldMetadataValidationError[] => { + const { + flatEntityToValidate: flatFieldMetadataToValidate, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatFieldMetadataMaps }, + remainingFlatEntityMapsToValidate, + } = args; + const { relationTargetFieldMetadataId } = flatFieldMetadataToValidate; + + const errors: FlatFieldMetadataValidationError[] = []; + + errors.push(...validateMorphOrRelationFlatFieldMetadata(args)); + + const targetFlatFieldMetadata = + remainingFlatEntityMapsToValidate?.byId[relationTargetFieldMetadataId] ?? + findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: relationTargetFieldMetadataId, + flatEntityMaps: flatFieldMetadataMaps, + }); + + if ( + isDefined(targetFlatFieldMetadata) && + !isFlatFieldMetadataOfType( + targetFlatFieldMetadata, + FieldMetadataType.RELATION, + ) + ) { + errors.push({ + code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + message: 'A morph relation field can only target a RELATION field', + userFriendlyMessage: msg`Invalid relation field target`, + }); + } + + const sourceObjectMetadataId = flatFieldMetadataToValidate.objectMetadataId; + const targetObjectMetadataId = + flatFieldMetadataToValidate.relationTargetObjectMetadataId; + + if (sourceObjectMetadataId === targetObjectMetadataId) { + errors.push({ + code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + message: 'Source object cannot be the target object', + userFriendlyMessage: msg`Source object cannot be the target object`, + }); + } + + return errors; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts index 30d02940e4..b7be171a25 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts @@ -132,39 +132,37 @@ export class WorkspaceMigrationBuildOrchestratorService { flatObjectMetadataMaps; const objectResult = - await this.workspaceMigrationV2ObjectActionsBuilderService.validateAndBuild( - { - buildOptions, - // Note: That's a hacky way to allow validating object against field metadatas, not optimal - dependencyOptimisticFlatEntityMaps: { - flatFieldMetadataMaps: { - byId: { - ...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps?.byId, - ...flatFieldMetadataMaps?.from.byId, - ...flatFieldMetadataMaps?.to.byId, - }, - idByUniversalIdentifier: { - ...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps - ?.idByUniversalIdentifier, - ...flatFieldMetadataMaps?.from.idByUniversalIdentifier, - ...flatFieldMetadataMaps?.to.idByUniversalIdentifier, - }, - universalIdentifiersByApplicationId: { - ...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps - ?.universalIdentifiersByApplicationId, - ...flatFieldMetadataMaps?.from - .universalIdentifiersByApplicationId, - ...flatFieldMetadataMaps?.to - .universalIdentifiersByApplicationId, - }, + this.workspaceMigrationV2ObjectActionsBuilderService.validateAndBuild({ + buildOptions, + // Note: That's a hacky way to allow validating object against field metadatas, not optimal + dependencyOptimisticFlatEntityMaps: { + flatFieldMetadataMaps: { + byId: { + ...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps?.byId, + ...flatFieldMetadataMaps?.from.byId, + ...flatFieldMetadataMaps?.to.byId, + }, + idByUniversalIdentifier: { + ...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps + ?.idByUniversalIdentifier, + ...flatFieldMetadataMaps?.from.idByUniversalIdentifier, + ...flatFieldMetadataMaps?.to.idByUniversalIdentifier, + }, + universalIdentifiersByApplicationId: { + ...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps + ?.universalIdentifiersByApplicationId, + ...flatFieldMetadataMaps?.from + .universalIdentifiersByApplicationId, + ...flatFieldMetadataMaps?.to + .universalIdentifiersByApplicationId, }, }, - /// - from: fromFlatObjectMetadataMaps, - to: toFlatObjectMetadataMaps, - workspaceId, }, - ); + /// + from: fromFlatObjectMetadataMaps, + to: toFlatObjectMetadataMaps, + workspaceId, + }); this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation( { @@ -185,18 +183,16 @@ export class WorkspaceMigrationBuildOrchestratorService { const { from: fromFlatFieldMetadataMaps, to: toFlatFieldMetadataMaps } = flatFieldMetadataMaps; const fieldResult = - await this.workspaceMigrationV2FieldActionsBuilderService.validateAndBuild( - { - from: fromFlatFieldMetadataMaps, - to: toFlatFieldMetadataMaps, - buildOptions, - dependencyOptimisticFlatEntityMaps: { - flatObjectMetadataMaps: - optimisticAllFlatEntityMaps.flatObjectMetadataMaps, - }, - workspaceId, + this.workspaceMigrationV2FieldActionsBuilderService.validateAndBuild({ + from: fromFlatFieldMetadataMaps, + to: toFlatFieldMetadataMaps, + buildOptions, + dependencyOptimisticFlatEntityMaps: { + flatObjectMetadataMaps: + optimisticAllFlatEntityMaps.flatObjectMetadataMaps, }, - ); + workspaceId, + }); this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation( { @@ -216,20 +212,18 @@ export class WorkspaceMigrationBuildOrchestratorService { if (isDefined(flatIndexMaps)) { const { from: fromFlatIndexMaps, to: toFlatIndexMaps } = flatIndexMaps; const indexResult = - await this.workspaceMigrationV2IndexActionsBuilderService.validateAndBuild( - { - from: fromFlatIndexMaps, - to: toFlatIndexMaps, - buildOptions, - dependencyOptimisticFlatEntityMaps: { - flatFieldMetadataMaps: - optimisticAllFlatEntityMaps.flatFieldMetadataMaps, - flatObjectMetadataMaps: - optimisticAllFlatEntityMaps.flatObjectMetadataMaps, - }, - workspaceId, + this.workspaceMigrationV2IndexActionsBuilderService.validateAndBuild({ + from: fromFlatIndexMaps, + to: toFlatIndexMaps, + buildOptions, + dependencyOptimisticFlatEntityMaps: { + flatFieldMetadataMaps: + optimisticAllFlatEntityMaps.flatFieldMetadataMaps, + flatObjectMetadataMaps: + optimisticAllFlatEntityMaps.flatObjectMetadataMaps, }, - ); + workspaceId, + }); this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation( { @@ -249,20 +243,18 @@ export class WorkspaceMigrationBuildOrchestratorService { if (isDefined(flatViewMaps)) { const { from: fromFlatViewMaps, to: toFlatViewMaps } = flatViewMaps; const viewResult = - await this.workspaceMigrationV2ViewActionsBuilderService.validateAndBuild( - { - dependencyOptimisticFlatEntityMaps: { - flatObjectMetadataMaps: - optimisticAllFlatEntityMaps.flatObjectMetadataMaps, - flatFieldMetadataMaps: - optimisticAllFlatEntityMaps.flatFieldMetadataMaps, - }, - from: fromFlatViewMaps, - to: toFlatViewMaps, - buildOptions, - workspaceId, + this.workspaceMigrationV2ViewActionsBuilderService.validateAndBuild({ + dependencyOptimisticFlatEntityMaps: { + flatObjectMetadataMaps: + optimisticAllFlatEntityMaps.flatObjectMetadataMaps, + flatFieldMetadataMaps: + optimisticAllFlatEntityMaps.flatFieldMetadataMaps, }, - ); + from: fromFlatViewMaps, + to: toFlatViewMaps, + buildOptions, + workspaceId, + }); this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation( { @@ -283,7 +275,7 @@ export class WorkspaceMigrationBuildOrchestratorService { const { from: fromFlatViewFieldMaps, to: toFlatViewFieldMaps } = flatViewFieldMaps; const viewFieldResult = - await this.workspaceMigrationV2ViewFieldActionsBuilderService.validateAndBuild( + this.workspaceMigrationV2ViewFieldActionsBuilderService.validateAndBuild( { from: fromFlatViewFieldMaps, to: toFlatViewFieldMaps, @@ -318,7 +310,7 @@ export class WorkspaceMigrationBuildOrchestratorService { const { from: fromFlatViewFilterMaps, to: toFlatViewFilterMaps } = flatViewFilterMaps; const viewFilterResult = - await this.workspaceMigrationV2ViewFilterActionsBuilderService.validateAndBuild( + this.workspaceMigrationV2ViewFilterActionsBuilderService.validateAndBuild( { from: fromFlatViewFilterMaps, to: toFlatViewFilterMaps, @@ -351,7 +343,7 @@ export class WorkspaceMigrationBuildOrchestratorService { const { from: fromFlatViewGroupMaps, to: toFlatViewGroupMaps } = flatViewGroupMaps; const viewGroupResult = - await this.workspaceMigrationV2ViewGroupActionsBuilderService.validateAndBuild( + this.workspaceMigrationV2ViewGroupActionsBuilderService.validateAndBuild( { from: fromFlatViewGroupMaps, to: toFlatViewGroupMaps, @@ -387,7 +379,7 @@ export class WorkspaceMigrationBuildOrchestratorService { } = flatServerlessFunctionMaps; const serverlessFunctionResult = - await this.workspaceMigrationV2ServerlessFunctionActionsBuilderService.validateAndBuild( + this.workspaceMigrationV2ServerlessFunctionActionsBuilderService.validateAndBuild( { from: fromFlatServerlessFunctionMaps, to: toFlatServerlessFunctionMaps, @@ -422,7 +414,7 @@ export class WorkspaceMigrationBuildOrchestratorService { } = flatDatabaseEventTriggerMaps; const databaseEventTriggerResult = - await this.workspaceMigrationV2DatabaseEventTriggerActionsBuilderService.validateAndBuild( + this.workspaceMigrationV2DatabaseEventTriggerActionsBuilderService.validateAndBuild( { from: fromFlatDatabaseEventTriggerMaps, to: toFlatDatabaseEventTriggerMaps, @@ -458,7 +450,7 @@ export class WorkspaceMigrationBuildOrchestratorService { flatCronTriggerMaps; const cronTriggerResult = - await this.workspaceMigrationV2CronTriggerActionsBuilderService.validateAndBuild( + this.workspaceMigrationV2CronTriggerActionsBuilderService.validateAndBuild( { from: fromFlatCronTriggerMaps, to: toFlatCronTriggerMaps, @@ -491,7 +483,7 @@ export class WorkspaceMigrationBuildOrchestratorService { flatRouteTriggerMaps; const routeTriggerResult = - await this.workspaceMigrationV2RouteTriggerActionsBuilderService.validateAndBuild( + this.workspaceMigrationV2RouteTriggerActionsBuilderService.validateAndBuild( { from: fromFlatRouteTriggerMaps, to: toFlatRouteTriggerMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service.ts index eca52d8a8e..8eb49ea05a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service.ts @@ -19,16 +19,14 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp super(ALL_METADATA_NAME.cronTrigger); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.cronTrigger, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.cronTrigger, + 'created' > { const validationResult = - await this.flatCronTriggerValidatorService.validateFlatCronTriggerCreation( + this.flatCronTriggerValidatorService.validateFlatCronTriggerCreation( args, ); @@ -50,13 +48,11 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.cronTrigger, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.cronTrigger, + 'deleted' > { const validationResult = this.flatCronTriggerValidatorService.validateFlatCronTriggerDeletion( @@ -81,13 +77,11 @@ export class WorkspaceMigrationV2CronTriggerActionsBuilderService extends Worksp }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.cronTrigger, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.cronTrigger, + 'updated' > { const validationResult = this.flatCronTriggerValidatorService.validateFlatCronTriggerUpdate(args); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service.ts index 550b25215d..c7d6d92e6d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service.ts @@ -19,18 +19,16 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten super(ALL_METADATA_NAME.databaseEventTrigger); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs< typeof ALL_METADATA_NAME.databaseEventTrigger >, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.databaseEventTrigger, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.databaseEventTrigger, + 'created' > { const validationResult = - await this.flatDatabaseEventTriggerValidatorService.validateFlatDatabaseEventTriggerCreation( + this.flatDatabaseEventTriggerValidatorService.validateFlatDatabaseEventTriggerCreation( args, ); @@ -52,15 +50,13 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs< typeof ALL_METADATA_NAME.databaseEventTrigger >, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.databaseEventTrigger, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.databaseEventTrigger, + 'deleted' > { const validationResult = this.flatDatabaseEventTriggerValidatorService.validateFlatDatabaseEventTriggerDeletion( @@ -85,15 +81,13 @@ export class WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService exten }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs< typeof ALL_METADATA_NAME.databaseEventTrigger >, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.databaseEventTrigger, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.databaseEventTrigger, + 'updated' > { const validationResult = this.flatDatabaseEventTriggerValidatorService.validateFlatDatabaseEventTriggerUpdate( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service.ts index 5e90956e37..17d1027a74 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service.ts @@ -20,18 +20,14 @@ export class WorkspaceMigrationV2FieldActionsBuilderService extends WorkspaceEnt super(ALL_METADATA_NAME.fieldMetadata); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.fieldMetadata, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.fieldMetadata, + 'created' > { const validationResult = - await this.flatFieldValidatorService.validateFlatFieldMetadataCreation( - args, - ); + this.flatFieldValidatorService.validateFlatFieldMetadataCreation(args); if (validationResult.errors.length > 0) { return { @@ -52,13 +48,11 @@ export class WorkspaceMigrationV2FieldActionsBuilderService extends WorkspaceEnt }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.fieldMetadata, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.fieldMetadata, + 'deleted' > { const validationResult = this.flatFieldValidatorService.validateFlatFieldMetadataDeletion(args); @@ -82,20 +76,16 @@ export class WorkspaceMigrationV2FieldActionsBuilderService extends WorkspaceEnt }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs< typeof ALL_METADATA_NAME.fieldMetadata >, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.fieldMetadata, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.fieldMetadata, + 'updated' > { const validationResult = - await this.flatFieldValidatorService.validateFlatFieldMetadataUpdate( - args, - ); + this.flatFieldValidatorService.validateFlatFieldMetadataUpdate(args); if (validationResult.errors.length > 0) { return { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service.ts index 08d1ad9f74..74b4dd46fa 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service.ts @@ -25,11 +25,9 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt super(ALL_METADATA_NAME.index); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType - > { + ): FlatEntityValidationReturnType { const validationResult = this.flatIndexValidatorService.validateFlatIndexCreation(args); @@ -51,11 +49,9 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType - > { + ): FlatEntityValidationReturnType { const validationResult = this.flatIndexValidatorService.validateFlatIndexDeletion(args); @@ -77,14 +73,17 @@ export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEnt }; } - protected async validateFlatEntityUpdate({ + protected validateFlatEntityUpdate({ optimisticFlatEntityMapsAndRelatedFlatEntityMaps, flatEntityId, flatEntityUpdates, buildOptions, workspaceId, - }: FlatEntityUpdateValidationArgs): Promise< - FlatEntityValidationReturnType + }: FlatEntityUpdateValidationArgs< + typeof ALL_METADATA_NAME.index + >): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.index, + 'updated' > { const flatEntity = findFlatEntityByIdInFlatEntityMaps({ flatEntityId, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service.ts index 31f13aa7fa..35f5d43bdd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service.ts @@ -19,18 +19,14 @@ export class WorkspaceMigrationV2ObjectActionsBuilderService extends WorkspaceEn super(ALL_METADATA_NAME.objectMetadata); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.objectMetadata, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.objectMetadata, + 'created' > { const validationResult = - await this.flatObjectValidatorService.validateFlatObjectMetadataCreation( - args, - ); + this.flatObjectValidatorService.validateFlatObjectMetadataCreation(args); if (validationResult.errors.length > 0) { return { @@ -51,13 +47,11 @@ export class WorkspaceMigrationV2ObjectActionsBuilderService extends WorkspaceEn }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.objectMetadata, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.objectMetadata, + 'deleted' > { const validationResult = this.flatObjectValidatorService.validateFlatObjectMetadataDeletion(args); @@ -80,15 +74,13 @@ export class WorkspaceMigrationV2ObjectActionsBuilderService extends WorkspaceEn }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs< typeof ALL_METADATA_NAME.objectMetadata >, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.objectMetadata, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.objectMetadata, + 'updated' > { const validationResult = this.flatObjectValidatorService.validateFlatObjectMetadataUpdate(args); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service.ts index 3888f16174..92800fbf6b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service.ts @@ -19,16 +19,14 @@ export class WorkspaceMigrationV2RouteTriggerActionsBuilderService extends Works super(ALL_METADATA_NAME.routeTrigger); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.routeTrigger, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.routeTrigger, + 'created' > { const validationResult = - await this.flatRouteTriggerValidatorService.validateFlatRouteTriggerCreation( + this.flatRouteTriggerValidatorService.validateFlatRouteTriggerCreation( args, ); @@ -50,13 +48,11 @@ export class WorkspaceMigrationV2RouteTriggerActionsBuilderService extends Works }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.routeTrigger, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.routeTrigger, + 'deleted' > { const validationResult = this.flatRouteTriggerValidatorService.validateFlatRouteTriggerDeletion( @@ -81,13 +77,11 @@ export class WorkspaceMigrationV2RouteTriggerActionsBuilderService extends Works }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.routeTrigger, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.routeTrigger, + 'updated' > { const validationResult = this.flatRouteTriggerValidatorService.validateFlatRouteTriggerUpdate( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service.ts index 08fe87d2a9..3ca817f3d9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service.ts @@ -24,13 +24,11 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends super(ALL_METADATA_NAME.serverlessFunction); } - public async validateAndBuild( + public validateAndBuild( args: ValidateAndBuildArgs, - ): Promise< - ValidateAndBuildReturnType - > { + ): ValidateAndBuildReturnType { const { to: toFlatEntityMaps } = args; - const baseResult = await super.validateAndBuild(args); + const baseResult = super.validateAndBuild(args); if (baseResult.status === 'fail') { return baseResult; @@ -61,16 +59,14 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends }; } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.serverlessFunction, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.serverlessFunction, + 'created' > { const validationResult = - await this.flatServerlessFunctionValidatorService.validateFlatServerlessFunctionCreation( + this.flatServerlessFunctionValidatorService.validateFlatServerlessFunctionCreation( args, ); @@ -92,13 +88,11 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.serverlessFunction, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.serverlessFunction, + 'deleted' > { const validationResult = this.flatServerlessFunctionValidatorService.validateFlatServerlessFunctionDeletion( @@ -123,15 +117,13 @@ export class WorkspaceMigrationV2ServerlessFunctionActionsBuilderService extends }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs< typeof ALL_METADATA_NAME.serverlessFunction >, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.serverlessFunction, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.serverlessFunction, + 'updated' > { const validationResult = this.flatServerlessFunctionValidatorService.validateFlatServerlessFunctionUpdate( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts index 56286f5558..2f57a9997f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts @@ -19,13 +19,11 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac super(ALL_METADATA_NAME.viewField); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewField, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewField, + 'created' > { const validationResult = this.flatViewFieldValidatorService.validateFlatViewFieldCreation(args); @@ -46,13 +44,11 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewField, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewField, + 'deleted' > { const validationResult = this.flatViewFieldValidatorService.validateFlatViewFieldDeletion(args); @@ -75,13 +71,11 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewField, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewField, + 'updated' > { const validationResult = this.flatViewFieldValidatorService.validateFlatViewFieldUpdate(args); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service.ts index 2f359dcda1..67b460f8ec 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service.ts @@ -19,13 +19,11 @@ export class WorkspaceMigrationV2ViewFilterActionsBuilderService extends Workspa super(ALL_METADATA_NAME.viewFilter); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewFilter, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewFilter, + 'created' > { const validationResult = this.flatViewFilterValidatorService.validateFlatViewFilterCreation(args); @@ -48,13 +46,11 @@ export class WorkspaceMigrationV2ViewFilterActionsBuilderService extends Workspa }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewFilter, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewFilter, + 'deleted' > { const validationResult = this.flatViewFilterValidatorService.validateFlatViewFilterDeletion(args); @@ -77,13 +73,11 @@ export class WorkspaceMigrationV2ViewFilterActionsBuilderService extends Workspa }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewFilter, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewFilter, + 'updated' > { const validationResult = this.flatViewFilterValidatorService.validateFlatViewFilterUpdate(args); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-group/workspace-migration-v2-view-group-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-group/workspace-migration-v2-view-group-actions-builder.service.ts index 2e75bb90b4..a6a393664b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-group/workspace-migration-v2-view-group-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-group/workspace-migration-v2-view-group-actions-builder.service.ts @@ -19,13 +19,11 @@ export class WorkspaceMigrationV2ViewGroupActionsBuilderService extends Workspac super(ALL_METADATA_NAME.viewGroup); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewGroup, - 'created' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewGroup, + 'created' > { const validationResult = this.flatViewGroupValidatorService.validateFlatViewGroupCreation(args); @@ -48,13 +46,11 @@ export class WorkspaceMigrationV2ViewGroupActionsBuilderService extends Workspac }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewGroup, - 'deleted' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewGroup, + 'deleted' > { const validationResult = this.flatViewGroupValidatorService.validateFlatViewGroupDeletion(args); @@ -77,13 +73,11 @@ export class WorkspaceMigrationV2ViewGroupActionsBuilderService extends Workspac }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs, - ): Promise< - FlatEntityValidationReturnType< - typeof ALL_METADATA_NAME.viewGroup, - 'updated' - > + ): FlatEntityValidationReturnType< + typeof ALL_METADATA_NAME.viewGroup, + 'updated' > { const validationResult = this.flatViewGroupValidatorService.validateFlatViewGroupUpdate(args); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service.ts index 41ba3cfa12..5e6f377f9f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service.ts @@ -19,13 +19,11 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti super(ALL_METADATA_NAME.view); } - protected async validateFlatEntityCreation( + protected validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType - > { + ): FlatEntityValidationReturnType { const validationResult = - await this.flatViewValidatorService.validateFlatViewCreation(args); + this.flatViewValidatorService.validateFlatViewCreation(args); if (validationResult.errors.length > 0) { return { @@ -45,11 +43,9 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti }; } - protected async validateFlatEntityDeletion( + protected validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise< - FlatEntityValidationReturnType - > { + ): FlatEntityValidationReturnType { const validationResult = this.flatViewValidatorService.validateFlatViewDeletion(args); @@ -71,11 +67,9 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti }; } - protected async validateFlatEntityUpdate( + protected validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs, - ): Promise< - FlatEntityValidationReturnType - > { + ): FlatEntityValidationReturnType { const validationResult = this.flatViewValidatorService.validateFlatViewUpdate(args); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service.ts index 3484b22f97..87cf45705a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service.ts @@ -50,13 +50,13 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service< this.metadataName = metadataName; } - public async validateAndBuild({ + public validateAndBuild({ buildOptions, dependencyOptimisticFlatEntityMaps: inputDependencyOptimisticFlatEntityMaps, from: fromFlatEntityMaps, to: toFlatEntityMaps, workspaceId, - }: ValidateAndBuildArgs): Promise> { + }: ValidateAndBuildArgs): ValidateAndBuildReturnType { this.logger.time(`EntityBuilder ${this.metadataName}`, 'validateAndBuild'); this.logger.time( `EntityBuilder ${this.metadataName}`, @@ -122,7 +122,7 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service< flatEntityMapsToMutate: remainingFlatEntityMapsToCreate, }); - const validationResult = await this.validateFlatEntityCreation({ + const validationResult = this.validateFlatEntityCreation({ flatEntityToValidate: flatEntityToCreate, workspaceId, optimisticFlatEntityMapsAndRelatedFlatEntityMaps, @@ -185,7 +185,7 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service< flatEntityMapsToMutate: remainingFlatEntityMapsToDelete, }); - const validationResult = await this.validateFlatEntityDeletion({ + const validationResult = this.validateFlatEntityDeletion({ flatEntityToValidate: flatEntityToDelete, workspaceId, remainingFlatEntityMapsToValidate: remainingFlatEntityMapsToDelete, @@ -229,7 +229,7 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service< ); } - const validationResult = await this.validateFlatEntityUpdate({ + const validationResult = this.validateFlatEntityUpdate({ flatEntityUpdates: flatEntityToUpdate.updates, flatEntityId: flatEntityToUpdateId, optimisticFlatEntityMapsAndRelatedFlatEntityMaps, @@ -307,13 +307,13 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service< protected abstract validateFlatEntityCreation( args: FlatEntityValidationArgs, - ): Promise>; + ): FlatEntityValidationReturnType; protected abstract validateFlatEntityDeletion( args: FlatEntityValidationArgs, - ): Promise>; + ): FlatEntityValidationReturnType; protected abstract validateFlatEntityUpdate( args: FlatEntityUpdateValidationArgs, - ): Promise>; + ): FlatEntityValidationReturnType; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts index ac7f00f8bb..ab8ecb9a86 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service.ts @@ -99,15 +99,15 @@ export class FlatCronTriggerValidatorService { return validationResult; } - public async validateFlatCronTriggerCreation({ + public validateFlatCronTriggerCreation({ flatEntityToValidate: flatCronTriggerToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatCronTriggerMaps: optimisticFlatCronTriggerMaps, flatServerlessFunctionMaps, }, - }: FlatEntityValidationArgs): Promise< - FailedFlatEntityValidation - > { + }: FlatEntityValidationArgs< + typeof ALL_METADATA_NAME.cronTrigger + >): FailedFlatEntityValidation { const validationResult: FailedFlatEntityValidation = { type: 'create_cron_trigger', errors: [], diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts index 1fa1e3e9ba..8fb41d10bf 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service.ts @@ -104,7 +104,7 @@ export class FlatDatabaseEventTriggerValidatorService { return validationResult; } - public async validateFlatDatabaseEventTriggerCreation({ + public validateFlatDatabaseEventTriggerCreation({ flatEntityToValidate: flatDatabaseEventTriggerToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatDatabaseEventTriggerMaps: optimisticFlatDatabaseEventTriggerMaps, @@ -112,7 +112,7 @@ export class FlatDatabaseEventTriggerValidatorService { }, }: FlatEntityValidationArgs< typeof ALL_METADATA_NAME.databaseEventTrigger - >): Promise> { + >): FailedFlatEntityValidation { const validationResult: FailedFlatEntityValidation = { type: 'create_database_event_trigger', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts index 86fe78281c..35abe0e89d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts @@ -27,7 +27,7 @@ export class FlatFieldMetadataValidatorService { private readonly flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService, ) {} - async validateFlatFieldMetadataUpdate({ + validateFlatFieldMetadataUpdate({ flatEntityId, flatEntityUpdates: updates, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { @@ -38,7 +38,7 @@ export class FlatFieldMetadataValidatorService { buildOptions, }: FlatEntityUpdateValidationArgs< typeof ALL_METADATA_NAME.fieldMetadata - >): Promise> { + >): FailedFlatEntityValidation { const validationResult: FailedFlatEntityValidation = { type: 'update_field', errors: [], @@ -152,7 +152,7 @@ export class FlatFieldMetadataValidatorService { } const fieldMetadataTypeValidationErrors = - await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities( + this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities( { updates, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { @@ -262,7 +262,7 @@ export class FlatFieldMetadataValidatorService { return validationResult; } - async validateFlatFieldMetadataCreation({ + validateFlatFieldMetadataCreation({ flatEntityToValidate: flatFieldMetadataToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatFieldMetadataMaps: optimisticFlatFieldMetadataMaps, @@ -271,9 +271,9 @@ export class FlatFieldMetadataValidatorService { workspaceId, buildOptions, remainingFlatEntityMapsToValidate, - }: FlatEntityValidationArgs): Promise< - FailedFlatEntityValidation - > { + }: FlatEntityValidationArgs< + typeof ALL_METADATA_NAME.fieldMetadata + >): FailedFlatEntityValidation { const validationResult: FailedFlatEntityValidation = { errors: [], flatEntityMinimalInformation: { @@ -340,7 +340,7 @@ export class FlatFieldMetadataValidatorService { ); validationResult.errors.push( - ...(await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities( + ...this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities( { optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatFieldMetadataMaps: optimisticFlatFieldMetadataMaps, @@ -351,7 +351,7 @@ export class FlatFieldMetadataValidatorService { workspaceId, remainingFlatEntityMapsToValidate, }, - )), + ), ); return validationResult; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts index 6a425a8641..35353ba8aa 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-object-metadata-validator.service.ts @@ -156,7 +156,7 @@ export class FlatObjectMetadataValidatorService { return validationResult; } - public async validateFlatObjectMetadataCreation({ + public validateFlatObjectMetadataCreation({ flatEntityToValidate: flatObjectMetadataToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, @@ -164,7 +164,7 @@ export class FlatObjectMetadataValidatorService { }, }: FlatEntityValidationArgs< typeof ALL_METADATA_NAME.objectMetadata - >): Promise> { + >): FailedFlatEntityValidation { const objectValidationResult: FailedFlatEntityValidation = { type: 'create_object', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts index 679b091290..6fd895bdbd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-route-trigger-validator.service.ts @@ -99,15 +99,15 @@ export class FlatRouteTriggerValidatorService { return validationResult; } - public async validateFlatRouteTriggerCreation({ + public validateFlatRouteTriggerCreation({ flatEntityToValidate: flatRouteTriggerToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatRouteTriggerMaps: optimisticFlatRouteTriggerMaps, flatServerlessFunctionMaps, }, - }: FlatEntityValidationArgs): Promise< - FailedFlatEntityValidation - > { + }: FlatEntityValidationArgs< + typeof ALL_METADATA_NAME.routeTrigger + >): FailedFlatEntityValidation { const validationResult: FailedFlatEntityValidation = { type: 'create_route_trigger', errors: [], diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts index 5d0533b8e4..6c0585a699 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service.ts @@ -76,14 +76,14 @@ export class FlatServerlessFunctionValidatorService { return validationResult; } - public async validateFlatServerlessFunctionCreation({ + public validateFlatServerlessFunctionCreation({ flatEntityToValidate: flatServerlessFunctionToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatServerlessFunctionMaps: optimisticFlatServerlessFunctionMaps, }, }: FlatEntityValidationArgs< typeof ALL_METADATA_NAME.serverlessFunction - >): Promise> { + >): FailedFlatEntityValidation { const validationResult: FailedFlatEntityValidation = { type: 'create_serverless_function', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts index 178ce40447..910d19ed97 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service.ts @@ -96,16 +96,16 @@ export class FlatViewValidatorService { return validationResult; } - public async validateFlatViewCreation({ + public validateFlatViewCreation({ flatEntityToValidate: flatViewToValidate, optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { flatViewMaps: optimisticFlatViewMaps, flatFieldMetadataMaps, flatObjectMetadataMaps, }, - }: FlatEntityValidationArgs): Promise< - FailedFlatEntityValidation - > { + }: FlatEntityValidationArgs< + typeof ALL_METADATA_NAME.view + >): FailedFlatEntityValidation { const validationResult: FailedFlatEntityValidation = { type: 'create_view', errors: [], diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap new file mode 100644 index 0000000000..b27af52cb8 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap @@ -0,0 +1,189 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`updateOne FieldMetadataService morph relation fields v2 - Add one target Should fail adding one object metadata target to pre-existing morph relation where object is already a target 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [ + { + "errors": [ + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldNamePersonForMorphRelationSecond" is not available as it is already used by another field", + "userFriendlyMessage": "Name "fieldNamePersonForMorphRelationSecond" is not available as it is already used by another field", + "value": "fieldNamePersonForMorphRelationSecond", + }, + { + "code": "NOT_AVAILABLE", + "message": "Name "fieldNamePersonForMorphRelationSecondId" is not available as it is already used by another join column name", + "userFriendlyMessage": "Name "fieldNamePersonForMorphRelationSecondId" is not available as it is already used by join column name", + "value": "fieldNamePersonForMorphRelationSecondId", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldNamePersonForMorphRelationSecond", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, + { + "errors": [ + { + "code": "FIELD_METADATA_NOT_FOUND", + "message": "Relation field target metadata not found in both existing and about to be created field metadatas", + "userFriendlyMessage": "Relation field target metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "toto", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, + ], + "index": [ + { + "errors": [ + { + "code": "INDEX_ALREADY_EXISTS", + "message": "Index with same name already exists", + "userFriendlyMessage": "Index with same name already exists", + }, + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_4ff93dd3375abd83d29ed3d01b4", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating field", + "name": "GraphQLError", +} +`; + +exports[`updateOne FieldMetadataService morph relation fields v2 - Add one target Should fail adding one object metadata target to pre-existing morph relation where object is already source object 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [ + { + "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Source object cannot be the target object", + "userFriendlyMessage": "Source object cannot be the target object", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "fieldNameCompanyForMorphRelationSecond", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, + { + "errors": [ + { + "code": "FIELD_METADATA_NOT_FOUND", + "message": "Relation field target metadata not found in both existing and about to be created field metadatas", + "userFriendlyMessage": "Relation field target metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "toto", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, + ], + "index": [ + { + "errors": [ + { + "code": "INDEX_FIELD_NOT_FOUND", + "message": "Could not find index field related field metadata", + "userFriendlyMessage": "Field referenced in index does not exist", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "IDX_59563a0476bb3b8760e33ae6d5b", + }, + "status": "fail", + "type": "create_index", + }, + ], + "objectMetadata": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating field", + "name": "GraphQLError", +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts new file mode 100644 index 0000000000..1f9dca207b --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts @@ -0,0 +1,238 @@ +import { isDefined } from 'class-validator'; +import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; +import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; +import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { FieldMetadataType } from 'twenty-shared/types'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +describe('updateOne FieldMetadataService morph relation fields v2 - Add one target', () => { + let createdObjectMetadataPersonId: string; + let createdObjectMetadataOpportunityId: string; + let createdObjectMetadataCompanyId: string; + let createdObjectMetadataNewTargetId: string; + let createdFieldMetadataId: string; + + beforeAll(async () => { + const { + data: { + createOneObject: { id: objectMetadataPersonId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'personForMorphRelationSecond', + namePlural: 'peopleForMorphRelationSecond', + labelSingular: 'Person For Morph Relation', + labelPlural: 'People For Morph Relation', + icon: 'IconPerson', + }, + }); + + createdObjectMetadataPersonId = objectMetadataPersonId; + + const { + data: { + createOneObject: { id: objectMetadataCompanyId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'companyForMorphRelationSecond', + namePlural: 'companiesForMorphRelationSecond', + labelSingular: 'Company For Morph Relation', + labelPlural: 'Companies For Morph Relation', + icon: 'IconCompany', + }, + }); + + createdObjectMetadataCompanyId = objectMetadataCompanyId; + + const { + data: { + createOneObject: { id: objectMetadataOpportunityId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'opportunityForMorphRelationSecond', + namePlural: 'opportunitiesForMorphRelationSecond', + labelSingular: 'Opportunity For Morph Relation', + labelPlural: 'Opportunities For Morph Relation', + icon: 'IconOpportunity', + }, + }); + + createdObjectMetadataOpportunityId = objectMetadataOpportunityId; + + const { + data: { + createOneObject: { id: objectMetadataNewTargetId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'newTargetForMorphRelation', + namePlural: 'newTargetsForMorphRelation', + labelSingular: 'New Target For Morph Relation', + labelPlural: 'New Targets For Morph Relation', + icon: 'IconNewTarget', + }, + }); + + createdObjectMetadataNewTargetId = objectMetadataNewTargetId; + }); + + afterAll(async () => { + const createdObjectMetadataIds = [ + createdObjectMetadataPersonId, + createdObjectMetadataOpportunityId, + createdObjectMetadataCompanyId, + createdObjectMetadataNewTargetId, + ]; + + for (const objectMetadataId of createdObjectMetadataIds) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: objectMetadataId }, + }); + } + }); + + beforeEach(async () => { + const { + data: { createOneField: rawCreateOneField }, + } = await createOneFieldMetadata({ + input: { + label: 'field label', + name: 'fieldName', + objectMetadataId: createdObjectMetadataCompanyId, + description: 'Description for all', + type: FieldMetadataType.MORPH_RELATION, + morphRelationsCreationPayload: [ + { + targetFieldIcon: 'Icon123', + targetFieldLabel: 'toto', + targetObjectMetadataId: createdObjectMetadataOpportunityId, + type: RelationType.MANY_TO_ONE, + }, + { + targetFieldIcon: 'Icon123', + targetFieldLabel: 'tata', + targetObjectMetadataId: createdObjectMetadataPersonId, + type: RelationType.MANY_TO_ONE, + }, + ], + }, + expectToFail: false, + }); + + createdFieldMetadataId = rawCreateOneField.id; + }); + + afterEach(async () => { + if (!isDefined(createdFieldMetadataId)) { + return; + } + + await updateOneFieldMetadata({ + input: { + idToUpdate: createdFieldMetadataId, + updatePayload: { + isActive: false, + }, + }, + expectToFail: false, + }); + + await deleteOneFieldMetadata({ + input: { + idToDelete: createdFieldMetadataId, + }, + expectToFail: false, + }); + + createdFieldMetadataId = ''; + }); + + it('Should fail adding one object metadata target to pre-existing morph relation where object is already a target', async () => { + const input = { + idToUpdate: createdFieldMetadataId, + updatePayload: { + morphRelationsUpdatePayload: [ + { + targetObjectMetadataId: createdObjectMetadataPersonId, + }, + ], + }, + }; + + const { errors } = await updateOneFieldMetadata({ + expectToFail: true, + input, + gqlFields: ` + id + name + morphRelations { + type + targetFieldMetadata { + id + name + } + } + `, + }); + + expectOneNotInternalServerErrorSnapshot({ errors }); + }); + + it('Should fail adding one object metadata target to pre-existing morph relation where object is already source object', async () => { + const input = { + idToUpdate: createdFieldMetadataId, + updatePayload: { + morphRelationsUpdatePayload: [ + { + targetObjectMetadataId: createdObjectMetadataCompanyId, + }, + ], + }, + }; + + const { errors } = await updateOneFieldMetadata({ + expectToFail: true, + input, + gqlFields: ` + id + name + morphRelations { + type + targetFieldMetadata { + id + name + } + targetObjectMetadata { + id + nameSingular + } + sourceObjectMetadata { + id + nameSingular + } + } + `, + }); + + expectOneNotInternalServerErrorSnapshot({ errors }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts new file mode 100644 index 0000000000..dfe2d0625a --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/successful-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts @@ -0,0 +1,296 @@ +import { isDefined } from 'class-validator'; +import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; +import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; +import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; +import { findManyObjectMetadataWithIndexes } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-with-indexes.util'; +import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; +import { FieldMetadataType } from 'twenty-shared/types'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { type RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; + +describe('updateOne FieldMetadataService morph relation fields v2 - Add one target', () => { + let createdObjectMetadataPersonId: string; + let createdObjectMetadataOpportunityId: string; + let createdObjectMetadataCompanyId: string; + let createdObjectMetadataNewTargetId: string; + let createdFieldMetadataId: string; + + beforeAll(async () => { + const { + data: { + createOneObject: { id: objectMetadataPersonId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'personForMorphRelationSecond', + namePlural: 'peopleForMorphRelationSecond', + labelSingular: 'Person For Morph Relation', + labelPlural: 'People For Morph Relation', + icon: 'IconPerson', + }, + }); + + createdObjectMetadataPersonId = objectMetadataPersonId; + + const { + data: { + createOneObject: { id: objectMetadataCompanyId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'companyForMorphRelationSecond', + namePlural: 'companiesForMorphRelationSecond', + labelSingular: 'Company For Morph Relation', + labelPlural: 'Companies For Morph Relation', + icon: 'IconCompany', + }, + }); + + createdObjectMetadataCompanyId = objectMetadataCompanyId; + + const { + data: { + createOneObject: { id: objectMetadataOpportunityId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'opportunityForMorphRelationSecond', + namePlural: 'opportunitiesForMorphRelationSecond', + labelSingular: 'Opportunity For Morph Relation', + labelPlural: 'Opportunities For Morph Relation', + icon: 'IconOpportunity', + }, + }); + + createdObjectMetadataOpportunityId = objectMetadataOpportunityId; + + const { + data: { + createOneObject: { id: objectMetadataNewTargetId }, + }, + } = await createOneObjectMetadata({ + expectToFail: false, + input: { + nameSingular: 'newTargetForMorphRelation', + namePlural: 'newTargetsForMorphRelation', + labelSingular: 'New Target For Morph Relation', + labelPlural: 'New Targets For Morph Relation', + icon: 'IconNewTarget', + }, + }); + + createdObjectMetadataNewTargetId = objectMetadataNewTargetId; + }); + + afterAll(async () => { + const createdObjectMetadataIds = [ + createdObjectMetadataPersonId, + createdObjectMetadataOpportunityId, + createdObjectMetadataCompanyId, + createdObjectMetadataNewTargetId, + ]; + + for (const objectMetadataId of createdObjectMetadataIds) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: objectMetadataId }, + }); + } + }); + + beforeEach(async () => { + const { + data: { createOneField: rawCreateOneField }, + } = await createOneFieldMetadata({ + input: { + label: 'field label', + name: 'fieldName', + objectMetadataId: createdObjectMetadataCompanyId, + description: 'Description for all', + type: FieldMetadataType.MORPH_RELATION, + morphRelationsCreationPayload: [ + { + targetFieldIcon: 'Icon123', + targetFieldLabel: 'toto', + targetObjectMetadataId: createdObjectMetadataOpportunityId, + type: RelationType.MANY_TO_ONE, + }, + { + targetFieldIcon: 'Icon123', + targetFieldLabel: 'tata', + targetObjectMetadataId: createdObjectMetadataPersonId, + type: RelationType.MANY_TO_ONE, + }, + ], + }, + expectToFail: false, + }); + + createdFieldMetadataId = rawCreateOneField.id; + }); + + afterEach(async () => { + if (!isDefined(createdFieldMetadataId)) { + return; + } + + await updateOneFieldMetadata({ + input: { + idToUpdate: createdFieldMetadataId, + updatePayload: { + isActive: false, + }, + }, + expectToFail: false, + }); + + await deleteOneFieldMetadata({ + input: { + idToDelete: createdFieldMetadataId, + }, + expectToFail: false, + }); + + createdFieldMetadataId = ''; + }); + + it('When adding one object metadata target to pre-existing morph relation, the relation on the target object metadata should be updated', async () => { + const input = { + idToUpdate: createdFieldMetadataId, + updatePayload: { + morphRelationsUpdatePayload: [ + { + targetObjectMetadataId: createdObjectMetadataNewTargetId, + }, + ], + }, + }; + + const result = await updateOneFieldMetadata({ + expectToFail: false, + input, + gqlFields: ` + id + name + morphRelations { + type + targetFieldMetadata { + id + name + } + targetObjectMetadata { + id + nameSingular + } + sourceFieldMetadata { + id + name + } + } + `, + }); + + const updateOneField = result.data + ?.updateOneField as unknown as FieldMetadataDTO & { + morphRelations: RelationDTO[]; + }; + + expect(updateOneField?.morphRelations.length).toBe(3); + + const objectMetadatasWithIndexes = await findManyObjectMetadataWithIndexes({ + expectToFail: false, + }); + + const newLyCreatedMorphRelation = updateOneField?.morphRelations.find( + (morphRelation) => + morphRelation.targetObjectMetadata.id === + createdObjectMetadataNewTargetId, + ); + + jestExpectToBeDefined(newLyCreatedMorphRelation); + + const sourceObjectMetadataIndexFieldMetadataList = + objectMetadatasWithIndexes.find( + (objectMetadataWithIndexes) => + objectMetadataWithIndexes.id === createdObjectMetadataCompanyId, + )?.indexMetadataList; + + const isIndexCreated = sourceObjectMetadataIndexFieldMetadataList?.some( + (indexList) => + indexList.indexFieldMetadataList.some( + (indexField) => + indexField.fieldMetadataId === + newLyCreatedMorphRelation.sourceFieldMetadata.id, + ), + ); + + expect(isIndexCreated).toBe(true); + }); + + // todo and the name ? + it('Should add a new morph relation target and update the label of the field at the same time and expect the added created morph and the old morphs to follow the updated label', async () => { + const input = { + idToUpdate: createdFieldMetadataId, + updatePayload: { + label: 'new label for morph Fields', + morphRelationsUpdatePayload: [ + { + targetObjectMetadataId: createdObjectMetadataNewTargetId, + }, + ], + }, + }; + + const result = await updateOneFieldMetadata({ + expectToFail: false, + input, + gqlFields: ` + id + name + label + morphRelations { + type + targetFieldMetadata { + id + name + } + sourceFieldMetadata { + id + name + label + } + } + `, + }); + + const updateOneField = result.data + ?.updateOneField as unknown as FieldMetadataDTO & { + morphRelations: RelationDTO[]; + }; + + const morphRelationsLabels = updateOneField.morphRelations.map( + (morphRelation) => morphRelation.sourceFieldMetadata.label, + ); + + expect(morphRelationsLabels[0]).toBe('new label for morph Fields'); + const morphRelationsLabelsSet = new Set(morphRelationsLabels); + + expect(morphRelationsLabelsSet.size).toBe(1); + }); +}); diff --git a/packages/twenty-shared/src/types/RelationUpdatePayload.ts b/packages/twenty-shared/src/types/RelationUpdatePayload.ts new file mode 100644 index 0000000000..ccb87c4328 --- /dev/null +++ b/packages/twenty-shared/src/types/RelationUpdatePayload.ts @@ -0,0 +1,3 @@ +export type RelationUpdatePayload = { + targetObjectMetadataId: string; +}; diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index 82c4bb0d0b..e32ba4eb00 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -172,6 +172,7 @@ export type { RelationAndMorphRelationFieldMetadataType } from './RelationAndMor export type { RelationCreationPayload } from './RelationCreationPayload'; export { RelationOnDeleteAction } from './RelationOnDeleteAction.type'; export { RelationType } from './RelationType'; +export type { RelationUpdatePayload } from './RelationUpdatePayload'; export type { RestrictedFieldPermissions } from './RestrictedFieldPermissions'; export type { RestrictedFieldsPermissions } from './RestrictedFieldsPermissions'; export { SettingsPath } from './SettingsPath';