From 342ec9c59282066c7bbd5a4fb17302e75c498845 Mon Sep 17 00:00:00 2001 From: Svetoslav Dekov Date: Mon, 3 Nov 2025 10:47:20 +0200 Subject: [PATCH] feat(editor): Tweak command bar labels (no-changelog) (#21228) --- .../frontend/@n8n/i18n/src/locales/en.json | 1 + .../useCredentialNavigationCommands.test.ts | 16 +++ .../useCredentialNavigationCommands.ts | 21 ++- .../useDataTableNavigationCommands.test.ts | 8 ++ .../useDataTableNavigationCommands.ts | 18 ++- .../composables/useNodeCommands.test.ts | 11 +- .../commandBar/composables/useNodeCommands.ts | 130 ++++++++++-------- .../useProjectNavigationCommands.test.ts | 22 +-- .../useProjectNavigationCommands.ts | 27 ++-- .../composables/useRecentResources.test.ts | 8 +- .../composables/useRecentResources.ts | 10 +- .../useWorkflowNavigationCommands.test.ts | 39 ++++++ .../useWorkflowNavigationCommands.ts | 21 ++- 13 files changed, 207 insertions(+), 125 deletions(-) diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index f89129c8ec1..9e2b89ce709 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -42,6 +42,7 @@ "generic.any": "Any", "generic.cancel": "Cancel", "generic.open": "Open", + "generic.openResource": "Open {resource}", "generic.add": "Add", "generic.close": "Close", "generic.clear": "Clear", diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.test.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.test.ts index 11103cf824b..8b056ed1465 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.test.ts @@ -222,6 +222,14 @@ describe('useCredentialNavigationCommands', () => { await vi.waitFor(() => { const openCommand = commands.value.find((cmd) => cmd.id === 'open-credential'); expect(openCommand?.children).toHaveLength(1); + expect(openCommand?.children?.[0].section).toBe('commandBar.credentials.open'); + expect(openCommand?.children?.[0].title).toEqual( + expect.objectContaining({ + props: expect.objectContaining({ + title: 'My API Key', + }), + }), + ); }); }); }); @@ -344,6 +352,14 @@ describe('useCredentialNavigationCommands', () => { await vi.waitFor(() => { const rootCredentials = commands.value.filter((cmd) => cmd.id === 'cred-1'); expect(rootCredentials).toHaveLength(1); + expect(rootCredentials[0].section).toBe('commandBar.sections.credentials'); + expect(rootCredentials[0].title).toEqual( + expect.objectContaining({ + props: expect.objectContaining({ + title: 'generic.openResource', + }), + }), + ); }); }); }); diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.ts index 7f98f5c6ab1..bbe5280e0dc 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useCredentialNavigationCommands.ts @@ -79,21 +79,30 @@ export function useCredentialNavigationCommands(options: { return credential.homeProject?.name ?? ''; }; - const createCredentialCommand = (credential: ICredentialsResponse): CommandBarItem => { + const createCredentialCommand = ( + credential: ICredentialsResponse, + isRoot: boolean, + ): CommandBarItem => { // Add credential name to keywords since we're using a custom component for the title const keywords = [credential.name]; + const title = isRoot + ? i18n.baseText('generic.openResource', { interpolate: { resource: credential.name } }) + : credential.name; + const section = isRoot + ? i18n.baseText('commandBar.sections.credentials') + : i18n.baseText('commandBar.credentials.open'); + return { id: credential.id, title: { component: CommandBarItemTitle, props: { - title: credential.name, + title, suffix: getCredentialProjectSuffix(credential), - actionText: i18n.baseText('generic.open'), }, }, - section: i18n.baseText('commandBar.sections.credentials'), + section, keywords, icon: { component: CredentialIcon, @@ -108,14 +117,14 @@ export function useCredentialNavigationCommands(options: { }; const openCredentialCommands = computed(() => { - return credentialResults.value.map((credential) => createCredentialCommand(credential)); + return credentialResults.value.map((credential) => createCredentialCommand(credential, false)); }); const rootCredentialItems = computed(() => { if (lastQuery.value.length <= 2) { return []; } - return credentialResults.value.map((credential) => createCredentialCommand(credential)); + return credentialResults.value.map((credential) => createCredentialCommand(credential, true)); }); const credentialNavigationCommands = computed(() => { diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.test.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.test.ts index 6148539e124..615bc5a322c 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.test.ts @@ -299,6 +299,14 @@ describe('useDataTableNavigationCommands', () => { await vi.waitFor(() => { const rootDataTables = commands.value.filter((cmd) => cmd.id === 'dt-1'); expect(rootDataTables).toHaveLength(1); + expect(rootDataTables[0].section).toBe('commandBar.sections.dataTables'); + expect(rootDataTables[0].title).toEqual( + expect.objectContaining({ + props: expect.objectContaining({ + title: 'generic.openResource', + }), + }), + ); }); }); }); diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.ts index b098459b549..fef0dd39a5e 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useDataTableNavigationCommands.ts @@ -87,21 +87,27 @@ export function useDataTableNavigationCommands(options: { return dataTable.project?.name ?? ''; }; - const createDataTableCommand = (dataTable: DataTable): CommandBarItem => { + const createDataTableCommand = (dataTable: DataTable, isRoot: boolean): CommandBarItem => { // Add data table name to keywords since we're using a custom component for the title const keywords = [dataTable.name]; + const title = isRoot + ? i18n.baseText('generic.openResource', { interpolate: { resource: dataTable.name } }) + : dataTable.name; + const section = isRoot + ? i18n.baseText('commandBar.sections.dataTables') + : i18n.baseText('commandBar.dataTables.open'); + return { id: dataTable.id, title: { component: CommandBarItemTitle, props: { - title: dataTable.name, + title, suffix: getDataTableProjectSuffix(dataTable), - actionText: i18n.baseText('generic.open'), }, }, - section: i18n.baseText('commandBar.sections.dataTables'), + section, keywords, handler: () => { void router.push({ @@ -116,14 +122,14 @@ export function useDataTableNavigationCommands(options: { }; const openDataTableCommands = computed(() => { - return dataTableResults.value.map((dataTable) => createDataTableCommand(dataTable)); + return dataTableResults.value.map((dataTable) => createDataTableCommand(dataTable, false)); }); const rootDataTableItems = computed(() => { if (lastQuery.value.length <= 2) { return []; } - return dataTableResults.value.map((dataTable) => createDataTableCommand(dataTable)); + return dataTableResults.value.map((dataTable) => createDataTableCommand(dataTable, true)); }); const dataTableNavigationCommands = computed(() => { diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.test.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.test.ts index a1e80d6e2b8..9c3bf50ecf0 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.test.ts @@ -244,6 +244,7 @@ describe('useNodeCommands', () => { const openCommand = commands.value.find((cmd) => cmd.id === 'open-node'); expect(openCommand?.children).toHaveLength(2); + expect(openCommand?.children?.[0].title).toEqual('Start'); }); }); @@ -297,15 +298,6 @@ describe('useNodeCommands', () => { expect(commands.value.length).toBeLessThanOrEqual(3); } }); - - it('should show root add node items when query is longer than 2 characters', () => { - const { commands } = useNodeCommands({ - lastQuery: ref('htt'), - activeNodeId: ref(null), - }); - - expect(commands.value).toBeDefined(); - }); }); describe('root open node items', () => { @@ -333,6 +325,7 @@ describe('useNodeCommands', () => { const rootOpenNodes = commands.value.filter((cmd) => cmd.id === 'node-1'); expect(rootOpenNodes).toHaveLength(1); + expect(rootOpenNodes[0].title).toEqual('generic.openResource'); }); }); }); diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.ts index d10a942698a..6a53bfc44d7 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useNodeCommands.ts @@ -13,6 +13,7 @@ import { useWorkflowsStore } from '@/app/stores/workflows.store'; import { getResourcePermissions } from '@n8n/permissions'; import NodeIcon from '@/app/components/NodeIcon.vue'; import CommandBarItemTitle from '@/features/shared/commandBar/components/CommandBarItemTitle.vue'; +import type { INodeUi, SimplifiedNodeType } from '@/Interface'; const ITEM_ID = { ADD_NODE: 'add-node', @@ -45,81 +46,88 @@ export function useNodeCommands(options: { (workflowPermissions.value[permission] === true && !isReadOnly.value && !isArchived.value) || workflowsStore.isNewWorkflow; + const mergedNodes = computed(() => { + const httpOnlyCredentials = credentialsStore.httpOnlyCredentialTypes; + const nodeTypes = nodeTypesStore.visibleNodeTypes; + return generateMergedNodesAndActions(nodeTypes, httpOnlyCredentials).mergedNodes; + }); + + const buildAddNodeCommand = (node: SimplifiedNodeType, isRoot: boolean): CommandBarItem => { + const { name, displayName } = node; + + const title = isRoot ? `${i18n.baseText('generic.add')} ${displayName}` : displayName; + const section = isRoot + ? i18n.baseText('commandBar.sections.nodes') + : i18n.baseText('commandBar.nodes.addNode'); + + return { + id: name, + title, + section, + keywords: [displayName], + icon: { + component: NodeIcon as Component, + props: { + nodeType: node, + size: 16, + }, + }, + handler: async () => { + const nodes = await addNodes([{ type: name }]); + if (nodes && nodes.length > 0) { + canvasEventBus.emit('nodes:select', { ids: [nodes[0].id] }); + } + }, + }; + }; + const addNodeCommands = computed(() => { if (!hasPermission('update')) { return []; } - const httpOnlyCredentials = credentialsStore.httpOnlyCredentialTypes; - const nodeTypes = nodeTypesStore.visibleNodeTypes; - const { mergedNodes } = generateMergedNodesAndActions(nodeTypes, httpOnlyCredentials); - return mergedNodes.map((node) => { - const { name, displayName } = node; - - return { - id: name, - title: { - component: CommandBarItemTitle, - props: { - title: displayName, - actionText: i18n.baseText('generic.add'), - }, - }, - keywords: [displayName], - icon: { - component: NodeIcon as Component, - props: { - nodeType: node, - size: 16, - }, - }, - handler: async () => { - const nodes = await addNodes([{ type: name }]); - if (nodes && nodes.length > 0) { - canvasEventBus.emit('nodes:select', { ids: [nodes[0].id] }); - } - }, - }; - }); + return mergedNodes.value.map((node) => buildAddNodeCommand(node, false)); }); const rootAddNodeCommandItems = computed(() => { - if (lastQuery.value.length <= 2) { + if (lastQuery.value.length <= 2 || !hasPermission('update')) { return []; } - return addNodeCommands.value; + return mergedNodes.value.map((node) => buildAddNodeCommand(node, true)); }); - const openNodeCommands = computed(() => { - return editableWorkflow.value.nodes.map((node) => { - const { id, name, type } = node; - const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion); + const buildOpenNodeCommand = (node: INodeUi, isRoot: boolean): CommandBarItem => { + const { id, name, type } = node; + const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion); + const title = isRoot + ? i18n.baseText('generic.openResource', { interpolate: { resource: name } }) + : name; + const section = isRoot + ? i18n.baseText('commandBar.sections.nodes') + : i18n.baseText('commandBar.nodes.openNode'); - return { - id, - title: { - component: CommandBarItemTitle, - props: { - title: name, - actionText: i18n.baseText('generic.open'), - }, + return { + id, + title, + section, + keywords: [name, type], + icon: { + component: NodeIcon, + props: { + nodeType, + size: 16, }, - section: i18n.baseText('commandBar.sections.nodes'), - keywords: [name, type], - icon: { - component: NodeIcon, - props: { - nodeType, - size: 16, - }, - }, - handler: () => { - setNodeActive(id, 'command_bar'); - }, - placeholder: i18n.baseText('commandBar.nodes.searchPlaceholder'), - }; - }); + }, + handler: () => { + setNodeActive(id, 'command_bar'); + }, + placeholder: i18n.baseText('commandBar.nodes.searchPlaceholder'), + }; + }; + + const openNodeCommands = computed(() => { + return editableWorkflow.value.nodes.map((node) => buildOpenNodeCommand(node, false)); }); const rootOpenNodeCommandItems = computed(() => { @@ -127,7 +135,7 @@ export function useNodeCommands(options: { return []; } - return openNodeCommands.value; + return editableWorkflow.value.nodes.map((node) => buildOpenNodeCommand(node, true)); }); const nodeCommands = computed(() => { diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.test.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.test.ts index c79264f6e88..8294ab3c07a 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.test.ts @@ -291,6 +291,7 @@ describe('useProjectNavigationCommands', () => { const rootProjects = commands.value.filter((cmd) => cmd.id === 'project-1'); expect(rootProjects).toHaveLength(1); + expect(rootProjects[0].title).toEqual('generic.openResource'); }); }); @@ -341,12 +342,7 @@ describe('useProjectNavigationCommands', () => { const openCommand = commands.value.find((cmd) => cmd.id === 'open-project'); const personalProject = openCommand?.children?.[0]; expect(personalProject).toBeDefined(); - expect(personalProject?.title).toEqual({ - component: expect.any(Object), - props: expect.objectContaining({ - title: 'projects.menu.personal', - }), - }); + expect(personalProject?.title).toEqual('projects.menu.personal'); }); it('should show project name for team project', () => { @@ -365,12 +361,7 @@ describe('useProjectNavigationCommands', () => { const openCommand = commands.value.find((cmd) => cmd.id === 'open-project'); const teamProject = openCommand?.children?.[0]; expect(teamProject).toBeDefined(); - expect(teamProject?.title).toEqual({ - component: expect.any(Object), - props: expect.objectContaining({ - title: 'Marketing Team', - }), - }); + expect(teamProject?.title).toEqual('Marketing Team'); }); it('should show unnamed text for team project without name', () => { @@ -389,12 +380,7 @@ describe('useProjectNavigationCommands', () => { const openCommand = commands.value.find((cmd) => cmd.id === 'open-project'); const unnamedProject = openCommand?.children?.[0]; expect(unnamedProject).toBeDefined(); - expect(unnamedProject?.title).toEqual({ - component: expect.any(Object), - props: expect.objectContaining({ - title: 'commandBar.projects.unnamed', - }), - }); + expect(unnamedProject?.title).toEqual('commandBar.projects.unnamed'); }); }); diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.ts index 771db1a033b..852277f4144 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useProjectNavigationCommands.ts @@ -7,7 +7,6 @@ import type { ProjectListItem } from '@/features/collaboration/projects/projects import { useProjectsStore } from '@/features/collaboration/projects/projects.store'; import type { CommandBarItem } from '../types'; import { useGlobalEntityCreation } from '@/app/composables/useGlobalEntityCreation'; -import CommandBarItemTitle from '@/features/shared/commandBar/components/CommandBarItemTitle.vue'; const ITEM_ID = { CREATE_PROJECT: 'create-project', @@ -40,24 +39,26 @@ export function useProjectNavigationCommands(options: { ); }); - const createProjectCommand = (project: ProjectListItem): CommandBarItem => { - const title = + const createProjectCommand = (project: ProjectListItem, isRoot: boolean): CommandBarItem => { + let title = project.type === 'personal' ? i18n.baseText('projects.menu.personal') : project.name ? project.name : i18n.baseText('commandBar.projects.unnamed'); + if (isRoot) { + title = i18n.baseText('generic.openResource', { interpolate: { resource: title } }); + } + + const section = isRoot + ? i18n.baseText('commandBar.sections.projects') + : i18n.baseText('commandBar.projects.open'); + return { id: project.id, - title: { - component: CommandBarItemTitle, - props: { - title, - actionText: i18n.baseText('generic.open'), - }, - }, - section: i18n.baseText('commandBar.sections.projects'), + title, + section, keywords: [title], handler: () => { void router.push({ @@ -71,13 +72,13 @@ export function useProjectNavigationCommands(options: { const openProjectCommands = computed(() => { const isInProjectParent = activeNodeId.value === ITEM_ID.OPEN_PROJECT; if (!isInProjectParent) return []; - return filteredProjects.value.map((project) => createProjectCommand(project)); + return filteredProjects.value.map((project) => createProjectCommand(project, false)); }); const rootProjectItems = computed(() => { const isRootWithQuery = activeNodeId.value === null && lastQuery.value.trim().length > 2; if (!isRootWithQuery) return []; - return filteredProjects.value.map((project) => createProjectCommand(project)); + return filteredProjects.value.map((project) => createProjectCommand(project, true)); }); const projectNavigationCommands = computed(() => { diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.test.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.test.ts index 28b99314238..b9b5c78ded9 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.test.ts @@ -308,7 +308,7 @@ describe('useRecentResources', () => { expect(nodeItems).toHaveLength(2); // node-2 is most recent since it was tracked last expect(nodeItems[0].id).toBe('recent-node-workflow-1-node-2'); - expect(nodeItems[0].title).toBe('Test Node 2'); + expect(nodeItems[0].title).toBe('generic.openResource'); expect(nodeItems[1].id).toBe('recent-node-workflow-1-node-1'); expect(nodeItems[0].section).toBe('commandBar.sections.recent'); }); @@ -374,9 +374,9 @@ describe('useRecentResources', () => { expect(workflowItems).toHaveLength(2); expect(workflowItems[0].id).toBe('recent-workflow-workflow-2'); - expect(workflowItems[0].title).toBe('Workflow 2'); + expect(workflowItems[0].title).toBe('generic.openResource'); expect(workflowItems[1].id).toBe('recent-workflow-workflow-1'); - expect(workflowItems[1].title).toBe('Workflow 1'); + expect(workflowItems[1].title).toBe('generic.openResource'); }); it('should limit workflow items to MAX_RECENT_WORKFLOWS_TO_DISPLAY (3)', () => { @@ -437,7 +437,7 @@ describe('useRecentResources', () => { const items = commands.value; const workflowItem = items.find((item) => item.id === 'recent-workflow-workflow-unnamed'); - expect(workflowItem?.title).toBe('commandBar.workflows.unnamed'); + expect(workflowItem?.title).toBe('generic.openResource'); }); it('should navigate using window.location.href when workflow item handler is executed', () => { diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.ts index db05d25cd49..83b8dac2107 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useRecentResources.ts @@ -109,7 +109,9 @@ export function useRecentResources() { items.push({ id: `recent-node-${currentWorkflowId}-${recentNode.nodeId}`, - title: node.name, + title: i18n.baseText('generic.openResource', { + interpolate: { resource: node.name }, + }), section: i18n.baseText('commandBar.sections.recent'), icon: { component: NodeIcon as Component, @@ -145,7 +147,11 @@ export function useRecentResources() { items.push({ id: `recent-workflow-${recentWorkflow.id}`, - title: workflow.name || i18n.baseText('commandBar.workflows.unnamed'), + title: i18n.baseText('generic.openResource', { + interpolate: { + resource: workflow.name || i18n.baseText('commandBar.workflows.unnamed'), + }, + }), section: i18n.baseText('commandBar.sections.recent'), icon: { component: N8nIcon, diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.test.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.test.ts index b4199bda3ba..c61244b94a2 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.test.ts @@ -304,4 +304,43 @@ describe('useWorkflowNavigationCommands', () => { // Icon present when matched by node type expect(item.icon).toBeDefined(); }); + + it('root workflow items have correct title and section', async () => { + const api = useWorkflowNavigationCommands({ + lastQuery: ref('Alpha'), + activeNodeId: ref(null), + currentProjectName: ref('My Project'), + }); + (api.handlers?.onCommandBarChange as (q: string) => void)('Alpha'); + await waitFor(() => { + expect(api.commands.value.length).toBeGreaterThan(2); + }); + const alphaWf = api.commands.value.find((c) => c.id === 'w1'); + expect((alphaWf?.title as unknown as { props?: { title?: string } }).props?.title).toBe( + 'generic.openResource', + ); + expect(alphaWf?.section).toBe('commandBar.sections.workflows'); + }); + + it('open workflow children have correct title and section', async () => { + const api = useWorkflowNavigationCommands({ + lastQuery: ref(''), + activeNodeId: ref(null), + currentProjectName: ref('My Project'), + }); + api.handlers?.onCommandBarNavigateTo?.('open-workflow'); + await waitFor(() => { + const open = api.commands.value.find((c) => c.id === 'open-workflow'); + expect(open?.children?.length).toBeGreaterThan(0); + }); + const children = api.commands.value.find((c) => c.id === 'open-workflow')?.children; + if (!children) { + throw new Error('Open workflow command not found'); + } + expect(children).toHaveLength(2); + expect((children[0].title as unknown as { props?: { title?: string } }).props?.title).toBe( + 'Alpha', + ); + expect(children[0].section).toBe('commandBar.workflows.open'); + }); }); diff --git a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.ts b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.ts index 20bfccbebf8..8c7d3e07011 100644 --- a/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.ts +++ b/packages/frontend/editor-ui/src/features/shared/commandBar/composables/useWorkflowNavigationCommands.ts @@ -219,7 +219,7 @@ export function useWorkflowNavigationCommands(options: { return parts.join(' / '); }; - const createWorkflowCommand = (workflow: IWorkflowDb): CommandBarItem => { + const createWorkflowCommand = (workflow: IWorkflowDb, isRoot: boolean): CommandBarItem => { let keywords = workflowKeywords.value.get(workflow.id) ?? []; const matchedNodeType = workflowMatchedNodeTypes.value.get(workflow.id); @@ -251,18 +251,27 @@ export function useWorkflowNavigationCommands(options: { const suffix = getWorkflowProjectSuffix(workflow); + const name = workflow.name || i18n.baseText('commandBar.workflows.unnamed'); + const title = isRoot + ? i18n.baseText('generic.openResource', { + interpolate: { resource: name }, + }) + : name; + const section = isRoot + ? i18n.baseText('commandBar.sections.workflows') + : i18n.baseText('commandBar.workflows.open'); + return { id: workflow.id, title: { component: CommandBarItemTitle, props: { - title: workflow.name || i18n.baseText('commandBar.workflows.unnamed'), + title, suffix, ...(suffix ? { suffixIcon: getProjectIcon(workflow) } : {}), - actionText: i18n.baseText('generic.open'), }, }, - section: i18n.baseText('commandBar.sections.workflows'), + section, ...(keywords.length > 0 ? { keywords } : {}), ...(icon ? { icon } : {}), handler: () => { @@ -276,14 +285,14 @@ export function useWorkflowNavigationCommands(options: { }; const openWorkflowCommands = computed(() => { - return workflowResults.value.map((workflow) => createWorkflowCommand(workflow)); + return workflowResults.value.map((workflow) => createWorkflowCommand(workflow, false)); }); const rootWorkflowItems = computed(() => { if (lastQuery.value.length <= 2) { return []; } - return workflowResults.value.map((workflow) => createWorkflowCommand(workflow)); + return workflowResults.value.map((workflow) => createWorkflowCommand(workflow, true)); }); const workflowNavigationCommands = computed(() => {