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,