This commit is contained in:
ehconitin
2026-06-03 20:48:07 +05:30
parent 94badfd1c6
commit ff7819a353
3 changed files with 47 additions and 22 deletions
@@ -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,
@@ -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');
});
});
@@ -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<string, FeatureFlagKey>
> = {
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,