first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.splitOut",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"details": "",
|
||||
"categories": ["Core Nodes"],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.splitout/"
|
||||
}
|
||||
],
|
||||
"generic": []
|
||||
},
|
||||
"alias": ["Split", "Nested", "Transform", "Array", "List", "Item"],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Data Transformation"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
import get from 'lodash/get';
|
||||
import unset from 'lodash/unset';
|
||||
import { NodeOperationError, deepCopy, NodeConnectionTypes } from 'n8n-workflow';
|
||||
import type {
|
||||
IBinaryData,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { prepareFieldsArray } from '../utils/utils';
|
||||
import { FieldsTracker } from './utils';
|
||||
|
||||
export class SplitOut implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Split Out',
|
||||
name: 'splitOut',
|
||||
icon: 'file:splitOut.svg',
|
||||
group: ['transform'],
|
||||
subtitle: '',
|
||||
version: 1,
|
||||
description: 'Turn a list inside item(s) into separate items',
|
||||
defaults: {
|
||||
name: 'Split Out',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
builderHint: {
|
||||
relatedNodes: [
|
||||
{
|
||||
nodeType: 'n8n-nodes-base.aggregate',
|
||||
relationHint: 'Reverse operation - combine items back',
|
||||
},
|
||||
],
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Fields To Split Out',
|
||||
name: 'fieldToSplitOut',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'Drag fields from the left or type their names',
|
||||
description:
|
||||
'The name of the input fields to break out into separate items. Separate multiple field names by commas. For binary data, use $binary.',
|
||||
requiresDataPath: 'multiple',
|
||||
hint: 'Use $binary to split out the input item by binary data',
|
||||
},
|
||||
{
|
||||
displayName: 'Include',
|
||||
name: 'include',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'No Other Fields',
|
||||
value: 'noOtherFields',
|
||||
},
|
||||
{
|
||||
name: 'All Other Fields',
|
||||
value: 'allOtherFields',
|
||||
},
|
||||
{
|
||||
name: 'Selected Other Fields',
|
||||
value: 'selectedOtherFields',
|
||||
},
|
||||
],
|
||||
default: 'noOtherFields',
|
||||
description: 'Whether to copy any other fields into the new items',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields To Include',
|
||||
name: 'fieldsToInclude',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. email, name',
|
||||
requiresDataPath: 'multiple',
|
||||
description: 'Fields in the input items to aggregate together',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
include: ['selectedOtherFields'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Disable Dot Notation',
|
||||
name: 'disableDotNotation',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to disallow referencing child fields using `parent.child` in the field name',
|
||||
},
|
||||
{
|
||||
displayName: 'Destination Field Name',
|
||||
name: 'destinationFieldName',
|
||||
type: 'string',
|
||||
requiresDataPath: 'multiple',
|
||||
default: '',
|
||||
description: 'The field in the output under which to put the split field contents',
|
||||
},
|
||||
{
|
||||
displayName: 'Include Binary',
|
||||
name: 'includeBinary',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to include the binary data in the new items',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const items = this.getInputData();
|
||||
const fieldsTracker = new FieldsTracker();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const fieldsToSplitOut = (this.getNodeParameter('fieldToSplitOut', i) as string)
|
||||
.split(',')
|
||||
.map((field) => field.trim().replace(/^\$json\./, ''));
|
||||
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
|
||||
const disableDotNotation = options.disableDotNotation as boolean;
|
||||
|
||||
const destinationFields = ((options.destinationFieldName as string) || '')
|
||||
.split(',')
|
||||
.filter((field) => field.trim() !== '')
|
||||
.map((field) => field.trim());
|
||||
|
||||
if (destinationFields.length && destinationFields.length !== fieldsToSplitOut.length) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'If multiple fields to split out are given, the same number of destination fields must be given',
|
||||
);
|
||||
}
|
||||
|
||||
const include = this.getNodeParameter('include', i) as
|
||||
| 'selectedOtherFields'
|
||||
| 'allOtherFields'
|
||||
| 'noOtherFields';
|
||||
|
||||
const multiSplit = fieldsToSplitOut.length > 1;
|
||||
|
||||
const item = { ...items[i].json };
|
||||
const splited: INodeExecutionData[] = [];
|
||||
for (const [entryIndex, fieldToSplitOut] of fieldsToSplitOut.entries()) {
|
||||
const destinationFieldName = destinationFields[entryIndex] || '';
|
||||
|
||||
let entityToSplit: IDataObject[] = [];
|
||||
|
||||
if (fieldToSplitOut === '$binary') {
|
||||
entityToSplit = Object.entries(items[i].binary || {}).map(([key, value]) => ({
|
||||
[key]: value,
|
||||
}));
|
||||
} else {
|
||||
if (!disableDotNotation) {
|
||||
entityToSplit = get(item, fieldToSplitOut) as IDataObject[];
|
||||
} else {
|
||||
entityToSplit = item[fieldToSplitOut] as IDataObject[];
|
||||
}
|
||||
|
||||
fieldsTracker.add(fieldToSplitOut);
|
||||
|
||||
const entryExists = entityToSplit !== undefined;
|
||||
|
||||
if (!entryExists) {
|
||||
entityToSplit = [];
|
||||
}
|
||||
|
||||
fieldsTracker.update(fieldToSplitOut, entryExists);
|
||||
|
||||
if (typeof entityToSplit !== 'object' || entityToSplit === null) {
|
||||
entityToSplit = [entityToSplit] as unknown as IDataObject[];
|
||||
}
|
||||
|
||||
if (!Array.isArray(entityToSplit)) {
|
||||
entityToSplit = Object.values(entityToSplit);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [elementIndex, element] of entityToSplit.entries()) {
|
||||
if (splited[elementIndex] === undefined) {
|
||||
splited[elementIndex] = { json: {}, pairedItem: { item: i } };
|
||||
}
|
||||
|
||||
const fieldName = destinationFieldName || fieldToSplitOut;
|
||||
|
||||
if (fieldToSplitOut === '$binary') {
|
||||
if (splited[elementIndex].binary === undefined) {
|
||||
splited[elementIndex].binary = {};
|
||||
}
|
||||
splited[elementIndex].binary[Object.keys(element)[0]] = Object.values(
|
||||
element,
|
||||
)[0] as IBinaryData;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof element === 'object' && element !== null && include === 'noOtherFields') {
|
||||
if (destinationFieldName === '' && !multiSplit) {
|
||||
splited[elementIndex] = {
|
||||
json: { ...splited[elementIndex].json, ...element },
|
||||
pairedItem: { item: i },
|
||||
};
|
||||
} else {
|
||||
splited[elementIndex].json[fieldName] = element;
|
||||
}
|
||||
} else {
|
||||
splited[elementIndex].json[fieldName] = element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const splitEntry of splited) {
|
||||
let newItem: INodeExecutionData = splitEntry;
|
||||
|
||||
if (include === 'allOtherFields') {
|
||||
const itemCopy = deepCopy(item);
|
||||
for (const fieldToSplitOut of fieldsToSplitOut) {
|
||||
if (!disableDotNotation) {
|
||||
unset(itemCopy, fieldToSplitOut);
|
||||
} else {
|
||||
delete itemCopy[fieldToSplitOut];
|
||||
}
|
||||
}
|
||||
newItem.json = { ...itemCopy, ...splitEntry.json };
|
||||
}
|
||||
|
||||
if (include === 'selectedOtherFields') {
|
||||
const fieldsToInclude = prepareFieldsArray(
|
||||
this.getNodeParameter('fieldsToInclude', i, '') as string,
|
||||
'Fields To Include',
|
||||
);
|
||||
|
||||
if (!fieldsToInclude.length) {
|
||||
throw new NodeOperationError(this.getNode(), 'No fields specified', {
|
||||
description: 'Please add a field to include',
|
||||
});
|
||||
}
|
||||
|
||||
for (const field of fieldsToInclude) {
|
||||
if (!disableDotNotation) {
|
||||
splitEntry.json[field] = get(item, field);
|
||||
} else {
|
||||
splitEntry.json[field] = item[field];
|
||||
}
|
||||
}
|
||||
|
||||
newItem = splitEntry;
|
||||
}
|
||||
|
||||
const includeBinary = options.includeBinary as boolean;
|
||||
|
||||
if (includeBinary) {
|
||||
if (items[i].binary && !newItem.binary) {
|
||||
newItem.binary = items[i].binary;
|
||||
}
|
||||
}
|
||||
|
||||
returnData.push(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
const hints = fieldsTracker.getHints();
|
||||
|
||||
if (hints.length) {
|
||||
this.addExecutionHints(...hints);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" fill="none"><g fill="#9B6DD5" clip-path="url(#a)"><path fill-rule="evenodd" d="M480 148c0-6.627-5.373-12-12-12H322c-6.627 0-12 5.373-12 12v24c0 6.627 5.373 12 12 12h146c6.627 0 12-5.373 12-12zm0 96c0-6.627-5.373-12-12-12H322c-6.627 0-12 5.373-12 12v24c0 6.627 5.373 12 12 12h146c6.627 0 12-5.373 12-12zm0 96c0-6.627-5.373-12-12-12H322c-6.627 0-12 5.373-12 12v24c0 6.627 5.373 12 12 12h146c6.627 0 12-5.373 12-12z" clip-rule="evenodd"/><path d="M438 76c0 6.627-5.373 12-12 12H309.783c-17.673 0-32 14.327-32 32v56c0 26.978-10.272 51.557-27.119 70.039-5.055 5.545-5.055 14.377 0 19.922 16.847 18.482 27.119 43.061 27.119 70.039v56c0 17.673 14.327 32 32 32H426c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12H309.783c-44.183 0-80-35.817-80-80v-56c0-30.928-25.072-56-56-56a5.783 5.783 0 0 1-5.783-5.783v-36.434a5.783 5.783 0 0 1 5.783-5.783c30.928 0 56-25.072 56-56v-56c0-44.183 35.817-80 80-80H426c6.627 0 12 5.373 12 12z"/><path fill-rule="evenodd" d="M136 244c0-6.627-5.373-12-12-12H12c-6.627 0-12 5.373-12 12v24c0 6.627 5.373 12 12 12h112c6.627 0 12-5.373 12-12z" clip-rule="evenodd"/></g><defs><clipPath id="a"><path fill="#fff" d="M512 0H0v512h512z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,5 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
describe('Test Split Out Node', () => {
|
||||
new NodeTestHarness().setupTests();
|
||||
});
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,388 @@
|
||||
{
|
||||
"name": "splitOut test",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "6c90bf81-0c0e-4c5f-9f0c-297f06d9668a",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [-400, 400]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": {
|
||||
"data": [
|
||||
{
|
||||
"id": 3,
|
||||
"char": "c"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"char": "d"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"char": "e"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"char": "a"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"char": "b"
|
||||
}
|
||||
],
|
||||
"data2": [
|
||||
{
|
||||
"text": "foo"
|
||||
}
|
||||
],
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "2e0011d5-c6a0-4a40-ab8c-9d011cde40d5",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [-180, 400]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data",
|
||||
"options": {}
|
||||
},
|
||||
"id": "e7eac465-8fe6-498c-9942-ebd47df537c1",
|
||||
"name": "Item Lists",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [80, 160]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data",
|
||||
"include": "allOtherFields",
|
||||
"options": {}
|
||||
},
|
||||
"id": "09b7fe15-dbad-4ca6-bf1e-3093139d14e5",
|
||||
"name": "Item Lists1",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [80, 320]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data",
|
||||
"include": "selectedOtherFields",
|
||||
"fieldsToInclude": ["data3"],
|
||||
|
||||
"options": {}
|
||||
},
|
||||
"id": "7ea63dc7-8141-4233-af47-9894919c7fe4",
|
||||
"name": "Item Lists2",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [80, 480]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data",
|
||||
"options": {
|
||||
"destinationFieldName": "output"
|
||||
}
|
||||
},
|
||||
"id": "89c3c1b4-9577-480a-931f-3b34450b23cb",
|
||||
"name": "Item Lists3",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [80, 660]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Item Lists": [
|
||||
{
|
||||
"json": {
|
||||
"id": 3,
|
||||
"char": "c"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": 4,
|
||||
"char": "d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": 5,
|
||||
"char": "e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": 1,
|
||||
"char": "a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": 2,
|
||||
"char": "b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Item Lists1": [
|
||||
{
|
||||
"json": {
|
||||
"data2": [
|
||||
{
|
||||
"text": "foo"
|
||||
}
|
||||
],
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 3,
|
||||
"char": "c"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data2": [
|
||||
{
|
||||
"text": "foo"
|
||||
}
|
||||
],
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 4,
|
||||
"char": "d"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data2": [
|
||||
{
|
||||
"text": "foo"
|
||||
}
|
||||
],
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 5,
|
||||
"char": "e"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data2": [
|
||||
{
|
||||
"text": "foo"
|
||||
}
|
||||
],
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 1,
|
||||
"char": "a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data2": [
|
||||
{
|
||||
"text": "foo"
|
||||
}
|
||||
],
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 2,
|
||||
"char": "b"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Item Lists2": [
|
||||
{
|
||||
"json": {
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 3,
|
||||
"char": "c"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 4,
|
||||
"char": "d"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 5,
|
||||
"char": "e"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 1,
|
||||
"char": "a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data3": [
|
||||
{
|
||||
"text": "bar"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"id": 2,
|
||||
"char": "b"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"Item Lists3": [
|
||||
{
|
||||
"json": {
|
||||
"output": {
|
||||
"id": 3,
|
||||
"char": "c"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"output": {
|
||||
"id": 4,
|
||||
"char": "d"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"output": {
|
||||
"id": 5,
|
||||
"char": "e"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"output": {
|
||||
"id": 1,
|
||||
"char": "a"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"output": {
|
||||
"id": 2,
|
||||
"char": "b"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Item Lists",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Item Lists1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Item Lists2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Item Lists3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "9230f580-6f41-47c9-9949-bf258fc3fa47",
|
||||
"id": "105",
|
||||
"meta": {
|
||||
"instanceId": "36203ea1ce3cef713fa25999bd9874ae26b9e4c2c3a90a365f2882a154d031d0"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
{
|
||||
"name": "itemList split Object",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "ade46a75-ab57-48c6-886b-0c118f5ef1c6",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [520, 800]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data",
|
||||
"include": "selectedOtherFields",
|
||||
"fieldsToInclude": ["tag"],
|
||||
"options": {}
|
||||
},
|
||||
"id": "45e1d7a3-d6e8-4b69-a68a-1038db13be4c",
|
||||
"name": "Item Lists1",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [1120, 340]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": {
|
||||
"data": {
|
||||
"entry1": {
|
||||
"id": 1,
|
||||
"info": "some info 1"
|
||||
},
|
||||
"entry2": {
|
||||
"id": 2,
|
||||
"info": "some info 2"
|
||||
},
|
||||
"entry3": {
|
||||
"id": 3,
|
||||
"info": "some info 3"
|
||||
}
|
||||
},
|
||||
"data2": ["a", "b", "c"],
|
||||
"data3": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3
|
||||
},
|
||||
"data4": null,
|
||||
"tag": "bar"
|
||||
}
|
||||
},
|
||||
"id": "faa78fac-468d-42b8-96e9-0fb62c312da3",
|
||||
"name": "Code1",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [760, 800]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "5baaf321-7e89-473d-a313-7cb90b3f13b3",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1380, 340]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data3",
|
||||
"include": "allOtherFields",
|
||||
"options": {
|
||||
"destinationFieldName": "extracted"
|
||||
}
|
||||
},
|
||||
"id": "a786bea9-eb29-4c6d-aea6-a22aee622bc6",
|
||||
"name": "Item Lists",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [1120, 720]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "0521a24b-c74a-48fa-ae50-48a242b97806",
|
||||
"name": "No Operation, do nothing1",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1380, 720]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data3",
|
||||
"options": {}
|
||||
},
|
||||
"id": "0c1c8827-72ab-4738-918c-d529e66505c6",
|
||||
"name": "Item Lists2",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [1120, 540]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "4c0dca36-c2ae-4d40-8952-0e728ac93fa3",
|
||||
"name": "No Operation, do nothing2",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1380, 540]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data2",
|
||||
"options": {}
|
||||
},
|
||||
"id": "b2031380-b2a8-426d-8f7a-ab072d23b979",
|
||||
"name": "Item Lists3",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [1120, 920]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "617f7259-beee-42f1-bba2-4e75a83fe369",
|
||||
"name": "No Operation, do nothing3",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1380, 920]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data4",
|
||||
"include": "allOtherFields",
|
||||
"options": {}
|
||||
},
|
||||
"id": "8909b8eb-e5a9-4436-8e62-09d8c9670ac1",
|
||||
"name": "Item Lists4",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [1120, 1140],
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "a9278f90-8ad9-42dc-85b6-28bf1b6764b7",
|
||||
"name": "No Operation, do nothing4",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1380, 1140]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"tag": "bar",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"info": "some info 1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"tag": "bar",
|
||||
"data": {
|
||||
"id": 2,
|
||||
"info": "some info 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"tag": "bar",
|
||||
"data": {
|
||||
"id": 3,
|
||||
"info": "some info 3"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"No Operation, do nothing2": [
|
||||
{
|
||||
"json": {
|
||||
"data3": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data3": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data3": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"No Operation, do nothing1": [
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"entry1": {
|
||||
"id": 1,
|
||||
"info": "some info 1"
|
||||
},
|
||||
"entry2": {
|
||||
"id": 2,
|
||||
"info": "some info 2"
|
||||
},
|
||||
"entry3": {
|
||||
"id": 3,
|
||||
"info": "some info 3"
|
||||
}
|
||||
},
|
||||
"data2": ["a", "b", "c"],
|
||||
"data4": null,
|
||||
"tag": "bar",
|
||||
"extracted": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"entry1": {
|
||||
"id": 1,
|
||||
"info": "some info 1"
|
||||
},
|
||||
"entry2": {
|
||||
"id": 2,
|
||||
"info": "some info 2"
|
||||
},
|
||||
"entry3": {
|
||||
"id": 3,
|
||||
"info": "some info 3"
|
||||
}
|
||||
},
|
||||
"data2": ["a", "b", "c"],
|
||||
"data4": null,
|
||||
"tag": "bar",
|
||||
"extracted": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"entry1": {
|
||||
"id": 1,
|
||||
"info": "some info 1"
|
||||
},
|
||||
"entry2": {
|
||||
"id": 2,
|
||||
"info": "some info 2"
|
||||
},
|
||||
"entry3": {
|
||||
"id": 3,
|
||||
"info": "some info 3"
|
||||
}
|
||||
},
|
||||
"data2": ["a", "b", "c"],
|
||||
"data4": null,
|
||||
"tag": "bar",
|
||||
"extracted": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"No Operation, do nothing3": [
|
||||
{
|
||||
"json": {
|
||||
"data2": "a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data2": "b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"data2": "c"
|
||||
}
|
||||
}
|
||||
],
|
||||
"No Operation, do nothing4": [
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"entry1": {
|
||||
"id": 1,
|
||||
"info": "some info 1"
|
||||
},
|
||||
"entry2": {
|
||||
"id": 2,
|
||||
"info": "some info 2"
|
||||
},
|
||||
"entry3": {
|
||||
"id": 3,
|
||||
"info": "some info 3"
|
||||
}
|
||||
},
|
||||
"data2": ["a", "b", "c"],
|
||||
"data3": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3
|
||||
},
|
||||
"data4": null,
|
||||
"tag": "bar"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Item Lists1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Item Lists2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Item Lists",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Item Lists3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Item Lists4",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Item Lists1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Item Lists": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Item Lists2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Item Lists3": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Item Lists4": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing4",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user