feat(editor): Connect adjacent nodes when a node is deleted in canvas v2 (no-changelog) (#10125)

This commit is contained in:
Elias Meire 2024-07-22 12:41:43 +02:00 committed by GitHub
parent b1816db449
commit a036aa43dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 115 additions and 1 deletions

View file

@ -398,6 +398,70 @@ describe('useCanvasOperations', () => {
expect(removeNodeExecutionDataByIdSpy).toHaveBeenCalledWith(id); expect(removeNodeExecutionDataByIdSpy).toHaveBeenCalledWith(id);
expect(pushCommandToUndoSpy).not.toHaveBeenCalled(); 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', () => { describe('revertDeleteNode', () => {

View file

@ -74,6 +74,7 @@ import type {
IPinData, IPinData,
ITelemetryTrackProperties, ITelemetryTrackProperties,
IWorkflowBase, IWorkflowBase,
NodeInputConnections,
NodeParameterValueType, NodeParameterValueType,
Workflow, Workflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -202,6 +203,49 @@ export function useCanvasOperations({
await renameNode(currentName, previousName); 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 } = {}) { function deleteNode(id: string, { trackHistory = false, trackBulk = true } = {}) {
const node = workflowsStore.getNodeById(id); const node = workflowsStore.getNodeById(id);
if (!node) { if (!node) {
@ -212,6 +256,7 @@ export function useCanvasOperations({
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
} }
connectAdjacentNodes(id);
workflowsStore.removeNodeConnectionsById(id); workflowsStore.removeNodeConnectionsById(id);
workflowsStore.removeNodeExecutionDataById(id); workflowsStore.removeNodeExecutionDataById(id);
workflowsStore.removeNodeById(id); workflowsStore.removeNodeById(id);

View file

@ -224,6 +224,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
return {}; return {};
} }
function incomingConnectionsByNodeName(nodeName: string): INodeConnections {
return getCurrentWorkflow().connectionsByDestinationNode[nodeName] ?? {};
}
function nodeHasOutputConnection(nodeName: string): boolean { function nodeHasOutputConnection(nodeName: string): boolean {
return workflow.value.connections.hasOwnProperty(nodeName); return workflow.value.connections.hasOwnProperty(nodeName);
} }
@ -885,7 +889,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
uiStore.stateIsDirty = true; uiStore.stateIsDirty = true;
// Remove all source connections // Remove all source connections
if (!preserveOutputConnections && workflow.value.connections.hasOwnProperty(node.name)) { if (!preserveOutputConnections) {
delete workflow.value.connections[node.name]; delete workflow.value.connections[node.name];
} }
@ -1583,6 +1587,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
getTotalFinishedExecutionsCount, getTotalFinishedExecutionsCount,
getPastChatMessages, getPastChatMessages,
outgoingConnectionsByNodeName, outgoingConnectionsByNodeName,
incomingConnectionsByNodeName,
nodeHasOutputConnection, nodeHasOutputConnection,
isNodeInOutgoingNodeConnections, isNodeInOutgoingNodeConnections,
getWorkflowById, getWorkflowById,