mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(core)!: Change data processing for multi-input-nodes (#4238)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
9194d8bb0e
commit
b8458a53f6
|
@ -143,6 +143,26 @@ export class WorkflowExecute {
|
||||||
return this.processRunExecutionData(workflow);
|
return this.processRunExecutionData(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forceInputNodeExecution(workflow: Workflow, node: INode): boolean {
|
||||||
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
|
|
||||||
|
// Check if the incoming nodes should be forced to execute
|
||||||
|
let forceInputNodeExecution = nodeType.description.forceInputNodeExecution;
|
||||||
|
if (forceInputNodeExecution !== undefined) {
|
||||||
|
if (typeof forceInputNodeExecution === 'string') {
|
||||||
|
forceInputNodeExecution = !!workflow.expression.getSimpleParameterValue(
|
||||||
|
node,
|
||||||
|
forceInputNodeExecution,
|
||||||
|
this.mode,
|
||||||
|
this.additionalData.timezone,
|
||||||
|
{ $version: node.typeVersion },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return forceInputNodeExecution;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the given workflow but only
|
* Executes the given workflow but only
|
||||||
*
|
*
|
||||||
|
@ -329,6 +349,27 @@ export class WorkflowExecute {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareWaitingToExecution(nodeName: string, numberOfConnections: number, runIndex: number) {
|
||||||
|
if (!this.runExecutionData.executionData!.waitingExecutionSource) {
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.runExecutionData.executionData!.waitingExecution[nodeName][runIndex] = {
|
||||||
|
main: [],
|
||||||
|
};
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[nodeName][runIndex] = {
|
||||||
|
main: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < numberOfConnections; i++) {
|
||||||
|
this.runExecutionData.executionData!.waitingExecution[nodeName][runIndex].main.push(null);
|
||||||
|
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[nodeName][runIndex].main.push(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addNodeToBeExecuted(
|
addNodeToBeExecuted(
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
connectionData: IConnection,
|
connectionData: IConnection,
|
||||||
|
@ -338,6 +379,7 @@ export class WorkflowExecute {
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
): void {
|
): void {
|
||||||
let stillDataMissing = false;
|
let stillDataMissing = false;
|
||||||
|
let waitingNodeIndex: number | undefined;
|
||||||
|
|
||||||
// Check if node has multiple inputs as then we have to wait for all input data
|
// Check if node has multiple inputs as then we have to wait for all input data
|
||||||
// to be present before we can add it to the node-execution-stack
|
// to be present before we can add it to the node-execution-stack
|
||||||
|
@ -358,47 +400,64 @@ export class WorkflowExecute {
|
||||||
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node] = {};
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node] = {};
|
||||||
nodeWasWaiting = false;
|
nodeWasWaiting = false;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] ===
|
|
||||||
undefined
|
|
||||||
) {
|
|
||||||
// Node does not have data for runIndex yet so create also empty one and init it
|
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
|
||||||
main: [],
|
|
||||||
};
|
|
||||||
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][runIndex] =
|
|
||||||
{
|
|
||||||
main: [],
|
|
||||||
};
|
|
||||||
for (
|
|
||||||
let i = 0;
|
|
||||||
i < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
|
||||||
runIndex
|
|
||||||
].main.push(null);
|
|
||||||
|
|
||||||
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
// Figure out if the node is already waiting with partial data to which to add the
|
||||||
runIndex
|
// data to or if a new entry has to get created
|
||||||
].main.push(null);
|
let createNewWaitingEntry = true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node])
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
// Check if there is already data for the input on all of the waiting nodes
|
||||||
|
for (const index of Object.keys(
|
||||||
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node],
|
||||||
|
)) {
|
||||||
|
if (
|
||||||
|
!this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
|
parseInt(index)
|
||||||
|
].main[connectionData.index]
|
||||||
|
) {
|
||||||
|
// Data for the input is missing so we can add it to the existing entry
|
||||||
|
createNewWaitingEntry = false;
|
||||||
|
waitingNodeIndex = parseInt(index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (waitingNodeIndex === undefined) {
|
||||||
|
waitingNodeIndex = Object.values(
|
||||||
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node],
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createNewWaitingEntry) {
|
||||||
|
// There is currently no node waiting that does not already have data for
|
||||||
|
// the given input, so create a new entry
|
||||||
|
|
||||||
|
this.prepareWaitingToExecution(
|
||||||
|
connectionData.node,
|
||||||
|
workflow.connectionsByDestinationNode[connectionData.node].main.length,
|
||||||
|
waitingNodeIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Add the new data
|
// Add the new data
|
||||||
if (nodeSuccessData === null) {
|
if (nodeSuccessData === null) {
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
connectionData.index
|
waitingNodeIndex
|
||||||
] = null;
|
].main[connectionData.index] = null;
|
||||||
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
runIndex
|
waitingNodeIndex
|
||||||
].main[connectionData.index] = null;
|
].main[connectionData.index] = null;
|
||||||
} else {
|
} else {
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
connectionData.index
|
waitingNodeIndex
|
||||||
] = nodeSuccessData[outputIndex];
|
].main[connectionData.index] = nodeSuccessData[outputIndex];
|
||||||
|
|
||||||
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
runIndex
|
waitingNodeIndex
|
||||||
].main[connectionData.index] = {
|
].main[connectionData.index] = {
|
||||||
previousNode: parentNodeName,
|
previousNode: parentNodeName,
|
||||||
previousNodeOutput: outputIndex || undefined,
|
previousNodeOutput: outputIndex || undefined,
|
||||||
|
@ -412,14 +471,14 @@ export class WorkflowExecute {
|
||||||
for (
|
for (
|
||||||
let i = 0;
|
let i = 0;
|
||||||
i <
|
i <
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][waitingNodeIndex]
|
||||||
.length;
|
.main.length;
|
||||||
i++
|
i++
|
||||||
) {
|
) {
|
||||||
thisExecutionData =
|
thisExecutionData =
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
i
|
waitingNodeIndex
|
||||||
];
|
].main[i];
|
||||||
if (thisExecutionData === null) {
|
if (thisExecutionData === null) {
|
||||||
allDataFound = false;
|
allDataFound = false;
|
||||||
break;
|
break;
|
||||||
|
@ -433,11 +492,11 @@ export class WorkflowExecute {
|
||||||
const executionStackItem = {
|
const executionStackItem = {
|
||||||
node: workflow.nodes[connectionData.node],
|
node: workflow.nodes[connectionData.node],
|
||||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
runIndex
|
waitingNodeIndex
|
||||||
],
|
],
|
||||||
source:
|
source:
|
||||||
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
runIndex
|
waitingNodeIndex
|
||||||
],
|
],
|
||||||
} as IExecuteData;
|
} as IExecuteData;
|
||||||
|
|
||||||
|
@ -447,16 +506,18 @@ export class WorkflowExecute {
|
||||||
) {
|
) {
|
||||||
executionStackItem.source =
|
executionStackItem.source =
|
||||||
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
runIndex
|
waitingNodeIndex
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionStackItem);
|
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionStackItem);
|
||||||
|
|
||||||
// Remove the data from waiting
|
// Remove the data from waiting
|
||||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
|
waitingNodeIndex
|
||||||
|
];
|
||||||
delete this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
delete this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
runIndex
|
waitingNodeIndex
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -492,6 +553,10 @@ export class WorkflowExecute {
|
||||||
// checked. So we have to go through all the inputs and check if they
|
// checked. So we have to go through all the inputs and check if they
|
||||||
// are already on the list to be processed.
|
// are already on the list to be processed.
|
||||||
// If that is not the case add it.
|
// If that is not the case add it.
|
||||||
|
|
||||||
|
const node = workflow.getNode(connectionData.node);
|
||||||
|
const forceInputNodeExecution = this.forceInputNodeExecution(workflow, node!);
|
||||||
|
|
||||||
for (
|
for (
|
||||||
let inputIndex = 0;
|
let inputIndex = 0;
|
||||||
inputIndex < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
inputIndex < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||||
|
@ -540,6 +605,12 @@ export class WorkflowExecute {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!forceInputNodeExecution) {
|
||||||
|
// Do not automatically follow all incoming nodes and force them
|
||||||
|
// to execute
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if any of the parent nodes does not have any inputs. That
|
// Check if any of the parent nodes does not have any inputs. That
|
||||||
// would mean that it has to get added to the list of nodes to process.
|
// would mean that it has to get added to the list of nodes to process.
|
||||||
const parentNodes = workflow.getParentNodes(inputData.node, 'main', -1);
|
const parentNodes = workflow.getParentNodes(inputData.node, 'main', -1);
|
||||||
|
@ -650,14 +721,26 @@ export class WorkflowExecute {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stillDataMissing) {
|
if (stillDataMissing) {
|
||||||
|
waitingNodeIndex = waitingNodeIndex!;
|
||||||
|
|
||||||
// Additional data is needed to run node so add it to waiting
|
// Additional data is needed to run node so add it to waiting
|
||||||
if (
|
this.prepareWaitingToExecution(
|
||||||
!this.runExecutionData.executionData!.waitingExecution.hasOwnProperty(connectionData.node)
|
connectionData.node,
|
||||||
) {
|
workflow.connectionsByDestinationNode[connectionData.node].main.length,
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
waitingNodeIndex,
|
||||||
}
|
);
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
|
||||||
main: connectionDataArray,
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][waitingNodeIndex] =
|
||||||
|
{
|
||||||
|
main: connectionDataArray,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource![connectionData.node][
|
||||||
|
waitingNodeIndex
|
||||||
|
].main[connectionData.index] = {
|
||||||
|
previousNode: parentNodeName,
|
||||||
|
previousNodeOutput: outputIndex || undefined,
|
||||||
|
previousNodeRun: runIndex || undefined,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// All data is there so add it directly to stack
|
// All data is there so add it directly to stack
|
||||||
|
@ -854,6 +937,8 @@ export class WorkflowExecute {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const node = workflow.getNode(executionNode.name);
|
||||||
|
|
||||||
// Check if all the data which is needed to run the node is available
|
// Check if all the data which is needed to run the node is available
|
||||||
if (workflow.connectionsByDestinationNode.hasOwnProperty(executionNode.name)) {
|
if (workflow.connectionsByDestinationNode.hasOwnProperty(executionNode.name)) {
|
||||||
// Check if the node has incoming connections
|
// Check if the node has incoming connections
|
||||||
|
@ -886,17 +971,20 @@ export class WorkflowExecute {
|
||||||
continue executionLoop;
|
continue executionLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it has the data for all the inputs
|
if (this.forceInputNodeExecution(workflow, node!)) {
|
||||||
// The most nodes just have one but merge node for example has two and data
|
// Check if it has the data for all the inputs
|
||||||
// of both inputs has to be available to be able to process the node.
|
// The most nodes just have one but merge node for example has two and data
|
||||||
if (
|
// of both inputs has to be available to be able to process the node.
|
||||||
executionData.data.main.length < connectionIndex ||
|
if (
|
||||||
executionData.data.main[connectionIndex] === null
|
executionData.data.main.length < connectionIndex ||
|
||||||
) {
|
executionData.data.main[connectionIndex] === null
|
||||||
// Does not have the data of the connections so add back to stack
|
) {
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
// Does not have the data of the connections so add back to stack
|
||||||
lastExecutionTry = currentExecutionTry;
|
this.runExecutionData.executionData!.nodeExecutionStack.push(executionData);
|
||||||
continue executionLoop;
|
lastExecutionTry = currentExecutionTry;
|
||||||
|
|
||||||
|
continue executionLoop;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1207,9 +1295,16 @@ export class WorkflowExecute {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectionDestinationNode = workflow.getNode(connectionData.node);
|
||||||
|
const forceInputNodeExecution = this.forceInputNodeExecution(
|
||||||
|
workflow,
|
||||||
|
connectionDestinationNode!,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nodeSuccessData![outputIndex] &&
|
nodeSuccessData![outputIndex] &&
|
||||||
(nodeSuccessData![outputIndex].length !== 0 || connectionData.index > 0)
|
(nodeSuccessData![outputIndex].length !== 0 ||
|
||||||
|
(connectionData.index > 0 && forceInputNodeExecution))
|
||||||
) {
|
) {
|
||||||
// Add the node only if it did execute or if connected to second "optional" input
|
// Add the node only if it did execute or if connected to second "optional" input
|
||||||
const nodeToAdd = workflow.getNode(connectionData.node);
|
const nodeToAdd = workflow.getNode(connectionData.node);
|
||||||
|
@ -1260,6 +1355,163 @@ export class WorkflowExecute {
|
||||||
taskData,
|
taskData,
|
||||||
this.runExecutionData,
|
this.runExecutionData,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
let waitingNodes: string[] = Object.keys(
|
||||||
|
this.runExecutionData.executionData!.waitingExecution,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.runExecutionData.executionData!.nodeExecutionStack.length === 0 &&
|
||||||
|
waitingNodes.length
|
||||||
|
) {
|
||||||
|
// There are no more nodes in the execution stack. Check if there are
|
||||||
|
// waiting nodes that do not require data on all inputs and execute them,
|
||||||
|
// one by one.
|
||||||
|
|
||||||
|
// TODO: Should this also care about workflow position (top-left first?)
|
||||||
|
for (let i = 0; i < waitingNodes.length; i++) {
|
||||||
|
const nodeName = waitingNodes[i];
|
||||||
|
|
||||||
|
const checkNode = workflow.getNode(nodeName);
|
||||||
|
if (!checkNode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(
|
||||||
|
checkNode.type,
|
||||||
|
checkNode.typeVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if the node is only allowed execute if all inputs received data
|
||||||
|
let requiredInputs = nodeType.description.requiredInputs;
|
||||||
|
if (requiredInputs !== undefined) {
|
||||||
|
if (typeof requiredInputs === 'string') {
|
||||||
|
requiredInputs = workflow.expression.getSimpleParameterValue(
|
||||||
|
checkNode,
|
||||||
|
requiredInputs,
|
||||||
|
this.mode,
|
||||||
|
this.additionalData.timezone,
|
||||||
|
{ $version: checkNode.typeVersion },
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
) as number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(requiredInputs !== undefined &&
|
||||||
|
Array.isArray(requiredInputs) &&
|
||||||
|
requiredInputs.length === nodeType.description.inputs.length) ||
|
||||||
|
requiredInputs === nodeType.description.inputs.length
|
||||||
|
) {
|
||||||
|
// All inputs are required, but not all have data so do not continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentNodes = workflow.getParentNodes(nodeName);
|
||||||
|
|
||||||
|
// Check if input nodes (of same run) got already executed
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
const parentIsWaiting = parentNodes.some((value) => waitingNodes.includes(value));
|
||||||
|
if (parentIsWaiting) {
|
||||||
|
// Execute node later as one of its dependencies is still outstanding
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runIndexes = Object.keys(
|
||||||
|
this.runExecutionData.executionData!.waitingExecution[nodeName],
|
||||||
|
).sort();
|
||||||
|
|
||||||
|
// The run-index of the earliest outstanding one
|
||||||
|
const firstRunIndex = parseInt(runIndexes[0]);
|
||||||
|
|
||||||
|
// Find all the inputs which received any kind of data, even if it was an empty
|
||||||
|
// array as this shows that the parent nodes executed but they did not have any
|
||||||
|
// data to pass on.
|
||||||
|
const inputsWithData = this.runExecutionData
|
||||||
|
.executionData!.waitingExecution[nodeName][firstRunIndex].main.map((data, index) =>
|
||||||
|
data === null ? null : index,
|
||||||
|
)
|
||||||
|
.filter((data) => data !== null);
|
||||||
|
|
||||||
|
if (requiredInputs !== undefined) {
|
||||||
|
// Certain inputs are required that the node can execute
|
||||||
|
|
||||||
|
if (Array.isArray(requiredInputs)) {
|
||||||
|
// Specific inputs are required (array of input indexes)
|
||||||
|
let inputDataMissing = false;
|
||||||
|
for (const requiredInput of requiredInputs) {
|
||||||
|
if (!inputsWithData.includes(requiredInput)) {
|
||||||
|
inputDataMissing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inputDataMissing) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A certain amout of inputs are required (amount of inputs)
|
||||||
|
if (inputsWithData.length < requiredInputs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskDataMain = this.runExecutionData.executionData!.waitingExecution[nodeName][
|
||||||
|
firstRunIndex
|
||||||
|
].main.map((data) => {
|
||||||
|
// For the inputs for which never any data got received set it to an empty array
|
||||||
|
return data === null ? [] : data;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (taskDataMain.filter((data) => data.length).length !== 0) {
|
||||||
|
// Add the node to be executed
|
||||||
|
|
||||||
|
// Make sure that each input at least receives an empty array
|
||||||
|
if (taskDataMain.length < nodeType.description.inputs.length) {
|
||||||
|
for (; taskDataMain.length < nodeType.description.inputs.length; ) {
|
||||||
|
taskDataMain.push([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
||||||
|
node: workflow.nodes[nodeName],
|
||||||
|
data: {
|
||||||
|
main: taskDataMain,
|
||||||
|
},
|
||||||
|
source:
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource![nodeName][
|
||||||
|
firstRunIndex
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the node from waiting
|
||||||
|
delete this.runExecutionData.executionData!.waitingExecution[nodeName][firstRunIndex];
|
||||||
|
delete this.runExecutionData.executionData!.waitingExecutionSource![nodeName][
|
||||||
|
firstRunIndex
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.keys(this.runExecutionData.executionData!.waitingExecution[nodeName])
|
||||||
|
.length === 0
|
||||||
|
) {
|
||||||
|
// No more data left for the node so also delete that one
|
||||||
|
delete this.runExecutionData.executionData!.waitingExecution[nodeName];
|
||||||
|
delete this.runExecutionData.executionData!.waitingExecutionSource![nodeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskDataMain.filter((data) => data.length).length !== 0) {
|
||||||
|
// Node to execute got found and added to stop
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Node to add did not get found, rather an empty one removed so continue with search
|
||||||
|
waitingNodes = Object.keys(this.runExecutionData.executionData!.waitingExecution);
|
||||||
|
// Set counter to start again from the beginning. Set it to -1 as it auto increments
|
||||||
|
// after run. So only like that will we end up again ot 0.
|
||||||
|
i = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -57,7 +57,6 @@ describe('WorkflowExecute', () => {
|
||||||
return nodeData.data.main[0]!.map((entry) => entry.json);
|
return nodeData.data.main[0]!.map((entry) => entry.json);
|
||||||
});
|
});
|
||||||
|
|
||||||
// expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
|
||||||
expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,12 +18,14 @@ export class CompareDatasets implements INodeType {
|
||||||
name: 'compareDatasets',
|
name: 'compareDatasets',
|
||||||
icon: 'file:compare.svg',
|
icon: 'file:compare.svg',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1, 2, 2.1, 2.2],
|
version: [1, 2, 2.1, 2.2, 2.3],
|
||||||
description: 'Compare two inputs for changes',
|
description: 'Compare two inputs for changes',
|
||||||
defaults: { name: 'Compare Datasets' },
|
defaults: { name: 'Compare Datasets' },
|
||||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
|
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
|
||||||
inputs: ['main', 'main'],
|
inputs: ['main', 'main'],
|
||||||
inputNames: ['Input A', 'Input B'],
|
inputNames: ['Input A', 'Input B'],
|
||||||
|
forceInputNodeExecution: '={{ $version < 2.3 }}',
|
||||||
|
requiredInputs: '={{ $version < 2.3 ? undefined : 1 }}',
|
||||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
|
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
|
||||||
outputs: ['main', 'main', 'main', 'main'],
|
outputs: ['main', 'main', 'main', 'main'],
|
||||||
outputNames: ['In A only', 'Same', 'Different', 'In B only'],
|
outputNames: ['In A only', 'Same', 'Different', 'In B only'],
|
||||||
|
|
|
@ -0,0 +1,496 @@
|
||||||
|
{
|
||||||
|
"name": "Compare Datasets Node Test",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "0312bddf-aae0-423c-9041-d54fb124934f",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [480, 720]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n {\n json: {\n number: 0\n }\n },\n {\n json: {\n number: 1\n }\n },\n {\n json: {\n number: 2\n }\n }\n];"
|
||||||
|
},
|
||||||
|
"id": "0542886d-6ab2-4695-b686-2cd60729ba9a",
|
||||||
|
"name": "Code",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [900, 640]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"mergeByFields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"field1": "number",
|
||||||
|
"field2": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "f3e5e43b-a3bf-46c7-acd7-ae3d7d19d9f9",
|
||||||
|
"name": "Compare Datasets 2.2 - Old",
|
||||||
|
"type": "n8n-nodes-base.compareDatasets",
|
||||||
|
"typeVersion": 2.2,
|
||||||
|
"position": [1260, 40]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n {\n json: {\n number: 0\n }\n },\n {\n json: {\n number: 1,\n k: 2,\n }\n },\n {\n json: {\n number: 10\n }\n },\n {\n json: {\n number: 11\n }\n },\n {\n json: {\n number: 12\n }\n }\n];"
|
||||||
|
},
|
||||||
|
"id": "c62e90b3-f84a-48a5-94bf-3267a4c8b69e",
|
||||||
|
"name": "Code1",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [900, 60]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n {\n json: {\n number: 0\n }\n },\n {\n json: {\n number: 1,\n k: 2,\n }\n },\n {\n json: {\n number: 10\n }\n },\n {\n json: {\n number: 11\n }\n },\n {\n json: {\n number: 12\n }\n }\n];"
|
||||||
|
},
|
||||||
|
"id": "46320ca2-8e8e-4ecf-b4f6-5899807c1500",
|
||||||
|
"name": "Code2",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [900, 840]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "4ae12a83-5d3f-4d5b-845b-65c930d8ef5a",
|
||||||
|
"name": "Old - A only",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, -180]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "d4f5fd94-4b46-4b8c-8b8a-073e8c32ad85",
|
||||||
|
"name": "New - A only",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 440]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "0939f79b-fd75-4d2f-b40b-50780114c3f2",
|
||||||
|
"name": "Old - Same",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, -40]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "199ea52c-b30a-401d-a920-9db5c8e10d38",
|
||||||
|
"name": "Old - Different",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 100]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "1ebcb5bb-3061-47ef-8c79-847ae8bdb568",
|
||||||
|
"name": "Old - B only",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 240]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "38689dbf-49f2-4f3b-855b-abd821ec316f",
|
||||||
|
"name": "New - B only",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 860]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "dfcac903-95dc-4519-b49f-a6a65bf8fdb8",
|
||||||
|
"name": "New - Different",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 720]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "b8588ebc-4dc8-41f5-9a0a-64d151d7122e",
|
||||||
|
"name": "New - Same",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 580]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n {\n json: {\n number: 0\n }\n },\n {\n json: {\n number: 1,\n k: 2,\n }\n },\n {\n json: {\n number: 10\n }\n },\n {\n json: {\n number: 11\n }\n },\n {\n json: {\n number: 12\n }\n }\n];"
|
||||||
|
},
|
||||||
|
"id": "d35f3f52-c967-46e8-be3a-0bf709b20ef8",
|
||||||
|
"name": "Code3",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [880, 1340]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "75690ad6-c870-4950-9085-12fdd9c12ddd",
|
||||||
|
"name": "New - A only1",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 1100]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "9abb523f-349c-48b0-b36b-0c74064a6219",
|
||||||
|
"name": "New - B only1",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 1520]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "c4230d94-eaa6-420d-baff-288463722a03",
|
||||||
|
"name": "New - Different1",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 1380]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "58287181-dc90-4806-a053-83b3ef36e673",
|
||||||
|
"name": "New - Same1",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1520, 1240]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"mergeByFields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"field1": "number",
|
||||||
|
"field2": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "5514c636-ee64-45f3-8832-77ad6652cc08",
|
||||||
|
"name": "Compare Datasets 2.3 - New - Connected",
|
||||||
|
"type": "n8n-nodes-base.compareDatasets",
|
||||||
|
"typeVersion": 2.3,
|
||||||
|
"position": [1260, 1320]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"mergeByFields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"field1": "number",
|
||||||
|
"field2": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "a25f4766-9bb4-40a0-9ae2-ef0004f5938a",
|
||||||
|
"name": "Compare Datasets 2.3 - New - Not Connected",
|
||||||
|
"type": "n8n-nodes-base.compareDatasets",
|
||||||
|
"typeVersion": 2.3,
|
||||||
|
"position": [1260, 660]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Old - A only": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Old - Same": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Old - Different": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"keys": {
|
||||||
|
"number": 1
|
||||||
|
},
|
||||||
|
"same": {
|
||||||
|
"number": 1
|
||||||
|
},
|
||||||
|
"different": {
|
||||||
|
"k": {
|
||||||
|
"inputA": null,
|
||||||
|
"inputB": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Old - B only": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"New - A only": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"New - A only1": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"New - Same1": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"New - Different1": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"keys": {
|
||||||
|
"number": 1
|
||||||
|
},
|
||||||
|
"same": {
|
||||||
|
"number": 1
|
||||||
|
},
|
||||||
|
"different": {
|
||||||
|
"k": {
|
||||||
|
"inputA": null,
|
||||||
|
"inputB": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"New - B only1": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"number": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Code",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Code3",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Code": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Compare Datasets 2.3 - New - Not Connected",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Compare Datasets 2.2 - Old",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Compare Datasets 2.3 - New - Connected",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Code1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Compare Datasets 2.2 - Old",
|
||||||
|
"type": "main",
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Code2": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Compare Datasets 2.3 - New - Not Connected",
|
||||||
|
"type": "main",
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Compare Datasets 2.2 - Old": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Old - A only",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Old - Same",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Old - Different",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Old - B only",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Code3": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Compare Datasets 2.3 - New - Connected",
|
||||||
|
"type": "main",
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Compare Datasets 2.3 - New - Connected": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - A only1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - Same1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - Different1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - B only1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Compare Datasets 2.3 - New - Not Connected": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - A only",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - Same",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - Different",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "New - B only",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {},
|
||||||
|
"versionId": "d5c0f040-7406-4e69-bd5d-a362a739c8d8",
|
||||||
|
"id": "1114",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "021d3c82ba2d3bc090cbf4fc81c9312668bcc34297e022bb3438c5c88a43a5ff"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ export class Merge extends VersionedNodeType {
|
||||||
1: new MergeV1(baseDescription),
|
1: new MergeV1(baseDescription),
|
||||||
2: new MergeV2(baseDescription),
|
2: new MergeV2(baseDescription),
|
||||||
2.1: new MergeV2(baseDescription),
|
2.1: new MergeV2(baseDescription),
|
||||||
|
2.2: new MergeV2(baseDescription),
|
||||||
};
|
};
|
||||||
|
|
||||||
super(nodeVersions, baseDescription);
|
super(nodeVersions, baseDescription);
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -30,6 +30,7 @@ const versionDescription: INodeTypeDescription = {
|
||||||
inputs: ['main', 'main'],
|
inputs: ['main', 'main'],
|
||||||
outputs: ['main'],
|
outputs: ['main'],
|
||||||
inputNames: ['Input 1', 'Input 2'],
|
inputNames: ['Input 1', 'Input 2'],
|
||||||
|
forceInputNodeExecution: true,
|
||||||
properties: [
|
properties: [
|
||||||
oldVersionNotice,
|
oldVersionNotice,
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,7 +35,7 @@ const versionDescription: INodeTypeDescription = {
|
||||||
name: 'merge',
|
name: 'merge',
|
||||||
icon: 'fa:code-branch',
|
icon: 'fa:code-branch',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [2, 2.1],
|
version: [2, 2.1, 2.2],
|
||||||
subtitle: '={{$parameter["mode"]}}',
|
subtitle: '={{$parameter["mode"]}}',
|
||||||
description: 'Merges data of multiple streams once data from both is available',
|
description: 'Merges data of multiple streams once data from both is available',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
@ -46,6 +46,11 @@ const versionDescription: INodeTypeDescription = {
|
||||||
inputs: ['main', 'main'],
|
inputs: ['main', 'main'],
|
||||||
outputs: ['main'],
|
outputs: ['main'],
|
||||||
inputNames: ['Input 1', 'Input 2'],
|
inputNames: ['Input 1', 'Input 2'],
|
||||||
|
// If the node is of version 2.2 or if mode is chooseBranch data from both branches is required
|
||||||
|
// to continue, else data from any input suffices
|
||||||
|
requiredInputs:
|
||||||
|
'={{ $version < 2.2 ? undefined : ($parameter["mode"] === "chooseBranch" ? [0, 1] : 1) }}',
|
||||||
|
forceInputNodeExecution: '={{ $version < 2.2 }}',
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
displayName: 'Mode',
|
displayName: 'Mode',
|
||||||
|
@ -374,6 +379,12 @@ export class MergeV2 implements INodeType {
|
||||||
let input1 = this.getInputData(0);
|
let input1 = this.getInputData(0);
|
||||||
let input2 = this.getInputData(1);
|
let input2 = this.getInputData(1);
|
||||||
|
|
||||||
|
if (input1.length === 0 || input2.length === 0) {
|
||||||
|
// If data of any input is missing, return the data of
|
||||||
|
// the input that contains data
|
||||||
|
return [[...input1, ...input2]];
|
||||||
|
}
|
||||||
|
|
||||||
if (clashHandling.resolveClash === 'preferInput1') {
|
if (clashHandling.resolveClash === 'preferInput1') {
|
||||||
[input1, input2] = [input2, input1];
|
[input1, input2] = [input2, input1];
|
||||||
}
|
}
|
||||||
|
@ -454,6 +465,7 @@ export class MergeV2 implements INodeType {
|
||||||
|
|
||||||
let input1 = this.getInputData(0);
|
let input1 = this.getInputData(0);
|
||||||
let input2 = this.getInputData(1);
|
let input2 = this.getInputData(1);
|
||||||
|
|
||||||
if (nodeVersion < 2.1) {
|
if (nodeVersion < 2.1) {
|
||||||
input1 = checkInput(
|
input1 = checkInput(
|
||||||
this.getInputData(0),
|
this.getInputData(0),
|
||||||
|
@ -473,6 +485,24 @@ export class MergeV2 implements INodeType {
|
||||||
if (!input1) return [returnData];
|
if (!input1) return [returnData];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input1.length === 0 || input2.length === 0) {
|
||||||
|
if (joinMode === 'keepMatches') {
|
||||||
|
// Stop the execution
|
||||||
|
return [[]];
|
||||||
|
} else if (joinMode === 'enrichInput1' && input1.length === 0) {
|
||||||
|
// No data to enrich so stop
|
||||||
|
return [[]];
|
||||||
|
} else if (joinMode === 'enrichInput2' && input2.length === 0) {
|
||||||
|
// No data to enrich so stop
|
||||||
|
return [[]];
|
||||||
|
} else {
|
||||||
|
// Return the data of any of the inputs that contains data
|
||||||
|
return [[...input1, ...input2]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input1) return [returnData];
|
||||||
|
|
||||||
if (!input2 || !matchFields.length) {
|
if (!input2 || !matchFields.length) {
|
||||||
if (
|
if (
|
||||||
joinMode === 'keepMatches' ||
|
joinMode === 'keepMatches' ||
|
||||||
|
|
|
@ -358,8 +358,8 @@ export class Expression {
|
||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
executeData?: IExecuteData,
|
executeData?: IExecuteData,
|
||||||
defaultValue?: boolean | number | string,
|
defaultValue?: boolean | number | string | unknown[],
|
||||||
): boolean | number | string | undefined {
|
): boolean | number | string | undefined | unknown[] {
|
||||||
if (parameterValue === undefined) {
|
if (parameterValue === undefined) {
|
||||||
// Value is not set so return the default
|
// Value is not set so return the default
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
|
|
@ -1429,6 +1429,8 @@ export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||||
eventTriggerDescription?: string;
|
eventTriggerDescription?: string;
|
||||||
activationMessage?: string;
|
activationMessage?: string;
|
||||||
inputs: string[];
|
inputs: string[];
|
||||||
|
forceInputNodeExecution?: string | boolean; // TODO: This option should be deprecated after a while
|
||||||
|
requiredInputs?: string | number[] | number;
|
||||||
inputNames?: string[];
|
inputNames?: string[];
|
||||||
outputs: string[];
|
outputs: string[];
|
||||||
outputNames?: string[];
|
outputNames?: string[];
|
||||||
|
|
|
@ -1186,11 +1186,35 @@ export class Workflow {
|
||||||
// because then it is a trigger node. As they only pass data through and so the input-data
|
// because then it is a trigger node. As they only pass data through and so the input-data
|
||||||
// becomes output-data it has to be possible.
|
// becomes output-data it has to be possible.
|
||||||
|
|
||||||
if (inputData.hasOwnProperty('main') && inputData.main.length > 0) {
|
if (inputData.main?.length > 0) {
|
||||||
// We always use the data of main input and the first input for execute
|
// We always use the data of main input and the first input for execute
|
||||||
connectionInputData = inputData.main[0] as INodeExecutionData[];
|
connectionInputData = inputData.main[0] as INodeExecutionData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let forceInputNodeExecution = nodeType.description.forceInputNodeExecution;
|
||||||
|
if (forceInputNodeExecution !== undefined) {
|
||||||
|
if (typeof forceInputNodeExecution === 'string') {
|
||||||
|
forceInputNodeExecution = !!this.expression.getSimpleParameterValue(
|
||||||
|
node,
|
||||||
|
forceInputNodeExecution,
|
||||||
|
mode,
|
||||||
|
additionalData.timezone,
|
||||||
|
{ $version: node.typeVersion },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forceInputNodeExecution) {
|
||||||
|
// If the nodes do not get force executed data of some inputs may be missing
|
||||||
|
// for that reason do we use the data of the first one that contains any
|
||||||
|
for (const mainData of inputData.main) {
|
||||||
|
if (mainData?.length) {
|
||||||
|
connectionInputData = mainData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (connectionInputData.length === 0) {
|
if (connectionInputData.length === 0) {
|
||||||
// No data for node so return
|
// No data for node so return
|
||||||
return { data: undefined };
|
return { data: undefined };
|
||||||
|
|
Loading…
Reference in a new issue