[BREAKING CHANGE] remove call recording feature flag and backfill upgrade command for existing command menu items navigation command (#22176)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22176?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -1777,7 +1777,6 @@ enum FeatureFlagKey {
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT
|
||||
IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED
|
||||
IS_SETTINGS_DISCOVERY_HERO_ENABLED
|
||||
IS_CALL_RECORDING_ENABLED
|
||||
IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED
|
||||
}
|
||||
|
||||
|
||||
@@ -1409,7 +1409,7 @@ export interface FeatureFlag {
|
||||
__typename: 'FeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' | 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED' | 'IS_SETTINGS_DISCOVERY_HERO_ENABLED' | 'IS_CALL_RECORDING_ENABLED' | 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_EMAIL_GROUP_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' | 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED' | 'IS_SETTINGS_DISCOVERY_HERO_ENABLED' | 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED'
|
||||
|
||||
export interface WorkspaceUrls {
|
||||
customUrl?: Scalars['String']
|
||||
@@ -9090,7 +9090,6 @@ export const enumFeatureFlagKey = {
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT: 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT' as const,
|
||||
IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED: 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED' as const,
|
||||
IS_SETTINGS_DISCOVERY_HERO_ENABLED: 'IS_SETTINGS_DISCOVERY_HERO_ENABLED' as const,
|
||||
IS_CALL_RECORDING_ENABLED: 'IS_CALL_RECORDING_ENABLED' as const,
|
||||
IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED: 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED' as const
|
||||
}
|
||||
|
||||
|
||||
@@ -297,7 +297,6 @@ export type FeatureFlag = {
|
||||
};
|
||||
|
||||
export enum FeatureFlagKey {
|
||||
IS_CALL_RECORDING_ENABLED = 'IS_CALL_RECORDING_ENABLED',
|
||||
IS_EMAIL_GROUP_ENABLED = 'IS_EMAIL_GROUP_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
|
||||
@@ -1708,7 +1708,6 @@ export type FeatureFlag = {
|
||||
};
|
||||
|
||||
export enum FeatureFlagKey {
|
||||
IS_CALL_RECORDING_ENABLED = 'IS_CALL_RECORDING_ENABLED',
|
||||
IS_EMAIL_GROUP_ENABLED = 'IS_EMAIL_GROUP_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
||||
import { SyncCallRecordingNavigationCommandMenuItemAvailabilityExpressionCommand } from 'src/database/commands/upgrade-version-command/2-17/2-17-workspace-command-1801000000000-sync-call-recording-navigation-command-menu-item-availability-expression.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ApplicationModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceIteratorModule,
|
||||
WorkspaceMigrationModule,
|
||||
],
|
||||
providers: [
|
||||
SyncCallRecordingNavigationCommandMenuItemAvailabilityExpressionCommand,
|
||||
],
|
||||
})
|
||||
export class V2_17_UpgradeVersionCommandModule {}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { Command } from 'nest-commander';
|
||||
|
||||
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
|
||||
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
|
||||
import { buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations } from 'src/database/commands/upgrade-version-command/2-17/utils/build-call-recording-navigation-command-menu-item-availability-expression-sync-operations.util';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@RegisteredWorkspaceCommand('2.17.0', 1801000000000)
|
||||
@Command({
|
||||
name: 'upgrade:2-17:sync-call-recording-navigation-command-menu-item-availability-expression',
|
||||
description:
|
||||
'Remove the retired call recording feature-flag gate from existing CallRecording navigation command menu items',
|
||||
})
|
||||
export class SyncCallRecordingNavigationCommandMenuItemAvailabilityExpressionCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
|
||||
constructor(
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const { flatCommandMenuItemMaps, flatObjectMetadataMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatCommandMenuItemMaps',
|
||||
'flatObjectMetadataMaps',
|
||||
]);
|
||||
|
||||
const commandMenuItemOperations =
|
||||
buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations(
|
||||
{
|
||||
existingFlatCommandMenuItemMaps: flatCommandMenuItemMaps,
|
||||
existingFlatObjectMetadataMaps: flatObjectMetadataMaps,
|
||||
now: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
|
||||
const commandMenuItemsToUpdate =
|
||||
commandMenuItemOperations.flatEntityToUpdate;
|
||||
|
||||
if (commandMenuItemsToUpdate.length === 0) {
|
||||
this.logger.log(
|
||||
`CallRecording navigation command menu item availability expression already synced for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Syncing ${commandMenuItemsToUpdate.length} CallRecording navigation command menu item availability expression(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: commandMenuItemOperations,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to sync CallRecording navigation command menu item availability expression:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Failed to sync CallRecording navigation command menu item availability expression for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully synced ${commandMenuItemsToUpdate.length} CallRecording navigation command menu item availability expression(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import {
|
||||
buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations,
|
||||
CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION,
|
||||
LEGACY_CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION,
|
||||
} from 'src/database/commands/upgrade-version-command/2-17/utils/build-call-recording-navigation-command-menu-item-availability-expression-sync-operations.util';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { buildNavigationFlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
const APPLICATION_ID = 'application-id';
|
||||
const WORKSPACE_ID = 'workspace-id';
|
||||
const CREATED_AT = '2026-06-01T00:00:00.000Z';
|
||||
const NOW = '2026-06-25T00:00:00.000Z';
|
||||
|
||||
const buildFlatCommandMenuItemMaps = (
|
||||
flatCommandMenuItems: FlatCommandMenuItem[],
|
||||
): FlatEntityMaps<FlatCommandMenuItem> => ({
|
||||
byUniversalIdentifier: Object.fromEntries(
|
||||
flatCommandMenuItems.map((flatCommandMenuItem) => [
|
||||
flatCommandMenuItem.universalIdentifier,
|
||||
flatCommandMenuItem,
|
||||
]),
|
||||
),
|
||||
universalIdentifierById: Object.fromEntries(
|
||||
flatCommandMenuItems.map((flatCommandMenuItem) => [
|
||||
flatCommandMenuItem.id,
|
||||
flatCommandMenuItem.universalIdentifier,
|
||||
]),
|
||||
),
|
||||
universalIdentifiersByApplicationId: {
|
||||
[APPLICATION_ID]: flatCommandMenuItems.map(
|
||||
(flatCommandMenuItem) => flatCommandMenuItem.universalIdentifier,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const buildFlatObjectMetadataMaps = (
|
||||
flatObjectMetadatas: FlatObjectMetadata[],
|
||||
): FlatEntityMaps<FlatObjectMetadata> => ({
|
||||
byUniversalIdentifier: Object.fromEntries(
|
||||
flatObjectMetadatas.map((flatObjectMetadata) => [
|
||||
flatObjectMetadata.universalIdentifier,
|
||||
flatObjectMetadata,
|
||||
]),
|
||||
),
|
||||
universalIdentifierById: Object.fromEntries(
|
||||
flatObjectMetadatas.map((flatObjectMetadata) => [
|
||||
flatObjectMetadata.id,
|
||||
flatObjectMetadata.universalIdentifier,
|
||||
]),
|
||||
),
|
||||
universalIdentifiersByApplicationId: {
|
||||
[APPLICATION_ID]: flatObjectMetadatas.map(
|
||||
(flatObjectMetadata) => flatObjectMetadata.universalIdentifier,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const buildCallRecordingObjectMetadataMaps = () =>
|
||||
buildFlatObjectMetadataMaps([
|
||||
getFlatObjectMetadataMock({
|
||||
id: 'call-recording-object-metadata-id',
|
||||
universalIdentifier: STANDARD_OBJECTS.callRecording.universalIdentifier,
|
||||
nameSingular: 'callRecording',
|
||||
namePlural: 'callRecordings',
|
||||
applicationId: APPLICATION_ID,
|
||||
applicationUniversalIdentifier:
|
||||
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
workspaceId: WORKSPACE_ID,
|
||||
}),
|
||||
]);
|
||||
|
||||
const buildCallRecordingNavigationCommandMenuItem = ({
|
||||
conditionalAvailabilityExpression,
|
||||
}: {
|
||||
conditionalAvailabilityExpression: string | null;
|
||||
}): FlatCommandMenuItem => ({
|
||||
...buildNavigationFlatCommandMenuItem({
|
||||
objectMetadata: {
|
||||
id: 'call-recording-object-metadata-id',
|
||||
universalIdentifier: STANDARD_OBJECTS.callRecording.universalIdentifier,
|
||||
nameSingular: 'callRecording',
|
||||
shortcut: null,
|
||||
},
|
||||
commandMenuItemId: 'call-recording-navigation-command-menu-item-id',
|
||||
applicationId: APPLICATION_ID,
|
||||
applicationUniversalIdentifier:
|
||||
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
workspaceId: WORKSPACE_ID,
|
||||
position: 0,
|
||||
now: CREATED_AT,
|
||||
}),
|
||||
conditionalAvailabilityExpression,
|
||||
});
|
||||
|
||||
describe('buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations', () => {
|
||||
it('rewrites the legacy call recording feature-flag gate to the read-permission gate', () => {
|
||||
const legacyCallRecordingNavigationCommandMenuItem =
|
||||
buildCallRecordingNavigationCommandMenuItem({
|
||||
conditionalAvailabilityExpression:
|
||||
LEGACY_CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION,
|
||||
});
|
||||
|
||||
const result =
|
||||
buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations(
|
||||
{
|
||||
existingFlatCommandMenuItemMaps: buildFlatCommandMenuItemMaps([
|
||||
legacyCallRecordingNavigationCommandMenuItem,
|
||||
]),
|
||||
existingFlatObjectMetadataMaps: buildCallRecordingObjectMetadataMaps(),
|
||||
now: NOW,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.flatEntityToCreate).toHaveLength(0);
|
||||
expect(result.flatEntityToDelete).toHaveLength(0);
|
||||
expect(result.flatEntityToUpdate).toHaveLength(1);
|
||||
expect(result.flatEntityToUpdate[0]).toMatchObject({
|
||||
id: legacyCallRecordingNavigationCommandMenuItem.id,
|
||||
conditionalAvailabilityExpression:
|
||||
CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION,
|
||||
updatedAt: NOW,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not update the command menu item when the expression is already synced', () => {
|
||||
const syncedCallRecordingNavigationCommandMenuItem =
|
||||
buildCallRecordingNavigationCommandMenuItem({
|
||||
conditionalAvailabilityExpression:
|
||||
CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION,
|
||||
});
|
||||
|
||||
const result =
|
||||
buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations(
|
||||
{
|
||||
existingFlatCommandMenuItemMaps: buildFlatCommandMenuItemMaps([
|
||||
syncedCallRecordingNavigationCommandMenuItem,
|
||||
]),
|
||||
existingFlatObjectMetadataMaps: buildCallRecordingObjectMetadataMaps(),
|
||||
now: NOW,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.flatEntityToUpdate).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not update unrelated command menu item availability expressions', () => {
|
||||
const customCallRecordingNavigationCommandMenuItem =
|
||||
buildCallRecordingNavigationCommandMenuItem({
|
||||
conditionalAvailabilityExpression:
|
||||
'targetObjectReadPermissions.callRecording and permissionFlags.DATA_MODEL',
|
||||
});
|
||||
|
||||
const result =
|
||||
buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations(
|
||||
{
|
||||
existingFlatCommandMenuItemMaps: buildFlatCommandMenuItemMaps([
|
||||
customCallRecordingNavigationCommandMenuItem,
|
||||
]),
|
||||
existingFlatObjectMetadataMaps: buildCallRecordingObjectMetadataMaps(),
|
||||
now: NOW,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.flatEntityToUpdate).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not update when the call recording object metadata is missing', () => {
|
||||
const legacyCallRecordingNavigationCommandMenuItem =
|
||||
buildCallRecordingNavigationCommandMenuItem({
|
||||
conditionalAvailabilityExpression:
|
||||
LEGACY_CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION,
|
||||
});
|
||||
|
||||
const result =
|
||||
buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations(
|
||||
{
|
||||
existingFlatCommandMenuItemMaps: buildFlatCommandMenuItemMaps([
|
||||
legacyCallRecordingNavigationCommandMenuItem,
|
||||
]),
|
||||
existingFlatObjectMetadataMaps: buildFlatObjectMetadataMaps([]),
|
||||
now: NOW,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.flatEntityToUpdate).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does not update when the call recording navigation command menu item is missing', () => {
|
||||
const result =
|
||||
buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations(
|
||||
{
|
||||
existingFlatCommandMenuItemMaps: buildFlatCommandMenuItemMaps([]),
|
||||
existingFlatObjectMetadataMaps: buildCallRecordingObjectMetadataMaps(),
|
||||
now: NOW,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v5 } from 'uuid';
|
||||
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { NAVIGATION_COMMAND_UUID_NAMESPACE } from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatEntityToCreateDeleteUpdate } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-to-create-delete-update.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
export const LEGACY_CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION =
|
||||
'featureFlags.IS_CALL_RECORDING_ENABLED and targetObjectReadPermissions.callRecording';
|
||||
|
||||
export const CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION =
|
||||
'targetObjectReadPermissions.callRecording';
|
||||
|
||||
const CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER =
|
||||
STANDARD_OBJECTS.callRecording.universalIdentifier;
|
||||
|
||||
const CALL_RECORDING_NAVIGATION_COMMAND_MENU_ITEM_UNIVERSAL_IDENTIFIER = v5(
|
||||
CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
NAVIGATION_COMMAND_UUID_NAMESPACE,
|
||||
);
|
||||
|
||||
export const buildCallRecordingNavigationCommandMenuItemAvailabilityExpressionSyncOperations =
|
||||
({
|
||||
existingFlatCommandMenuItemMaps,
|
||||
existingFlatObjectMetadataMaps,
|
||||
now,
|
||||
}: {
|
||||
existingFlatCommandMenuItemMaps: FlatEntityMaps<FlatCommandMenuItem>;
|
||||
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
now: string;
|
||||
}): FlatEntityToCreateDeleteUpdate<'commandMenuItem'> => {
|
||||
const existingCallRecordingObjectMetadata =
|
||||
existingFlatObjectMetadataMaps.byUniversalIdentifier[
|
||||
CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER
|
||||
];
|
||||
const existingCallRecordingNavigationCommandMenuItem =
|
||||
existingFlatCommandMenuItemMaps.byUniversalIdentifier[
|
||||
CALL_RECORDING_NAVIGATION_COMMAND_MENU_ITEM_UNIVERSAL_IDENTIFIER
|
||||
];
|
||||
|
||||
if (
|
||||
!isDefined(existingCallRecordingObjectMetadata) ||
|
||||
!isDefined(existingCallRecordingNavigationCommandMenuItem) ||
|
||||
existingCallRecordingNavigationCommandMenuItem.conditionalAvailabilityExpression !==
|
||||
LEGACY_CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION
|
||||
) {
|
||||
return {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [
|
||||
{
|
||||
...existingCallRecordingNavigationCommandMenuItem,
|
||||
conditionalAvailabilityExpression:
|
||||
CALL_RECORDING_NAVIGATION_AVAILABILITY_EXPRESSION,
|
||||
updatedAt: now,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
+2
@@ -17,6 +17,7 @@ import { V2_13_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
|
||||
import { V2_14_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-14/2-14-upgrade-version-command.module';
|
||||
import { V2_15_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-15/2-15-upgrade-version-command.module';
|
||||
import { V2_16_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-16/2-16-upgrade-version-command.module';
|
||||
import { V2_17_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-17/2-17-upgrade-version-command.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -37,6 +38,7 @@ import { V2_16_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
|
||||
V2_14_UpgradeVersionCommandModule,
|
||||
V2_15_UpgradeVersionCommandModule,
|
||||
V2_16_UpgradeVersionCommandModule,
|
||||
V2_17_UpgradeVersionCommandModule,
|
||||
],
|
||||
})
|
||||
export class WorkspaceCommandProviderModule {}
|
||||
|
||||
+12
-10
@@ -119,13 +119,14 @@ describe('buildNavigationFlatCommandMenuItem', () => {
|
||||
...baseArgs,
|
||||
objectMetadata: {
|
||||
...baseObjectMetadata,
|
||||
universalIdentifier: STANDARD_OBJECTS.callRecording.universalIdentifier,
|
||||
nameSingular: 'callRecording',
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.messageCampaign.universalIdentifier,
|
||||
nameSingular: 'messageCampaign',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.conditionalAvailabilityExpression).toBe(
|
||||
'featureFlags.IS_CALL_RECORDING_ENABLED and targetObjectReadPermissions.callRecording',
|
||||
'featureFlags.IS_EMAIL_GROUP_ENABLED and targetObjectReadPermissions.messageCampaign',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -154,14 +155,15 @@ describe('buildNavigationFlatCommandMenuItem', () => {
|
||||
});
|
||||
|
||||
describe('buildNavigationConditionalAvailabilityExpression', () => {
|
||||
it('gates the standard call recording object behind both the flag and read permission', () => {
|
||||
it('gates a feature-flagged standard object behind both the flag and read permission', () => {
|
||||
expect(
|
||||
buildNavigationConditionalAvailabilityExpression({
|
||||
universalIdentifier: STANDARD_OBJECTS.callRecording.universalIdentifier,
|
||||
nameSingular: 'callRecording',
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.messageCampaign.universalIdentifier,
|
||||
nameSingular: 'messageCampaign',
|
||||
}),
|
||||
).toBe(
|
||||
'featureFlags.IS_CALL_RECORDING_ENABLED and targetObjectReadPermissions.callRecording',
|
||||
'featureFlags.IS_EMAIL_GROUP_ENABLED and targetObjectReadPermissions.messageCampaign',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -174,12 +176,12 @@ describe('buildNavigationConditionalAvailabilityExpression', () => {
|
||||
).toBe('targetObjectReadPermissions.person');
|
||||
});
|
||||
|
||||
it('does not gate a custom object that reuses the callRecording name', () => {
|
||||
it('does not gate a custom object that reuses a feature-flagged object name', () => {
|
||||
expect(
|
||||
buildNavigationConditionalAvailabilityExpression({
|
||||
universalIdentifier: 'custom-object-universal-id',
|
||||
nameSingular: 'callRecording',
|
||||
nameSingular: 'messageCampaign',
|
||||
}),
|
||||
).toBe('targetObjectReadPermissions.callRecording');
|
||||
).toBe('targetObjectReadPermissions.messageCampaign');
|
||||
});
|
||||
});
|
||||
|
||||
-2
@@ -20,8 +20,6 @@ export const NAVIGATION_INTERPOLATED_ICON =
|
||||
const NAVIGATION_FEATURE_FLAG_GATE_BY_OBJECT_UNIVERSAL_IDENTIFIER: Partial<
|
||||
Record<string, FeatureFlagKey>
|
||||
> = {
|
||||
[STANDARD_OBJECTS.callRecording.universalIdentifier]:
|
||||
FeatureFlagKey.IS_CALL_RECORDING_ENABLED,
|
||||
[STANDARD_OBJECTS.messageCampaign.universalIdentifier]:
|
||||
FeatureFlagKey.IS_EMAIL_GROUP_ENABLED,
|
||||
[STANDARD_OBJECTS.messageList.universalIdentifier]:
|
||||
|
||||
-1
@@ -240,7 +240,6 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT: false,
|
||||
IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED: false,
|
||||
IS_SETTINGS_DISCOVERY_HERO_ENABLED: false,
|
||||
IS_CALL_RECORDING_ENABLED: false,
|
||||
IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED: false,
|
||||
},
|
||||
userWorkspaceRoleMap: {},
|
||||
|
||||
@@ -7,6 +7,5 @@ export enum FeatureFlagKey {
|
||||
IS_REST_METADATA_API_NEW_FORMAT_DIRECT = 'IS_REST_METADATA_API_NEW_FORMAT_DIRECT',
|
||||
IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED = 'IS_LOGIC_FUNCTION_PREBUILT_MODE_ENABLED',
|
||||
IS_SETTINGS_DISCOVERY_HERO_ENABLED = 'IS_SETTINGS_DISCOVERY_HERO_ENABLED',
|
||||
IS_CALL_RECORDING_ENABLED = 'IS_CALL_RECORDING_ENABLED',
|
||||
IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED = 'IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user