fix(editor): Update node execution itemCount to support multiple outputs (no-changelog) (#19646)

This commit is contained in:
Alex Grozav
2025-09-18 12:11:04 +01:00
committed by GitHub
parent dee22162f4
commit 83b2a5772e
13 changed files with 435 additions and 27 deletions

View File

@@ -345,7 +345,14 @@ describe('Execution Lifecycle Hooks', () => {
1,
{
type: 'nodeExecuteAfter',
data: { executionId, nodeName, itemCount: 1, data: taskDataWithoutData },
data: {
executionId,
nodeName,
itemCountByConnectionType: {
main: [1],
},
data: taskDataWithoutData,
},
},
pushRef,
);
@@ -354,7 +361,14 @@ describe('Execution Lifecycle Hooks', () => {
2,
{
type: 'nodeExecuteAfterData',
data: { executionId, nodeName, itemCount: 1, data: mockTaskData },
data: {
executionId,
nodeName,
itemCountByConnectionType: {
main: [1],
},
data: mockTaskData,
},
},
pushRef,
true,

View File

@@ -27,6 +27,7 @@ import {
updateExistingExecution,
} from './shared/shared-hook-functions';
import { type ExecutionSaveSettings, toSaveSettings } from './to-save-settings';
import { getItemCountByConnectionType } from '@/utils/get-item-count-by-connection-type';
@Service()
class ModulesHooksRegistry {
@@ -185,11 +186,14 @@ function hookFunctionsPush(
workflowId: this.workflowData.id,
});
const itemCount = data.data?.main?.[0]?.length ?? 0;
const itemCountByConnectionType = getItemCountByConnectionType(data?.data);
const { data: _, ...taskData } = data;
pushInstance.send(
{ type: 'nodeExecuteAfter', data: { executionId, nodeName, itemCount, data: taskData } },
{
type: 'nodeExecuteAfter',
data: { executionId, nodeName, itemCountByConnectionType, data: taskData },
},
pushRef,
);
@@ -203,7 +207,7 @@ function hookFunctionsPush(
pushInstance.send(
{
type: 'nodeExecuteAfterData',
data: { executionId, nodeName, itemCount, data },
data: { executionId, nodeName, itemCountByConnectionType, data },
},
pushRef,
asBinary,

View File

@@ -0,0 +1,145 @@
import type { ITaskData } from 'n8n-workflow';
import { getItemCountByConnectionType } from '../get-item-count-by-connection-type';
describe('getItemCountByConnectionType', () => {
it('should return an empty object when data is undefined', () => {
const result = getItemCountByConnectionType(undefined);
expect(result).toEqual({});
});
it('should return an empty object when data is an empty object', () => {
const result = getItemCountByConnectionType({});
expect(result).toEqual({});
});
it('should count items for a single connection with single output', () => {
const data: ITaskData['data'] = {
main: [[{ json: { id: 1 } }, { json: { id: 2 } }]],
};
const result = getItemCountByConnectionType(data);
expect(result).toEqual({
main: [2],
});
});
it('should count items for a single connection with multiple outputs', () => {
const data: ITaskData['data'] = {
main: [
[{ json: { id: 1 } }, { json: { id: 2 } }],
[{ json: { id: 3 } }],
[{ json: { id: 4 } }, { json: { id: 5 } }, { json: { id: 6 } }],
],
};
const result = getItemCountByConnectionType(data);
expect(result).toEqual({
main: [2, 1, 3],
});
});
it('should handle multiple connection types', () => {
const data: ITaskData['data'] = {
main: [[{ json: { id: 1 } }, { json: { id: 2 } }]],
ai_agent: [[{ json: { error: 'test' } }]],
ai_memory: [
[
{ json: { data: 'custom' } },
{ json: { data: 'custom2' } },
{ json: { data: 'custom3' } },
],
],
};
const result = getItemCountByConnectionType(data);
expect(result).toEqual({
main: [2],
ai_agent: [1],
ai_memory: [3],
});
});
it('should handle empty arrays in connection data', () => {
const data: ITaskData['data'] = {
main: [[], [{ json: { id: 1 } }], []],
};
const result = getItemCountByConnectionType(data);
expect(result).toEqual({
main: [0, 1, 0],
});
});
it('should handle null values in connection data arrays', () => {
const data: ITaskData['data'] = {
main: [null, [{ json: { id: 1 } }], null],
};
const result = getItemCountByConnectionType(data);
expect(result).toEqual({
main: [0, 1, 0],
});
});
it('should handle connection data with mixed null and valid arrays', () => {
const data: ITaskData['data'] = {
main: [[{ json: { id: 1 } }], null, [{ json: { id: 2 } }, { json: { id: 3 } }], null, []],
};
const result = getItemCountByConnectionType(data);
expect(result).toEqual({
main: [1, 0, 2, 0, 0],
});
});
it('should discard unknown connection types', () => {
const data: ITaskData['data'] = {
main: [[{ json: { id: 1 } }, { json: { id: 2 } }]],
unknownType: [[{ json: { data: 'should be ignored' } }]],
anotherInvalid: [[{ json: { test: 'data' } }]],
};
const result = getItemCountByConnectionType(data);
// Should only include 'main' and discard unknown types
expect(result).toEqual({
main: [2],
});
expect(result).not.toHaveProperty('unknownType');
expect(result).not.toHaveProperty('anotherInvalid');
});
it('should handle mix of valid and invalid connection types', () => {
const data: ITaskData['data'] = {
invalidType1: [[{ json: { data: 'ignored' } }]],
main: [[{ json: { id: 1 } }]],
invalidType2: [[{ json: { data: 'also ignored' } }]],
ai_agent: [[{ json: { error: 'test error' } }]],
notAValidType: [[{ json: { foo: 'bar' } }]],
};
const result = getItemCountByConnectionType(data);
// Should only include valid NodeConnectionTypes
expect(result).toEqual({
main: [1],
ai_agent: [1],
});
expect(Object.keys(result)).toHaveLength(2);
});
it('should handle data with only invalid connection types', () => {
const data: ITaskData['data'] = {
fakeType1: [[{ json: { data: 'test' } }]],
fakeType2: [[{ json: { data: 'test2' } }]],
notReal: [[{ json: { id: 1 } }, { json: { id: 2 } }]],
};
const result = getItemCountByConnectionType(data);
// Should return empty object when no valid types found
expect(result).toEqual({});
expect(Object.keys(result)).toHaveLength(0);
});
});

View File

@@ -0,0 +1,22 @@
import type { NodeConnectionType, ITaskData } from 'n8n-workflow';
import { isNodeConnectionType } from 'n8n-workflow';
export function getItemCountByConnectionType(
data: ITaskData['data'],
): Partial<Record<NodeConnectionType, number[]>> {
const itemCountByConnectionType: Partial<Record<NodeConnectionType, number[]>> = {};
for (const [connectionType, connectionData] of Object.entries(data ?? {})) {
if (!isNodeConnectionType(connectionType)) {
continue;
}
if (Array.isArray(connectionData)) {
itemCountByConnectionType[connectionType] = connectionData.map((d) => (d ? d.length : 0));
} else {
itemCountByConnectionType[connectionType] = [0];
}
}
return itemCountByConnectionType;
}