Refactor navigation commands to use NAVIGATION engine key with payload (#19303)

- Adds a payload JSON column to `CommandMenuItem` and introduces a
unified `NAVIGATION` engine component key that replaces all individual
GO_TO_* keys
- Navigation commands now use the payload to determine their target
(either an objectMetadataItemId or a path), making navigation commands
dynamic and eliminating the need for a hardcoded engine key per object
- Includes a 1.21 upgrade command (refactor-navigation-commands) that
migrates existing GO_TO_* items to NAVIGATION items with the appropriate
payload, and applies a CHECK constraint enforcing payload coherence



https://github.com/user-attachments/assets/4d305ba2-ae0b-4556-bb0e-e9d899777350


TODO: In a second PR, create the sync between object metadata items and
the navigation command menu items
- Object metadata item created or enabled -> Create navigation command
- Object metadata item deleted or disabled -> Delete associated
navigation command

In another PR:
- Allow `label`, `shortLabel` and `icon` to resolve the
`navigateToObjectMetadataItem` dynamically in their interpolation
instead of being hardcoded in the command menu item
- Make the icon dynamic in the command menu items as the label so that
we can resolve ${navigateToObjectMetadataItem.icon} at runTime -> This
way we won't need to keep update the command menu item icon when we
update the objectMetadataItem icon
This commit is contained in:
Raphaël Bosi
2026-04-07 17:00:04 +02:00
committed by GitHub
parent 5e9792009f
commit d341d0d624
41 changed files with 780 additions and 172 deletions
@@ -10,6 +10,7 @@ import {
IsString,
IsUUID,
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
@@ -68,6 +69,10 @@ export class CommandMenuItemDTO {
@Field(() => CommandMenuItemAvailabilityType)
availabilityType: CommandMenuItemAvailabilityType;
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })
payload?: Record<string, unknown>;
@IsString({ each: true })
@IsOptional()
@Field(() => [String], { nullable: true })
@@ -5,10 +5,12 @@ import {
IsEnum,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import GraphQLJSON from 'graphql-type-json';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
@@ -75,4 +77,9 @@ export class CreateCommandMenuItemInput {
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
availabilityObjectMetadataId?: string;
@IsObject()
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })
payload?: Record<string, unknown>;
}
@@ -31,7 +31,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti
])
@Check(
'CHK_CMD_MENU_ITEM_ENGINE_KEY_COHERENCE',
`("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL)`,
`("engineComponentKey" = 'TRIGGER_WORKFLOW_VERSION' AND "workflowVersionId" IS NOT NULL AND "frontComponentId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'FRONT_COMPONENT_RENDERER' AND "frontComponentId" IS NOT NULL AND "workflowVersionId" IS NULL AND "payload" IS NULL) OR ("engineComponentKey" = 'NAVIGATION' AND "payload" IS NOT NULL AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL) OR ("engineComponentKey" NOT IN ('TRIGGER_WORKFLOW_VERSION', 'FRONT_COMPONENT_RENDERER', 'NAVIGATION') AND "workflowVersionId" IS NULL AND "frontComponentId" IS NULL AND "payload" IS NULL)`,
)
export class CommandMenuItemEntity
extends SyncableEntity
@@ -79,6 +79,9 @@ export class CommandMenuItemEntity
})
availabilityType: CommandMenuItemAvailabilityType;
@Column({ type: 'jsonb', nullable: true })
payload: Record<string, unknown> | null;
@Column({ type: 'text', array: true, nullable: true })
hotKeys: string[] | null;
@@ -18,19 +18,11 @@ export enum EngineComponentKey {
SEE_DELETED_RECORDS = 'SEE_DELETED_RECORDS',
CREATE_NEW_VIEW = 'CREATE_NEW_VIEW',
HIDE_DELETED_RECORDS = 'HIDE_DELETED_RECORDS',
GO_TO_PEOPLE = 'GO_TO_PEOPLE',
GO_TO_COMPANIES = 'GO_TO_COMPANIES',
GO_TO_DASHBOARDS = 'GO_TO_DASHBOARDS',
GO_TO_OPPORTUNITIES = 'GO_TO_OPPORTUNITIES',
GO_TO_SETTINGS = 'GO_TO_SETTINGS',
GO_TO_TASKS = 'GO_TO_TASKS',
GO_TO_NOTES = 'GO_TO_NOTES',
EDIT_RECORD_PAGE_LAYOUT = 'EDIT_RECORD_PAGE_LAYOUT',
EDIT_DASHBOARD_LAYOUT = 'EDIT_DASHBOARD_LAYOUT',
SAVE_DASHBOARD_LAYOUT = 'SAVE_DASHBOARD_LAYOUT',
CANCEL_DASHBOARD_LAYOUT = 'CANCEL_DASHBOARD_LAYOUT',
DUPLICATE_DASHBOARD = 'DUPLICATE_DASHBOARD',
GO_TO_WORKFLOWS = 'GO_TO_WORKFLOWS',
ACTIVATE_WORKFLOW = 'ACTIVATE_WORKFLOW',
DEACTIVATE_WORKFLOW = 'DEACTIVATE_WORKFLOW',
DISCARD_DRAFT_WORKFLOW = 'DISCARD_DRAFT_WORKFLOW',
@@ -41,7 +33,6 @@ export enum EngineComponentKey {
ADD_NODE_WORKFLOW = 'ADD_NODE_WORKFLOW',
TIDY_UP_WORKFLOW = 'TIDY_UP_WORKFLOW',
DUPLICATE_WORKFLOW = 'DUPLICATE_WORKFLOW',
GO_TO_RUNS = 'GO_TO_RUNS',
SEE_VERSION_WORKFLOW_RUN = 'SEE_VERSION_WORKFLOW_RUN',
SEE_WORKFLOW_WORKFLOW_RUN = 'SEE_WORKFLOW_WORKFLOW_RUN',
STOP_WORKFLOW_RUN = 'STOP_WORKFLOW_RUN',
@@ -53,11 +44,24 @@ export enum EngineComponentKey {
SEARCH_RECORDS_FALLBACK = 'SEARCH_RECORDS_FALLBACK',
ASK_AI = 'ASK_AI',
VIEW_PREVIOUS_AI_CHATS = 'VIEW_PREVIOUS_AI_CHATS',
NAVIGATION = 'NAVIGATION',
TRIGGER_WORKFLOW_VERSION = 'TRIGGER_WORKFLOW_VERSION',
FRONT_COMPONENT_RENDERER = 'FRONT_COMPONENT_RENDERER',
REPLY_TO_EMAIL_THREAD = 'REPLY_TO_EMAIL_THREAD',
COMPOSE_EMAIL = 'COMPOSE_EMAIL',
// TODO: Remove deprecated keys once upgrade:1-21:refactor-navigation-commands has run on all workspaces
// Deprecated: replaced by NAVIGATION engine key with payload
GO_TO_PEOPLE = 'GO_TO_PEOPLE',
GO_TO_COMPANIES = 'GO_TO_COMPANIES',
GO_TO_DASHBOARDS = 'GO_TO_DASHBOARDS',
GO_TO_OPPORTUNITIES = 'GO_TO_OPPORTUNITIES',
GO_TO_SETTINGS = 'GO_TO_SETTINGS',
GO_TO_TASKS = 'GO_TO_TASKS',
GO_TO_NOTES = 'GO_TO_NOTES',
GO_TO_WORKFLOWS = 'GO_TO_WORKFLOWS',
GO_TO_RUNS = 'GO_TO_RUNS',
// Deprecated keys kept for backward compatibility until migration runs
DELETE_SINGLE_RECORD = 'DELETE_SINGLE_RECORD',
DELETE_MULTIPLE_RECORDS = 'DELETE_MULTIPLE_RECORDS',