fix(Execution Data Node): Set nulish values as empty string, continue on fail support (#16696)

Co-authored-by: Elias Meire <elias@meire.dev>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
Michael Kret
2025-06-25 21:23:54 +03:00
committed by GitHub
parent b70cc944fc
commit e6515a2a74
2 changed files with 76 additions and 14 deletions

View File

@@ -1,11 +1,14 @@
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
type DataToSave = {
values: Array<{ key: string; value: string }>;
};
export class ExecutionData implements INodeType {
description: INodeTypeDescription = {
@@ -14,7 +17,7 @@ export class ExecutionData implements INodeType {
icon: 'fa:tasks',
group: ['input'],
iconColor: 'light-green',
version: 1,
version: [1, 1.1],
description: 'Add execution data for search',
defaults: {
name: 'Execution Data',
@@ -70,6 +73,7 @@ export class ExecutionData implements INodeType {
type: 'string',
default: '',
placeholder: 'e.g. myKey',
requiresDataPath: 'single',
},
{
displayName: 'Value',
@@ -102,26 +106,51 @@ export class ExecutionData implements INodeType {
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const context = this.getWorkflowDataProxy(0);
const dataProxy = this.getWorkflowDataProxy(0);
const nodeVersion = this.getNode().typeVersion;
const items = this.getInputData();
const operations = this.getNodeParameter('operation', 0);
const returnData: INodeExecutionData[] = [];
if (operations === 'save') {
for (let i = 0; i < items.length; i++) {
const dataToSave =
((this.getNodeParameter('dataToSave', i, {}) as IDataObject).values as IDataObject[]) ||
[];
try {
const dataToSave =
(this.getNodeParameter('dataToSave', i, {}) as DataToSave).values || [];
const values = dataToSave.reduce((acc, { key, value }) => {
acc[key as string] = value;
return acc;
}, {} as IDataObject);
const values = dataToSave.reduce(
(acc, { key, value }) => {
const valueToSet = value ? value : nodeVersion >= 1.1 ? '' : value;
acc[key] = valueToSet;
return acc;
},
{} as { [key: string]: string },
);
context.$execution.customData.setAll(values);
dataProxy.$execution.customData.setAll(values);
returnData.push(items[i]);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
pairedItem: {
item: i,
},
});
continue;
}
throw new NodeOperationError(this.getNode(), error);
}
}
} else {
return [items];
}
return [items];
return [returnData];
}
}

View File

@@ -1,6 +1,11 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import { mock } from 'jest-mock-extended';
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import type {
IExecuteFunctions,
INodeExecutionData,
IWorkflowDataProxyData,
INode,
} from 'n8n-workflow';
import { ExecutionData } from '../ExecutionData.node';
@@ -12,11 +17,39 @@ describe('ExecutionData Node', () => {
];
const executeFns = mock<IExecuteFunctions>({
getInputData: () => mockInputData,
getNode: () => mock<INode>({ typeVersion: 1 }),
});
const result = await new ExecutionData().execute.call(executeFns);
expect(result).toEqual([mockInputData]);
});
it('should set nullish values to empty string', async () => {
const mockInputData: INodeExecutionData[] = [
{ json: { item: 0, foo: undefined } },
{ json: { item: 1, foo: null } },
{ json: { item: 1, foo: 'bar' } },
];
const setAllMock = jest.fn();
const executeFns = mock<IExecuteFunctions>({
getInputData: () => mockInputData,
getWorkflowDataProxy: () =>
mock<IWorkflowDataProxyData>({ $execution: { customData: { setAll: setAllMock } } }),
getNode: () => mock<INode>({ typeVersion: 1.1 }),
});
executeFns.getNodeParameter.mockReturnValueOnce('save');
executeFns.getNodeParameter.mockReturnValueOnce({ values: [{ key: 'foo', value: undefined }] });
executeFns.getNodeParameter.mockReturnValueOnce({ values: [{ key: 'foo', value: null }] });
executeFns.getNodeParameter.mockReturnValueOnce({ values: [{ key: 'foo', value: 'bar' }] });
const result = await new ExecutionData().execute.call(executeFns);
expect(setAllMock).toBeCalledTimes(3);
expect(setAllMock).toBeCalledWith({ foo: '' });
expect(setAllMock).toBeCalledWith({ foo: '' });
expect(setAllMock).toBeCalledWith({ foo: 'bar' });
expect(result).toEqual([mockInputData]);
});
});
describe('ExecutionData -> Should run the workflow', () => {