diff --git a/packages/frontend/editor-ui/src/composables/__snapshots__/useContextMenu.test.ts.snap b/packages/frontend/editor-ui/src/composables/__snapshots__/useContextMenu.test.ts.snap index f24bfdafc6..ab38f536f1 100644 --- a/packages/frontend/editor-ui/src/composables/__snapshots__/useContextMenu.test.ts.snap +++ b/packages/frontend/editor-ui/src/composables/__snapshots__/useContextMenu.test.ts.snap @@ -191,128 +191,6 @@ exports[`useContextMenu > Read-only mode > should return the correct actions whe ] `; -exports[`useContextMenu > should include "Open Sub-workflow" action when node is "Execute Workflow" with a set workflow 1`] = ` -[ - { - "id": "open", - "label": "Open...", - "shortcut": { - "keys": [ - "↵", - ], - }, - }, - { - "disabled": false, - "id": "execute", - "label": "Execute step", - }, - { - "disabled": false, - "id": "rename", - "label": "Rename", - "shortcut": { - "keys": [ - "Space", - ], - }, - }, - { - "disabled": false, - "id": "open_sub_workflow", - "label": "Open Sub-workflow", - "shortcut": { - "keys": [ - "O", - ], - "metaKey": true, - "shiftKey": true, - }, - }, - { - "disabled": false, - "id": "toggle_activation", - "label": "Deactivate", - "shortcut": { - "keys": [ - "D", - ], - }, - }, - { - "disabled": true, - "id": "toggle_pin", - "label": "Pin", - "shortcut": { - "keys": [ - "p", - ], - }, - }, - { - "id": "copy", - "label": "Copy", - "shortcut": { - "keys": [ - "C", - ], - "metaKey": true, - }, - }, - { - "disabled": true, - "id": "duplicate", - "label": "Duplicate", - "shortcut": { - "keys": [ - "D", - ], - "metaKey": true, - }, - }, - { - "divided": true, - "id": "tidy_up", - "label": "Tidy up workflow", - "shortcut": { - "altKey": true, - "keys": [ - "T", - ], - "shiftKey": true, - }, - }, - { - "disabled": false, - "divided": true, - "id": "select_all", - "label": "Select all", - "shortcut": { - "keys": [ - "A", - ], - "metaKey": true, - }, - }, - { - "disabled": false, - "id": "deselect_all", - "label": "Clear selection", - }, - { - "disabled": false, - "divided": true, - "id": "delete", - "label": "Delete", - "shortcut": { - "keys": [ - "Del", - ], - }, - }, -] -`; - exports[`useContextMenu > should return the correct actions opening the menu from the button 1`] = ` [ { @@ -614,7 +492,7 @@ exports[`useContextMenu > should return the correct actions when right clicking ] `; -exports[`useContextMenu > should show "Open Sub-workflow" action (disabled) when node is "Execute Workflow" without a set workflow 1`] = ` +exports[`useContextMenu > should show "Go to Sub-workflow" action (disabled) when node is "Execute Workflow" without a set workflow 1`] = ` [ { "id": "open", @@ -643,7 +521,7 @@ exports[`useContextMenu > should show "Open Sub-workflow" action (disabled) when { "disabled": true, "id": "open_sub_workflow", - "label": "Open Sub-workflow", + "label": "Go to Sub-workflow", "shortcut": { "keys": [ "O", @@ -736,7 +614,129 @@ exports[`useContextMenu > should show "Open Sub-workflow" action (disabled) when ] `; -exports[`useContextMenu > should show "Open Sub-workflow" action (enabled) when node is "Execute Workflow" with a set workflow 1`] = ` +exports[`useContextMenu > should show "Go to Sub-workflow" action (disabled) when node is "Workflow Tool" without a set workflow 1`] = ` +[ + { + "id": "open", + "label": "Open...", + "shortcut": { + "keys": [ + "↵", + ], + }, + }, + { + "disabled": false, + "id": "execute", + "label": "Execute step", + }, + { + "disabled": false, + "id": "rename", + "label": "Rename", + "shortcut": { + "keys": [ + "Space", + ], + }, + }, + { + "disabled": true, + "id": "open_sub_workflow", + "label": "Go to Sub-workflow", + "shortcut": { + "keys": [ + "O", + ], + "metaKey": true, + "shiftKey": true, + }, + }, + { + "disabled": false, + "id": "toggle_activation", + "label": "Deactivate", + "shortcut": { + "keys": [ + "D", + ], + }, + }, + { + "disabled": true, + "id": "toggle_pin", + "label": "Pin", + "shortcut": { + "keys": [ + "p", + ], + }, + }, + { + "id": "copy", + "label": "Copy", + "shortcut": { + "keys": [ + "C", + ], + "metaKey": true, + }, + }, + { + "disabled": true, + "id": "duplicate", + "label": "Duplicate", + "shortcut": { + "keys": [ + "D", + ], + "metaKey": true, + }, + }, + { + "divided": true, + "id": "tidy_up", + "label": "Tidy up workflow", + "shortcut": { + "altKey": true, + "keys": [ + "T", + ], + "shiftKey": true, + }, + }, + { + "disabled": false, + "divided": true, + "id": "select_all", + "label": "Select all", + "shortcut": { + "keys": [ + "A", + ], + "metaKey": true, + }, + }, + { + "disabled": false, + "id": "deselect_all", + "label": "Clear selection", + }, + { + "disabled": false, + "divided": true, + "id": "delete", + "label": "Delete", + "shortcut": { + "keys": [ + "Del", + ], + }, + }, +] +`; + +exports[`useContextMenu > should show "Go to Sub-workflow" action (enabled) when node is "Execute Workflow" with a set workflow 1`] = ` [ { "id": "open", @@ -765,7 +765,129 @@ exports[`useContextMenu > should show "Open Sub-workflow" action (enabled) when { "disabled": false, "id": "open_sub_workflow", - "label": "Open Sub-workflow", + "label": "Go to Sub-workflow", + "shortcut": { + "keys": [ + "O", + ], + "metaKey": true, + "shiftKey": true, + }, + }, + { + "disabled": false, + "id": "toggle_activation", + "label": "Deactivate", + "shortcut": { + "keys": [ + "D", + ], + }, + }, + { + "disabled": true, + "id": "toggle_pin", + "label": "Pin", + "shortcut": { + "keys": [ + "p", + ], + }, + }, + { + "id": "copy", + "label": "Copy", + "shortcut": { + "keys": [ + "C", + ], + "metaKey": true, + }, + }, + { + "disabled": true, + "id": "duplicate", + "label": "Duplicate", + "shortcut": { + "keys": [ + "D", + ], + "metaKey": true, + }, + }, + { + "divided": true, + "id": "tidy_up", + "label": "Tidy up workflow", + "shortcut": { + "altKey": true, + "keys": [ + "T", + ], + "shiftKey": true, + }, + }, + { + "disabled": false, + "divided": true, + "id": "select_all", + "label": "Select all", + "shortcut": { + "keys": [ + "A", + ], + "metaKey": true, + }, + }, + { + "disabled": false, + "id": "deselect_all", + "label": "Clear selection", + }, + { + "disabled": false, + "divided": true, + "id": "delete", + "label": "Delete", + "shortcut": { + "keys": [ + "Del", + ], + }, + }, +] +`; + +exports[`useContextMenu > should show "Go to Sub-workflow" action (enabled) when node is "Workflow Tool" with a set workflow 1`] = ` +[ + { + "id": "open", + "label": "Open...", + "shortcut": { + "keys": [ + "↵", + ], + }, + }, + { + "disabled": false, + "id": "execute", + "label": "Execute step", + }, + { + "disabled": false, + "id": "rename", + "label": "Rename", + "shortcut": { + "keys": [ + "Space", + ], + }, + }, + { + "disabled": false, + "id": "open_sub_workflow", + "label": "Go to Sub-workflow", "shortcut": { "keys": [ "O", diff --git a/packages/frontend/editor-ui/src/composables/useContextMenu.test.ts b/packages/frontend/editor-ui/src/composables/useContextMenu.test.ts index 2efe500ae2..db1ca7b705 100644 --- a/packages/frontend/editor-ui/src/composables/useContextMenu.test.ts +++ b/packages/frontend/editor-ui/src/composables/useContextMenu.test.ts @@ -6,7 +6,12 @@ import { createPinia, setActivePinia } from 'pinia'; import { useSourceControlStore } from '@/stores/sourceControl.store'; import { useUIStore } from '@/stores/ui.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; -import { EXECUTE_WORKFLOW_NODE_TYPE, NodeConnectionTypes, NodeHelpers } from 'n8n-workflow'; +import { + EXECUTE_WORKFLOW_NODE_TYPE, + NodeConnectionTypes, + NodeHelpers, + WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE, +} from 'n8n-workflow'; const nodeFactory = (data: Partial = {}): INodeUi => ({ id: faker.string.uuid(), @@ -92,7 +97,7 @@ describe('useContextMenu', () => { expect(targetNodeIds.value).toEqual([sticky.id]); }); - it('should show "Open Sub-workflow" action (enabled) when node is "Execute Workflow" with a set workflow', () => { + it('should show "Go to Sub-workflow" action (enabled) when node is "Execute Workflow" with a set workflow', () => { const { open, isOpen, actions, targetNodeIds } = useContextMenu(); const executeWorkflow = nodeFactory({ type: EXECUTE_WORKFLOW_NODE_TYPE, @@ -113,7 +118,7 @@ describe('useContextMenu', () => { expect(targetNodeIds.value).toEqual([executeWorkflow.id]); }); - it('should show "Open Sub-workflow" action (disabled) when node is "Execute Workflow" without a set workflow', () => { + it('should show "Go to Sub-workflow" action (disabled) when node is "Execute Workflow" without a set workflow', () => { const { open, isOpen, actions, targetNodeIds } = useContextMenu(); const executeWorkflow = nodeFactory({ type: EXECUTE_WORKFLOW_NODE_TYPE, @@ -129,6 +134,43 @@ describe('useContextMenu', () => { expect(targetNodeIds.value).toEqual([executeWorkflow.id]); }); + it('should show "Go to Sub-workflow" action (enabled) when node is "Workflow Tool" with a set workflow', () => { + const { open, isOpen, actions, targetNodeIds } = useContextMenu(); + const executeWorkflow = nodeFactory({ + type: WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE, + parameters: { + workflowId: { + __rl: true, + value: 'qseYRPbw6joqU7RC', + mode: 'list', + cachedResultName: '', + }, + }, + }); + vi.spyOn(workflowsStore, 'getNodeById').mockReturnValue(executeWorkflow); + open(mockEvent, { source: 'node-right-click', nodeId: executeWorkflow.id }); + + expect(isOpen.value).toBe(true); + expect(actions.value).toMatchSnapshot(); + expect(targetNodeIds.value).toEqual([executeWorkflow.id]); + }); + + it('should show "Go to Sub-workflow" action (disabled) when node is "Workflow Tool" without a set workflow', () => { + const { open, isOpen, actions, targetNodeIds } = useContextMenu(); + const executeWorkflow = nodeFactory({ + type: WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE, + parameters: { + workflowId: {}, + }, + }); + vi.spyOn(workflowsStore, 'getNodeById').mockReturnValue(executeWorkflow); + open(mockEvent, { source: 'node-right-click', nodeId: executeWorkflow.id }); + + expect(isOpen.value).toBe(true); + expect(actions.value).toMatchSnapshot(); + expect(targetNodeIds.value).toEqual([executeWorkflow.id]); + }); + it('should disable pinning for node that has other inputs then "main"', () => { const { open, isOpen, actions, targetNodeIds } = useContextMenu(); const basicChain = nodeFactory({ type: BASIC_CHAIN_NODE_TYPE }); diff --git a/packages/frontend/editor-ui/src/composables/useContextMenu.ts b/packages/frontend/editor-ui/src/composables/useContextMenu.ts index 5ccfc9a5c9..944a6d22cd 100644 --- a/packages/frontend/editor-ui/src/composables/useContextMenu.ts +++ b/packages/frontend/editor-ui/src/composables/useContextMenu.ts @@ -1,9 +1,5 @@ import type { ActionDropdownItem, XYPosition, INodeUi } from '@/Interface'; -import { - NOT_DUPLICATABLE_NODE_TYPES, - STICKY_NODE_TYPE, - EXECUTE_WORKFLOW_NODE_TYPE, -} from '@/constants'; +import { NOT_DUPLICATABLE_NODE_TYPES, STICKY_NODE_TYPE } from '@/constants'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useSourceControlStore } from '@/stores/sourceControl.store'; import { useUIStore } from '@/stores/ui.store'; @@ -70,9 +66,10 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) = if (targetNodes.value.length !== 1) return false; const node = targetNodes.value[0]; - if (node.type !== EXECUTE_WORKFLOW_NODE_TYPE) return false; - return NodeHelpers.getSubworkflowId(node); + if (!NodeHelpers.isNodeWithWorkflowSelector(node)) return false; + + return !!NodeHelpers.getSubworkflowId(node); }); const targetNodeIds = computed(() => { @@ -236,7 +233,6 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) = ].filter(Boolean) as ActionDropdownItem[]; if (nodes.length === 1) { - const isExecuteWorkflowNode = nodes[0].type === EXECUTE_WORKFLOW_NODE_TYPE; const singleNodeActions: ActionDropdownItem[] = onlyStickies ? [ { @@ -270,7 +266,7 @@ export const useContextMenu = (onAction: ContextMenuActionCallback = () => {}) = }, ]; - if (isExecuteWorkflowNode) { + if (NodeHelpers.isNodeWithWorkflowSelector(nodes[0])) { singleNodeActions.push({ id: 'open_sub_workflow', label: i18n.baseText('contextMenu.openSubworkflow'), diff --git a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json index d89a6bf409..26f530a5cd 100644 --- a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json @@ -1513,7 +1513,7 @@ "contextMenu.open": "Open...", "contextMenu.test": "Execute step", "contextMenu.rename": "Rename", - "contextMenu.openSubworkflow": "Open Sub-workflow", + "contextMenu.openSubworkflow": "Go to Sub-workflow", "contextMenu.copy": "Copy | Copy {count} {subject}", "contextMenu.deactivate": "Deactivate | Deactivate {count} {subject}", "contextMenu.activate": "Activate | Activate {count} nodes", diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 8b640313e1..f92f974bdd 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -6,7 +6,7 @@ import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; -import { EXECUTE_WORKFLOW_NODE_TYPE } from './Constants'; +import { EXECUTE_WORKFLOW_NODE_TYPE, WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE } from './Constants'; import { ApplicationError } from './errors/application.error'; import { NodeConnectionTypes } from './Interfaces'; import type { @@ -1565,15 +1565,15 @@ export function isExecutable(workflow: Workflow, node: INode, nodeTypeData: INod ); } +export function isNodeWithWorkflowSelector(node: INode) { + return [EXECUTE_WORKFLOW_NODE_TYPE, WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE].includes(node.type); +} + /** * Attempts to retrieve the ID of a subworkflow from a execute workflow node. */ export function getSubworkflowId(node: INode): string | undefined { - if ( - node && - node.type === EXECUTE_WORKFLOW_NODE_TYPE && - isResourceLocatorValue(node.parameters.workflowId) - ) { + if (isNodeWithWorkflowSelector(node) && isResourceLocatorValue(node.parameters.workflowId)) { return node.parameters.workflowId.value as string; } return;