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 90a9ef28d7c..1d732d431bf 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 @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; +import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; @@ -13,10 +14,13 @@ 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 { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; +import { mergeFlatFieldMetadatasInFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-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 { removeFlatFieldMetadataFromFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util'; +import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-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'; @@ -58,62 +62,132 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({ - existingFlatObjectMetadatas, + existingFlatObjectMetadataMaps, rawCreateFieldInput: fieldMetadataInput, }), ), ) ).flat(); - const optimisticRenderedFlatObjectMetadatas = - dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas({ - flatObjectMetadatas: existingFlatObjectMetadatas, - flatFieldMetadatas: flatFieldMetadatasToCreate, - }); + const existingFlatObjectMetadatas = Object.values( + existingFlatObjectMetadataMaps.byId, + ).map(fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata); - const flatFieldMetadataValidationPromises = flatFieldMetadatasToCreate.map( - (flatFieldMetadataToValidate) => - this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({ - existingFlatObjectMetadatas: - removeFlatFieldMetadataFromFlatObjectMetadatas({ - flatFieldMetadata: flatFieldMetadataToValidate, - flatObjectMetadatas: optimisticRenderedFlatObjectMetadatas, - }), - flatFieldMetadataToValidate, - workspaceId, - }), + const impactedObjectMetadataIds = Array.from( + new Set( + flatFieldMetadatasToCreate.map( + (flatFieldMetadata) => flatFieldMetadata.objectMetadataId, + ), + ), + ); + const filterFlatObjectMetadatasByImpactedIds = ( + flatObjectMetadatas: FlatObjectMetadata[], + ) => + flatObjectMetadatas.filter((flatObjectMetadata) => + impactedObjectMetadataIds.includes(flatObjectMetadata.id), + ); + const impactedExistingFlatObjectMetadatas = + filterFlatObjectMetadatasByImpactedIds(existingFlatObjectMetadatas); + + const allValidationErrors: FailedFlatFieldMetadataValidationExceptions[] = + []; + let sequentiallyOptimisticallyRenderedFlatObjectMetadatas = structuredClone( + existingFlatObjectMetadatas, ); - const createdFlatFieldMetadataValidationResult = ( - await Promise.all(flatFieldMetadataValidationPromises) - ) - .flat() - .filter(isDefined); + for (const flatFieldMetadataToCreate of flatFieldMetadatasToCreate) { + let otherFlatObjectMetadataToValidate: FlatObjectMetadata | undefined = + undefined; - if (createdFlatFieldMetadataValidationResult.length > 0) { + if ( + isFlatFieldMetadataEntityOfType( + flatFieldMetadataToCreate, + FieldMetadataType.RELATION, + ) || + isFlatFieldMetadataEntityOfType( + flatFieldMetadataToCreate, + FieldMetadataType.MORPH_RELATION, + ) + ) { + const relatedFlatFieldMetadataToCreate = + flatFieldMetadatasToCreate.find( + (relatedFlatFieldMetadata) => + isFlatFieldMetadataEntityOfType( + relatedFlatFieldMetadata, + FieldMetadataType.RELATION, + ) && + relatedFlatFieldMetadata.id === + flatFieldMetadataToCreate.relationTargetFieldMetadataId, + ); + const relatedFlatObjectMetadata = isDefined( + relatedFlatFieldMetadataToCreate, + ) + ? existingFlatObjectMetadataMaps.byId[ + relatedFlatFieldMetadataToCreate.objectMetadataId + ] + : undefined; + + otherFlatObjectMetadataToValidate = + isDefined(relatedFlatObjectMetadata) && + isDefined(relatedFlatFieldMetadataToCreate) + ? mergeFlatFieldMetadatasInFlatObjectMetadata({ + flatFieldMetadatas: [relatedFlatFieldMetadataToCreate], + flatObjectMetadata: relatedFlatObjectMetadata, + }) + : undefined; + } + + const validationErrors = + await this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata( + { + existingFlatObjectMetadatas: + sequentiallyOptimisticallyRenderedFlatObjectMetadatas, + flatFieldMetadataToValidate: flatFieldMetadataToCreate, + workspaceId, + othersFlatObjectMetadataToValidate: isDefined( + otherFlatObjectMetadataToValidate, + ) + ? [otherFlatObjectMetadataToValidate] + : undefined, + }, + ); + + if (validationErrors.length > 0) { + allValidationErrors.push(...validationErrors); + continue; + } + + sequentiallyOptimisticallyRenderedFlatObjectMetadatas = + dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas({ + flatFieldMetadatas: [flatFieldMetadataToCreate], + flatObjectMetadatas: + sequentiallyOptimisticallyRenderedFlatObjectMetadatas, + }); + } + + if (allValidationErrors.length > 0) { throw new MultipleMetadataValidationErrors( - createdFlatFieldMetadataValidationResult, + allValidationErrors, 'Multiple validation errors occurred while creating field', ); } const workspaceMigration = this.workspaceMigrationBuilderV2.build({ objectMetadataFromToInputs: { - from: existingFlatObjectMetadatas, - to: optimisticRenderedFlatObjectMetadatas, + from: impactedExistingFlatObjectMetadatas, + to: filterFlatObjectMetadatasByImpactedIds( + sequentiallyOptimisticallyRenderedFlatObjectMetadatas, + ), }, inferDeletionFromMissingObjectFieldIndex: false, workspaceId, 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 c2d05b13e90..de4644b5c31 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 @@ -18,16 +18,16 @@ import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadat import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { fromRelationCreateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-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 { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; type FromCreateFieldInputToFlatObjectMetadata = { rawCreateFieldInput: CreateFieldInput; - existingFlatObjectMetadatas: FlatObjectMetadata[]; + existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; }; export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ - existingFlatObjectMetadatas, rawCreateFieldInput, + existingFlatObjectMetadataMaps, }: FromCreateFieldInputToFlatObjectMetadata): Promise => { if (rawCreateFieldInput.isRemoteCreation) { throw new FieldMetadataException( @@ -40,10 +40,8 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ rawCreateFieldInput, ['description', 'icon', 'label', 'name', 'objectMetadataId', 'type'], ); - const parentFlatObjectMetadata = existingFlatObjectMetadatas.find( - (existingFlatObjectMetadata) => - existingFlatObjectMetadata.id === createFieldInput.objectMetadataId, - ); + const parentFlatObjectMetadata = + existingFlatObjectMetadataMaps.byId[createFieldInput.objectMetadataId]; if (!isDefined(parentFlatObjectMetadata)) { throw new FieldMetadataException( @@ -70,7 +68,7 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({ } case FieldMetadataType.RELATION: { return fromRelationCreateFieldInputToFlatFieldMetadata({ - existingFlatObjectMetadatas, + existingFlatObjectMetadataMaps, sourceParentFlatObjectMetadata: parentFlatObjectMetadata, createFieldInput, }); 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 23d16c0e83c..383922cb684 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 @@ -12,6 +12,7 @@ import { 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 { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util'; +import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; 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'; @@ -39,11 +40,11 @@ const computeFieldMetadataRelationSettingsForRelationType = ({ type FromRelationCreateFieldInputToFlatFieldMetadataArgs = { createFieldInput: CreateFieldInput; - existingFlatObjectMetadatas: FlatObjectMetadata[]; + existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; sourceParentFlatObjectMetadata: FlatObjectMetadata; }; export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ - existingFlatObjectMetadatas, + existingFlatObjectMetadataMaps, sourceParentFlatObjectMetadata, createFieldInput, }: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise< @@ -59,10 +60,10 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({ } await validateRelationCreationPayloadOrThrow(relationCreationPayload); - const targetParentFlatObjectMetadata = existingFlatObjectMetadatas.find( - (existingFlatObject) => - existingFlatObject.id === relationCreationPayload.targetObjectMetadataId, - ); + const targetParentFlatObjectMetadata = + existingFlatObjectMetadataMaps.byId[ + relationCreationPayload.targetObjectMetadataId + ]; if (!isDefined(targetParentFlatObjectMetadata)) { throw new FieldMetadataException( diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-flat-object-metadata.util.ts new file mode 100644 index 00000000000..307cdcceeaf --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-flat-object-metadata.util.ts @@ -0,0 +1,19 @@ +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'; + +export const mergeFlatFieldMetadatasInFlatObjectMetadata = ({ + flatFieldMetadatas, + flatObjectMetadata, +}: { + flatObjectMetadata: FlatObjectMetadata; + flatFieldMetadatas: FlatFieldMetadata[]; +}) => { + return { + ...flatObjectMetadata, + flatFieldMetadatas: mergeTwoFlatFieldMetadatas({ + destFlatFieldMetadatas: flatObjectMetadata.flatFieldMetadatas, + toMergeFlatFieldMetadatas: flatFieldMetadatas, + }), + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type.ts new file mode 100644 index 00000000000..a285496fc89 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type.ts @@ -0,0 +1,6 @@ +import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type'; + +export type FlatObjectMetadataMaps = { + byId: Partial>; + idByNameSingular: Partial>; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type.ts new file mode 100644 index 00000000000..7af7bf77fb6 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type.ts @@ -0,0 +1,11 @@ +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 type FlatFieldMetadataMaps = { + fieldsById: Partial>; + fieldIdByJoinColumnName: Partial>; + fieldIdByName: Partial>; +}; + +export type FlatObjectMetadataWithFlatFieldMaps = FlatObjectMetadata & + FlatFieldMetadataMaps; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-field-metadata-to-flat-field-metadata-maps.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-field-metadata-to-flat-field-metadata-maps.util.ts new file mode 100644 index 00000000000..e12304699d6 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-field-metadata-to-flat-field-metadata-maps.util.ts @@ -0,0 +1,42 @@ +import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type'; +import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; + +type FromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMapsArgs = + { + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; + objectMetadataMaps: ObjectMetadataMaps; + }; +export const fromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMaps = + ({ + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + }: FromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMapsArgs): FlatObjectMetadataWithFlatFieldMaps => { + const flatObjectMetadata = + fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({ + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + }); + + const initialAccumulator: Record = {}; + + const fieldsById = flatObjectMetadata.flatFieldMetadatas.reduce( + (acc, flatFieldMetadata) => ({ + ...acc, + [flatFieldMetadata.id]: flatFieldMetadata, + }), + initialAccumulator, + ); + + const { fieldIdByJoinColumnName, fieldIdByName } = + objectMetadataItemWithFieldMaps; + + return { + ...flatObjectMetadata, + fieldsById, + fieldIdByJoinColumnName, + fieldIdByName, + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts new file mode 100644 index 00000000000..d8ed28b987b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts @@ -0,0 +1,30 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { fromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-field-metadata-to-flat-field-metadata-maps.util'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; + +export const fromObjectMetadataMapsToFlatObjectMetadataMaps = ( + objectMetadataMaps: ObjectMetadataMaps, +): FlatObjectMetadataMaps => { + const initialAccumulator: FlatObjectMetadataMaps['byId'] = {}; + + const byId = Object.values(objectMetadataMaps.byId) + .filter(isDefined) + .reduce((acc, objectMetadataItemWithFieldMaps) => { + const { id: objectMetadataId } = objectMetadataItemWithFieldMaps; + + return { + ...acc, + [objectMetadataId]: + fromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMaps( + { objectMetadataItemWithFieldMaps, objectMetadataMaps }, + ), + }; + }, initialAccumulator); + + return { + idByNameSingular: objectMetadataMaps.idByNameSingular, + byId, + }; +}; 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 index e18a9405771..6c75860e15b 100644 --- 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 @@ -1,7 +1,7 @@ -import { isDefined } from 'twenty-shared/utils'; +import { fromArrayToValuesByKeyRecord, 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 { mergeFlatFieldMetadatasInFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-flat-object-metadata.util'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; type DispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatasArgs = { @@ -12,23 +12,15 @@ export const dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas = ({ flatFieldMetadatas, flatObjectMetadatas, }: DispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatasArgs): FlatObjectMetadata[] => { - const initialAccumulator: Record = {}; + if (flatFieldMetadatas.length === 0) { + return flatObjectMetadatas; + } + 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); + fromArrayToValuesByKeyRecord({ + array: flatFieldMetadatas, + key: 'objectMetadataId', + }); return flatObjectMetadatas.map((flatObjectMetadata) => { const toMergeFlatFieldMetadatas = @@ -38,12 +30,9 @@ export const dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas = ({ return flatObjectMetadata; } - return { - ...flatObjectMetadata, - flatFieldMetadatas: mergeTwoFlatFieldMetadatas({ - destFlatFieldMetadatas: flatObjectMetadata.flatFieldMetadatas, - toMergeFlatFieldMetadatas, - }), - }; + return mergeFlatFieldMetadatasInFlatObjectMetadata({ + flatFieldMetadatas: toMergeFlatFieldMetadatas, + flatObjectMetadata, + }); }); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util.ts new file mode 100644 index 00000000000..fd5370d9692 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util.ts @@ -0,0 +1,14 @@ +import { removePropertiesFromRecord } from 'twenty-shared/utils'; + +import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type'; +import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; + +export const fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata = ( + flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps, +): FlatObjectMetadata => { + return removePropertiesFromRecord(flatObjectMetadataWithFlatFieldMaps, [ + 'fieldsById', + 'fieldIdByName', + 'fieldIdByJoinColumnName', + ]); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts index e296a0831f5..f7abdc263b5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util.ts @@ -3,30 +3,16 @@ import { isDefined } from 'twenty-shared/utils'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; -import { - WorkspaceMetadataCacheException, - WorkspaceMetadataCacheExceptionCode, -} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; export const fromObjectMetadataMapsToFlatObjectMetadatas = ( objectMetadataMaps: ObjectMetadataMaps, ): FlatObjectMetadata[] => { - const objectMetadataIds = Object.keys(objectMetadataMaps.byId); - - return objectMetadataIds.flatMap((objectMetadataId) => { - const objectMetadataItemWithFieldMaps = - objectMetadataMaps.byId[objectMetadataId]; - - if (!isDefined(objectMetadataItemWithFieldMaps)) { - throw new WorkspaceMetadataCacheException( - 'Object metadata not found in cache', - WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND, - ); - } - - return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({ - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - }); - }); + return Object.values(objectMetadataMaps.byId) + .filter(isDefined) + .map((objectMetadataItemWithFieldMaps) => + fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({ + objectMetadataItemWithFieldMaps, + objectMetadataMaps, + }), + ); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts index 44b47a65f19..5683d072afc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts @@ -5,6 +5,8 @@ import { isDefined } from 'twenty-shared/utils'; import { In, Repository } from 'typeorm'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { fromObjectMetadataMapsToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; @@ -20,6 +22,11 @@ type GetExistingOrRecomputeMetadataMapsResult = { metadataVersion: number; }; +type GetExistingOrRecomputeFlatObjectMetadataMapsResult = { + flatObjectMetadataMaps: FlatObjectMetadataMaps; + metadataVersion: number; +}; + @Injectable() export class WorkspaceMetadataCacheService { logger = new Logger(WorkspaceMetadataCacheService.name); @@ -34,6 +41,23 @@ export class WorkspaceMetadataCacheService { private readonly indexMetadataRepository: Repository, ) {} + async getExistingOrRecomputeFlatObjectMetadataMaps({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + const { objectMetadataMaps, metadataVersion } = + await this.getExistingOrRecomputeMetadataMaps({ + workspaceId, + }); + + return { + flatObjectMetadataMaps: + fromObjectMetadataMapsToFlatObjectMetadataMaps(objectMetadataMaps), + metadataVersion, + }; + } + async getExistingOrRecomputeMetadataMaps({ workspaceId, }: { diff --git a/packages/twenty-shared/src/utils/fromArrayToValuesByKeyRecord.util.ts b/packages/twenty-shared/src/utils/fromArrayToValuesByKeyRecord.util.ts new file mode 100644 index 00000000000..cf8fb3808fb --- /dev/null +++ b/packages/twenty-shared/src/utils/fromArrayToValuesByKeyRecord.util.ts @@ -0,0 +1,27 @@ +import { StringPropertyKeys } from '@/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties'; +import { isDefined } from '@/utils/validation'; + +export const fromArrayToValuesByKeyRecord = ({ + array, + key, +}: { + array: T[]; + key: StringPropertyKeys; +}) => { + return array.reduce>((acc, value) => { + const computedKey = value[key] as string; + const occurrence = acc[computedKey]; + + if (isDefined(occurrence)) { + return { + ...acc, + [computedKey]: [...occurrence, value], + }; + } + + return { + ...acc, + [computedKey]: [value], + }; + }, {}); +}; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index 1bd9e519651..431c0a8be10 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -12,6 +12,7 @@ export { deepMerge } from './deepMerge'; export { extractAndSanitizeObjectStringFields } from './extractAndSanitizeObjectStringFields'; export { isFieldMetadataDateKind } from './fieldMetadata/isFieldMetadataDateKind'; export { fromArrayToUniqueKeyRecord } from './from-array-to-unique-key-record.util'; +export { fromArrayToValuesByKeyRecord } from './fromArrayToValuesByKeyRecord.util'; export { getURLSafely } from './getURLSafely'; export { getImageAbsoluteURI } from './image/getImageAbsoluteURI'; export {