diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1752993324991-makeFieldMetadataRelationConstraintDeferrable.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1752993324991-makeFieldMetadataRelationConstraintDeferrable.ts new file mode 100644 index 00000000000..144317c308d --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1752993324991-makeFieldMetadataRelationConstraintDeferrable.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MakeFieldMetadataRelationConstraintDeferrable1752993324991 + implements MigrationInterface +{ + name = 'MakeFieldMetadataRelationConstraintDeferrable1752993324991'; + + public async up(queryRunner: QueryRunner): Promise { + // Drop the existing constraint + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" DROP CONSTRAINT "FK_47a6c57e1652b6475f8248cff78"`, + ); + + // Recreate it as deferrable and initially deferred + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" ADD CONSTRAINT "FK_47a6c57e1652b6475f8248cff78" FOREIGN KEY ("relationTargetFieldMetadataId") REFERENCES "core"."fieldMetadata"("id") DEFERRABLE INITIALLY DEFERRED`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + // Drop the deferrable constraint + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" DROP CONSTRAINT "FK_47a6c57e1652b6475f8248cff78"`, + ); + + // Recreate it as non-deferrable + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" ADD CONSTRAINT "FK_47a6c57e1652b6475f8248cff78" FOREIGN KEY ("relationTargetFieldMetadataId") REFERENCES "core"."fieldMetadata"("id")`, + ); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts index 97406d66fde..dbc87663d14 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface.ts @@ -49,13 +49,13 @@ export type FieldMetadataAddressSettings = { }; type FieldMetadataSettingsMapping = { - [FieldMetadataType.NUMBER]: FieldMetadataNumberSettings; - [FieldMetadataType.DATE]: FieldMetadataDateSettings; - [FieldMetadataType.DATE_TIME]: FieldMetadataDateTimeSettings; - [FieldMetadataType.TEXT]: FieldMetadataTextSettings; + [FieldMetadataType.NUMBER]: FieldMetadataNumberSettings | null; + [FieldMetadataType.DATE]: FieldMetadataDateSettings | null; + [FieldMetadataType.DATE_TIME]: FieldMetadataDateTimeSettings | null; + [FieldMetadataType.TEXT]: FieldMetadataTextSettings | null; [FieldMetadataType.RELATION]: FieldMetadataRelationSettings; - [FieldMetadataType.MORPH_RELATION]: FieldMetadataRelationSettings; - [FieldMetadataType.ADDRESS]: FieldMetadataAddressSettings; + [FieldMetadataType.ADDRESS]: FieldMetadataAddressSettings | null; + [FieldMetadataType.MORPH_RELATION]: FieldMetadataRelationSettings | null; // TODO Should not be null }; export type AllFieldMetadataSettings = @@ -67,5 +67,5 @@ export type FieldMetadataSettings< IsExactly extends true ? null | AllFieldMetadataSettings // Could be improved to be | unknown : T extends keyof FieldMetadataSettingsMapping - ? FieldMetadataSettingsMapping[T] | null + ? FieldMetadataSettingsMapping[T] : never | null; 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 663d8595f54..90a9ef28d7c 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 @@ -13,12 +13,10 @@ import { FieldMetadataExceptionCode, } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service'; -import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type'; import { fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util'; -import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/dispatch-and-merge-flat-field-metadatas-in-flat-object-metadatas.util'; import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util'; -import { mergeTwoFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util'; -import { getFieldMetadataEntityFromCachedObjectMetadataMaps } from 'src/engine/metadata-modules/utils/get-field-metadata-entity-from-cached-object-metadata-maps.util'; +import { removeFlatFieldMetadataFromFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service'; import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service'; @@ -68,67 +66,54 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService - await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({ - existingFlatObjectMetadatas, - rawCreateFieldInput: fieldMetadataInput, - }), - ), + const flatFieldMetadatasToCreate = ( + await Promise.all( + fieldMetadataInputs.map( + async (fieldMetadataInput) => + await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({ + existingFlatObjectMetadatas, + rawCreateFieldInput: fieldMetadataInput, + }), + ), + ) + ).flat(); + + const optimisticRenderedFlatObjectMetadatas = + dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas({ + flatObjectMetadatas: existingFlatObjectMetadatas, + flatFieldMetadatas: flatFieldMetadatasToCreate, + }); + + const flatFieldMetadataValidationPromises = flatFieldMetadatasToCreate.map( + (flatFieldMetadataToValidate) => + this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({ + existingFlatObjectMetadatas: + removeFlatFieldMetadataFromFlatObjectMetadatas({ + flatFieldMetadata: flatFieldMetadataToValidate, + flatObjectMetadatas: optimisticRenderedFlatObjectMetadatas, + }), + flatFieldMetadataToValidate, + workspaceId, + }), ); - let flatObjectMetadatasWithNewFields: FlatObjectMetadata[] = []; + const createdFlatFieldMetadataValidationResult = ( + await Promise.all(flatFieldMetadataValidationPromises) + ) + .flat() + .filter(isDefined); - for (const flatFieldMetadataAndParentPair of flatFieldToCreateAndItsFlatObjectMetadataArray) { - const flatFieldMetadataValidationPromises = - flatFieldMetadataAndParentPair.flatMap< - Promise - >(({ flatFieldMetadata: flatFieldMetadataToValidate }) => - this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({ - existingFlatObjectMetadatas, - flatFieldMetadataToValidate, - workspaceId, - }), - ); - const createdFlatFieldMetadataValidationResult = ( - await Promise.all(flatFieldMetadataValidationPromises) - ) - .flat() - .filter(isDefined); - - if (createdFlatFieldMetadataValidationResult.length > 0) { - const errors = createdFlatFieldMetadataValidationResult.flat(); - - throw new MultipleMetadataValidationErrors( - errors, - 'Multiple validation errors occurred while creating field', - ); - } - - const updatedFlatObjectMetadatas = - flatFieldMetadataAndParentPair.map( - ({ flatFieldMetadata, parentFlatObjectMetadata }) => { - return { - ...parentFlatObjectMetadata, - flatFieldMetadatas: [ - ...parentFlatObjectMetadata.flatFieldMetadatas, - flatFieldMetadata, - ], - }; - }, - ); - - flatObjectMetadatasWithNewFields = mergeTwoFlatObjectMetadatas({ - destFlatObjectMetadatas: flatObjectMetadatasWithNewFields, - toMergeFlatObjectMetadatas: updatedFlatObjectMetadatas, - }); + if (createdFlatFieldMetadataValidationResult.length > 0) { + throw new MultipleMetadataValidationErrors( + createdFlatFieldMetadataValidationResult, + 'Multiple validation errors occurred while creating field', + ); } const workspaceMigration = this.workspaceMigrationBuilderV2.build({ objectMetadataFromToInputs: { from: existingFlatObjectMetadatas, - to: flatObjectMetadatasWithNewFields, + to: optimisticRenderedFlatObjectMetadatas, }, inferDeletionFromMissingObjectFieldIndex: false, workspaceId, @@ -136,27 +121,7 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService( - (createdFlatFieldAndItsParentFlatObject) => { - return createdFlatFieldAndItsParentFlatObject - .map( - ({ - flatFieldMetadata: { id: fieldMetadataId }, - parentFlatObjectMetadata: { id: objectMetadataId }, - }) => - getFieldMetadataEntityFromCachedObjectMetadataMaps({ - fieldMetadataId, - objectMetadataId, - objectMetadataMaps: recomputedCache.objectMetadataMaps, - }), - ) - .filter(isDefined); - }, - ); + // TODO refactor once the runner has been refactored to return created entities + return []; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts index 1a72cfcad75..bc1364a30de 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts @@ -165,7 +165,7 @@ type SettingsAssertions = [ Expect< HasAllProperties< RelationFieldMetadata, - { settings: FieldMetadataRelationSettings | null } + { settings: FieldMetadataRelationSettings } > >, Expect< diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts index 5d266d029e2..50d78eb46a4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service.ts @@ -12,6 +12,7 @@ import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { validateFlatFieldMetadataNameAvailability } from 'src/engine/metadata-modules/flat-field-metadata/validators/validate-flat-field-metadata-name-availability.validator'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { mergeTwoFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util'; import { ObjectMetadataException, ObjectMetadataExceptionCode, @@ -49,14 +50,18 @@ export class FlatFieldMetadataValidatorService { FailedFlatFieldMetadataValidationExceptions[] > { const errors: FailedFlatFieldMetadataValidationExceptions[] = []; - const allFlatObjectMetadata = [ - ...existingFlatObjectMetadatas, - ...(othersFlatObjectMetadataToValidate ?? []), - ]; + + const allFlatObjectMetadata = isDefined(othersFlatObjectMetadataToValidate) + ? mergeTwoFlatObjectMetadatas({ + destFlatObjectMetadatas: existingFlatObjectMetadatas, + toMergeFlatObjectMetadatas: othersFlatObjectMetadataToValidate, + }) + : existingFlatObjectMetadatas; + const parentFlatObjectMetadata = allFlatObjectMetadata.find( (existingFlatObjectMetadata) => existingFlatObjectMetadata.id === - flatFieldMetadataToValidate.objectMetadataId, // Question: Should we comparing unique identifier here ? + flatFieldMetadataToValidate.objectMetadataId, ); if (!isDefined(parentFlatObjectMetadata)) { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util.ts index 96a4a1d8698..c2d05b13e90 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util.ts @@ -24,19 +24,11 @@ type FromCreateFieldInputToFlatObjectMetadata = { rawCreateFieldInput: CreateFieldInput; existingFlatObjectMetadatas: FlatObjectMetadata[]; }; -export type FlatFieldAndItsFlatObjectMetadata< - T extends FieldMetadataType = FieldMetadataType, -> = { - flatFieldMetadata: FlatFieldMetadata; - parentFlatObjectMetadata: FlatObjectMetadata; -}; export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ existingFlatObjectMetadatas, rawCreateFieldInput, -}: FromCreateFieldInputToFlatObjectMetadata): Promise< - FlatFieldAndItsFlatObjectMetadata[] -> => { +}: FromCreateFieldInputToFlatObjectMetadata): Promise => { if (rawCreateFieldInput.isRemoteCreation) { throw new FieldMetadataException( "Remote fields aren't supported", @@ -86,15 +78,12 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ case FieldMetadataType.RATING: { return [ { - flatFieldMetadata: { - ...commonFlatFieldMetadata, - type: createFieldInput.type, - settings: null, - defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ? - options: generateRatingOptions(), - } satisfies FlatFieldMetadata, - parentFlatObjectMetadata, - }, + ...commonFlatFieldMetadata, + type: createFieldInput.type, + settings: null, + defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ? + options: generateRatingOptions(), + } satisfies FlatFieldMetadata, ]; } case FieldMetadataType.SELECT: @@ -110,15 +99,12 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ return [ { - flatFieldMetadata: { - ...commonFlatFieldMetadata, - type: createFieldInput.type, - options, - defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ? - settings: null, - } satisfies FlatFieldMetadata, - parentFlatObjectMetadata, - }, + ...commonFlatFieldMetadata, + type: createFieldInput.type, + options, + defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ? + settings: null, + } satisfies FlatFieldMetadata, ]; } case FieldMetadataType.UUID: @@ -143,11 +129,8 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ case FieldMetadataType.TS_VECTOR: { return [ { - flatFieldMetadata: { - ...commonFlatFieldMetadata, - type: createFieldInput.type, - }, - parentFlatObjectMetadata, + ...commonFlatFieldMetadata, + type: createFieldInput.type, }, ]; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts index 9eacf9d6f13..23d16c0e83c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util.ts @@ -2,6 +2,8 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { FieldMetadataException, @@ -9,11 +11,32 @@ import { } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload.util'; import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { FlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util'; import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; +type ComputeFieldMetadataRelationSettingsForRelationTypeArgs = { + relationType: RelationType; + fieldMetadataName: string; +}; +const computeFieldMetadataRelationSettingsForRelationType = ({ + fieldMetadataName, + relationType, +}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs) => { + if (relationType === RelationType.MANY_TO_ONE) { + return { + relationType: RelationType.MANY_TO_ONE, + onDelete: RelationOnDeleteAction.SET_NULL, + joinColumnName: fieldMetadataName, + }; + } + + return { + relationType: RelationType.ONE_TO_MANY, + }; +}; + type FromRelationCreateFieldInputToFlatFieldMetadataArgs = { createFieldInput: CreateFieldInput; existingFlatObjectMetadatas: FlatObjectMetadata[]; @@ -24,7 +47,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ sourceParentFlatObjectMetadata, createFieldInput, }: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise< - FlatFieldAndItsFlatObjectMetadata[] + FlatFieldMetadata[] > => { const { relationCreationPayload } = createFieldInput; @@ -48,6 +71,11 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ ); } + const sourceFlatFieldMetadataSettings = + computeFieldMetadataRelationSettingsForRelationType({ + fieldMetadataName: createFieldInput.name, + relationType: relationCreationPayload.type, + }); const targetRelationTargetFieldMetadataId = v4(); const sourceRelationTargetFieldMetadataId = v4(); const sourceFlatFieldMetadata: Omit< @@ -58,33 +86,43 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ createFieldInput, fieldMetadataId: sourceRelationTargetFieldMetadataId, }), + icon: createFieldInput.icon ?? 'IconRelationOneToMany', type: FieldMetadataType.RELATION, defaultValue: null, - settings: null, + settings: sourceFlatFieldMetadataSettings, options: null, relationTargetFieldMetadataId: targetRelationTargetFieldMetadataId, // Note: this won't work until we enable deferred transaction relationTargetObjectMetadataId: targetParentFlatObjectMetadata.id, flatRelationTargetObjectMetadata: targetParentFlatObjectMetadata, }; + const targetCreateFieldInput: CreateFieldInput = { + icon: relationCreationPayload.targetFieldIcon ?? 'Icon123', + label: relationCreationPayload.targetFieldLabel, + name: `${computeMetadataNameFromLabel( + relationCreationPayload.targetFieldLabel, + )}Id`, + objectMetadataId: targetParentFlatObjectMetadata.id, + type: FieldMetadataType.RELATION, + workspaceId: createFieldInput.workspaceId, + }; + const targetFlatFieldMetadataSettings = + computeFieldMetadataRelationSettingsForRelationType({ + fieldMetadataName: targetCreateFieldInput.name, + relationType: + relationCreationPayload.type === RelationType.ONE_TO_MANY + ? RelationType.MANY_TO_ONE + : RelationType.ONE_TO_MANY, + }); const targetFlatFieldMetadata: FlatFieldMetadata = { ...getDefaultFlatFieldMetadata({ - createFieldInput: { - icon: relationCreationPayload.targetFieldIcon, - label: relationCreationPayload.targetFieldLabel, - name: `${computeMetadataNameFromLabel( - relationCreationPayload.targetFieldLabel, - )}Id`, - objectMetadataId: targetParentFlatObjectMetadata.id, - type: FieldMetadataType.RELATION, - workspaceId: createFieldInput.workspaceId, - }, + createFieldInput: targetCreateFieldInput, fieldMetadataId: targetRelationTargetFieldMetadataId, }), type: FieldMetadataType.RELATION, defaultValue: null, - settings: null, + settings: targetFlatFieldMetadataSettings, options: null, relationTargetFieldMetadataId: sourceRelationTargetFieldMetadataId, relationTargetObjectMetadataId: sourceParentFlatObjectMetadata.id, @@ -94,15 +132,9 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ return [ { - flatFieldMetadata: { - ...sourceFlatFieldMetadata, - flatRelationTargetFieldMetadata: targetFlatFieldMetadata, - }, - parentFlatObjectMetadata: sourceParentFlatObjectMetadata, + ...sourceFlatFieldMetadata, + flatRelationTargetFieldMetadata: targetFlatFieldMetadata, }, - { - flatFieldMetadata: targetFlatFieldMetadata, - parentFlatObjectMetadata: targetParentFlatObjectMetadata, - }, - ] satisfies FlatFieldAndItsFlatObjectMetadata[]; + targetFlatFieldMetadata, + ] satisfies FlatFieldMetadata[]; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/dispatch-and-merge-flat-field-metadatas-in-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/dispatch-and-merge-flat-field-metadatas-in-flat-object-metadatas.util.ts new file mode 100644 index 00000000000..e18a9405771 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/dispatch-and-merge-flat-field-metadatas-in-flat-object-metadatas.util.ts @@ -0,0 +1,49 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { mergeTwoFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; + +type DispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatasArgs = { + flatObjectMetadatas: FlatObjectMetadata[]; + flatFieldMetadatas: FlatFieldMetadata[]; +}; +export const dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas = ({ + flatFieldMetadatas, + flatObjectMetadatas, +}: DispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatasArgs): FlatObjectMetadata[] => { + const initialAccumulator: Record = {}; + const flatFieldMetadataGroupedByFlatObjectMetadataId = + flatFieldMetadatas.reduce((acc, flatFieldMetadata) => { + const { objectMetadataId } = flatFieldMetadata; + const occurrence = acc[objectMetadataId]; + + if (isDefined(occurrence)) { + return { + ...acc, + [objectMetadataId]: [...occurrence, flatFieldMetadata], + }; + } + + return { + [flatFieldMetadata.objectMetadataId]: [flatFieldMetadata], + }; + }, initialAccumulator); + + return flatObjectMetadatas.map((flatObjectMetadata) => { + const toMergeFlatFieldMetadatas = + flatFieldMetadataGroupedByFlatObjectMetadataId[flatObjectMetadata.id]; + + if (!isDefined(toMergeFlatFieldMetadatas)) { + return flatObjectMetadata; + } + + return { + ...flatObjectMetadata, + flatFieldMetadatas: mergeTwoFlatFieldMetadatas({ + destFlatFieldMetadatas: flatObjectMetadata.flatFieldMetadatas, + toMergeFlatFieldMetadatas, + }), + }; + }); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util.ts new file mode 100644 index 00000000000..7a1c8ac1c60 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util.ts @@ -0,0 +1,26 @@ +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; + +export const removeFlatFieldMetadataFromFlatObjectMetadatas = ({ + flatFieldMetadata: flatFieldMetadataToRemove, + flatObjectMetadatas, +}: { + flatObjectMetadatas: FlatObjectMetadata[]; + flatFieldMetadata: FlatFieldMetadata; +}) => { + return flatObjectMetadatas.map((flatObjectMetadata) => { + if (flatObjectMetadata.id !== flatFieldMetadataToRemove.objectMetadataId) { + return flatObjectMetadata; + } + + const flatFieldMetadatas = flatObjectMetadata.flatFieldMetadatas.filter( + ({ id: flatFieldMetadataId }) => + flatFieldMetadataId !== flatFieldMetadataToRemove.id, + ); + + return { + ...flatObjectMetadata, + flatFieldMetadatas, + }; + }); +};