diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-9/2-9-workspace-command-1799000030000-sync-call-recording-standard-objects.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-9/2-9-workspace-command-1799000030000-sync-call-recording-standard-objects.command.ts index 091f0a1e8a4..fc4a1abf54e 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-9/2-9-workspace-command-1799000030000-sync-call-recording-standard-objects.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-9/2-9-workspace-command-1799000030000-sync-call-recording-standard-objects.command.ts @@ -246,9 +246,11 @@ export class SyncCallRecordingStandardObjectsCommand extends ActiveOrSuspendedWo flatEntity: { ...staleNavigationCommandMenuItem, conditionalAvailabilityExpression: - buildNavigationConditionalAvailabilityExpression( - renamedCollisionObjectMetadata.nameSingular, - ), + buildNavigationConditionalAvailabilityExpression({ + universalIdentifier: + renamedCollisionObjectMetadata.universalIdentifier, + nameSingular: renamedCollisionObjectMetadata.nameSingular, + }), updatedAt: now, }, flatEntityMaps: toMaps, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/__tests__/build-navigation-flat-command-menu-item.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/__tests__/build-navigation-flat-command-menu-item.util.spec.ts index b8949a038fe..c6d9da6ab47 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/__tests__/build-navigation-flat-command-menu-item.util.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/__tests__/build-navigation-flat-command-menu-item.util.spec.ts @@ -1,3 +1,4 @@ +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { v5 } from 'uuid'; import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum'; @@ -118,7 +119,11 @@ describe('buildNavigationFlatCommandMenuItem', () => { it('should additionally gate conditionalAvailabilityExpression behind the feature flag for feature-flagged objects', () => { const result = buildNavigationFlatCommandMenuItem({ ...baseArgs, - objectMetadata: { ...baseObjectMetadata, nameSingular: 'callRecording' }, + objectMetadata: { + ...baseObjectMetadata, + universalIdentifier: STANDARD_OBJECTS.callRecording.universalIdentifier, + nameSingular: 'callRecording', + }, }); expect(result.conditionalAvailabilityExpression).toBe( @@ -151,23 +156,32 @@ describe('buildNavigationFlatCommandMenuItem', () => { }); describe('buildNavigationConditionalAvailabilityExpression', () => { - it('gates feature-flagged objects behind both the flag and read permission', () => { + it('gates the standard call recording object behind both the flag and read permission', () => { expect( - buildNavigationConditionalAvailabilityExpression('callRecording'), + buildNavigationConditionalAvailabilityExpression({ + universalIdentifier: STANDARD_OBJECTS.callRecording.universalIdentifier, + nameSingular: 'callRecording', + }), ).toBe( 'featureFlags.IS_CALL_RECORDING_ENABLED and targetObjectReadPermissions.callRecording', ); }); it('returns only the read-permission expression for non-gated objects', () => { - expect(buildNavigationConditionalAvailabilityExpression('person')).toBe( - 'targetObjectReadPermissions.person', - ); + expect( + buildNavigationConditionalAvailabilityExpression({ + universalIdentifier: 'obj-universal-1', + nameSingular: 'person', + }), + ).toBe('targetObjectReadPermissions.person'); }); - it('does not gate a collision-renamed callRecordingOld object', () => { + it('does not gate a custom object that reuses the callRecording name', () => { expect( - buildNavigationConditionalAvailabilityExpression('callRecordingOld2'), - ).toBe('targetObjectReadPermissions.callRecordingOld2'); + buildNavigationConditionalAvailabilityExpression({ + universalIdentifier: 'custom-object-universal-id', + nameSingular: 'callRecording', + }), + ).toBe('targetObjectReadPermissions.callRecording'); }); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util.ts index bf1d3618ef2..3521ce21e54 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util.ts @@ -1,3 +1,4 @@ +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { FeatureFlagKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v5 } from 'uuid'; @@ -17,20 +18,27 @@ export const NAVIGATION_INTERPOLATED_SHORT_LABEL = export const NAVIGATION_INTERPOLATED_ICON = '${navigateToObjectMetadataItem.icon}'; -const NAVIGATION_FEATURE_FLAG_GATE_BY_OBJECT_NAME_SINGULAR: Partial< +const NAVIGATION_FEATURE_FLAG_GATE_BY_OBJECT_UNIVERSAL_IDENTIFIER: Partial< Record > = { - callRecording: FeatureFlagKey.IS_CALL_RECORDING_ENABLED, - callRecordingCalendarEventAssociation: + [STANDARD_OBJECTS.callRecording.universalIdentifier]: + FeatureFlagKey.IS_CALL_RECORDING_ENABLED, + [STANDARD_OBJECTS.callRecordingCalendarEventAssociation.universalIdentifier]: FeatureFlagKey.IS_CALL_RECORDING_ENABLED, }; -export const buildNavigationConditionalAvailabilityExpression = ( - nameSingular: string, -): string => { +export const buildNavigationConditionalAvailabilityExpression = ({ + universalIdentifier, + nameSingular, +}: { + universalIdentifier: string; + nameSingular: string; +}): string => { const targetObjectReadPermissionExpression = `targetObjectReadPermissions.${nameSingular}`; const featureFlagGate = - NAVIGATION_FEATURE_FLAG_GATE_BY_OBJECT_NAME_SINGULAR[nameSingular]; + NAVIGATION_FEATURE_FLAG_GATE_BY_OBJECT_UNIVERSAL_IDENTIFIER[ + universalIdentifier + ]; return isDefined(featureFlagGate) ? `featureFlags.${featureFlagGate} and ${targetObjectReadPermissionExpression}` @@ -63,9 +71,10 @@ export const buildNavigationFlatCommandMenuItem = ({ ); const conditionalAvailabilityExpression = - buildNavigationConditionalAvailabilityExpression( - objectMetadata.nameSingular, - ); + buildNavigationConditionalAvailabilityExpression({ + universalIdentifier: objectMetadata.universalIdentifier, + nameSingular: objectMetadata.nameSingular, + }); return { id: commandMenuItemId,