fix: restore isCustom gate in metadata label resolvers (#21432)
## Context #21228 removed the stored `isCustom` column and, with it, the `isCustom` early-return in `resolveObjectMetadataStandardOverride` / `resolveFieldMetadataStandardOverride`, on the assumption that falling through the `standardOverrides` checks was equivalent. It isn't: custom object/field labels now reach the Lingui lookup. A custom label that collides with a standard catalog string (e.g. a custom field labeled "Status") gets translated for non-English locales against the user's intent, and every other custom label pays a hash + catalog miss — and, in production, an "Uncompiled message detected" warning (#21415) — on each metadata resolution. ## Fix Restore the gate. `isCustom` is no longer stored, so call sites that build the resolver input from flat entities (dataloader, minimal-metadata, view controller, command-menu-item navigation context) derive it via `belongsToTwentyStandardApp`; GraphQL resolvers keep passing DTOs, which already carry the derived value. ## Testing - Unit tests for both resolvers, including a new regression test: a custom label matching a standard catalog entry is returned verbatim, Lingui never called. --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-m
|
|||||||
import { findManyFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps.util';
|
import { findManyFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps.util';
|
||||||
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||||
import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util';
|
import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util';
|
||||||
|
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
|
||||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||||
import { resolveMorphRelationsFromFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/resolve-morph-relations-from-flat-field-metadata.util';
|
import { resolveMorphRelationsFromFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/resolve-morph-relations-from-flat-field-metadata.util';
|
||||||
import { resolveRelationFromFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/resolve-relation-from-flat-field-metadata.util';
|
import { resolveRelationFromFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/resolve-relation-from-flat-field-metadata.util';
|
||||||
@@ -336,6 +337,8 @@ export class DataloaderService {
|
|||||||
label: flatFieldMetadata.label,
|
label: flatFieldMetadata.label,
|
||||||
description: flatFieldMetadata.description ?? undefined,
|
description: flatFieldMetadata.description ?? undefined,
|
||||||
icon: flatFieldMetadata.icon ?? undefined,
|
icon: flatFieldMetadata.icon ?? undefined,
|
||||||
|
isCustom:
|
||||||
|
!belongsToTwentyStandardApp(flatFieldMetadata),
|
||||||
standardOverrides:
|
standardOverrides:
|
||||||
flatFieldMetadata.standardOverrides ?? undefined,
|
flatFieldMetadata.standardOverrides ?? undefined,
|
||||||
},
|
},
|
||||||
|
|||||||
+2
@@ -9,6 +9,7 @@ export type NavigationInterpolationObjectMetadata = {
|
|||||||
labelSingular: string;
|
labelSingular: string;
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
icon?: string | null;
|
icon?: string | null;
|
||||||
|
isCustom: boolean;
|
||||||
standardOverrides?: ObjectStandardOverridesDTO | null;
|
standardOverrides?: ObjectStandardOverridesDTO | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export const buildNavigationInterpolationContext = ({
|
|||||||
labelSingular: objectMetadata.labelSingular,
|
labelSingular: objectMetadata.labelSingular,
|
||||||
description: objectMetadata.description ?? undefined,
|
description: objectMetadata.description ?? undefined,
|
||||||
icon: objectMetadata.icon ?? undefined,
|
icon: objectMetadata.icon ?? undefined,
|
||||||
|
isCustom: objectMetadata.isCustom,
|
||||||
standardOverrides: objectMetadata.standardOverrides ?? undefined,
|
standardOverrides: objectMetadata.standardOverrides ?? undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+27
@@ -26,6 +26,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
|
|||||||
label: 'Custom Label',
|
label: 'Custom Label',
|
||||||
description: 'Custom Description',
|
description: 'Custom Description',
|
||||||
icon: 'custom-icon',
|
icon: 'custom-icon',
|
||||||
|
isCustom: true,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,11 +40,36 @@ describe('resolveFieldMetadataStandardOverride', () => {
|
|||||||
expect(result).toBe('Custom Label');
|
expect(result).toBe('Custom Label');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should never translate a custom label even when it matches a standard catalog entry', () => {
|
||||||
|
const fieldMetadata = {
|
||||||
|
label: 'Status',
|
||||||
|
description: 'Custom Description',
|
||||||
|
icon: 'custom-icon',
|
||||||
|
isCustom: true,
|
||||||
|
standardOverrides: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockGenerateMessageId.mockReturnValue('status.message.id');
|
||||||
|
mockI18n._.mockReturnValue('Statut');
|
||||||
|
|
||||||
|
const result = resolveFieldMetadataStandardOverride(
|
||||||
|
fieldMetadata,
|
||||||
|
'label',
|
||||||
|
'fr-FR',
|
||||||
|
mockI18n,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe('Status');
|
||||||
|
expect(mockGenerateMessageId).not.toHaveBeenCalled();
|
||||||
|
expect(mockI18n._).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('should return the field value for custom description field', () => {
|
it('should return the field value for custom description field', () => {
|
||||||
const fieldMetadata = {
|
const fieldMetadata = {
|
||||||
label: 'Custom Label',
|
label: 'Custom Label',
|
||||||
description: 'Custom Description',
|
description: 'Custom Description',
|
||||||
icon: 'custom-icon',
|
icon: 'custom-icon',
|
||||||
|
isCustom: true,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +88,7 @@ describe('resolveFieldMetadataStandardOverride', () => {
|
|||||||
label: 'Custom Label',
|
label: 'Custom Label',
|
||||||
description: 'Custom Description',
|
description: 'Custom Description',
|
||||||
icon: 'custom-icon',
|
icon: 'custom-icon',
|
||||||
|
isCustom: true,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -10,7 +10,7 @@ import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadat
|
|||||||
export const resolveFieldMetadataStandardOverride = (
|
export const resolveFieldMetadataStandardOverride = (
|
||||||
fieldMetadata: Pick<
|
fieldMetadata: Pick<
|
||||||
FieldMetadataDTO,
|
FieldMetadataDTO,
|
||||||
'label' | 'description' | 'icon' | 'standardOverrides'
|
'label' | 'description' | 'icon' | 'isCustom' | 'standardOverrides'
|
||||||
>,
|
>,
|
||||||
labelKey: 'label' | 'description' | 'icon',
|
labelKey: 'label' | 'description' | 'icon',
|
||||||
locale: keyof typeof APP_LOCALES | undefined,
|
locale: keyof typeof APP_LOCALES | undefined,
|
||||||
@@ -18,6 +18,13 @@ export const resolveFieldMetadataStandardOverride = (
|
|||||||
): string => {
|
): string => {
|
||||||
const safeLocale = locale ?? SOURCE_LOCALE;
|
const safeLocale = locale ?? SOURCE_LOCALE;
|
||||||
|
|
||||||
|
// Custom field labels are user-authored: never overridden nor translated.
|
||||||
|
// Without this gate, a label colliding with a standard catalog string
|
||||||
|
// (e.g. "Status") would get translated against the user's intent.
|
||||||
|
if (fieldMetadata.isCustom) {
|
||||||
|
return fieldMetadata[labelKey] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
if (labelKey === 'icon' && isDefined(fieldMetadata.standardOverrides?.icon)) {
|
if (labelKey === 'icon' && isDefined(fieldMetadata.standardOverrides?.icon)) {
|
||||||
return fieldMetadata.standardOverrides.icon;
|
return fieldMetadata.standardOverrides.icon;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -78,12 +78,15 @@ export class MinimalMetadataService {
|
|||||||
.filter(isDefined)
|
.filter(isDefined)
|
||||||
.filter((flatObjectMetadata) => flatObjectMetadata.isActive === true)
|
.filter((flatObjectMetadata) => flatObjectMetadata.isActive === true)
|
||||||
.map((flatObjectMetadata) => {
|
.map((flatObjectMetadata) => {
|
||||||
|
const isCustom = !belongsToTwentyStandardApp(flatObjectMetadata);
|
||||||
|
|
||||||
const objectMetadataForOverride = {
|
const objectMetadataForOverride = {
|
||||||
labelPlural: flatObjectMetadata.labelPlural,
|
labelPlural: flatObjectMetadata.labelPlural,
|
||||||
labelSingular: flatObjectMetadata.labelSingular,
|
labelSingular: flatObjectMetadata.labelSingular,
|
||||||
description: flatObjectMetadata.description ?? undefined,
|
description: flatObjectMetadata.description ?? undefined,
|
||||||
icon: flatObjectMetadata.icon ?? undefined,
|
icon: flatObjectMetadata.icon ?? undefined,
|
||||||
color: flatObjectMetadata.color ?? undefined,
|
color: flatObjectMetadata.color ?? undefined,
|
||||||
|
isCustom,
|
||||||
standardOverrides: flatObjectMetadata.standardOverrides ?? undefined,
|
standardOverrides: flatObjectMetadata.standardOverrides ?? undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,7 +107,7 @@ export class MinimalMetadataService {
|
|||||||
i18nInstance,
|
i18nInstance,
|
||||||
),
|
),
|
||||||
icon: flatObjectMetadata.icon ?? undefined,
|
icon: flatObjectMetadata.icon ?? undefined,
|
||||||
isCustom: !belongsToTwentyStandardApp(flatObjectMetadata),
|
isCustom,
|
||||||
isActive: flatObjectMetadata.isActive,
|
isActive: flatObjectMetadata.isActive,
|
||||||
isSystem: flatObjectMetadata.isSystem,
|
isSystem: flatObjectMetadata.isSystem,
|
||||||
isRemote: flatObjectMetadata.isRemote,
|
isRemote: flatObjectMetadata.isRemote,
|
||||||
|
|||||||
+63
-3
@@ -29,6 +29,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
description: 'Custom Description',
|
description: 'Custom Description',
|
||||||
icon: 'custom-icon',
|
icon: 'custom-icon',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
|
isCustom: true,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
} satisfies Pick<
|
} satisfies Pick<
|
||||||
ObjectMetadataDTO,
|
ObjectMetadataDTO,
|
||||||
@@ -37,6 +38,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
| 'labelSingular'
|
| 'labelSingular'
|
||||||
| 'description'
|
| 'description'
|
||||||
| 'icon'
|
| 'icon'
|
||||||
|
| 'isCustom'
|
||||||
| 'standardOverrides'
|
| 'standardOverrides'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -50,13 +52,14 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
expect(result).toBe('My Custom');
|
expect(result).toBe('My Custom');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the object value for custom description object', () => {
|
it('should never translate a custom label even when it matches a standard catalog entry', () => {
|
||||||
const objectMetadata = {
|
const objectMetadata = {
|
||||||
labelSingular: 'My Custom',
|
labelSingular: 'Company',
|
||||||
labelPlural: 'My Customs',
|
labelPlural: 'Companies',
|
||||||
description: 'Custom Description',
|
description: 'Custom Description',
|
||||||
icon: 'custom-icon',
|
icon: 'custom-icon',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
|
isCustom: true,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
} satisfies Pick<
|
} satisfies Pick<
|
||||||
ObjectMetadataDTO,
|
ObjectMetadataDTO,
|
||||||
@@ -65,6 +68,42 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
| 'labelSingular'
|
| 'labelSingular'
|
||||||
| 'description'
|
| 'description'
|
||||||
| 'icon'
|
| 'icon'
|
||||||
|
| 'isCustom'
|
||||||
|
| 'standardOverrides'
|
||||||
|
>;
|
||||||
|
|
||||||
|
mockGenerateMessageId.mockReturnValue('company.message.id');
|
||||||
|
mockI18n._.mockReturnValue('Entreprise');
|
||||||
|
|
||||||
|
const result = resolveObjectMetadataStandardOverride(
|
||||||
|
objectMetadata,
|
||||||
|
'labelSingular',
|
||||||
|
'fr-FR',
|
||||||
|
mockI18n,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe('Company');
|
||||||
|
expect(mockGenerateMessageId).not.toHaveBeenCalled();
|
||||||
|
expect(mockI18n._).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the object value for custom description object', () => {
|
||||||
|
const objectMetadata = {
|
||||||
|
labelSingular: 'My Custom',
|
||||||
|
labelPlural: 'My Customs',
|
||||||
|
description: 'Custom Description',
|
||||||
|
icon: 'custom-icon',
|
||||||
|
color: 'blue',
|
||||||
|
isCustom: true,
|
||||||
|
standardOverrides: undefined,
|
||||||
|
} satisfies Pick<
|
||||||
|
ObjectMetadataDTO,
|
||||||
|
| 'color'
|
||||||
|
| 'labelPlural'
|
||||||
|
| 'labelSingular'
|
||||||
|
| 'description'
|
||||||
|
| 'icon'
|
||||||
|
| 'isCustom'
|
||||||
| 'standardOverrides'
|
| 'standardOverrides'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -85,6 +124,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
description: 'Custom Description',
|
description: 'Custom Description',
|
||||||
icon: 'custom-icon',
|
icon: 'custom-icon',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
|
isCustom: true,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
} satisfies Pick<
|
} satisfies Pick<
|
||||||
ObjectMetadataDTO,
|
ObjectMetadataDTO,
|
||||||
@@ -93,6 +133,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
| 'labelSingular'
|
| 'labelSingular'
|
||||||
| 'description'
|
| 'description'
|
||||||
| 'icon'
|
| 'icon'
|
||||||
|
| 'isCustom'
|
||||||
| 'standardOverrides'
|
| 'standardOverrides'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -113,6 +154,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
description: 'Custom Description',
|
description: 'Custom Description',
|
||||||
icon: 'custom-icon',
|
icon: 'custom-icon',
|
||||||
color: 'green',
|
color: 'green',
|
||||||
|
isCustom: true,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
} satisfies Pick<
|
} satisfies Pick<
|
||||||
ObjectMetadataDTO,
|
ObjectMetadataDTO,
|
||||||
@@ -121,6 +163,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
| 'labelSingular'
|
| 'labelSingular'
|
||||||
| 'description'
|
| 'description'
|
||||||
| 'icon'
|
| 'icon'
|
||||||
|
| 'isCustom'
|
||||||
| 'standardOverrides'
|
| 'standardOverrides'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -142,6 +185,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'My Customs',
|
labelPlural: 'My Customs',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
icon: 'override-icon',
|
icon: 'override-icon',
|
||||||
},
|
},
|
||||||
@@ -166,6 +210,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
color: 'red',
|
color: 'red',
|
||||||
},
|
},
|
||||||
@@ -188,6 +233,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -212,6 +258,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
translations: {
|
translations: {
|
||||||
'fr-FR': {
|
'fr-FR': {
|
||||||
@@ -255,6 +302,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
translations: {
|
translations: {
|
||||||
'es-ES': {
|
'es-ES': {
|
||||||
@@ -285,6 +333,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
translations: {
|
translations: {
|
||||||
'fr-FR': {
|
'fr-FR': {
|
||||||
@@ -314,6 +363,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
translations: {
|
translations: {
|
||||||
'fr-FR': {
|
'fr-FR': {
|
||||||
@@ -346,6 +396,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
labelSingular: 'Overridden Label',
|
labelSingular: 'Overridden Label',
|
||||||
labelPlural: 'Overridden Labels',
|
labelPlural: 'Overridden Labels',
|
||||||
@@ -394,6 +445,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
labelSingular: 'Overridden Label',
|
labelSingular: 'Overridden Label',
|
||||||
labelPlural: 'Overridden Labels',
|
labelPlural: 'Overridden Labels',
|
||||||
@@ -416,6 +468,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
labelSingular: undefined,
|
labelSingular: undefined,
|
||||||
},
|
},
|
||||||
@@ -442,6 +495,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -466,6 +520,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -492,6 +547,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
labelSingular: 'Source Override',
|
labelSingular: 'Source Override',
|
||||||
labelPlural: 'Source Overrides',
|
labelPlural: 'Source Overrides',
|
||||||
@@ -522,6 +578,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
labelSingular: 'Source Override',
|
labelSingular: 'Source Override',
|
||||||
labelPlural: 'Source Overrides',
|
labelPlural: 'Source Overrides',
|
||||||
@@ -546,6 +603,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {},
|
standardOverrides: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -572,6 +630,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: {
|
standardOverrides: {
|
||||||
labelSingular: 'Source Override',
|
labelSingular: 'Source Override',
|
||||||
},
|
},
|
||||||
@@ -598,6 +657,7 @@ describe('resolveObjectMetadataStandardOverride', () => {
|
|||||||
labelPlural: 'Standard Labels',
|
labelPlural: 'Standard Labels',
|
||||||
description: 'Standard Description',
|
description: 'Standard Description',
|
||||||
icon: 'default-icon',
|
icon: 'default-icon',
|
||||||
|
isCustom: false,
|
||||||
standardOverrides: undefined,
|
standardOverrides: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+8
@@ -14,6 +14,7 @@ export const resolveObjectMetadataStandardOverride = (
|
|||||||
| 'labelSingular'
|
| 'labelSingular'
|
||||||
| 'description'
|
| 'description'
|
||||||
| 'icon'
|
| 'icon'
|
||||||
|
| 'isCustom'
|
||||||
| 'standardOverrides'
|
| 'standardOverrides'
|
||||||
>,
|
>,
|
||||||
labelKey: 'color' | 'labelPlural' | 'labelSingular' | 'description' | 'icon',
|
labelKey: 'color' | 'labelPlural' | 'labelSingular' | 'description' | 'icon',
|
||||||
@@ -22,6 +23,13 @@ export const resolveObjectMetadataStandardOverride = (
|
|||||||
): string => {
|
): string => {
|
||||||
const safeLocale = locale ?? SOURCE_LOCALE;
|
const safeLocale = locale ?? SOURCE_LOCALE;
|
||||||
|
|
||||||
|
// Custom object labels are user-authored: never overridden nor translated.
|
||||||
|
// Without this gate, a label colliding with a standard catalog string
|
||||||
|
// (e.g. "Company") would get translated against the user's intent.
|
||||||
|
if (objectMetadata.isCustom) {
|
||||||
|
return objectMetadata[labelKey] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(labelKey === 'icon' || labelKey === 'color') &&
|
(labelKey === 'icon' || labelKey === 'color') &&
|
||||||
isDefined(objectMetadata.standardOverrides?.[labelKey])
|
isDefined(objectMetadata.standardOverrides?.[labelKey])
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
|||||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||||
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
|
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
|
||||||
|
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
|
||||||
import { CreateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard';
|
import { CreateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-permission.guard';
|
||||||
import { DeleteViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard';
|
import { DeleteViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-permission.guard';
|
||||||
import { UpdateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard';
|
import { UpdateViewPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-permission.guard';
|
||||||
@@ -216,6 +217,7 @@ export class ViewController {
|
|||||||
labelSingular: objectMetadata.labelSingular,
|
labelSingular: objectMetadata.labelSingular,
|
||||||
description: objectMetadata.description ?? undefined,
|
description: objectMetadata.description ?? undefined,
|
||||||
icon: objectMetadata.icon ?? undefined,
|
icon: objectMetadata.icon ?? undefined,
|
||||||
|
isCustom: !belongsToTwentyStandardApp(objectMetadata),
|
||||||
standardOverrides: objectMetadata.standardOverrides ?? undefined,
|
standardOverrides: objectMetadata.standardOverrides ?? undefined,
|
||||||
},
|
},
|
||||||
'labelPlural',
|
'labelPlural',
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export class ViewResolver {
|
|||||||
labelSingular: objectMetadata.labelSingular,
|
labelSingular: objectMetadata.labelSingular,
|
||||||
description: objectMetadata.description ?? undefined,
|
description: objectMetadata.description ?? undefined,
|
||||||
icon: objectMetadata.icon ?? undefined,
|
icon: objectMetadata.icon ?? undefined,
|
||||||
|
isCustom: objectMetadata.isCustom,
|
||||||
standardOverrides: objectMetadata.standardOverrides ?? undefined,
|
standardOverrides: objectMetadata.standardOverrides ?? undefined,
|
||||||
},
|
},
|
||||||
'labelPlural',
|
'labelPlural',
|
||||||
|
|||||||
+3
@@ -11,6 +11,7 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
|
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
|
||||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||||
import { enrichCommandMenuItemEventWithResolvedNavigation } from 'src/engine/subscriptions/metadata-event/utils/enrich-command-menu-item-event-with-resolved-navigation.util';
|
import { enrichCommandMenuItemEventWithResolvedNavigation } from 'src/engine/subscriptions/metadata-event/utils/enrich-command-menu-item-event-with-resolved-navigation.util';
|
||||||
|
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||||
|
|
||||||
const mockI18nInstance = {
|
const mockI18nInstance = {
|
||||||
_: (messageId: string) => messageId,
|
_: (messageId: string) => messageId,
|
||||||
@@ -26,6 +27,8 @@ const makeFlatObjectMetadata = (
|
|||||||
universalIdentifier: 'obj-uid-1',
|
universalIdentifier: 'obj-uid-1',
|
||||||
workspaceId: 'ws-1',
|
workspaceId: 'ws-1',
|
||||||
applicationId: 'app-1',
|
applicationId: 'app-1',
|
||||||
|
applicationUniversalIdentifier:
|
||||||
|
TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||||
labelPlural: 'People',
|
labelPlural: 'People',
|
||||||
labelSingular: 'Person',
|
labelSingular: 'Person',
|
||||||
icon: 'IconUser',
|
icon: 'IconUser',
|
||||||
|
|||||||
+5
-1
@@ -12,6 +12,7 @@ import { FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-me
|
|||||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||||
|
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
|
||||||
|
|
||||||
type EnrichCommandMenuItemEventArgs = {
|
type EnrichCommandMenuItemEventArgs = {
|
||||||
record: FlatCommandMenuItem;
|
record: FlatCommandMenuItem;
|
||||||
@@ -48,7 +49,10 @@ export const enrichCommandMenuItemEventWithResolvedNavigation = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const context = buildNavigationInterpolationContext({
|
const context = buildNavigationInterpolationContext({
|
||||||
objectMetadata: flatObjectMetadata,
|
objectMetadata: {
|
||||||
|
...flatObjectMetadata,
|
||||||
|
isCustom: !belongsToTwentyStandardApp(flatObjectMetadata),
|
||||||
|
},
|
||||||
locale,
|
locale,
|
||||||
i18nInstance,
|
i18nInstance,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user