fix: Load workflows with unconnected Switch outputs (#12020)

This commit is contained in:
Mutasem Aldmour
2024-12-04 13:44:25 +01:00
committed by GitHub
parent bd693162b8
commit abc851c0cf
24 changed files with 324 additions and 89 deletions

View File

@@ -76,6 +76,10 @@ export function getCanvasNodes() {
);
}
export function getCanvasNodeByName(nodeName: string) {
return getCanvasNodes().filter(`:contains(${nodeName})`);
}
export function getSaveButton() {
return cy.getByTestId('workflow-save-button');
}
@@ -194,3 +198,8 @@ export function pasteWorkflow(workflow: object) {
export function clickZoomToFit() {
getZoomToFitButton().click();
}
export function deleteNode(name: string) {
getCanvasNodeByName(name).first().click();
cy.get('body').type('{del}');
}

View File

@@ -0,0 +1,17 @@
import {
deleteNode,
getCanvasNodes,
navigateToNewWorkflowPage,
pasteWorkflow,
} from '../composables/workflow';
import Workflow from '../fixtures/Switch_node_with_null_connection.json';
describe('ADO-2929 can load Switch nodes', () => {
it('can load workflows with Switch nodes with null at connection index', () => {
navigateToNewWorkflowPage();
pasteWorkflow(Workflow);
getCanvasNodes().should('have.length', 3);
deleteNode('Switch');
getCanvasNodes().should('have.length', 2);
});
});

View File

@@ -0,0 +1,85 @@
{
"nodes": [
{
"parameters": {},
"id": "418350b8-b402-4d3b-93ba-3794d36c1ad5",
"name": "When clicking \"Test workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [440, 380]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "",
"rightValue": "",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
{},
{}
]
},
"options": {}
},
"id": "b67ad46f-6b0d-4ff4-b2d2-dfbde44e287c",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [660, 380]
},
{
"parameters": {
"options": {}
},
"id": "24731c11-e2a4-4854-81a6-277ce72e8a93",
"name": "Edit Fields",
"type": "n8n-nodes-base.set",
"typeVersion": 3.3,
"position": [840, 480]
}
],
"connections": {
"When clicking \"Test workflow\"": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
null,
null,
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {}
}

View File

@@ -38,7 +38,7 @@ export class PurgeInvalidWorkflowConnections1675940580449 implements Irreversibl
// It filters out all connections that are connected to a node that cannot receive input
outputConnection.forEach((outputConnectionItem, outputConnectionItemIdx) => {
outputConnection[outputConnectionItemIdx] = outputConnectionItem.filter(
outputConnection[outputConnectionItemIdx] = (outputConnectionItem ?? []).filter(
(outgoingConnections) =>
!nodesThatCannotReceiveInput.includes(outgoingConnections.node),
);

View File

@@ -119,7 +119,7 @@ export class InstanceRiskReporter implements RiskReporter {
node: WorkflowEntity['nodes'][number];
workflow: WorkflowEntity;
}) {
const childNodeNames = workflow.connections[node.name]?.main[0].map((i) => i.node);
const childNodeNames = workflow.connections[node.name]?.main[0]?.map((i) => i.node);
if (!childNodeNames) return false;

View File

@@ -448,7 +448,7 @@ export class DirectedGraph {
for (const [outputType, outputs] of Object.entries(iConnection)) {
for (const [outputIndex, conns] of outputs.entries()) {
for (const conn of conns) {
for (const conn of conns ?? []) {
// TODO: What's with the input type?
const { node: toNodeName, type: _inputType, index: inputIndex } = conn;
const to = workflow.getNode(toNodeName);

View File

@@ -42,6 +42,28 @@ describe('DirectedGraph', () => {
);
});
// ┌─────┐ ┌─────┐──► null
// │node1├───►│node2| ┌─────┐
// └─────┘ └─────┘──►│node3|
// └─────┘
//
test('linear workflow with null connections', () => {
// ARRANGE
const node1 = createNodeData({ name: 'Node1' });
const node2 = createNodeData({ name: 'Node2' });
const node3 = createNodeData({ name: 'Node3' });
// ACT
const graph = new DirectedGraph()
.addNodes(node1, node2, node3)
.addConnections({ from: node1, to: node2 }, { from: node2, to: node3, outputIndex: 1 });
// ASSERT
expect(DirectedGraph.fromWorkflow(graph.toWorkflow({ ...defaultWorkflowParameter }))).toEqual(
graph,
);
});
describe('getChildren', () => {
// ┌─────┐ ┌─────┐ ┌─────┐
// │node1├───►│node2├──►│node3│

View File

@@ -36,6 +36,7 @@ import type {
CloseFunction,
StartNodeData,
NodeExecutionHint,
NodeInputConnections,
} from 'n8n-workflow';
import {
LoggerProxy as Logger,
@@ -208,6 +209,9 @@ export class WorkflowExecute {
// Get the data of the incoming connections
incomingSourceData = { main: [] };
for (const connections of incomingNodeConnections.main) {
if (!connections) {
continue;
}
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
connection = connections[inputIndex];
@@ -249,6 +253,9 @@ export class WorkflowExecute {
incomingNodeConnections = workflow.connectionsByDestinationNode[destinationNode];
if (incomingNodeConnections !== undefined) {
for (const connections of incomingNodeConnections.main) {
if (!connections) {
continue;
}
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
connection = connections[inputIndex];
@@ -642,7 +649,7 @@ export class WorkflowExecute {
}
for (const connectionDataCheck of workflow.connectionsBySourceNode[parentNodeName].main[
outputIndexParent
]) {
] ?? []) {
checkOutputNodes.push(connectionDataCheck.node);
}
}
@@ -661,7 +668,7 @@ export class WorkflowExecute {
) {
for (const inputData of workflow.connectionsByDestinationNode[connectionData.node].main[
inputIndex
]) {
] ?? []) {
if (inputData.node === parentNodeName) {
// Is the node we come from so its data will be available for sure
continue;
@@ -681,7 +688,7 @@ export class WorkflowExecute {
if (
!this.incomingConnectionIsEmpty(
this.runExecutionData.resultData.runData,
workflow.connectionsByDestinationNode[inputData.node].main[0],
workflow.connectionsByDestinationNode[inputData.node].main[0] ?? [],
runIndex,
)
) {
@@ -770,7 +777,7 @@ export class WorkflowExecute {
} else if (
this.incomingConnectionIsEmpty(
this.runExecutionData.resultData.runData,
workflow.connectionsByDestinationNode[nodeToAdd].main[0],
workflow.connectionsByDestinationNode[nodeToAdd].main[0] ?? [],
runIndex,
)
) {
@@ -1066,7 +1073,7 @@ export class WorkflowExecute {
if (workflow.connectionsByDestinationNode.hasOwnProperty(executionNode.name)) {
// Check if the node has incoming connections
if (workflow.connectionsByDestinationNode[executionNode.name].hasOwnProperty('main')) {
let inputConnections: IConnection[][];
let inputConnections: NodeInputConnections;
let connectionIndex: number;
// eslint-disable-next-line prefer-const
@@ -1586,7 +1593,7 @@ export class WorkflowExecute {
// Iterate over all the different connections of this output
for (connectionData of workflow.connectionsBySourceNode[executionNode.name].main[
outputIndex
]) {
] ?? []) {
if (!workflow.nodes.hasOwnProperty(connectionData.node)) {
throw new ApplicationError('Destination node not found', {
extra: {

View File

@@ -7,6 +7,7 @@
"clean": "rimraf dist .turbo",
"build": "cross-env VUE_APP_PUBLIC_PATH=\"/{{BASE_PATH}}/\" NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
"typecheck": "vue-tsc --noEmit",
"typecheck:watch": "vue-tsc --watch --noEmit",
"dev": "pnpm serve",
"lint": "eslint src --ext .js,.ts,.vue --quiet",
"lintfix": "eslint src --ext .js,.ts,.vue --fix",

View File

@@ -256,7 +256,7 @@ export function useChatMessaging({
];
if (!connectedMemoryInputs) return [];
const memoryConnection = (connectedMemoryInputs ?? []).find((i) => i.length > 0)?.[0];
const memoryConnection = (connectedMemoryInputs ?? []).find((i) => (i ?? []).length > 0)?.[0];
if (!memoryConnection) return [];

View File

@@ -176,7 +176,7 @@ function useJsonFieldCompletions() {
if (activeNode) {
const workflow = workflowsStore.getCurrentWorkflow();
const input = workflow.connectionsByDestinationNode[activeNode.name];
return input.main[0][0].node;
return input.main[0] ? input.main[0][0].node : null;
}
} catch (e) {
console.error(e);

View File

@@ -86,7 +86,7 @@ function getMultipleNodesText(nodeName: string): string {
props.workflow.connectionsByDestinationNode[activeNode.value.name].main || [];
// Collect indexes of connected nodes
const connectedInputIndexes = activeNodeConnections.reduce((acc: number[], node, index) => {
if (node[0] && node[0].node === nodeName) return [...acc, index];
if (node?.[0] && node[0].node === nodeName) return [...acc, index];
return acc;
}, []);

View File

@@ -238,7 +238,7 @@ describe('useCanvasMapping', () => {
expect(
mappedNodes.value[0]?.data?.connections[CanvasConnectionMode.Output][
NodeConnectionType.Main
][0][0],
][0]?.[0],
).toEqual(
expect.objectContaining({
node: setNode.name,
@@ -253,7 +253,7 @@ describe('useCanvasMapping', () => {
expect(
mappedNodes.value[1]?.data?.connections[CanvasConnectionMode.Input][
NodeConnectionType.Main
][0][0],
][0]?.[0],
).toEqual(
expect.objectContaining({
node: manualTriggerNode.name,

View File

@@ -802,6 +802,82 @@ describe('useCanvasOperations', () => {
expect(workflowsStore.removeNodeExecutionDataById).toHaveBeenCalledWith(nodes[1].id);
expect(workflowsStore.removeNodeById).toHaveBeenCalledWith(nodes[1].id);
});
it('should handle nodes with null connections for unconnected indexes', () => {
const workflowsStore = mockedStore(useWorkflowsStore);
const nodeTypesStore = mockedStore(useNodeTypesStore);
nodeTypesStore.nodeTypes = {
[SET_NODE_TYPE]: { 1: mockNodeTypeDescription({ name: SET_NODE_TYPE }) },
};
const nodes = [
createTestNode({
id: 'input',
type: SET_NODE_TYPE,
position: [10, 20],
name: 'Input Node',
}),
createTestNode({
id: 'middle',
type: SET_NODE_TYPE,
position: [10, 20],
name: 'Middle Node',
}),
createTestNode({
id: 'output',
type: SET_NODE_TYPE,
position: [10, 20],
name: 'Output Node',
}),
];
workflowsStore.getNodeByName = vi
.fn()
.mockImplementation((name: string) => nodes.find((node) => node.name === name));
workflowsStore.workflow.nodes = nodes;
workflowsStore.workflow.connections = {
[nodes[0].name]: {
main: [
null,
[
{
node: nodes[1].name,
type: NodeConnectionType.Main,
index: 0,
},
],
],
},
[nodes[1].name]: {
main: [
// null here to simulate no connection at index
null,
[
{
node: nodes[2].name,
type: NodeConnectionType.Main,
index: 0,
},
],
],
},
};
const workflowObject = createTestWorkflowObject(workflowsStore.workflow);
workflowsStore.getCurrentWorkflow.mockReturnValue(workflowObject);
workflowsStore.incomingConnectionsByNodeName.mockReturnValue({});
workflowsStore.getNodeById.mockReturnValue(nodes[1]);
const { deleteNode } = useCanvasOperations({ router });
deleteNode(nodes[1].id);
expect(workflowsStore.removeNodeById).toHaveBeenCalledWith(nodes[1].id);
expect(workflowsStore.removeNodeExecutionDataById).toHaveBeenCalledWith(nodes[1].id);
expect(workflowsStore.removeNodeById).toHaveBeenCalledWith(nodes[1].id);
});
});
describe('revertDeleteNode', () => {

View File

@@ -1149,11 +1149,9 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
for (const type of Object.keys(connections[nodeName])) {
for (const index of Object.keys(connections[nodeName][type])) {
for (const connectionIndex of Object.keys(
connections[nodeName][type][parseInt(index, 10)],
)) {
const connectionData =
connections[nodeName][type][parseInt(index, 10)][parseInt(connectionIndex, 10)];
const connectionsToDelete = connections[nodeName][type][parseInt(index, 10)] ?? [];
for (const connectionIndex of Object.keys(connectionsToDelete)) {
const connectionData = connectionsToDelete[parseInt(connectionIndex, 10)];
if (!connectionData) {
continue;
}
@@ -1490,13 +1488,14 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
sourceIndex++
) {
const nodeSourceConnections = [];
if (currentConnections[sourceNode][type][sourceIndex]) {
const connectionsToCheck = currentConnections[sourceNode][type][sourceIndex];
if (connectionsToCheck) {
for (
connectionIndex = 0;
connectionIndex < currentConnections[sourceNode][type][sourceIndex].length;
connectionIndex < connectionsToCheck.length;
connectionIndex++
) {
connectionData = currentConnections[sourceNode][type][sourceIndex][connectionIndex];
connectionData = connectionsToCheck[connectionIndex];
if (!createNodeNames.includes(connectionData.node)) {
// Node does not get created so skip input connection
continue;
@@ -1814,7 +1813,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
for (const [type, typeConnections] of Object.entries(connections)) {
const validConnections = typeConnections.map((sourceConnections) =>
sourceConnections.filter((connection) => includeNodeNames.has(connection.node)),
(sourceConnections ?? []).filter((connection) => includeNodeNames.has(connection.node)),
);
if (validConnections.length) {

View File

@@ -413,7 +413,7 @@ export function executeData(
mainConnections: for (const mainConnections of workflow.connectionsByDestinationNode[
currentNode
].main) {
for (const connection of mainConnections) {
for (const connection of mainConnections ?? []) {
if (
connection.type === NodeConnectionType.Main &&
connection.node === parentNodeName

View File

@@ -156,7 +156,7 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
if (!activeNodeConections || activeNodeConections.length < 2) return returnData;
for (const [index, connection] of activeNodeConections.entries()) {
for (const node of connection) {
for (const node of connection ?? []) {
if (!returnData[node.node]) {
returnData[node.node] = [];
}

View File

@@ -891,23 +891,28 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
let propertyName: keyof IConnection;
let connectionExists = false;
connectionLoop: for (const existingConnection of workflow.value.connections[sourceData.node][
sourceData.type
][sourceData.index]) {
for (propertyName of checkProperties) {
if (existingConnection[propertyName] !== destinationData[propertyName]) {
continue connectionLoop;
const nodeConnections = workflow.value.connections[sourceData.node][sourceData.type];
const connectionsToCheck = nodeConnections[sourceData.index];
if (connectionsToCheck) {
connectionLoop: for (const existingConnection of connectionsToCheck) {
for (propertyName of checkProperties) {
if (existingConnection[propertyName] !== destinationData[propertyName]) {
continue connectionLoop;
}
}
connectionExists = true;
break;
}
connectionExists = true;
break;
}
// Add the new connection if it does not exist already
if (!connectionExists) {
workflow.value.connections[sourceData.node][sourceData.type][sourceData.index].push(
destinationData,
);
nodeConnections[sourceData.index] = nodeConnections[sourceData.index] ?? [];
const connections = nodeConnections[sourceData.index];
if (connections) {
connections.push(destinationData);
}
}
}
@@ -934,6 +939,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
const connections =
workflow.value.connections[sourceData.node][sourceData.type][sourceData.index];
if (!connections) {
return;
}
for (const index in connections) {
if (
connections[index].node === destinationData.node &&
@@ -979,23 +988,19 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
for (type of Object.keys(workflow.value.connections[sourceNode])) {
for (sourceIndex of Object.keys(workflow.value.connections[sourceNode][type])) {
indexesToRemove.length = 0;
for (connectionIndex of Object.keys(
workflow.value.connections[sourceNode][type][parseInt(sourceIndex, 10)],
)) {
connectionData =
workflow.value.connections[sourceNode][type][parseInt(sourceIndex, 10)][
parseInt(connectionIndex, 10)
];
if (connectionData.node === node.name) {
indexesToRemove.push(connectionIndex);
const connectionsToRemove =
workflow.value.connections[sourceNode][type][parseInt(sourceIndex, 10)];
if (connectionsToRemove) {
for (connectionIndex of Object.keys(connectionsToRemove)) {
connectionData = connectionsToRemove[parseInt(connectionIndex, 10)];
if (connectionData.node === node.name) {
indexesToRemove.push(connectionIndex);
}
}
indexesToRemove.forEach((index) => {
connectionsToRemove.splice(parseInt(index, 10), 1);
});
}
indexesToRemove.forEach((index) => {
workflow.value.connections[sourceNode][type][parseInt(sourceIndex, 10)].splice(
parseInt(index, 10),
1,
);
});
}
}
}

View File

@@ -56,7 +56,7 @@ describe('updateDynamicConnections', () => {
const updatedConnections = updateDynamicConnections(node, connections, parameterData);
expect(updatedConnections?.TestNode.main).toHaveLength(2);
expect(updatedConnections?.TestNode.main[1][0].node).toEqual('Node3');
expect(updatedConnections?.TestNode.main[1]?.[0].node).toEqual('Node3');
});
it('should handle fallbackOutput === "extra" and all rules removed', () => {
@@ -82,7 +82,7 @@ describe('updateDynamicConnections', () => {
const updatedConnections = updateDynamicConnections(node, connections, parameterData);
expect(updatedConnections?.TestNode.main).toHaveLength(1);
expect(updatedConnections?.TestNode.main[0][0].node).toEqual('Node3');
expect(updatedConnections?.TestNode.main[0]?.[0].node).toEqual('Node3');
});
it('should add a new connection when a rule is added', () => {
@@ -137,7 +137,7 @@ describe('updateDynamicConnections', () => {
expect(updatedConnections?.TestNode.main).toHaveLength(4);
expect(updatedConnections?.TestNode.main[2]).toEqual([]);
expect(updatedConnections?.TestNode.main[3][0].node).toEqual('Node3');
expect(updatedConnections?.TestNode.main[3]?.[0].node).toEqual('Node3');
});
it('should return null if no conditions are met', () => {

View File

@@ -77,7 +77,7 @@ export function updateDynamicConnections(
}
} else if (parameterData.name === 'parameters.rules.values') {
const curentRulesvalues = (node.parameters?.rules as { values: IDataObject[] })?.values;
let lastConnection: IConnection[] | undefined = undefined;
let lastConnection: IConnection[] | null | undefined = undefined;
if (
fallbackOutput === 'extra' &&
connections[node.name].main.length === curentRulesvalues.length + 1

View File

@@ -1394,7 +1394,11 @@ export default defineComponent({
lastSelectedNode.name,
);
if (connections.main === undefined || connections.main.length === 0) {
if (
connections.main === undefined ||
connections.main.length === 0 ||
!connections.main[0]
) {
return;
}
@@ -1428,7 +1432,11 @@ export default defineComponent({
const connections = workflow.connectionsByDestinationNode[lastSelectedNode.name];
if (connections.main === undefined || connections.main.length === 0) {
if (
connections.main === undefined ||
connections.main.length === 0 ||
!connections.main[0]
) {
return;
}
@@ -1460,7 +1468,11 @@ export default defineComponent({
return;
}
const parentNode = connections.main[0][0].node;
const parentNode = connections.main[0]?.[0].node;
if (!parentNode) {
return;
}
const connectionsParent = this.workflowsStore.outgoingConnectionsByNodeName(parentNode);
if (!Array.isArray(connectionsParent.main) || !connectionsParent.main.length) {
@@ -1472,7 +1484,7 @@ export default defineComponent({
let lastCheckedNodePosition = e.key === 'ArrowUp' ? -99999999 : 99999999;
let nextSelectNode: string | null = null;
for (const ouputConnections of connectionsParent.main) {
for (const ouputConnection of ouputConnections) {
for (const ouputConnection of ouputConnections ?? []) {
if (ouputConnection.node === lastSelectedNode.name) {
// Ignore current node
continue;
@@ -3877,13 +3889,10 @@ export default defineComponent({
sourceIndex++
) {
const nodeSourceConnections = [];
if (currentConnections[sourceNode][type][sourceIndex]) {
for (
connectionIndex = 0;
connectionIndex < currentConnections[sourceNode][type][sourceIndex].length;
connectionIndex++
) {
connectionData = currentConnections[sourceNode][type][sourceIndex][connectionIndex];
const connections = currentConnections[sourceNode][type][sourceIndex];
if (connections) {
for (connectionIndex = 0; connectionIndex < connections.length; connectionIndex++) {
connectionData = connections[connectionIndex];
if (!createNodeNames.includes(connectionData.node)) {
// Node does not get created so skip input connection
continue;
@@ -4013,14 +4022,17 @@ export default defineComponent({
for (type of Object.keys(connections)) {
for (sourceIndex = 0; sourceIndex < connections[type].length; sourceIndex++) {
connectionToKeep = [];
for (
connectionIndex = 0;
connectionIndex < connections[type][sourceIndex].length;
connectionIndex++
) {
connectionData = connections[type][sourceIndex][connectionIndex];
if (exportNodeNames.indexOf(connectionData.node) !== -1) {
connectionToKeep.push(connectionData);
const connectionsToCheck = connections[type][sourceIndex];
if (connectionsToCheck) {
for (
connectionIndex = 0;
connectionIndex < connectionsToCheck.length;
connectionIndex++
) {
connectionData = connectionsToCheck[connectionIndex];
if (exportNodeNames.indexOf(connectionData.node) !== -1) {
connectionToKeep.push(connectionData);
}
}
}

View File

@@ -364,7 +364,8 @@ export interface ICredentialDataDecryptedObject {
// First array index: The output/input-index (if node has multiple inputs/outputs of the same type)
// Second array index: The different connections (if one node is connected to multiple nodes)
export type NodeInputConnections = IConnection[][];
// Any index can be null, for example in a switch node with multiple indexes some of which are not connected
export type NodeInputConnections = Array<IConnection[] | null>;
export interface INodeConnection {
sourceIndex: number;

View File

@@ -194,7 +194,7 @@ export class Workflow {
returnConnection[connectionInfo.node][connectionInfo.type].push([]);
}
returnConnection[connectionInfo.node][connectionInfo.type][connectionInfo.index].push({
returnConnection[connectionInfo.node][connectionInfo.type][connectionInfo.index]?.push({
node: sourceNode,
type,
index: parseInt(inputIndex, 10),
@@ -551,18 +551,18 @@ export class Workflow {
let type: string;
let sourceIndex: string;
let connectionIndex: string;
let connectionData: IConnection;
let connectionData: IConnection | undefined;
for (sourceNode of Object.keys(this.connectionsBySourceNode)) {
for (type of Object.keys(this.connectionsBySourceNode[sourceNode])) {
for (sourceIndex of Object.keys(this.connectionsBySourceNode[sourceNode][type])) {
for (connectionIndex of Object.keys(
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)],
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)] || [],
)) {
connectionData =
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)][
this.connectionsBySourceNode[sourceNode][type][parseInt(sourceIndex, 10)]?.[
parseInt(connectionIndex, 10)
];
if (connectionData.node === currentName) {
if (connectionData?.node === currentName) {
connectionData.node = newName;
}
}
@@ -615,7 +615,7 @@ export class Workflow {
const returnNodes: string[] = [];
let addNodes: string[];
let connectionsByIndex: IConnection[];
let connectionsByIndex: IConnection[] | null;
for (
let connectionIndex = 0;
connectionIndex < this.connectionsByDestinationNode[nodeName][type].length;
@@ -627,7 +627,7 @@ export class Workflow {
}
connectionsByIndex = this.connectionsByDestinationNode[nodeName][type][connectionIndex];
// eslint-disable-next-line @typescript-eslint/no-loop-func
connectionsByIndex.forEach((connection) => {
connectionsByIndex?.forEach((connection) => {
if (checkedNodes.includes(connection.node)) {
// Node got checked already before
return;
@@ -742,7 +742,7 @@ export class Workflow {
checkedNodes.push(nodeName);
connections[nodeName][type].forEach((connectionsByIndex) => {
connectionsByIndex.forEach((connection) => {
connectionsByIndex?.forEach((connection) => {
if (checkedNodes.includes(connection.node)) {
// Node got checked already before
return;
@@ -839,7 +839,7 @@ export class Workflow {
}
connections[curr.name][type].forEach((connectionsByIndex) => {
connectionsByIndex.forEach((connection) => {
connectionsByIndex?.forEach((connection) => {
queue.push({
name: connection.node,
indicies: [connection.index],
@@ -943,6 +943,10 @@ export class Workflow {
let outputIndex: INodeConnection | undefined;
for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
if (!connectionsByIndex) {
continue;
}
for (
let destinationIndex = 0;
destinationIndex < connectionsByIndex.length;

View File

@@ -990,11 +990,8 @@ function alphanumericId() {
const chooseRandomly = <T>(array: T[]) => array[randomInt(array.length)];
function generateTestWorkflowAndRunData(): { workflow: IWorkflowBase; runData: IRunData } {
const workflow: IWorkflowBase = {
meta: {
instanceId: 'a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0',
},
function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; runData: IRunData } {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {},