From 861cb1c222fb0a20b0caa5d33e9096bf7211af6e Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:47:07 +0200 Subject: [PATCH] fix(server): morph relation fields filtering (#14365) Tested: object metadata items retrieval and standard overrides Please be sure to cache flush before testing this for the data loader to be hit --- .../engine/dataloaders/dataloader.service.ts | 83 ++++++++----------- ...er-morph-relation-duplicate-fields.util.ts | 65 +++++++++++---- .../utils/transpile-to-date-if-not.util.ts | 3 - 3 files changed, 84 insertions(+), 67 deletions(-) delete mode 100644 packages/twenty-server/src/utils/transpile-to-date-if-not.util.ts diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts index 704ac075b6..7ff87df1e0 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts @@ -10,12 +10,13 @@ import { type IndexMetadataInterface } from 'src/engine/metadata-modules/index-m import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { filterMorphRelationDuplicateFields } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util'; +import { FIELD_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/field-metadata/constants/field-metadata-standard-overrides-properties.constant'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util'; import { resolveFieldMetadataStandardOverride } from 'src/engine/metadata-modules/field-metadata/utils/resolve-field-metadata-standard-override.util'; import { findAllOthersMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util'; +import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util'; import { fromMorphOrRelationFlatFieldMetadataToRelationDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-morph-or-relation-flat-field-metadata-to-relation-dto.util'; import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; @@ -25,7 +26,6 @@ import { type IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadat import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; -import { transpileToDateIfNot } from 'src/utils/transpile-to-date-if-not.util'; export type RelationMetadataLoaderPayload = { workspaceId: string; @@ -138,11 +138,7 @@ export class DataloaderService { flatFieldMetadata: sourceFlatFieldMetadata.flatRelationTargetFieldMetadata, flatObjectMetadataMaps: existingFlatObjectMetadataMaps, - }).sort( - (a, b) => - transpileToDateIfNot(a.createdAt).getTime() - - transpileToDateIfNot(b.createdAt).getTime(), - ); + }).sort((a, b) => (a.id > b.id ? 1 : -1)); relationDtos.push( fromMorphOrRelationFlatFieldMetadataToRelationDto({ @@ -283,62 +279,49 @@ export class DataloaderService { (dataLoaderParam) => dataLoaderParam.objectMetadata.id, ); - const { objectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps( + const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = + await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( { workspaceId }, ); const fieldMetadataCollection = objectMetadataIds.map((id) => { - const objectMetadata = objectMetadataMaps.byId[id]; + const flatObjectMetadataWithFlatFieldMaps = + existingFlatObjectMetadataMaps.byId[id]; - if (!isDefined(objectMetadata)) { + if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) { return []; } - const overriddenFieldMetadataEntities = Object.values( - objectMetadata.fieldsById, - ).map((fieldMetadata) => { - const overridesFieldToCompute = [ - 'icon', - 'label', - 'description', - ] as const satisfies (keyof FieldMetadataEntity)[]; - - const overrides = overridesFieldToCompute.reduce< - Partial> - >( - (acc, field) => ({ - ...acc, - [field]: resolveFieldMetadataStandardOverride( - { - label: fieldMetadata.label, - description: fieldMetadata.description ?? undefined, - icon: fieldMetadata.icon ?? undefined, - isCustom: fieldMetadata.isCustom, - standardOverrides: - fieldMetadata.standardOverrides ?? undefined, - }, - field, - dataLoaderParams[0].locale, - i18nInstance, - ), - }), - {}, + const overriddenFieldMetadataEntities = + flatObjectMetadataWithFlatFieldMaps.flatFieldMetadatas.map( + (flatFieldMetadata) => { + return FIELD_METADATA_STANDARD_OVERRIDES_PROPERTIES.reduce( + (acc, property) => ({ + ...acc, + [property]: resolveFieldMetadataStandardOverride( + { + label: flatFieldMetadata.label, + description: flatFieldMetadata.description ?? undefined, + icon: flatFieldMetadata.icon ?? undefined, + isCustom: flatFieldMetadata.isCustom, + standardOverrides: + flatFieldMetadata.standardOverrides ?? undefined, + }, + property, + dataLoaderParams[0].locale, + i18nInstance, + ), + }), + flatFieldMetadata, + ); + }, ); - return { - ...fieldMetadata, - ...overrides, - }; - }); - const filteredFieldMetadataEntities = - filterMorphRelationDuplicateFields( - overriddenFieldMetadataEntities, - ); + filterMorphRelationDuplicateFields(overriddenFieldMetadataEntities); return filteredFieldMetadataEntities.map( - fromFieldMetadataEntityToFieldMetadataDto, + fromFlatFieldMetadataToFieldMetadataDto, ); }); diff --git a/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts b/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts index 199091f623..8208303ef1 100644 --- a/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts +++ b/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts @@ -1,16 +1,53 @@ -import { transpileToDateIfNot } from 'src/utils/transpile-to-date-if-not.util'; +import { FieldMetadataType } from 'twenty-shared/types'; -export const filterMorphRelationDuplicateFields = < - T extends { createdAt: Date; morphId: string | null }, ->( - fields: T[], -) => { - return fields.filter((currentField) => { - return !fields.some( - (otherField) => - otherField.morphId === currentField.morphId && - transpileToDateIfNot(otherField.createdAt).getTime() > - transpileToDateIfNot(currentField.createdAt).getTime(), - ); - }); +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; + +export const filterMorphRelationDuplicateFields = ( + flatFieldMetadatas: FlatFieldMetadata[], +): FlatFieldMetadata[] => { + const initialAccumulator: { + morphFlatFieldMetadatas: FlatFieldMetadata[]; + otherFlatFieldMetadatas: FlatFieldMetadata[]; + } = { + morphFlatFieldMetadatas: [], + otherFlatFieldMetadatas: [], + }; + const { morphFlatFieldMetadatas, otherFlatFieldMetadatas } = + flatFieldMetadatas.reduce((acc, flatFieldMetadata) => { + if ( + isFlatFieldMetadataOfType( + flatFieldMetadata, + FieldMetadataType.MORPH_RELATION, + ) + ) { + return { + ...acc, + morphFlatFieldMetadatas: [ + ...acc.morphFlatFieldMetadatas, + flatFieldMetadata, + ], + }; + } + + return { + ...acc, + otherFlatFieldMetadatas: [ + ...acc.otherFlatFieldMetadatas, + flatFieldMetadata, + ], + }; + }, initialAccumulator); + + const filteredMorphFlatFieldMetadatas = morphFlatFieldMetadatas.filter( + (currentField) => + !morphFlatFieldMetadatas.some( + (otherField) => + currentField.id !== otherField.id && + otherField.morphId === currentField.morphId && + otherField.id < currentField.id, + ), + ); + + return [...otherFlatFieldMetadatas, ...filteredMorphFlatFieldMetadatas]; }; diff --git a/packages/twenty-server/src/utils/transpile-to-date-if-not.util.ts b/packages/twenty-server/src/utils/transpile-to-date-if-not.util.ts deleted file mode 100644 index 215204c20a..0000000000 --- a/packages/twenty-server/src/utils/transpile-to-date-if-not.util.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Temporary quick workaround until we eject from nestjs-query that seems to convert date to string and vice-versa -export const transpileToDateIfNot = (dateToVerify: string | Date): Date => - dateToVerify instanceof Date ? dateToVerify : new Date(dateToVerify);