mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Connect adjacent nodes when a node is deleted in canvas v2 (no-changelog) (#10125)
This commit is contained in:
parent
b1816db449
commit
a036aa43dc
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue