diff --git a/packages/editor-ui/src/composables/__tests__/useCanvasOperations.spec.ts b/packages/editor-ui/src/composables/__tests__/useCanvasOperations.spec.ts index 4efaf81615..e59cffa552 100644 --- a/packages/editor-ui/src/composables/__tests__/useCanvasOperations.spec.ts +++ b/packages/editor-ui/src/composables/__tests__/useCanvasOperations.spec.ts @@ -398,6 +398,70 @@ describe('useCanvasOperations', () => { expect(removeNodeExecutionDataByIdSpy).toHaveBeenCalledWith(id); expect(pushCommandToUndoSpy).not.toHaveBeenCalled(); }); + + it('should connect adjacent nodes when deleting a node surrounded by other nodes', () => { + nodeTypesStore.setNodeTypes([mockNodeTypeDescription({ name: 'node' })]); + const nodes = [ + createTestNode({ + id: 'input', + type: 'node', + position: [10, 20], + name: 'Input Node', + }), + createTestNode({ + id: 'middle', + type: 'node', + position: [10, 20], + name: 'Middle Node', + }), + createTestNode({ + id: 'output', + type: 'node', + position: [10, 20], + name: 'Output Node', + }), + ]; + workflowsStore.setNodes(nodes); + workflowsStore.setConnections({ + 'Input Node': { + main: [ + [ + { + node: 'Middle Node', + type: NodeConnectionType.Main, + index: 0, + }, + ], + ], + }, + 'Middle Node': { + main: [ + [ + { + node: 'Output Node', + type: NodeConnectionType.Main, + index: 0, + }, + ], + ], + }, + }); + + canvasOperations.deleteNode('middle'); + expect(workflowsStore.allConnections).toEqual({ + 'Input Node': { + main: [ + [ + { + node: 'Output Node', + type: NodeConnectionType.Main, + index: 0, + }, + ], + ], + }, + }); + }); }); describe('revertDeleteNode', () => { diff --git a/packages/editor-ui/src/composables/useCanvasOperations.ts b/packages/editor-ui/src/composables/useCanvasOperations.ts index 6bf91358f2..b467a7238f 100644 --- a/packages/editor-ui/src/composables/useCanvasOperations.ts +++ b/packages/editor-ui/src/composables/useCanvasOperations.ts @@ -74,6 +74,7 @@ import type { IPinData, ITelemetryTrackProperties, IWorkflowBase, + NodeInputConnections, NodeParameterValueType, Workflow, } from 'n8n-workflow'; @@ -202,6 +203,49 @@ export function useCanvasOperations({ await renameNode(currentName, previousName); } + function connectAdjacentNodes(id: string) { + const node = workflowsStore.getNodeById(id); + + if (!node) { + return; + } + + const outputConnectionsByType = workflowsStore.outgoingConnectionsByNodeName(node.name); + const incomingConnectionsByType = workflowsStore.incomingConnectionsByNodeName(node.name); + + for (const [type, incomingConnectionsByInputIndex] of Object.entries( + incomingConnectionsByType, + ) as Array<[NodeConnectionType, NodeInputConnections]>) { + // Only connect nodes connected to the first input of a type + for (const incomingConnection of incomingConnectionsByInputIndex.at(0) ?? []) { + const incomingNodeId = workflowsStore.getNodeByName(incomingConnection.node)?.id; + + if (!incomingNodeId) continue; + + // Only connect to nodes connected to the first output of a type + // For example on an If node, connect to the "true" main output + for (const outgoingConnection of outputConnectionsByType[type]?.at(0) ?? []) { + const outgoingNodeId = workflowsStore.getNodeByName(outgoingConnection.node)?.id; + + if (!outgoingNodeId) continue; + + createConnection({ + source: incomingNodeId, + sourceHandle: createCanvasConnectionHandleString({ + mode: CanvasConnectionMode.Output, + type, + }), + target: outgoingNodeId, + targetHandle: createCanvasConnectionHandleString({ + mode: CanvasConnectionMode.Input, + type, + }), + }); + } + } + } + } + function deleteNode(id: string, { trackHistory = false, trackBulk = true } = {}) { const node = workflowsStore.getNodeById(id); if (!node) { @@ -212,6 +256,7 @@ export function useCanvasOperations({ historyStore.startRecordingUndo(); } + connectAdjacentNodes(id); workflowsStore.removeNodeConnectionsById(id); workflowsStore.removeNodeExecutionDataById(id); workflowsStore.removeNodeById(id); diff --git a/packages/editor-ui/src/stores/workflows.store.ts b/packages/editor-ui/src/stores/workflows.store.ts index 075e552680..18fc0261bf 100644 --- a/packages/editor-ui/src/stores/workflows.store.ts +++ b/packages/editor-ui/src/stores/workflows.store.ts @@ -224,6 +224,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { return {}; } + function incomingConnectionsByNodeName(nodeName: string): INodeConnections { + return getCurrentWorkflow().connectionsByDestinationNode[nodeName] ?? {}; + } + function nodeHasOutputConnection(nodeName: string): boolean { return workflow.value.connections.hasOwnProperty(nodeName); } @@ -885,7 +889,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { uiStore.stateIsDirty = true; // Remove all source connections - if (!preserveOutputConnections && workflow.value.connections.hasOwnProperty(node.name)) { + if (!preserveOutputConnections) { delete workflow.value.connections[node.name]; } @@ -1583,6 +1587,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => { getTotalFinishedExecutionsCount, getPastChatMessages, outgoingConnectionsByNodeName, + incomingConnectionsByNodeName, nodeHasOutputConnection, isNodeInOutgoingNodeConnections, getWorkflowById,