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
This commit is contained in:
Paul Rastoin
2025-09-09 12:47:07 +02:00
committed by GitHub
parent ef68236735
commit 861cb1c222
3 changed files with 84 additions and 67 deletions
@@ -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<FieldMetadataEntity>((fieldMetadata) => {
const overridesFieldToCompute = [
'icon',
'label',
'description',
] as const satisfies (keyof FieldMetadataEntity)[];
const overrides = overridesFieldToCompute.reduce<
Partial<Record<(typeof overridesFieldToCompute)[number], string>>
>(
(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<FieldMetadataEntity>(
overriddenFieldMetadataEntities,
);
filterMorphRelationDuplicateFields(overriddenFieldMetadataEntities);
return filteredFieldMetadataEntities.map(
fromFieldMetadataEntityToFieldMetadataDto,
fromFlatFieldMetadataToFieldMetadataDto,
);
});
@@ -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<FieldMetadataType.MORPH_RELATION>[];
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];
};
@@ -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);