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(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', () => {

View file

@ -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);

View file

@ -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,