mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
fix(Execute Sub-workflow Node): Improve paired item handling for child workflows (#17065)
This commit is contained in:
114
packages/nodes-base/utils/workflow-backtracking.ts
Normal file
114
packages/nodes-base/utils/workflow-backtracking.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import type {
|
||||
INodeExecutionData,
|
||||
IPairedItemData,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
/*
|
||||
* These functions do not cover all possible edge cases for backtracking through workflow run data.
|
||||
* They are designed to work for a simple and linear workflow execution.
|
||||
* If the workflow has branches or complex execution paths, additional logic may be needed.
|
||||
* We should follow up on this and improve the logic in the future.
|
||||
*/
|
||||
|
||||
/*
|
||||
* If we cannot backtrack correctly, we return undefined to fallback to the current paired item behavior
|
||||
* failing in these functions will cause the parent workflow to fail
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function retrieves the previous task data for a given task in the workflow run data.
|
||||
* Until there is no more source set
|
||||
*/
|
||||
export function previousTaskData(
|
||||
runData: IRunExecutionData['resultData']['runData'],
|
||||
currentRunData: ITaskData,
|
||||
): ITaskData | undefined {
|
||||
const nextNodeName = currentRunData.source?.[0]?.previousNode;
|
||||
if (!nextNodeName) {
|
||||
return undefined; // No next node
|
||||
}
|
||||
|
||||
const nextRunData = runData[nextNodeName];
|
||||
if (!nextRunData || nextRunData.length === 0) {
|
||||
// We don't expect this case to happen in practice, but if for some reason it happens, we fallback to undefined
|
||||
return undefined; // No run data for the next node
|
||||
}
|
||||
|
||||
const nextRunIndex = currentRunData.source?.[0]?.previousNodeRun ?? 0;
|
||||
|
||||
return nextRunData[nextRunIndex]; // Return the first run data for the next node
|
||||
}
|
||||
|
||||
export function findPairedItemTroughWorkflowData(
|
||||
workflowRunData: IRunExecutionData,
|
||||
item: INodeExecutionData,
|
||||
itemIndex: number,
|
||||
): IPairedItemData | IPairedItemData[] | number | undefined {
|
||||
// The provided item is already the item of the last node executed in this workflow run
|
||||
// So the item.pairedItem is the paired item of the last node executed and is therefore referencing
|
||||
// a node in the previous task data
|
||||
|
||||
const currentNodeName = workflowRunData.resultData.lastNodeExecuted;
|
||||
if (!currentNodeName) {
|
||||
// If no node name is available, then we don't know where to start backtracking
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// This is the run data of the last node executed in the workflow run
|
||||
const runData = workflowRunData.resultData.runData[currentNodeName];
|
||||
|
||||
if (!runData) {
|
||||
// No run data available for the last node executed
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Since we are backtracking through the workflow, we start with the last run data
|
||||
const runIndex = runData.length - 1;
|
||||
|
||||
const taskData = runData[runIndex];
|
||||
|
||||
if (!taskData) {
|
||||
// If no run data is available, then the workflow did not run at all
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Now we are getting the second last task data, because our initial pairedItem points to this.
|
||||
let runDataItem = previousTaskData(workflowRunData.resultData.runData, taskData);
|
||||
|
||||
let pairedItem = item.pairedItem;
|
||||
|
||||
// move the runDataItem to the previous node in the in the workflow execution data
|
||||
// and find the paired item of the current item in the previous task data
|
||||
// We do this until we reach the first task data of the workflow run
|
||||
|
||||
while (runDataItem !== undefined) {
|
||||
// We find the output items for the current run data item
|
||||
const nodeInformationArray = runDataItem.data?.['main'];
|
||||
|
||||
// We find and fallback to 0 for the input index and item index
|
||||
// The input index is the run the node was executed in case it was executed multiple times
|
||||
// The item index is the index of the paired item we are looking for
|
||||
let inputIndex = 0;
|
||||
let nodeIndex = itemIndex;
|
||||
if (typeof pairedItem === 'object') {
|
||||
inputIndex = (pairedItem as IPairedItemData).input ?? 0;
|
||||
nodeIndex = (pairedItem as IPairedItemData).item ?? itemIndex;
|
||||
} else if (typeof pairedItem === 'number') {
|
||||
// If the paired item is a number, we use it as the node index
|
||||
nodeIndex = pairedItem;
|
||||
// and fallback to 0 for the input index
|
||||
inputIndex = 0;
|
||||
}
|
||||
|
||||
// We found the paired item of the current run data item, this points to the node in the previous task data
|
||||
pairedItem = nodeInformationArray?.[inputIndex]?.[nodeIndex]?.pairedItem;
|
||||
|
||||
// We move the runDataItem to the previous task data
|
||||
runDataItem = previousTaskData(workflowRunData.resultData.runData, runDataItem);
|
||||
}
|
||||
|
||||
// This is the paired item that was in the first task data when the workflow was executed
|
||||
return pairedItem;
|
||||
}
|
||||
Reference in New Issue
Block a user