From 6c2dad79143f5b0c255ab8c97c3255314834c458 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Tue, 19 Nov 2024 14:13:19 +0200 Subject: [PATCH] fix(editor): Fix reordered switch connections when copying nodes on new canvas (#11788) --- .../composables/useCanvasOperations.test.ts | 138 +++++++++++++++--- .../src/composables/useCanvasOperations.ts | 11 +- 2 files changed, 121 insertions(+), 28 deletions(-) diff --git a/packages/editor-ui/src/composables/useCanvasOperations.test.ts b/packages/editor-ui/src/composables/useCanvasOperations.test.ts index 4069a24f88..c64230aa82 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.test.ts +++ b/packages/editor-ui/src/composables/useCanvasOperations.test.ts @@ -4,6 +4,7 @@ import type { INodeTypeDescription, IWebhookDescription, Workflow, + INodeConnections, } from 'n8n-workflow'; import { NodeConnectionType, NodeHelpers } from 'n8n-workflow'; import { useCanvasOperations } from '@/composables/useCanvasOperations'; @@ -2049,36 +2050,129 @@ describe('useCanvasOperations', () => { expect(workflowsStore.setNodes).toHaveBeenCalled(); expect(workflowsStore.setConnections).toHaveBeenCalled(); }); + + it('should initialize node data from node type description', () => { + const nodeTypesStore = mockedStore(useNodeTypesStore); + const type = SET_NODE_TYPE; + const version = 1; + const expectedDescription = mockNodeTypeDescription({ + name: type, + version, + properties: [ + { + displayName: 'Value', + name: 'value', + type: 'boolean', + default: true, + }, + ], + }); + + nodeTypesStore.nodeTypes = { [type]: { [version]: expectedDescription } }; + + const workflow = createTestWorkflow({ + nodes: [createTestNode()], + connections: {}, + }); + + const { initializeWorkspace } = useCanvasOperations({ router }); + initializeWorkspace(workflow); + + expect(workflow.nodes[0].parameters).toEqual({ value: true }); + }); }); - it('should initialize node data from node type description', () => { - const nodeTypesStore = mockedStore(useNodeTypesStore); - const type = SET_NODE_TYPE; - const version = 1; - const expectedDescription = mockNodeTypeDescription({ - name: type, - version, - properties: [ - { - displayName: 'Value', - name: 'value', - type: 'boolean', - default: true, - }, - ], + describe('filterConnectionsByNodes', () => { + it('should return filtered connections when all nodes are included', () => { + const connections: INodeConnections = { + [NodeConnectionType.Main]: [ + [ + { node: 'node1', type: NodeConnectionType.Main, index: 0 }, + { node: 'node2', type: NodeConnectionType.Main, index: 0 }, + ], + [{ node: 'node3', type: NodeConnectionType.Main, index: 0 }], + ], + }; + const includeNodeNames = new Set(['node1', 'node2', 'node3']); + + const { filterConnectionsByNodes } = useCanvasOperations({ router }); + const result = filterConnectionsByNodes(connections, includeNodeNames); + + expect(result).toEqual(connections); }); - nodeTypesStore.nodeTypes = { [type]: { [version]: expectedDescription } }; + it('should return empty connections when no nodes are included', () => { + const connections: INodeConnections = { + [NodeConnectionType.Main]: [ + [ + { node: 'node1', type: NodeConnectionType.Main, index: 0 }, + { node: 'node2', type: NodeConnectionType.Main, index: 0 }, + ], + [{ node: 'node3', type: NodeConnectionType.Main, index: 0 }], + ], + }; + const includeNodeNames = new Set(); - const workflow = createTestWorkflow({ - nodes: [createTestNode()], - connections: {}, + const { filterConnectionsByNodes } = useCanvasOperations({ router }); + const result = filterConnectionsByNodes(connections, includeNodeNames); + + expect(result).toEqual({ + [NodeConnectionType.Main]: [[], []], + }); }); - const { initializeWorkspace } = useCanvasOperations({ router }); - initializeWorkspace(workflow); + it('should return partially filtered connections when some nodes are included', () => { + const connections: INodeConnections = { + [NodeConnectionType.Main]: [ + [ + { node: 'node1', type: NodeConnectionType.Main, index: 0 }, + { node: 'node2', type: NodeConnectionType.Main, index: 0 }, + ], + [{ node: 'node3', type: NodeConnectionType.Main, index: 0 }], + ], + }; + const includeNodeNames = new Set(['node1']); - expect(workflow.nodes[0].parameters).toEqual({ value: true }); + const { filterConnectionsByNodes } = useCanvasOperations({ router }); + const result = filterConnectionsByNodes(connections, includeNodeNames); + + expect(result).toEqual({ + [NodeConnectionType.Main]: [ + [{ node: 'node1', type: NodeConnectionType.Main, index: 0 }], + [], + ], + }); + }); + + it('should handle empty connections input', () => { + const connections: INodeConnections = {}; + const includeNodeNames = new Set(['node1']); + + const { filterConnectionsByNodes } = useCanvasOperations({ router }); + const result = filterConnectionsByNodes(connections, includeNodeNames); + + expect(result).toEqual({}); + }); + + it('should handle connections with no valid nodes', () => { + const connections: INodeConnections = { + [NodeConnectionType.Main]: [ + [ + { node: 'node4', type: NodeConnectionType.Main, index: 0 }, + { node: 'node5', type: NodeConnectionType.Main, index: 0 }, + ], + [{ node: 'node6', type: NodeConnectionType.Main, index: 0 }], + ], + }; + const includeNodeNames = new Set(['node1', 'node2', 'node3']); + + const { filterConnectionsByNodes } = useCanvasOperations({ router }); + const result = filterConnectionsByNodes(connections, includeNodeNames); + + expect(result).toEqual({ + [NodeConnectionType.Main]: [[], []], + }); + }); }); }); diff --git a/packages/editor-ui/src/composables/useCanvasOperations.ts b/packages/editor-ui/src/composables/useCanvasOperations.ts index cbec5b44df..e4400ea665 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.ts +++ b/packages/editor-ui/src/composables/useCanvasOperations.ts @@ -1809,17 +1809,15 @@ export function useCanvasOperations({ router }: { router: ReturnType, + connections: INodeConnections, includeNodeNames: Set, ): INodeConnections { const filteredConnections: INodeConnections = {}; for (const [type, typeConnections] of Object.entries(connections)) { - const validConnections = typeConnections - .map((sourceConnections) => - sourceConnections.filter((connection) => includeNodeNames.has(connection.node)), - ) - .filter((sourceConnections) => sourceConnections.length > 0); + const validConnections = typeConnections.map((sourceConnections) => + sourceConnections.filter((connection) => includeNodeNames.has(connection.node)), + ); if (validConnections.length) { filteredConnections[type] = validConnections; @@ -1888,6 +1886,7 @@ export function useCanvasOperations({ router }: { router: ReturnType