From e2c3c7acebdae2014b32620277fce70d0db85dd0 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:36:20 +0300 Subject: [PATCH] fix(core): Missing pairing info (#7326) Github issue / Community forum post (link here to close automatically): --- .../test/v2/node/base/getMany.test.ts | 32 ++++++++++------- .../nodes/Airtable/test/v2/node/helpers.ts | 3 ++ .../test/v2/node/record/search.test.ts | 16 +++++---- .../nodes/Airtable/v1/AirtableV1.node.ts | 5 ++- .../v2/actions/base/getMany.operation.ts | 10 ++++-- .../v2/actions/record/search.operation.ts | 6 ++-- .../nodes/CompareDatasets/GenericFunctions.ts | 6 +++- .../nodes-base/nodes/Filter/Filter.node.ts | 4 +++ .../BigQuery/v1/GoogleBigQueryV1.node.ts | 7 ++-- .../v2/actions/database/insert.operation.ts | 5 +-- .../GoogleFirebaseCloudFirestore.node.ts | 8 +++-- .../Google/Sheet/v1/GoogleSheetsV1.node.ts | 12 ++++++- .../v2/actions/sheet/append.operation.ts | 9 ++++- .../actions/sheet/appendOrUpdate.operation.ts | 9 ++++- .../v2/actions/sheet/create.operation.ts | 12 +++++-- .../v2/actions/sheet/delete.operation.ts | 8 ++++- .../Sheet/v2/actions/sheet/read.operation.ts | 19 ++++++++--- .../v2/actions/sheet/remove.operation.ts | 13 +++++-- .../v2/actions/sheet/update.operation.ts | 10 +++++- .../actions/spreadsheet/create.operation.ts | 13 +++++-- .../actions/spreadsheet/delete.operation.ts | 25 +++++--------- .../nodes/Hubspot/V2/HubspotV2.node.ts | 11 ++++-- packages/nodes-base/nodes/If/If.node.ts | 4 +++ packages/nodes-base/nodes/Kafka/Kafka.node.ts | 11 ++++-- .../nodes/Merge/v2/GenericFunctions.ts | 22 ++++++++---- .../Excel/test/v2/utils/utils.test.ts | 6 ++++ .../Excel/v1/MicrosoftExcelV1.node.ts | 6 ++-- .../v2/actions/table/append.operation.ts | 5 +-- .../v2/actions/worksheet/update.operation.ts | 5 +-- .../v2/actions/worksheet/upsert.operation.ts | 5 +-- .../nodes/Microsoft/Excel/v2/helpers/utils.ts | 5 +-- .../nodes/Microsoft/Sql/MicrosoftSql.node.ts | 6 ++-- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 5 ++- .../nodes/MySql/test/v2/operations.test.ts | 24 +++++++------ .../nodes/MySql/test/v2/runQueries.test.ts | 6 ++-- .../nodes/MySql/v2/helpers/utils.ts | 25 ++++++++++---- .../nodes/NextCloud/NextCloud.node.ts | 34 ++++++++++++++----- .../nodes/Pipedrive/Pipedrive.node.ts | 3 +- .../nodes/Postgres/test/v2/runQueries.test.ts | 2 +- .../nodes/Postgres/v2/helpers/utils.ts | 11 ++++-- .../nodes/QuickBase/QuickBase.node.ts | 8 +++-- .../ReadBinaryFiles/ReadBinaryFiles.node.ts | 6 ++-- .../nodes/RssFeedRead/RssFeedRead.node.ts | 14 +++++--- .../nodes-base/nodes/Set/v1/SetV1.node.ts | 2 +- .../nodes-base/nodes/Set/v2/helpers/utils.ts | 2 +- .../nodes-base/nodes/Set/v2/manual.mode.ts | 2 +- packages/nodes-base/nodes/Set/v2/raw.mode.ts | 2 +- .../v1/SpreadsheetFileV1.node.ts | 11 +++--- .../v2/SpreadsheetFileV2.node.ts | 11 +++--- .../nodes-base/nodes/Stackby/Stackby.node.ts | 4 ++- packages/nodes-base/nodes/Wekan/Wekan.node.ts | 16 +++++---- packages/nodes-base/utils/utilities.ts | 14 ++++++++ 52 files changed, 363 insertions(+), 157 deletions(-) diff --git a/packages/nodes-base/nodes/Airtable/test/v2/node/base/getMany.test.ts b/packages/nodes-base/nodes/Airtable/test/v2/node/base/getMany.test.ts index 6a1e6daf7b..f535def3cb 100644 --- a/packages/nodes-base/nodes/Airtable/test/v2/node/base/getMany.test.ts +++ b/packages/nodes-base/nodes/Airtable/test/v2/node/base/getMany.test.ts @@ -61,9 +61,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 1', permissionLevel: 'create', }, - pairedItem: { - item: 0, - }, + pairedItem: [ + { + item: 0, + }, + ], }, { json: { @@ -71,9 +73,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 2', permissionLevel: 'edit', }, - pairedItem: { - item: 0, - }, + pairedItem: [ + { + item: 0, + }, + ], }, { json: { @@ -81,9 +85,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 3', permissionLevel: 'create', }, - pairedItem: { - item: 0, - }, + pairedItem: [ + { + item: 0, + }, + ], }, ]); }); @@ -107,9 +113,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 2', permissionLevel: 'edit', }, - pairedItem: { - item: 0, - }, + pairedItem: [ + { + item: 0, + }, + ], }, ]); }); diff --git a/packages/nodes-base/nodes/Airtable/test/v2/node/helpers.ts b/packages/nodes-base/nodes/Airtable/test/v2/node/helpers.ts index d9f6a21806..1edd17cd16 100644 --- a/packages/nodes-base/nodes/Airtable/test/v2/node/helpers.ts +++ b/packages/nodes-base/nodes/Airtable/test/v2/node/helpers.ts @@ -16,6 +16,9 @@ export const node: INode = { export const createMockExecuteFunction = (nodeParameters: IDataObject) => { const fakeExecuteFunction = { + getInputData() { + return [{ json: {} }]; + }, getNodeParameter( parameterName: string, _itemIndex: number, diff --git a/packages/nodes-base/nodes/Airtable/test/v2/node/record/search.test.ts b/packages/nodes-base/nodes/Airtable/test/v2/node/record/search.test.ts index 8105323641..83e2bab61b 100644 --- a/packages/nodes-base/nodes/Airtable/test/v2/node/record/search.test.ts +++ b/packages/nodes-base/nodes/Airtable/test/v2/node/record/search.test.ts @@ -110,9 +110,11 @@ describe('Test AirtableV2, search operation', () => { expect(result).toHaveLength(2); expect(result[0]).toEqual({ json: { id: 'recYYY', foo: 'foo 2', bar: 'bar 2' }, - pairedItem: { - item: 0, - }, + pairedItem: [ + { + item: 0, + }, + ], }); }); @@ -152,9 +154,11 @@ describe('Test AirtableV2, search operation', () => { expect(result).toHaveLength(1); expect(result[0]).toEqual({ json: { id: 'recYYY', foo: 'foo 2', bar: 'bar 2' }, - pairedItem: { - item: 0, - }, + pairedItem: [ + { + item: 0, + }, + ], }); }); }); diff --git a/packages/nodes-base/nodes/Airtable/v1/AirtableV1.node.ts b/packages/nodes-base/nodes/Airtable/v1/AirtableV1.node.ts index 9b78b57e53..353423dd0f 100644 --- a/packages/nodes-base/nodes/Airtable/v1/AirtableV1.node.ts +++ b/packages/nodes-base/nodes/Airtable/v1/AirtableV1.node.ts @@ -13,6 +13,7 @@ import type { IRecord } from './GenericFunctions'; import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from './GenericFunctions'; import { oldVersionNotice } from '../../../utils/descriptions'; +import { generatePairedItemData } from '../../../utils/utilities'; const versionDescription: INodeTypeDescription = { displayName: 'Airtable', @@ -727,9 +728,11 @@ export class AirtableV1 implements INodeType { } // We can return from here + const itemData = generatePairedItemData(items.length); + return [ this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(returnData), { - itemData: { item: 0 }, + itemData, }), ]; } catch (error) { diff --git a/packages/nodes-base/nodes/Airtable/v2/actions/base/getMany.operation.ts b/packages/nodes-base/nodes/Airtable/v2/actions/base/getMany.operation.ts index d29dd10960..a0ab51ac58 100644 --- a/packages/nodes-base/nodes/Airtable/v2/actions/base/getMany.operation.ts +++ b/packages/nodes-base/nodes/Airtable/v2/actions/base/getMany.operation.ts @@ -4,7 +4,11 @@ import type { INodeProperties, IExecuteFunctions, } from 'n8n-workflow'; -import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities'; +import { + generatePairedItemData, + updateDisplayOptions, + wrapData, +} from '../../../../../utils/utilities'; import { apiRequest } from '../../transport'; const properties: INodeProperties[] = [ @@ -107,8 +111,10 @@ export async function execute(this: IExecuteFunctions): Promise permissionLevel.includes(base.permissionLevel as string)); } + const itemData = generatePairedItemData(this.getInputData().length); + const returnData = this.helpers.constructExecutionMetaData(wrapData(bases), { - itemData: { item: 0 }, + itemData, }); return returnData; diff --git a/packages/nodes-base/nodes/Airtable/v2/actions/record/search.operation.ts b/packages/nodes-base/nodes/Airtable/v2/actions/record/search.operation.ts index a46aedf182..75f4e47d9a 100644 --- a/packages/nodes-base/nodes/Airtable/v2/actions/record/search.operation.ts +++ b/packages/nodes-base/nodes/Airtable/v2/actions/record/search.operation.ts @@ -4,7 +4,7 @@ import type { INodeProperties, IExecuteFunctions, } from 'n8n-workflow'; -import { updateDisplayOptions } from '../../../../../utils/utilities'; +import { generatePairedItemData, updateDisplayOptions } from '../../../../../utils/utilities'; import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from '../../transport'; import type { IRecord } from '../../helpers/interfaces'; import { flattenOutput } from '../../helpers/utils'; @@ -205,8 +205,10 @@ export async function execute( json: flattenOutput(record as IDataObject), })); + const itemData = generatePairedItemData(items.length); + returnData = this.helpers.constructExecutionMetaData(returnData, { - itemData: { item: 0 }, + itemData, }); } catch (error) { if (this.continueOnFail()) { diff --git a/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts b/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts index c8e4709c46..c89ac64040 100644 --- a/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts +++ b/packages/nodes-base/nodes/CompareDatasets/GenericFunctions.ts @@ -9,7 +9,7 @@ import unset from 'lodash/unset'; import { cloneDeep } from 'lodash'; import set from 'lodash/set'; import union from 'lodash/union'; -import { fuzzyCompare } from '@utils/utilities'; +import { fuzzyCompare, preparePairedItemDataArray } from '@utils/utilities'; type PairToMatch = { field1: string; @@ -141,6 +141,10 @@ function compareItems( return { json: { keys, same, different, ...(!isEmpty(skipped) && { skipped }) }, + pairedItem: [ + ...preparePairedItemDataArray(item1.pairedItem), + ...preparePairedItemDataArray(item2.pairedItem), + ], } as INodeExecutionData; } diff --git a/packages/nodes-base/nodes/Filter/Filter.node.ts b/packages/nodes-base/nodes/Filter/Filter.node.ts index bc8b006a65..d5a3e84d27 100644 --- a/packages/nodes-base/nodes/Filter/Filter.node.ts +++ b/packages/nodes-base/nodes/Filter/Filter.node.ts @@ -340,6 +340,10 @@ export class Filter implements INodeType { value2, ); + if (item.pairedItem === undefined) { + item.pairedItem = [{ item: itemIndex }]; + } + // If the operation is "OR" it means the item did match one condition no ned to check further if (compareResult && combineConditions === 'OR') { returnDataTrue.push(item); diff --git a/packages/nodes-base/nodes/Google/BigQuery/v1/GoogleBigQueryV1.node.ts b/packages/nodes-base/nodes/Google/BigQuery/v1/GoogleBigQueryV1.node.ts index dd603dbc69..e250fc3b69 100644 --- a/packages/nodes-base/nodes/Google/BigQuery/v1/GoogleBigQueryV1.node.ts +++ b/packages/nodes-base/nodes/Google/BigQuery/v1/GoogleBigQueryV1.node.ts @@ -20,6 +20,7 @@ import { recordFields, recordOperations } from './RecordDescription'; import { v4 as uuid } from 'uuid'; import { oldVersionNotice } from '@utils/descriptions'; +import { generatePairedItemData } from '../../../../utils/utilities'; const versionDescription: INodeTypeDescription = { displayName: 'Google BigQuery', @@ -198,6 +199,8 @@ export class GoogleBigQueryV1 implements INodeType { body.rows = rows; + const itemData = generatePairedItemData(items.length); + try { responseData = await googleApiRequest.call( this, @@ -208,14 +211,14 @@ export class GoogleBigQueryV1 implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionErrorData); } diff --git a/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/insert.operation.ts b/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/insert.operation.ts index 5a8fa3fc51..19c51467e0 100644 --- a/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/insert.operation.ts +++ b/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/insert.operation.ts @@ -6,7 +6,7 @@ import type { } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import { v4 as uuid } from 'uuid'; -import { updateDisplayOptions } from '@utils/utilities'; +import { generatePairedItemData, updateDisplayOptions } from '@utils/utilities'; import type { TableSchema } from '../../helpers/interfaces'; import { checkSchema, wrapData } from '../../helpers/utils'; import { googleApiRequest } from '../../transport'; @@ -225,6 +225,7 @@ export async function execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const itemData = generatePairedItemData(items.length); const returnData: INodeExecutionData[] = []; let responseData; const resource = this.getNodeParameter('resource', 0); @@ -130,7 +132,7 @@ export class GoogleFirebaseCloudFirestore implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); @@ -213,7 +215,7 @@ export class GoogleFirebaseCloudFirestore implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); @@ -395,7 +397,7 @@ export class GoogleFirebaseCloudFirestore implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); diff --git a/packages/nodes-base/nodes/Google/Sheet/v1/GoogleSheetsV1.node.ts b/packages/nodes-base/nodes/Google/Sheet/v1/GoogleSheetsV1.node.ts index 78b676a201..e276054e7f 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v1/GoogleSheetsV1.node.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v1/GoogleSheetsV1.node.ts @@ -26,6 +26,7 @@ import { googleApiRequest, hexToRgb } from './GenericFunctions'; import { versionDescription } from './versionDescription'; import { getGoogleAccessToken } from '../../GenericFunctions'; +import { generatePairedItemData } from '../../../../utils/utilities'; export class GoogleSheetsV1 implements INodeType { description: INodeTypeDescription; @@ -295,7 +296,16 @@ export class GoogleSheetsV1 implements INodeType { returnData = []; } - return [this.helpers.returnJsonArray(returnData)]; + const pairedItem = generatePairedItemData(items.length); + + const lookupOutput = returnData.map((item) => { + return { + json: item, + pairedItem, + }; + }); + + return [lookupOutput]; } catch (error) { if (this.continueOnFail()) { return [this.helpers.returnJsonArray({ error: error.message })]; diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts index 5e89da3a36..0ae45163ca 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts @@ -234,6 +234,13 @@ export async function execute( if (nodeVersion < 4 || dataMode === 'autoMapInputData') { return items; } else { - return this.helpers.returnJsonArray(setData); + const returnData: INodeExecutionData[] = []; + for (const [index, entry] of setData.entries()) { + returnData.push({ + json: entry, + pairedItems: { item: index }, + }); + } + return returnData; } } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts index c793989d69..0f5ab62f40 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts @@ -387,6 +387,13 @@ export async function execute( if (nodeVersion < 4 || dataMode === 'autoMapInputData') { return items; } else { - return this.helpers.returnJsonArray(mappedValues); + const returnData: INodeExecutionData[] = []; + for (const [index, entry] of mappedValues.entries()) { + returnData.push({ + json: entry, + pairedItems: { item: index }, + }); + } + return returnData; } } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/create.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/create.operation.ts index 2b3d024581..7dd880fb52 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/create.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/create.operation.ts @@ -3,6 +3,7 @@ import type { SheetProperties } from '../../helpers/GoogleSheets.types'; import { apiRequest } from '../../transport'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; import { getExistingSheetNames, hexToRgb } from '../../helpers/GoogleSheets.utils'; +import { wrapData } from '../../../../../../utils/utilities'; export const description: SheetProperties = [ { @@ -78,7 +79,7 @@ export async function execute( sheetName: string, ): Promise { let responseData; - const returnData: IDataObject[] = []; + const returnData: INodeExecutionData[] = []; const items = this.getInputData(); const existingSheetNames = await getExistingSheetNames(sheet); @@ -120,7 +121,12 @@ export async function execute( existingSheetNames.push(sheetTitle); - returnData.push(responseData as IDataObject); + const executionData = this.helpers.constructExecutionMetaData( + wrapData(responseData as IDataObject[]), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); } - return this.helpers.returnJsonArray(returnData); + return returnData; } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/delete.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/delete.operation.ts index 42ec063c5e..8edd179d0e 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/delete.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/delete.operation.ts @@ -2,6 +2,7 @@ import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-wor import type { SheetProperties } from '../../helpers/GoogleSheets.types'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; import { getColumnNumber, untilSheetSelected } from '../../helpers/GoogleSheets.utils'; +import { generatePairedItemData, wrapData } from '../../../../../../utils/utilities'; export const description: SheetProperties = [ { @@ -164,5 +165,10 @@ export async function execute( await sheet.spreadsheetBatchUpdate(requests); } - return this.helpers.returnJsonArray({ success: true }); + const itemData = generatePairedItemData(this.getInputData().length); + const returnData = this.helpers.constructExecutionMetaData(wrapData({ success: true }), { + itemData, + }); + + return returnData; } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts index b4b64d1786..626bfbcdc5 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts @@ -13,6 +13,7 @@ import type { SheetRangeData, ValueRenderOption, } from '../../helpers/GoogleSheets.types'; +import { generatePairedItemData } from '../../../../../../utils/utilities'; export const description: SheetProperties = [ { @@ -139,7 +140,7 @@ export async function execute( const { data, headerRow, firstDataRow } = prepareSheetData(sheetData, dataLocationOnSheetOptions); - let returnData = []; + let responseData = []; const lookupValues = this.getNodeParameter('filtersUI.values', 0, []) as ILookupValues[]; @@ -154,7 +155,7 @@ export async function execute( } } - returnData = await sheet.lookupValues( + responseData = await sheet.lookupValues( data as string[][], headerRow, firstDataRow, @@ -162,8 +163,18 @@ export async function execute( returnAllMatches, ); } else { - returnData = sheet.structureArrayDataByColumn(data as string[][], headerRow, firstDataRow); + responseData = sheet.structureArrayDataByColumn(data as string[][], headerRow, firstDataRow); } - return this.helpers.returnJsonArray(returnData); + const items = this.getInputData(); + const pairedItem = generatePairedItemData(items.length); + + const returnData: INodeExecutionData[] = responseData.map((item, index) => { + return { + json: item, + pairedItem, + }; + }); + + return returnData; } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/remove.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/remove.operation.ts index 7c856d2621..fd44494cd1 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/remove.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/remove.operation.ts @@ -1,13 +1,14 @@ import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; import { apiRequest } from '../../transport'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; +import { wrapData } from '../../../../../../utils/utilities'; export async function execute( this: IExecuteFunctions, sheet: GoogleSheet, sheetName: string, ): Promise { - const returnData: IDataObject[] = []; + const returnData: INodeExecutionData[] = []; const items = this.getInputData(); for (let i = 0; i < items.length; i++) { const [spreadsheetId, sheetWithinDocument] = sheetName.split('||'); @@ -26,8 +27,14 @@ export async function execute( { requests }, ); delete responseData.replies; - returnData.push(responseData as IDataObject); + + const executionData = this.helpers.constructExecutionMetaData( + wrapData(responseData as IDataObject[]), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); } - return this.helpers.returnJsonArray(returnData); + return returnData; } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts index 00546da233..ffea82f5ad 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts @@ -380,6 +380,14 @@ export async function execute( if (!updateData.length) { return []; } - return this.helpers.returnJsonArray(mappedValues); + + const returnData: INodeExecutionData[] = []; + for (const [index, entry] of mappedValues.entries()) { + returnData.push({ + json: entry, + pairedItems: { item: index }, + }); + } + return returnData; } } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/spreadsheet/create.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/spreadsheet/create.operation.ts index d1e93c7171..155203e376 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/spreadsheet/create.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/spreadsheet/create.operation.ts @@ -1,6 +1,7 @@ import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; import type { SpreadSheetProperties } from '../../helpers/GoogleSheets.types'; import { apiRequest } from '../../transport'; +import { wrapData } from '../../../../../../utils/utilities'; export const description: SpreadSheetProperties = [ { @@ -115,7 +116,7 @@ export const description: SpreadSheetProperties = [ export async function execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); - const returnData: IDataObject[] = []; + const returnData: INodeExecutionData[] = []; for (let i = 0; i < items.length; i++) { const title = this.getNodeParameter('title', i) as string; @@ -145,8 +146,14 @@ export async function execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); - const returnData: IDataObject[] = []; + const returnData: INodeExecutionData[] = []; for (let i = 0; i < items.length; i++) { // const spreadsheetId = this.getNodeParameter('spreadsheetId', i) as string; @@ -94,8 +83,12 @@ export async function execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const itemData = generatePairedItemData(items.length); const length = items.length; @@ -396,10 +398,15 @@ export class Kafka implements INodeType { await producer.disconnect(); - return [this.helpers.returnJsonArray(responseData)]; + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData), + { itemData }, + ); + + return [executionData]; } catch (error) { if (this.continueOnFail()) { - return [this.helpers.returnJsonArray({ error: error.message })]; + return [[{ json: { error: error.message }, pairedItem: itemData }]]; } else { throw error; } diff --git a/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts b/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts index a74ec6e44f..83d5a3deeb 100644 --- a/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Merge/v2/GenericFunctions.ts @@ -11,7 +11,7 @@ import assignWith from 'lodash/assignWith'; import get from 'lodash/get'; import merge from 'lodash/merge'; import mergeWith from 'lodash/mergeWith'; -import { fuzzyCompare } from '@utils/utilities'; +import { fuzzyCompare, preparePairedItemDataArray } from '@utils/utilities'; type PairToMatch = { field1: string; @@ -257,6 +257,7 @@ export function mergeMatched( let json: IDataObject = {}; let binary: IBinaryKeyData = {}; + let pairedItem: IPairedItemData[] = []; if (resolveClash === 'addSuffix') { const suffix1 = '1'; @@ -270,6 +271,10 @@ export function mergeMatched( { ...entry.binary }, ...matches.map((item) => item.binary as IDataObject), ); + pairedItem = [ + ...preparePairedItemDataArray(entry.pairedItem), + ...matches.map((item) => preparePairedItemDataArray(item.pairedItem)).flat(), + ]; } else { const preferInput1 = 'preferInput1'; const preferInput2 = 'preferInput2'; @@ -294,6 +299,12 @@ export function mergeMatched( ...restMatches.map((item) => item.binary as IDataObject), entry.binary as IDataObject, ); + + pairedItem = [ + ...preparePairedItemDataArray(firstMatch.pairedItem), + ...restMatches.map((item) => preparePairedItemDataArray(item.pairedItem)).flat(), + ...preparePairedItemDataArray(entry.pairedItem), + ]; } if (resolveClash === preferInput2) { @@ -302,14 +313,13 @@ export function mergeMatched( { ...entry.binary }, ...matches.map((item) => item.binary as IDataObject), ); + pairedItem = [ + ...preparePairedItemDataArray(entry.pairedItem), + ...matches.map((item) => preparePairedItemDataArray(item.pairedItem)).flat(), + ]; } } - const pairedItem = [ - entry.pairedItem as IPairedItemData, - ...matches.map((m) => m.pairedItem as IPairedItemData), - ]; - returnData.push({ json, binary, diff --git a/packages/nodes-base/nodes/Microsoft/Excel/test/v2/utils/utils.test.ts b/packages/nodes-base/nodes/Microsoft/Excel/test/v2/utils/utils.test.ts index c67729b028..35cd4133d6 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/test/v2/utils/utils.test.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/test/v2/utils/utils.test.ts @@ -19,6 +19,9 @@ const node: INode = { const fakeExecute = (nodeParameters: IDataObject[]) => { const fakeExecuteFunction = { + getInputData() { + return [{ json: {} }]; + }, getNodeParameter( parameterName: string, itemIndex: number, @@ -90,6 +93,9 @@ const responseData = { describe('Test MicrosoftExcelV2, prepareOutput', () => { const thisArg = mock({ helpers: mock({ constructExecutionMetaData }), + getInputData() { + return [{ json: {} }]; + }, }); it('should return empty array', () => { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/v1/MicrosoftExcelV1.node.ts b/packages/nodes-base/nodes/Microsoft/Excel/v1/MicrosoftExcelV1.node.ts index 082589514a..d810badc5c 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/v1/MicrosoftExcelV1.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/v1/MicrosoftExcelV1.node.ts @@ -25,6 +25,7 @@ import { worksheetFields, worksheetOperations } from './WorksheetDescription'; import { tableFields, tableOperations } from './TableDescription'; import { oldVersionNotice } from '@utils/descriptions'; +import { generatePairedItemData } from '../../../../utils/utilities'; const versionDescription: INodeTypeDescription = { displayName: 'Microsoft Excel', @@ -176,6 +177,7 @@ export class MicrosoftExcelV1 implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const itemData = generatePairedItemData(items.length); const returnData: INodeExecutionData[] = []; const length = items.length; let qs: IDataObject = {}; @@ -249,7 +251,7 @@ export class MicrosoftExcelV1 implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); @@ -257,7 +259,7 @@ export class MicrosoftExcelV1 implements INodeType { if (this.continueOnFail()) { const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionErrorData); } else { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/table/append.operation.ts b/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/table/append.operation.ts index 3b69f3ce45..81e8d35f0a 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/table/append.operation.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/table/append.operation.ts @@ -4,7 +4,7 @@ import type { INodeExecutionData, INodeProperties, } from 'n8n-workflow'; -import { processJsonInput, updateDisplayOptions } from '@utils/utilities'; +import { generatePairedItemData, processJsonInput, updateDisplayOptions } from '@utils/utilities'; import type { ExcelResponse } from '../../helpers/interfaces'; import { prepareOutput } from '../../helpers/utils'; import { microsoftApiRequest } from '../../transport'; @@ -272,9 +272,10 @@ export async function execute( ); } catch (error) { if (this.continueOnFail()) { + const itemData = generatePairedItemData(this.getInputData().length); const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionErrorData); } else { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/update.operation.ts b/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/update.operation.ts index 06496008ff..863a0c4fcf 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/update.operation.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/update.operation.ts @@ -5,7 +5,7 @@ import type { INodeProperties, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; -import { processJsonInput, updateDisplayOptions } from '@utils/utilities'; +import { generatePairedItemData, processJsonInput, updateDisplayOptions } from '@utils/utilities'; import type { ExcelResponse, UpdateSummary } from '../../helpers/interfaces'; import { prepareOutput, updateByAutoMaping, updateByDefinedValues } from '../../helpers/utils'; import { microsoftApiRequest } from '../../transport'; @@ -366,9 +366,10 @@ export async function execute( } } catch (error) { if (this.continueOnFail()) { + const itemData = generatePairedItemData(this.getInputData().length); const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionErrorData); } else { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/upsert.operation.ts b/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/upsert.operation.ts index 6e90cbc839..75484d7783 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/upsert.operation.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/v2/actions/worksheet/upsert.operation.ts @@ -5,7 +5,7 @@ import type { INodeProperties, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; -import { processJsonInput, updateDisplayOptions } from '@utils/utilities'; +import { generatePairedItemData, processJsonInput, updateDisplayOptions } from '@utils/utilities'; import type { ExcelResponse, UpdateSummary } from '../../helpers/interfaces'; import { prepareOutput, updateByAutoMaping, updateByDefinedValues } from '../../helpers/utils'; import { microsoftApiRequest } from '../../transport'; @@ -323,9 +323,10 @@ export async function execute( ); } catch (error) { if (this.continueOnFail()) { + const itemData = generatePairedItemData(this.getInputData().length); const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionErrorData); } else { diff --git a/packages/nodes-base/nodes/Microsoft/Excel/v2/helpers/utils.ts b/packages/nodes-base/nodes/Microsoft/Excel/v2/helpers/utils.ts index 133ee979f3..3dee1c9fcb 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/v2/helpers/utils.ts @@ -1,7 +1,7 @@ import type { IDataObject, IExecuteFunctions, INode, INodeExecutionData } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import type { ExcelResponse, SheetData, UpdateSummary } from './interfaces'; -import { wrapData } from '@utils/utilities'; +import { generatePairedItemData, wrapData } from '@utils/utilities'; type PrepareOutputConfig = { rawData: boolean; @@ -60,9 +60,10 @@ export function prepareOutput( returnData.push(...executionData); } } else { + const itemData = generatePairedItemData(this.getInputData().length); const executionData = this.helpers.constructExecutionMetaData( wrapData({ [config.dataProperty || 'data']: responseData }), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); diff --git a/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts b/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts index c5a61bcee7..f91a5dc9ad 100644 --- a/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts @@ -25,7 +25,7 @@ import { extractValues, formatColumns, } from './GenericFunctions'; -import { chunk, flatten, getResolvables } from '@utils/utilities'; +import { chunk, flatten, generatePairedItemData, getResolvables } from '@utils/utilities'; export class MicrosoftSql implements INodeType { description: INodeTypeDescription = { @@ -442,9 +442,11 @@ export class MicrosoftSql implements INodeType { // Close the connection await pool.close(); + + const itemData = generatePairedItemData(items.length); const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData), - { itemData: { item: 0 } }, + { itemData }, ); returnItems.push(...executionData); diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index 65babe3d87..b8a99573f3 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -29,6 +29,7 @@ import { } from './GenericFunctions'; import type { IMongoParametricCredentials } from './mongoDb.types'; +import { generatePairedItemData } from '../../utils/utilities'; export class MongoDb implements INodeType { description: INodeTypeDescription = { @@ -365,9 +366,11 @@ export class MongoDb implements INodeType { stringifyObjectIDs(responseData); + const itemData = generatePairedItemData(items.length); + const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData), - { itemData: { item: 0 } }, + { itemData }, ); returnItems.push(...executionData); diff --git a/packages/nodes-base/nodes/MySql/test/v2/operations.test.ts b/packages/nodes-base/nodes/MySql/test/v2/operations.test.ts index 58e011a731..9a7d4b544c 100644 --- a/packages/nodes-base/nodes/MySql/test/v2/operations.test.ts +++ b/packages/nodes-base/nodes/MySql/test/v2/operations.test.ts @@ -1,8 +1,8 @@ import type { IDataObject, INode } from 'n8n-workflow'; -import { createMockExecuteFunction } from '@test/nodes/Helpers'; - +import mysql2 from 'mysql2/promise'; import * as deleteTable from '../../v2/actions/database/deleteTable.operation'; + import * as executeQuery from '../../v2/actions/database/executeQuery.operation'; import * as insert from '../../v2/actions/database/insert.operation'; import * as select from '../../v2/actions/database/select.operation'; @@ -11,8 +11,7 @@ import * as upsert from '../../v2/actions/database/upsert.operation'; import type { Mysql2Pool, QueryRunner } from '../../v2/helpers/interfaces'; import { configureQueryRunner } from '../../v2/helpers/utils'; - -import mysql2 from 'mysql2/promise'; +import { createMockExecuteFunction } from '@test/nodes/Helpers'; const mySqlMockNode: INode = { id: '1', @@ -97,7 +96,7 @@ describe('Test MySql V2, operations', () => { const result = await deleteTable.execute.call(fakeExecuteFunction, emptyInputItems, runQueries); expect(result).toBeDefined(); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: [{ item: 0 }] }]); expect(poolQuerySpy).toBeCalledTimes(1); expect(poolQuerySpy).toBeCalledWith('DROP TABLE IF EXISTS `test_table`'); @@ -133,7 +132,7 @@ describe('Test MySql V2, operations', () => { const result = await deleteTable.execute.call(fakeExecuteFunction, emptyInputItems, runQueries); expect(result).toBeDefined(); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: [{ item: 0 }] }]); expect(poolQuerySpy).toBeCalledTimes(1); expect(poolQuerySpy).toBeCalledWith('TRUNCATE TABLE `test_table`'); @@ -183,7 +182,7 @@ describe('Test MySql V2, operations', () => { const result = await deleteTable.execute.call(fakeExecuteFunction, emptyInputItems, runQueries); expect(result).toBeDefined(); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: [{ item: 0 }] }]); expect(poolQuerySpy).toBeCalledTimes(1); expect(poolQuerySpy).toBeCalledWith( @@ -312,7 +311,7 @@ describe('Test MySql V2, operations', () => { const result = await select.execute.call(fakeExecuteFunction, emptyInputItems, runQueries); expect(result).toBeDefined(); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: { item: 0 } }]); const connectionBeginTransactionSpy = jest.spyOn(fakeConnection, 'beginTransaction'); const connectionCommitSpy = jest.spyOn(fakeConnection, 'commit'); @@ -378,7 +377,7 @@ describe('Test MySql V2, operations', () => { ); expect(result).toBeDefined(); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: { item: 0 } }]); expect(connectionQuerySpy).toBeCalledTimes(1); expect(connectionQuerySpy).toBeCalledWith( @@ -439,7 +438,10 @@ describe('Test MySql V2, operations', () => { ); expect(result).toBeDefined(); - expect(result).toEqual([{ json: { success: true } }, { json: { success: true } }]); + expect(result).toEqual([ + { json: { success: true }, pairedItem: { item: 0 } }, + { json: { success: true }, pairedItem: { item: 1 } }, + ]); expect(connectionQuerySpy).toBeCalledTimes(2); expect(connectionQuerySpy).toBeCalledWith( @@ -501,7 +503,7 @@ describe('Test MySql V2, operations', () => { ); expect(result).toBeDefined(); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: [{ item: 0 }, { item: 1 }] }]); expect(poolQuerySpy).toBeCalledTimes(1); expect(poolQuerySpy).toBeCalledWith( diff --git a/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts b/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts index fb3304dbcb..86dda585e6 100644 --- a/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts +++ b/packages/nodes-base/nodes/MySql/test/v2/runQueries.test.ts @@ -67,7 +67,7 @@ describe('Test MySql V2, runQueries', () => { expect(result).toBeDefined(); expect(result).toHaveLength(1); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: [{ item: 0 }] }]); expect(poolGetConnectionSpy).toBeCalledTimes(1); @@ -108,7 +108,7 @@ describe('Test MySql V2, runQueries', () => { expect(result).toBeDefined(); expect(result).toHaveLength(1); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: { item: 0 } }]); expect(poolGetConnectionSpy).toBeCalledTimes(1); @@ -155,7 +155,7 @@ describe('Test MySql V2, runQueries', () => { expect(result).toBeDefined(); expect(result).toHaveLength(1); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: { item: 0 } }]); expect(poolGetConnectionSpy).toBeCalledTimes(1); diff --git a/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts b/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts index 42f59bdf0c..5ab326c4e1 100644 --- a/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/MySql/v2/helpers/utils.ts @@ -19,6 +19,7 @@ import type { } from './interfaces'; import { BATCH_MODE } from './interfaces'; +import { generatePairedItemData } from '../../../../utils/utilities'; export const prepareQueryAndReplacements = (rawQuery: string, replacements?: QueryValues) => { if (replacements === undefined) { @@ -122,6 +123,7 @@ export function prepareOutput( itemData: IPairedItemData | IPairedItemData[]; }, ) => NodeExecutionWithMetadata[], + itemData: IPairedItemData | IPairedItemData[], ) { const returnData: INodeExecutionData[] = []; @@ -133,7 +135,7 @@ export function prepareOutput( }; const executionData = constructExecutionHelper(wrapData(item), { - itemData: { item: index }, + itemData, }); returnData.push(...executionData); @@ -141,9 +143,9 @@ export function prepareOutput( } else { response .filter((entry) => Array.isArray(entry)) - .forEach((entry, index) => { + .forEach((entry) => { const executionData = constructExecutionHelper(wrapData(entry), { - itemData: { item: index }, + itemData, }); returnData.push(...executionData); @@ -152,7 +154,7 @@ export function prepareOutput( if (!returnData.length) { if ((options?.nodeVersion as number) < 2.2) { - returnData.push({ json: { success: true } }); + returnData.push({ json: { success: true }, pairedItem: itemData }); } else { const isSelectQuery = statements .filter((statement) => !statement.startsWith('--')) @@ -165,7 +167,7 @@ export function prepareOutput( ); if (!isSelectQuery) { - returnData.push({ json: { success: true } }); + returnData.push({ json: { success: true }, pairedItem: itemData }); } } } @@ -218,8 +220,17 @@ export function configureQueryRunner( response = [response]; } + //because single query is used in this mode mapping itemIndex not posible, setting all items as paired + const pairedItem = generatePairedItemData(queries.length); + returnData.push( - ...prepareOutput(response, options, statements, this.helpers.constructExecutionMetaData), + ...prepareOutput( + response, + options, + statements, + this.helpers.constructExecutionMetaData, + pairedItem, + ), ); } catch (err) { const error = parseMySqlError.call(this, err, 0, formatedQueries); @@ -250,6 +261,7 @@ export function configureQueryRunner( options, statements, this.helpers.constructExecutionMetaData, + { item: index }, ), ); } catch (err) { @@ -288,6 +300,7 @@ export function configureQueryRunner( options, statements, this.helpers.constructExecutionMetaData, + { item: index }, ), ); } catch (err) { diff --git a/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts b/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts index 3c179a9f7a..47228f7d20 100644 --- a/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts +++ b/packages/nodes-base/nodes/NextCloud/NextCloud.node.ts @@ -14,6 +14,7 @@ import { URLSearchParams } from 'url'; import { parseString } from 'xml2js'; import { nextCloudApiRequest } from './GenericFunctions'; +import { wrapData } from '../../utils/utilities'; export class NextCloud implements INodeType { description: INodeTypeDescription = { @@ -860,7 +861,7 @@ export class NextCloud implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData().slice(); - const returnData: IDataObject[] = []; + const returnData: INodeExecutionData[] = []; const authenticationMethod = this.getNodeParameter('authentication', 0); let credentials; @@ -1103,7 +1104,7 @@ export class NextCloud implements INodeType { if (resource === 'file' && operation === 'download') { items[i].json = { error: error.message }; } else { - returnData.push({ error: error.message }); + returnData.push({ json: { error: error.message }, pairedItem: { item: i } }); } continue; } @@ -1114,6 +1115,7 @@ export class NextCloud implements INodeType { if (resource === 'file' && operation === 'download') { const newItem: INodeExecutionData = { json: items[i].json, + pairedItem: { item: i }, binary: {}, }; @@ -1153,7 +1155,12 @@ export class NextCloud implements INodeType { }); }); - returnData.push(jsonResponseData); + const executionData = this.helpers.constructExecutionMetaData( + wrapData(jsonResponseData), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); } else if (resource === 'user') { if (operation !== 'getAll') { // eslint-disable-next-line @typescript-eslint/no-loop-func @@ -1180,7 +1187,12 @@ export class NextCloud implements INodeType { }); }); - returnData.push(jsonResponseData); + const executionData = this.helpers.constructExecutionMetaData( + wrapData(jsonResponseData), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); } else { // eslint-disable-next-line @typescript-eslint/no-loop-func const jsonResponseData: IDataObject[] = await new Promise((resolve, reject) => { @@ -1204,7 +1216,7 @@ export class NextCloud implements INodeType { }); jsonResponseData.forEach((value) => { - returnData.push({ id: value } as IDataObject); + returnData.push({ json: { id: value }, pairedItem: { item: i } }); }); } } else if (resource === 'folder' && operation === 'list') { @@ -1265,19 +1277,23 @@ export class NextCloud implements INodeType { // @ts-ignore newItem.eTag = props['d:getetag'].slice(1, -1); - returnData.push(newItem); + returnData.push({ json: newItem, pairedItem: { item: i } }); } } } } else { - returnData.push(responseData as IDataObject); + const executionData = this.helpers.constructExecutionMetaData(wrapData(responseData), { + itemData: { item: i }, + }); + + returnData.push(...executionData); } } catch (error) { if (this.continueOnFail()) { if (resource === 'file' && operation === 'download') { items[i].json = { error: error.message }; } else { - returnData.push({ error: error.message }); + returnData.push({ json: { error: error.message }, pairedItem: { item: i } }); } continue; } @@ -1290,7 +1306,7 @@ export class NextCloud implements INodeType { return [items]; } else { // For all other ones does the output get replaced - return [this.helpers.returnJsonArray(returnData)]; + return [returnData]; } } } diff --git a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts index 7ae6d1997b..e3fe26861e 100644 --- a/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts +++ b/packages/nodes-base/nodes/Pipedrive/Pipedrive.node.ts @@ -4887,6 +4887,7 @@ export class Pipedrive implements INodeType { if (resource === 'file' && operation === 'download') { const newItem: INodeExecutionData = { json: items[i].json, + pairedItem: { item: i }, binary: {}, }; @@ -4940,7 +4941,7 @@ export class Pipedrive implements INodeType { if (resource === 'file' && operation === 'download') { items[i].json = { error: error.message }; } else { - returnData.push({ json: { error: error.message } }); + returnData.push({ json: { error: error.message }, pairedItem: { item: i } }); } continue; } diff --git a/packages/nodes-base/nodes/Postgres/test/v2/runQueries.test.ts b/packages/nodes-base/nodes/Postgres/test/v2/runQueries.test.ts index 4297924fe9..a61b1c9bed 100644 --- a/packages/nodes-base/nodes/Postgres/test/v2/runQueries.test.ts +++ b/packages/nodes-base/nodes/Postgres/test/v2/runQueries.test.ts @@ -50,7 +50,7 @@ describe('Test PostgresV2, runQueries', () => { expect(result).toBeDefined(); expect(result).toHaveLength(1); - expect(result).toEqual([{ json: { success: true } }]); + expect(result).toEqual([{ json: { success: true }, pairedItem: [{ item: 0 }] }]); expect(dbMultiSpy).toHaveBeenCalledWith('SELECT * FROM table'); }); }); diff --git a/packages/nodes-base/nodes/Postgres/v2/helpers/utils.ts b/packages/nodes-base/nodes/Postgres/v2/helpers/utils.ts index 8569f8ab75..175458bfa7 100644 --- a/packages/nodes-base/nodes/Postgres/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Postgres/v2/helpers/utils.ts @@ -18,6 +18,7 @@ import type { SortRule, WhereClause, } from './interfaces'; +import { generatePairedItemData } from '../../../../utils/utilities'; export function wrapData(data: IDataObject | IDataObject[]): INodeExecutionData[] { if (!Array.isArray(data)) { @@ -217,7 +218,8 @@ export function configureQueryRunner( ) { return async (queries: QueryWithValues[], items: INodeExecutionData[], options: IDataObject) => { let returnData: INodeExecutionData[] = []; - const emptyReturnData = options.operation === 'select' ? [] : [{ json: { success: true } }]; + const emptyReturnData: INodeExecutionData[] = + options.operation === 'select' ? [] : [{ json: { success: true } }]; const queryBatching = (options.queryBatching as QueryMode) || 'single'; @@ -232,12 +234,17 @@ export function configureQueryRunner( .flat(); if (!returnData.length) { + const pairedItem = generatePairedItemData(queries.length); + if ((options?.nodeVersion as number) < 2.3) { + if (emptyReturnData.length) { + emptyReturnData[0].pairedItem = pairedItem; + } returnData = emptyReturnData; } else { returnData = queries.every((query) => isSelectQuery(query.query)) ? [] - : [{ json: { success: true } }]; + : [{ json: { success: true }, pairedItem }]; } } } catch (err) { diff --git a/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts b/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts index d5aeb06ba0..6a77677a93 100644 --- a/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts +++ b/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts @@ -23,6 +23,7 @@ import { fileFields, fileOperations } from './FileDescription'; import { recordFields, recordOperations } from './RecordDescription'; import { reportFields, reportOperations } from './ReportDescription'; +import { generatePairedItemData } from '../../utils/utilities'; export class QuickBase implements INodeType { description: INodeTypeDescription = { @@ -116,6 +117,7 @@ export class QuickBase implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const itemData = generatePairedItemData(items.length); const returnData: INodeExecutionData[] = []; const length = items.length; const qs: IDataObject = {}; @@ -295,7 +297,7 @@ export class QuickBase implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); @@ -465,7 +467,7 @@ export class QuickBase implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); @@ -548,7 +550,7 @@ export class QuickBase implements INodeType { const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionData); diff --git a/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts b/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts index 42276f973f..1368de2066 100644 --- a/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts +++ b/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts @@ -6,6 +6,7 @@ import type { } from 'n8n-workflow'; import glob from 'fast-glob'; +import { generatePairedItemData } from '../../utils/utilities'; export class ReadBinaryFiles implements INodeType { description: INodeTypeDescription = { @@ -45,6 +46,7 @@ export class ReadBinaryFiles implements INodeType { async execute(this: IExecuteFunctions): Promise { const fileSelector = this.getNodeParameter('fileSelector', 0) as string; const dataPropertyName = this.getNodeParameter('dataPropertyName', 0); + const pairedItem = generatePairedItemData(this.getInputData().length); const files = await glob(fileSelector); @@ -56,9 +58,7 @@ export class ReadBinaryFiles implements INodeType { [dataPropertyName]: await this.helpers.prepareBinaryData(stream, filePath), }, json: {}, - pairedItem: { - item: 0, - }, + pairedItem, }); } diff --git a/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts b/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts index ff1df919a8..8152b49b38 100644 --- a/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts +++ b/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts @@ -9,6 +9,7 @@ import { NodeOperationError } from 'n8n-workflow'; import Parser from 'rss-parser'; import { URL } from 'url'; +import { generatePairedItemData } from '../../utils/utilities'; // Utility function @@ -64,6 +65,8 @@ export class RssFeedRead implements INodeType { }; async execute(this: IExecuteFunctions): Promise { + const pairedItem = generatePairedItemData(this.getInputData().length); + try { const url = this.getNodeParameter('url', 0) as string; const options = this.getNodeParameter('options', 0); @@ -97,19 +100,22 @@ export class RssFeedRead implements INodeType { throw new NodeOperationError(this.getNode(), error as Error); } - const returnData: IDataObject[] = []; + const returnData: INodeExecutionData[] = []; // For now we just take the items and ignore everything else if (feed.items) { feed.items.forEach((item) => { - returnData.push(item); + returnData.push({ + json: item, + pairedItem, + }); }); } - return [this.helpers.returnJsonArray(returnData)]; + return [returnData]; } catch (error) { if (this.continueOnFail()) { - return [[{ json: { error: error.message } }]]; + return [[{ json: { error: error.message }, pairedItem }]]; } throw error; } diff --git a/packages/nodes-base/nodes/Set/v1/SetV1.node.ts b/packages/nodes-base/nodes/Set/v1/SetV1.node.ts index 8d9e3ba32a..d4a08a2e73 100644 --- a/packages/nodes-base/nodes/Set/v1/SetV1.node.ts +++ b/packages/nodes-base/nodes/Set/v1/SetV1.node.ts @@ -164,7 +164,7 @@ export class SetV1 implements INodeType { const newItem: INodeExecutionData = { json: {}, - pairedItem: item.pairedItem, + pairedItem: { item: itemIndex }, }; if (!keepOnlySet) { diff --git a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts index 03e946f750..fc947e23a4 100644 --- a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts @@ -52,7 +52,7 @@ export function composeReturnItem( ) { const newItem: INodeExecutionData = { json: {}, - pairedItem: inputItem.pairedItem, + pairedItem: { item: itemIndex }, }; if (options.includeBinary && inputItem.binary !== undefined) { diff --git a/packages/nodes-base/nodes/Set/v2/manual.mode.ts b/packages/nodes-base/nodes/Set/v2/manual.mode.ts index 3e34973080..8ab10be69a 100644 --- a/packages/nodes-base/nodes/Set/v2/manual.mode.ts +++ b/packages/nodes-base/nodes/Set/v2/manual.mode.ts @@ -204,7 +204,7 @@ export async function execute( return composeReturnItem.call(this, i, item, newData, options); } catch (error) { if (this.continueOnFail()) { - return { json: { error: (error as Error).message } }; + return { json: { error: (error as Error).message, pairedItem: { item: i } } }; } throw new NodeOperationError(this.getNode(), error as Error, { itemIndex: i, diff --git a/packages/nodes-base/nodes/Set/v2/raw.mode.ts b/packages/nodes-base/nodes/Set/v2/raw.mode.ts index 98abf4a103..eb73534720 100644 --- a/packages/nodes-base/nodes/Set/v2/raw.mode.ts +++ b/packages/nodes-base/nodes/Set/v2/raw.mode.ts @@ -59,7 +59,7 @@ export async function execute( return composeReturnItem.call(this, i, item, newData, options); } catch (error) { if (this.continueOnFail()) { - return { json: { error: (error as Error).message } }; + return { json: { error: (error as Error).message }, pairedItem: { item: i } }; } throw new NodeOperationError(node, error as Error, { itemIndex: i, diff --git a/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts b/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts index 17f57f3014..35ff2f80bb 100644 --- a/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts +++ b/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts @@ -29,7 +29,7 @@ import { toFileProperties, optionsProperties, } from '../description'; -import { flattenObject } from '@utils/utilities'; +import { flattenObject, generatePairedItemData } from '@utils/utilities'; import { oldVersionNotice } from '@utils/descriptions'; export class SpreadsheetFileV1 implements INodeType { @@ -57,6 +57,7 @@ export class SpreadsheetFileV1 implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const pairedItem = generatePairedItemData(items.length); const operation = this.getNodeParameter('operation', 0); @@ -227,9 +228,7 @@ export class SpreadsheetFileV1 implements INodeType { const newItem: INodeExecutionData = { json: {}, binary: {}, - pairedItem: { - item: 0, - }, + pairedItem, }; let fileName = `spreadsheet.${fileFormat}`; @@ -246,9 +245,7 @@ export class SpreadsheetFileV1 implements INodeType { json: { error: error.message, }, - pairedItem: { - item: 0, - }, + pairedItem, }); } else { throw error; diff --git a/packages/nodes-base/nodes/SpreadsheetFile/v2/SpreadsheetFileV2.node.ts b/packages/nodes-base/nodes/SpreadsheetFile/v2/SpreadsheetFileV2.node.ts index a749e20e5a..41d4f5ec10 100644 --- a/packages/nodes-base/nodes/SpreadsheetFile/v2/SpreadsheetFileV2.node.ts +++ b/packages/nodes-base/nodes/SpreadsheetFile/v2/SpreadsheetFileV2.node.ts @@ -33,7 +33,7 @@ import { optionsProperties, fromFileV2Properties, } from '../description'; -import { flattenObject } from '@utils/utilities'; +import { flattenObject, generatePairedItemData } from '@utils/utilities'; export class SpreadsheetFileV2 implements INodeType { description: INodeTypeDescription; @@ -202,6 +202,7 @@ export class SpreadsheetFileV2 implements INodeType { return [newItems]; } else if (operation === 'toFile') { + const pairedItem = generatePairedItemData(items.length); try { // Write the workflow data to spreadsheet file const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0); @@ -260,9 +261,7 @@ export class SpreadsheetFileV2 implements INodeType { const newItem: INodeExecutionData = { json: {}, binary: {}, - pairedItem: { - item: 0, - }, + pairedItem, }; let fileName = `spreadsheet.${fileFormat}`; @@ -279,9 +278,7 @@ export class SpreadsheetFileV2 implements INodeType { json: { error: error.message, }, - pairedItem: { - item: 0, - }, + pairedItem, }); } else { throw error; diff --git a/packages/nodes-base/nodes/Stackby/Stackby.node.ts b/packages/nodes-base/nodes/Stackby/Stackby.node.ts index fc06e395bd..ad4e491497 100644 --- a/packages/nodes-base/nodes/Stackby/Stackby.node.ts +++ b/packages/nodes-base/nodes/Stackby/Stackby.node.ts @@ -9,6 +9,7 @@ import { NodeOperationError } from 'n8n-workflow'; import type { IRecord } from './GenericFunction'; import { apiRequest, apiRequestAllItems } from './GenericFunction'; +import { generatePairedItemData } from '../../utils/utilities'; export class Stackby implements INodeType { description: INodeTypeDescription = { @@ -282,9 +283,10 @@ export class Stackby implements INodeType { ); } catch (error) { if (this.continueOnFail()) { + const itemData = generatePairedItemData(items.length); const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), - { itemData: { item: 0 } }, + { itemData }, ); returnData.push(...executionErrorData); } else { diff --git a/packages/nodes-base/nodes/Wekan/Wekan.node.ts b/packages/nodes-base/nodes/Wekan/Wekan.node.ts index 203ca3524f..5c421dd143 100644 --- a/packages/nodes-base/nodes/Wekan/Wekan.node.ts +++ b/packages/nodes-base/nodes/Wekan/Wekan.node.ts @@ -22,6 +22,7 @@ import { checklistFields, checklistOperations } from './ChecklistDescription'; import { checklistItemFields, checklistItemOperations } from './ChecklistItemDescription'; import { listFields, listOperations } from './ListDescription'; +import { wrapData } from '../../utils/utilities'; // https://wekan.github.io/api/v4.41/ @@ -234,7 +235,7 @@ export class Wekan implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); - const returnData: IDataObject[] = []; + const returnData: INodeExecutionData[] = []; let returnAll; let limit; @@ -659,14 +660,15 @@ export class Wekan implements INodeType { responseData = responseData.splice(0, limit); } - if (Array.isArray(responseData)) { - returnData.push.apply(returnData, responseData as IDataObject[]); - } else { - returnData.push(responseData as IDataObject); - } + const executionData = this.helpers.constructExecutionMetaData( + wrapData(responseData as IDataObject[]), + { itemData: { item: i } }, + ); + + returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { - returnData.push({ error: error.message }); + returnData.push({ json: { error: error.message }, pairedItem: { item: i } }); continue; } throw error; diff --git a/packages/nodes-base/utils/utilities.ts b/packages/nodes-base/utils/utilities.ts index cb76ab7937..9149c80e2e 100644 --- a/packages/nodes-base/utils/utilities.ts +++ b/packages/nodes-base/utils/utilities.ts @@ -303,3 +303,17 @@ export function generatePairedItemData(length: number): IPairedItemData[] { item, })); } + +/** + * Output Paired Item Data Array + * + * @param {number | IPairedItemData | IPairedItemData[] | undefined} pairedItem + */ +export function preparePairedItemDataArray( + pairedItem: number | IPairedItemData | IPairedItemData[] | undefined, +): IPairedItemData[] { + if (pairedItem === undefined) return []; + if (typeof pairedItem === 'number') return [{ item: pairedItem }]; + if (Array.isArray(pairedItem)) return pairedItem; + return [pairedItem]; +}