From fb501d6ded58b605e26c74499866664fdaba85b2 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:10:04 +0300 Subject: [PATCH] feat(Split Out Node): Incorrect warning fix (#20468) --- .../nodes/Transform/SplitOut/SplitOut.node.ts | 37 ++++------ .../Transform/SplitOut/test/utils.test.ts | 68 +++++++++++++++++++ .../nodes/Transform/SplitOut/utils.ts | 32 +++++++++ 3 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 packages/nodes-base/nodes/Transform/SplitOut/test/utils.test.ts create mode 100644 packages/nodes-base/nodes/Transform/SplitOut/utils.ts diff --git a/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts b/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts index ef1e4a03117..8876bd8e8b9 100644 --- a/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts +++ b/packages/nodes-base/nodes/Transform/SplitOut/SplitOut.node.ts @@ -8,10 +8,10 @@ import type { INodeExecutionData, INodeType, INodeTypeDescription, - NodeExecutionHint, } from 'n8n-workflow'; import { prepareFieldsArray } from '../utils/utils'; +import { FieldsTracker } from './utils'; export class SplitOut implements INodeType { description: INodeTypeDescription = { @@ -113,7 +113,7 @@ export class SplitOut implements INodeType { async execute(this: IExecuteFunctions): Promise { const returnData: INodeExecutionData[] = []; const items = this.getInputData(); - const notFoundedFields: { [key: string]: boolean[] } = {}; + const fieldsTracker = new FieldsTracker(); for (let i = 0; i < items.length; i++) { const fieldsToSplitOut = (this.getNodeParameter('fieldToSplitOut', i) as string) @@ -161,18 +161,16 @@ export class SplitOut implements INodeType { entityToSplit = item[fieldToSplitOut] as IDataObject[]; } - if (entityToSplit === undefined) { + fieldsTracker.add(fieldToSplitOut); + + const entryExists = entityToSplit !== undefined; + + if (!entryExists) { entityToSplit = []; - if (!notFoundedFields[fieldToSplitOut]) { - notFoundedFields[fieldToSplitOut] = []; - } - notFoundedFields[fieldToSplitOut].push(false); - } else { - if (notFoundedFields[fieldToSplitOut]) { - notFoundedFields[fieldToSplitOut].push(true); - } } + fieldsTracker.update(fieldToSplitOut, entryExists); + if (typeof entityToSplit !== 'object' || entityToSplit === null) { entityToSplit = [entityToSplit] as unknown as IDataObject[]; } @@ -265,21 +263,10 @@ export class SplitOut implements INodeType { } } - if (Object.keys(notFoundedFields).length) { - const hints: NodeExecutionHint[] = []; + const hints = fieldsTracker.getHints(); - for (const [field, values] of Object.entries(notFoundedFields)) { - if (values.every((value) => !value)) { - hints.push({ - message: `The field '${field}' wasn't found in any input item`, - location: 'outputPane', - }); - } - } - - if (hints.length) { - this.addExecutionHints(...hints); - } + if (hints.length) { + this.addExecutionHints(...hints); } return [returnData]; diff --git a/packages/nodes-base/nodes/Transform/SplitOut/test/utils.test.ts b/packages/nodes-base/nodes/Transform/SplitOut/test/utils.test.ts new file mode 100644 index 00000000000..afa8c6da858 --- /dev/null +++ b/packages/nodes-base/nodes/Transform/SplitOut/test/utils.test.ts @@ -0,0 +1,68 @@ +import { FieldsTracker } from '../utils'; + +describe('FieldsTracker', () => { + let fieldsTracker: FieldsTracker; + + beforeEach(() => { + fieldsTracker = new FieldsTracker(); + }); + + describe('add', () => { + it('should add field with false value', () => { + fieldsTracker.add('testField'); + + expect(fieldsTracker.fields.testField).toBe(false); + }); + + it('should not overwrite existing field', () => { + fieldsTracker.add('testField'); + fieldsTracker.fields.testField = true; + fieldsTracker.add('testField'); + + expect(fieldsTracker.fields.testField).toBe(true); + }); + }); + + describe('update', () => { + it('should update field from false to true', () => { + fieldsTracker.add('testField'); + fieldsTracker.update('testField', true); + + expect(fieldsTracker.fields.testField).toBe(true); + }); + + it('should not update field from true to false', () => { + fieldsTracker.add('testField'); + fieldsTracker.fields.testField = true; + fieldsTracker.update('testField', false); + + expect(fieldsTracker.fields.testField).toBe(true); + }); + }); + + describe('getHints', () => { + it('should return empty array when no fields tracked', () => { + expect(fieldsTracker.getHints()).toEqual([]); + }); + + it('should return hint for missing field', () => { + fieldsTracker.add('missingField'); + + const hints = fieldsTracker.getHints(); + + expect(hints).toEqual([ + { + message: "The field 'missingField' wasn't found in any input item", + location: 'outputPane', + }, + ]); + }); + + it('should not return hint for found field', () => { + fieldsTracker.add('foundField'); + fieldsTracker.update('foundField', true); + + expect(fieldsTracker.getHints()).toEqual([]); + }); + }); +}); diff --git a/packages/nodes-base/nodes/Transform/SplitOut/utils.ts b/packages/nodes-base/nodes/Transform/SplitOut/utils.ts new file mode 100644 index 00000000000..2d93740d1f6 --- /dev/null +++ b/packages/nodes-base/nodes/Transform/SplitOut/utils.ts @@ -0,0 +1,32 @@ +import type { NodeExecutionHint } from 'n8n-workflow'; + +export class FieldsTracker { + fields: { [key: string]: boolean } = {}; + + add(key: string) { + if (this.fields[key] === undefined) { + this.fields[key] = false; + } + } + + update(key: string, value: boolean) { + if (!this.fields[key] && value) { + this.fields[key] = true; + } + } + + getHints() { + const hints: NodeExecutionHint[] = []; + + for (const [field, value] of Object.entries(this.fields)) { + if (!value) { + hints.push({ + message: `The field '${field}' wasn't found in any input item`, + location: 'outputPane', + }); + } + } + + return hints; + } +}