fix(core): Increment executionIndex in partial executions (no-changelog) (#14946)

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Mutasem Aldmour
2025-04-30 13:16:27 +02:00
committed by GitHub
co-authored by कारतोफ्फेलस्क्रिप्ट™
parent 9c0e0f0d2e
commit b4a06aaff9
10 changed files with 384 additions and 2 deletions
@@ -33,7 +33,7 @@ type TaskData = {
nodeConnectionType?: NodeConnectionType;
};
export function toITaskData(taskData: TaskData[]): ITaskData {
export function toITaskData(taskData: TaskData[], overrides?: Partial<ITaskData>): ITaskData {
const result: ITaskData = {
executionStatus: 'success',
executionTime: 0,
@@ -41,6 +41,7 @@ export function toITaskData(taskData: TaskData[]): ITaskData {
executionIndex: 0,
source: [],
data: {},
...(overrides ?? {}),
};
// NOTE: Here to make TS happy.
@@ -0,0 +1,61 @@
import { mock } from 'jest-mock-extended';
import type { IRunData } from 'n8n-workflow';
import { getNextExecutionIndex } from '../run-data-utils';
describe('getNextExecutionIndex', () => {
it('should return 0 if runData is undefined', () => {
const result = getNextExecutionIndex(undefined);
expect(result).toBe(0);
});
it('should return 0 if runData is empty', () => {
const result = getNextExecutionIndex({});
expect(result).toBe(0);
});
it('should return the next execution index based on the highest executionIndex in runData', () => {
const runData = mock<IRunData>({
node1: [{ executionIndex: 0 }, { executionIndex: 1 }],
node2: [{ executionIndex: 2 }],
});
const result = getNextExecutionIndex(runData);
expect(result).toBe(3);
});
it('should return 1 if all tasks in runData have executionIndex 0', () => {
const runData = mock<IRunData>({
node1: [{ executionIndex: 0 }, { executionIndex: 0 }],
node2: [{ executionIndex: 0 }],
});
const result = getNextExecutionIndex(runData);
expect(result).toBe(1);
});
it('should handle runData with mixed executionIndex values', () => {
const runData = mock<IRunData>({
node1: [{ executionIndex: 5 }, { executionIndex: 3 }],
node2: [{ executionIndex: 7 }, { executionIndex: 2 }],
});
const result = getNextExecutionIndex(runData);
expect(result).toBe(8);
});
it('should handle runData with missing executionIndex values', () => {
const runData = mock<IRunData>({
node1: [{}],
node2: [{}, {}],
});
const result = getNextExecutionIndex(runData);
expect(result).toBe(0);
});
it('should handle runData with negative executionIndex values', () => {
const runData = mock<IRunData>({
node1: [{ executionIndex: -5 }, { executionIndex: -10 }],
node2: [{ executionIndex: -2 }],
});
const result = getNextExecutionIndex(runData);
expect(result).toBe(-1);
});
});
@@ -8,3 +8,4 @@ export { handleCycles } from './handle-cycles';
export { filterDisabledNodes } from './filter-disabled-nodes';
export { isTool } from './is-tool';
export { rewireGraph } from './rewire-graph';
export { getNextExecutionIndex } from './run-data-utils';
@@ -0,0 +1,26 @@
import type { IRunData } from 'n8n-workflow';
/**
* Calculates the next execution index by finding the highest existing index in the run data and incrementing by 1.
*
* The execution index is used to track the sequence of workflow executions.
*
* @param {IRunData} [runData={}]
* @returns {number} The next execution index (previous highest index + 1, or 0 if no previous executionIndex exist).
*/
export function getNextExecutionIndex(runData: IRunData = {}): number {
// If runData is empty, return 0 as the first execution index
if (!runData || Object.keys(runData).length === 0) return 0;
const previousIndices = Object.values(runData)
.flat()
.map((taskData) => taskData.executionIndex)
// filter out undefined if previous execution does not have index
// this can happen if rerunning execution before executionIndex was introduced
.filter((value) => typeof value === 'number');
// If no valid indices were found, return 0 as the first execution index
if (previousIndices.length === 0) return 0;
return Math.max(...previousIndices) + 1;
}