From 85d649e831bd3b4f498195cb6a3a4faaff7d5541 Mon Sep 17 00:00:00 2001
From: Marie <51697796+ijreilly@users.noreply.github.com>
Date: Sat, 23 May 2026 15:22:25 +0200
Subject: [PATCH] [Fix] Backfill missing command menu items conditional
availability expression (#20852)
## Description
Following [report in
discord](https://discord.com/channels/1130383047699738754/1498690477044793386/1506602927412744242)
Some command menu items were showing to all users because they had no
conditional availability expression, whereas users did not actually have
access to the page or feature behind. For instance: "Go to Admin panel",
"Go to AI settings", "Send email" etc.
## Fix
- Add conditional availability expressions
- Backfill expressions for existing workspaces as they are stored in db
(commandMenuItems table)
---------
Co-authored-by: Charles Bochet
---
.../components/StandalonePageCommandMenu.tsx | 6 +
.../constants/EmptyCommandMenuContextApi.ts | 2 +
.../__tests__/useCloseCommandMenu.test.tsx | 2 +
.../hooks/useCurrentCommandMenuContextApi.ts | 7 +
...nditional-availability-expressions.test.ts | 2 +
.../conditional-availability-variables.ts | 4 +
.../src/sdk/front-component/index.ts | 2 +
.../2-8/2-8-upgrade-version-command.module.ts | 2 +
...d-menu-items-by-permission-flag.command.ts | 159 ++++++++++++++++++
.../standard-command-menu-item.constant.ts | 44 ++---
.../src/types/CommandMenuContextApi.ts | 2 +
...eConditionalAvailabilityExpression.test.ts | 2 +
...interpolateCommandMenuItemTemplate.test.ts | 2 +
13 files changed, 216 insertions(+), 20 deletions(-)
create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798100010000-gate-default-command-menu-items-by-permission-flag.command.ts
diff --git a/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx b/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx
index c818f0aded..c81b5e8beb 100644
--- a/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/components/StandalonePageCommandMenu.tsx
@@ -1,3 +1,4 @@
+import { currentUserState } from '@/auth/states/currentUserState';
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector';
@@ -27,6 +28,7 @@ export const StandalonePageCommandMenu = () => {
const commandMenuItems = useAtomStateValue(commandMenuItemsSelector);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
+ const currentUser = useAtomStateValue(currentUserState);
const currentPageLayoutId = useAtomStateValue(currentPageLayoutIdState);
const isLayoutCustomizationModeEnabled = useAtomStateValue(
isLayoutCustomizationModeEnabledState,
@@ -85,12 +87,16 @@ export const StandalonePageCommandMenu = () => {
permissionFlags,
targetObjectReadPermissions,
targetObjectWritePermissions,
+ canImpersonate: currentUser?.canImpersonate === true,
+ canAccessFullAdminPanel: currentUser?.canAccessFullAdminPanel === true,
objectMetadataItem: {},
objectMetadataLabel: '',
};
}, [
currentWorkspace?.featureFlags,
currentUserWorkspace?.permissionFlags,
+ currentUser?.canImpersonate,
+ currentUser?.canAccessFullAdminPanel,
isLayoutCustomizationModeEnabled,
objectMetadataItems,
store,
diff --git a/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts
index 04e3eb24b9..044b3a9d3b 100644
--- a/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts
+++ b/packages/twenty-front/src/modules/command-menu-item/constants/EmptyCommandMenuContextApi.ts
@@ -27,6 +27,8 @@ export const EMPTY_COMMAND_MENU_CONTEXT_API: CommandMenuContextApi = {
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
+ canImpersonate: false,
+ canAccessFullAdminPanel: false,
objectMetadataItem: {},
objectMetadataLabel: '',
};
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
index 502468875a..776dc75755 100644
--- a/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
+++ b/packages/twenty-front/src/modules/command-menu-item/hooks/__tests__/useCloseCommandMenu.test.tsx
@@ -67,6 +67,8 @@ const getWrapper =
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
+ canImpersonate: false,
+ canAccessFullAdminPanel: false,
objectMetadataItem: {},
objectMetadataLabel: '',
},
diff --git a/packages/twenty-front/src/modules/command-menu-item/hooks/useCurrentCommandMenuContextApi.ts b/packages/twenty-front/src/modules/command-menu-item/hooks/useCurrentCommandMenuContextApi.ts
index 430dce88d0..74fb8ba971 100644
--- a/packages/twenty-front/src/modules/command-menu-item/hooks/useCurrentCommandMenuContextApi.ts
+++ b/packages/twenty-front/src/modules/command-menu-item/hooks/useCurrentCommandMenuContextApi.ts
@@ -1,3 +1,4 @@
+import { currentUserState } from '@/auth/states/currentUserState';
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector';
@@ -151,6 +152,10 @@ export const useCurrentCommandMenuContextApi = (): CommandMenuContextApi => {
permissionFlags[flag] = true;
}
+ const currentUser = useAtomStateValue(currentUserState);
+ const canImpersonate = currentUser?.canImpersonate === true;
+ const canAccessFullAdminPanel = currentUser?.canAccessFullAdminPanel === true;
+
const targetObjectReadPermissions: Record = {};
const targetObjectWritePermissions: Record = {};
@@ -188,6 +193,8 @@ export const useCurrentCommandMenuContextApi = (): CommandMenuContextApi => {
permissionFlags,
targetObjectReadPermissions,
targetObjectWritePermissions,
+ canImpersonate,
+ canAccessFullAdminPanel,
objectMetadataItem: objectMetadataItem ?? {},
objectMetadataLabel,
};
diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts b/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts
index df982e7521..78f8431228 100644
--- a/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts
+++ b/packages/twenty-sdk/src/cli/utilities/build/common/conditional-availability/__tests__/transform-conditional-availability-expressions.test.ts
@@ -39,6 +39,8 @@ const buildMockCommandMenuContextApi = (
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
+ canImpersonate: false,
+ canAccessFullAdminPanel: false,
objectMetadataItem: {},
objectMetadataLabel: '',
...overrides,
diff --git a/packages/twenty-sdk/src/sdk/front-component/conditional-availability/conditional-availability-variables.ts b/packages/twenty-sdk/src/sdk/front-component/conditional-availability/conditional-availability-variables.ts
index 55127adb06..d027735c19 100644
--- a/packages/twenty-sdk/src/sdk/front-component/conditional-availability/conditional-availability-variables.ts
+++ b/packages/twenty-sdk/src/sdk/front-component/conditional-availability/conditional-availability-variables.ts
@@ -25,6 +25,10 @@ export const targetObjectReadPermissions =
null as unknown as CommandMenuContextApi['targetObjectReadPermissions'];
export const targetObjectWritePermissions =
null as unknown as CommandMenuContextApi['targetObjectWritePermissions'];
+export const canImpersonate =
+ null as unknown as CommandMenuContextApi['canImpersonate'];
+export const canAccessFullAdminPanel =
+ null as unknown as CommandMenuContextApi['canAccessFullAdminPanel'];
export const objectMetadataItem =
null as unknown as CommandMenuContextApi['objectMetadataItem'];
diff --git a/packages/twenty-sdk/src/sdk/front-component/index.ts b/packages/twenty-sdk/src/sdk/front-component/index.ts
index a48e2231a7..d0ea55aa59 100644
--- a/packages/twenty-sdk/src/sdk/front-component/index.ts
+++ b/packages/twenty-sdk/src/sdk/front-component/index.ts
@@ -12,6 +12,8 @@ export {
featureFlags,
targetObjectReadPermissions,
targetObjectWritePermissions,
+ canImpersonate,
+ canAccessFullAdminPanel,
isDefined,
isNonEmptyString,
includes,
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module.ts
index 6f998d8f53..dea3fecab6 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module.ts
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { DropChannelStandardObjectsCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798000050000-drop-channel-standard-objects.command';
import { BackfillRelationJoinColumnIndexesCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798100000000-backfill-relation-join-column-indexes.command';
+import { GateDefaultCommandMenuItemsByPermissionFlagCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798100010000-gate-default-command-menu-items-by-permission-flag.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
@@ -19,6 +20,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
providers: [
DropChannelStandardObjectsCommand,
BackfillRelationJoinColumnIndexesCommand,
+ GateDefaultCommandMenuItemsByPermissionFlagCommand,
],
})
export class V2_8_UpgradeVersionCommandModule {}
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798100010000-gate-default-command-menu-items-by-permission-flag.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798100010000-gate-default-command-menu-items-by-permission-flag.command.ts
new file mode 100644
index 0000000000..5b51889af4
--- /dev/null
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-8/2-8-workspace-command-1798100010000-gate-default-command-menu-items-by-permission-flag.command.ts
@@ -0,0 +1,159 @@
+import { Command } from 'nest-commander';
+import { isDefined } from 'twenty-shared/utils';
+
+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 { 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 { STANDARD_COMMAND_MENU_ITEMS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant';
+import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
+import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
+
+const UNIVERSAL_IDENTIFIERS_TO_FIX = new Set([
+ STANDARD_COMMAND_MENU_ITEMS.askAi.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.viewPreviousAiChats.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.composeEmail.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.composeEmailToPerson.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.composeEmailToCompany.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.composeEmailToOpportunity.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsAccounts.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsAccountsEmails.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsAccountsCalendars.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsGeneral.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsObjects.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsMembers.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsRoles.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsDomains.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsBilling.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsApiWebhooks.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsApplications.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsAI.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsSecurity.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsUpdates.universalIdentifier,
+ STANDARD_COMMAND_MENU_ITEMS.goToSettingsAdminPanel.universalIdentifier,
+]);
+
+@RegisteredWorkspaceCommand('2.8.0', 1798100010000)
+@Command({
+ name: 'upgrade:2-8:gate-default-command-menu-items-by-permission-flag',
+ description:
+ 'Gate default command menu items (Ask AI, settings navigation, compose email) behind their relevant permission flags so members without permission no longer see them',
+})
+export class GateDefaultCommandMenuItemsByPermissionFlagCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
+ constructor(
+ protected readonly workspaceIteratorService: WorkspaceIteratorService,
+ private readonly applicationService: ApplicationService,
+ private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
+ private readonly workspaceCacheService: WorkspaceCacheService,
+ ) {
+ super(workspaceIteratorService);
+ }
+
+ override async runOnWorkspace({
+ workspaceId,
+ options,
+ }: RunOnWorkspaceArgs): Promise {
+ const isDryRun = options.dryRun ?? false;
+
+ this.logger.log(
+ `${isDryRun ? '[DRY RUN] ' : ''}Gating default command menu items by permission flag for workspace ${workspaceId}`,
+ );
+
+ const { twentyStandardFlatApplication } =
+ await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
+ { workspaceId },
+ );
+
+ const { flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps } =
+ await this.workspaceCacheService.getOrRecompute(workspaceId, [
+ 'flatCommandMenuItemMaps',
+ ]);
+
+ const { allFlatEntityMaps: standardAllFlatEntityMaps } =
+ computeTwentyStandardApplicationAllFlatEntityMaps({
+ now: new Date().toISOString(),
+ workspaceId,
+ twentyStandardApplicationId: twentyStandardFlatApplication.id,
+ });
+
+ const itemsToUpdate = [...UNIVERSAL_IDENTIFIERS_TO_FIX]
+ .map((universalIdentifier) => {
+ const standardItem =
+ standardAllFlatEntityMaps.flatCommandMenuItemMaps
+ .byUniversalIdentifier[universalIdentifier];
+ const existingItem =
+ existingFlatCommandMenuItemMaps.byUniversalIdentifier[
+ universalIdentifier
+ ];
+
+ if (
+ !isDefined(standardItem) ||
+ !isDefined(existingItem) ||
+ existingItem.conditionalAvailabilityExpression ===
+ standardItem.conditionalAvailabilityExpression
+ ) {
+ return undefined;
+ }
+
+ return {
+ ...existingItem,
+ conditionalAvailabilityExpression:
+ standardItem.conditionalAvailabilityExpression,
+ updatedAt: new Date().toISOString(),
+ };
+ })
+ .filter(isDefined);
+
+ if (itemsToUpdate.length === 0) {
+ this.logger.log(
+ `Default command menu item expressions already up to date for workspace ${workspaceId}`,
+ );
+
+ return;
+ }
+
+ this.logger.log(
+ `Found ${itemsToUpdate.length} command menu item(s) to update for workspace ${workspaceId}`,
+ );
+
+ if (isDryRun) {
+ this.logger.log(
+ `[DRY RUN] Would update ${itemsToUpdate.length} command menu item availability expression(s) for workspace ${workspaceId}`,
+ );
+
+ return;
+ }
+
+ const validateAndBuildResult =
+ await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
+ {
+ allFlatEntityOperationByMetadataName: {
+ commandMenuItem: {
+ flatEntityToCreate: [],
+ flatEntityToDelete: [],
+ flatEntityToUpdate: itemsToUpdate,
+ },
+ },
+ workspaceId,
+ applicationUniversalIdentifier:
+ twentyStandardFlatApplication.universalIdentifier,
+ },
+ );
+
+ if (validateAndBuildResult.status === 'fail') {
+ this.logger.error(
+ `Failed to update command menu item availability expressions:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
+ );
+
+ throw new Error(
+ `Failed to gate default command menu items by permission flag for workspace ${workspaceId}`,
+ );
+ }
+
+ this.logger.log(
+ `Successfully updated ${itemsToUpdate.length} command menu item availability expression(s) for workspace ${workspaceId}`,
+ );
+ }
+}
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts
index f243b77563..ef5195855f 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts
@@ -644,7 +644,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 42,
shortLabel: 'Ask AI',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.AI',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.ASK_AI,
@@ -658,7 +658,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 43,
shortLabel: 'Previous AI Chats',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.AI',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.VIEW_PREVIOUS_AI_CHATS,
@@ -687,7 +687,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 45,
shortLabel: 'Compose',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.SEND_EMAIL_TOOL',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.COMPOSE_EMAIL,
@@ -731,7 +731,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 48,
shortLabel: 'Accounts',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.CONNECTED_ACCOUNTS',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -746,7 +746,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 49,
shortLabel: 'Emails',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.CONNECTED_ACCOUNTS',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -761,7 +761,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 50,
shortLabel: 'Calendars',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.CONNECTED_ACCOUNTS',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -776,7 +776,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 51,
shortLabel: 'General',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.WORKSPACE',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -791,7 +791,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 52,
shortLabel: 'Data Model',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.DATA_MODEL',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -806,7 +806,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 53,
shortLabel: 'Members',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.WORKSPACE_MEMBERS',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -821,7 +821,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 54,
shortLabel: 'Roles',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.WORKSPACE_MEMBERS',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -836,7 +836,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 55,
shortLabel: 'Domains',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.WORKSPACE',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -851,7 +851,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 56,
shortLabel: 'Billing',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.WORKSPACE',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -866,7 +866,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 57,
shortLabel: 'APIs & Webhooks',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.API_KEYS_AND_WEBHOOKS',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -881,7 +881,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 58,
shortLabel: 'Apps',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.APPLICATIONS',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -896,7 +896,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 59,
shortLabel: 'AI',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.WORKSPACE',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -911,7 +911,7 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 60,
shortLabel: 'Security',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression: 'permissionFlags.SECURITY',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -926,7 +926,8 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 61,
shortLabel: 'Admin Panel',
availabilityType: CommandMenuItemAvailabilityType.GLOBAL,
- conditionalAvailabilityExpression: null,
+ conditionalAvailabilityExpression:
+ 'canImpersonate or canAccessFullAdminPanel',
availabilityObjectMetadataUniversalIdentifier: null,
frontComponentUniversalIdentifier: null,
engineComponentKey: EngineComponentKey.NAVIGATION,
@@ -956,7 +957,8 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 63,
shortLabel: 'Send Email',
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
- conditionalAvailabilityExpression: 'numberOfSelectedRecords >= 1',
+ conditionalAvailabilityExpression:
+ 'numberOfSelectedRecords >= 1 and permissionFlags.SEND_EMAIL_TOOL',
availabilityObjectMetadataUniversalIdentifier:
STANDARD_OBJECTS.person.universalIdentifier,
frontComponentUniversalIdentifier: null,
@@ -971,7 +973,8 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 64,
shortLabel: 'Send Email',
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
- conditionalAvailabilityExpression: 'numberOfSelectedRecords == 1',
+ conditionalAvailabilityExpression:
+ 'numberOfSelectedRecords == 1 and permissionFlags.SEND_EMAIL_TOOL',
availabilityObjectMetadataUniversalIdentifier:
STANDARD_OBJECTS.company.universalIdentifier,
frontComponentUniversalIdentifier: null,
@@ -986,7 +989,8 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
position: 65,
shortLabel: 'Send Email',
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
- conditionalAvailabilityExpression: 'numberOfSelectedRecords == 1',
+ conditionalAvailabilityExpression:
+ 'numberOfSelectedRecords == 1 and permissionFlags.SEND_EMAIL_TOOL',
availabilityObjectMetadataUniversalIdentifier:
STANDARD_OBJECTS.opportunity.universalIdentifier,
frontComponentUniversalIdentifier: null,
diff --git a/packages/twenty-shared/src/types/CommandMenuContextApi.ts b/packages/twenty-shared/src/types/CommandMenuContextApi.ts
index 15c640ccf3..00ab5d2585 100644
--- a/packages/twenty-shared/src/types/CommandMenuContextApi.ts
+++ b/packages/twenty-shared/src/types/CommandMenuContextApi.ts
@@ -17,6 +17,8 @@ export type CommandMenuContextApi = {
permissionFlags: Record;
targetObjectReadPermissions: Record;
targetObjectWritePermissions: Record;
+ canImpersonate: boolean;
+ canAccessFullAdminPanel: boolean;
objectMetadataItem: Record;
objectMetadataLabel: string;
};
diff --git a/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts b/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts
index b316af8295..0c60167dac 100644
--- a/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts
+++ b/packages/twenty-shared/src/utils/command-menu-items/__tests__/evaluateConditionalAvailabilityExpression.test.ts
@@ -27,6 +27,8 @@ const buildContext = (
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
+ canImpersonate: false,
+ canAccessFullAdminPanel: false,
objectMetadataItem: {},
objectMetadataLabel: '',
...overrides,
diff --git a/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts b/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts
index c7ec84407a..44de1bd255 100644
--- a/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts
+++ b/packages/twenty-shared/src/utils/command-menu-items/__tests__/interpolateCommandMenuItemTemplate.test.ts
@@ -27,6 +27,8 @@ const buildContext = (
permissionFlags: {},
targetObjectReadPermissions: {},
targetObjectWritePermissions: {},
+ canImpersonate: false,
+ canAccessFullAdminPanel: false,
objectMetadataItem: {},
objectMetadataLabel: '',
...overrides,