mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Add "Go to Sub-workflow" menu context action to Workflow Tool (#15396)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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> = {}): 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 });
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user