From 2ef6f111d0845577ad45e3e15cd6303418cbc046 Mon Sep 17 00:00:00 2001 From: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:38:35 +0000 Subject: [PATCH] fix: Manually reverted PR 11716 Do not assign all paired items (no-changelog) (#13429) --- .../test/v2/node/base/getMany.test.ts | 20 ++++++++++++++ .../test/v2/node/record/search.test.ts | 12 +++++++-- .../nodes/Airtable/v1/AirtableV1.node.ts | 13 +++++++-- .../v2/actions/base/getMany.operation.ts | 12 +++++++-- .../v2/actions/record/search.operation.ts | 9 ++++--- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 7 +++++ .../actions/spreadsheet.operation.ts | 5 +++- .../ConvertToFile/actions/toJson.operation.ts | 5 +++- packages/nodes-base/nodes/Ftp/Ftp.node.ts | 6 +++-- .../BigQuery/v1/GoogleBigQueryV1.node.ts | 13 +++++++-- .../v2/actions/database/insert.operation.ts | 8 ++++-- .../GoogleFirebaseCloudFirestore.node.ts | 10 ++++--- .../Google/Sheet/test/v2/node/delete.test.ts | 8 +++++- .../Google/Sheet/v1/GoogleSheetsV1.node.ts | 4 +++ .../v2/actions/sheet/delete.operation.ts | 7 +++-- .../nodes/Hubspot/V2/HubspotV2.node.ts | 8 +++++- packages/nodes-base/nodes/Kafka/Kafka.node.ts | 10 +++++-- .../Excel/v1/MicrosoftExcelV1.node.ts | 12 +++++++-- .../v2/actions/table/append.operation.ts | 8 ++++-- .../v2/actions/worksheet/update.operation.ts | 8 ++++-- .../v2/actions/worksheet/upsert.operation.ts | 8 ++++-- .../nodes/Microsoft/Excel/v2/helpers/utils.ts | 8 ++++-- .../nodes/Microsoft/Sql/MicrosoftSql.node.ts | 9 +++++-- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 27 +++++++++++++++---- .../nodes/Postgres/test/v2/runQueries.test.ts | 2 +- .../nodes/Postgres/v2/helpers/utils.ts | 7 +++-- .../nodes/QuickBase/QuickBase.node.ts | 17 +++++++++--- .../ReadBinaryFiles/ReadBinaryFiles.node.ts | 4 +++ .../RespondToWebhook/RespondToWebhook.node.ts | 8 ++++-- .../test/RespondToWebhook.test.ts | 1 + .../nodes/RssFeedRead/RssFeedRead.node.ts | 5 ++-- .../v1/SpreadsheetFileV1.node.ts | 5 +++- .../SpreadsheetFile/v2/toFile.operation.ts | 5 ++++ .../nodes-base/nodes/Stackby/Stackby.node.ts | 7 ++++- 34 files changed, 243 insertions(+), 55 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 88f570620d..cb6b662dd4 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 @@ -49,6 +49,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 1', permissionLevel: 'create', }, + pairedItem: [ + { + item: 0, + }, + ], }, { json: { @@ -56,6 +61,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 2', permissionLevel: 'edit', }, + pairedItem: [ + { + item: 0, + }, + ], }, { json: { @@ -63,6 +73,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 3', permissionLevel: 'create', }, + pairedItem: [ + { + item: 0, + }, + ], }, ]); }); @@ -86,6 +101,11 @@ describe('Test AirtableV2, base => getMany', () => { name: 'base 2', permissionLevel: 'edit', }, + pairedItem: [ + { + item: 0, + }, + ], }, ]); }); 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 c3099248b1..83e5f246f2 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 @@ -98,7 +98,11 @@ describe('Test AirtableV2, search operation', () => { expect(result).toHaveLength(2); expect(result[0]).toEqual({ json: { id: 'recYYY', foo: 'foo 2', bar: 'bar 2' }, - pairedItem: [], + pairedItem: [ + { + item: 0, + }, + ], }); }); @@ -138,7 +142,11 @@ describe('Test AirtableV2, search operation', () => { expect(result).toHaveLength(1); expect(result[0]).toEqual({ json: { id: 'recYYY', foo: 'foo 2', bar: 'bar 2' }, - pairedItem: [], + 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 2ba25cf84e..27214c6192 100644 --- a/packages/nodes-base/nodes/Airtable/v1/AirtableV1.node.ts +++ b/packages/nodes-base/nodes/Airtable/v1/AirtableV1.node.ts @@ -12,6 +12,7 @@ import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; 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', @@ -736,16 +737,24 @@ export class AirtableV1 implements INodeType { const downloadFieldNames = ( this.getNodeParameter('downloadFieldNames', 0) as string ).split(','); - + const pairedItem = generatePairedItemData(items.length); const data = await downloadRecordAttachments.call( this, responseData.records as IRecord[], downloadFieldNames, + pairedItem, ); return [data]; } - return [this.helpers.returnJsonArray(returnData)]; + // We can return from here + const itemData = generatePairedItemData(items.length); + + return [ + this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(returnData), { + itemData, + }), + ]; } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error.message } }); 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 869c3c0c63..5272be1a2a 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 @@ -5,7 +5,11 @@ import type { IExecuteFunctions, } from 'n8n-workflow'; -import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities'; +import { + generatePairedItemData, + updateDisplayOptions, + wrapData, +} from '../../../../../utils/utilities'; import { apiRequest } from '../../transport'; const properties: INodeProperties[] = [ @@ -108,7 +112,11 @@ export async function execute(this: IExecuteFunctions): Promise permissionLevel.includes(base.permissionLevel as string)); } - const returnData = wrapData(bases); + const itemData = generatePairedItemData(this.getInputData().length); + + const returnData = this.helpers.constructExecutionMetaData(wrapData(bases), { + 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 324a0d611d..ae698d8a7a 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 @@ -5,7 +5,7 @@ import type { IExecuteFunctions, } from 'n8n-workflow'; -import { updateDisplayOptions } from '../../../../../utils/utilities'; +import { generatePairedItemData, updateDisplayOptions } from '../../../../../utils/utilities'; import type { IRecord } from '../../helpers/interfaces'; import { flattenOutput } from '../../helpers/utils'; import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from '../../transport'; @@ -156,9 +156,12 @@ export async function execute( const endpoint = `${base}/${table}`; let itemsLength = items.length ? 1 : 0; + let fallbackPairedItems; if (nodeVersion >= 2.1) { itemsLength = items.length; + } else { + fallbackPairedItems = generatePairedItemData(items.length); } for (let i = 0; i < itemsLength; i++) { @@ -205,7 +208,7 @@ export async function execute( this, responseData.records as IRecord[], options.downloadFields as string[], - nodeVersion >= 2.1 ? [{ item: i }] : undefined, + fallbackPairedItems || [{ item: i }], ); returnData.push(...itemWithAttachments); continue; @@ -217,7 +220,7 @@ export async function execute( json: flattenOutput(record), })) as INodeExecutionData[]; - const itemData = nodeVersion >= 2.1 ? [{ item: i }] : []; + const itemData = fallbackPairedItems || [{ item: i }]; const executionData = this.helpers.constructExecutionMetaData(records, { itemData, diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index c9f5c2315b..462a2fe00e 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -9,6 +9,7 @@ import type { import { getWorkflowInfo } from './GenericFunctions'; import { localResourceMapping } from './methods'; +import { generatePairedItemData } from '../../../utils/utilities'; import { getCurrentWorkflowInputData } from '../../../utils/workflowInputsResourceMapping/GenericFunctions'; export class ExecuteWorkflow implements INodeType { description: INodeTypeDescription = { @@ -426,6 +427,8 @@ export class ExecuteWorkflow implements INodeType { const workflowResult = executionResult.data as INodeExecutionData[][]; + const fallbackPairedItemData = generatePairedItemData(items.length); + for (const output of workflowResult) { const sameLength = output.length === items.length; @@ -434,12 +437,15 @@ export class ExecuteWorkflow implements INodeType { if (sameLength) { item.pairedItem = { item: itemIndex }; + } else { + item.pairedItem = fallbackPairedItemData; } } } return workflowResult; } catch (error) { + const pairedItem = generatePairedItemData(items.length); if (this.continueOnFail()) { const metadata = parseErrorMetadata(error); return [ @@ -447,6 +453,7 @@ export class ExecuteWorkflow implements INodeType { { json: { error: error.message }, metadata, + pairedItem, }, ], ]; diff --git a/packages/nodes-base/nodes/Files/ConvertToFile/actions/spreadsheet.operation.ts b/packages/nodes-base/nodes/Files/ConvertToFile/actions/spreadsheet.operation.ts index 7921ac55d9..96f87bddc6 100644 --- a/packages/nodes-base/nodes/Files/ConvertToFile/actions/spreadsheet.operation.ts +++ b/packages/nodes-base/nodes/Files/ConvertToFile/actions/spreadsheet.operation.ts @@ -7,7 +7,7 @@ import { import type { JsonToSpreadsheetBinaryOptions, JsonToSpreadsheetBinaryFormat } from '@utils/binary'; import { convertJsonToSpreadsheetBinary } from '@utils/binary'; -import { updateDisplayOptions } from '@utils/utilities'; +import { generatePairedItemData, updateDisplayOptions } from '@utils/utilities'; export const operations = ['csv', 'html', 'rtf', 'ods', 'xls', 'xlsx']; @@ -98,6 +98,7 @@ export async function execute( ) { let returnData: INodeExecutionData[] = []; + const pairedItem = generatePairedItemData(items.length); try { const options = this.getNodeParameter('options', 0, {}) as JsonToSpreadsheetBinaryOptions; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0, 'data'); @@ -115,6 +116,7 @@ export async function execute( binary: { [binaryPropertyName]: binaryData, }, + pairedItem, }; returnData = [newItem]; @@ -124,6 +126,7 @@ export async function execute( json: { error: error.message, }, + pairedItem, }); } else { throw new NodeOperationError(this.getNode(), error); diff --git a/packages/nodes-base/nodes/Files/ConvertToFile/actions/toJson.operation.ts b/packages/nodes-base/nodes/Files/ConvertToFile/actions/toJson.operation.ts index 283b9b59a5..cabcfa8da1 100644 --- a/packages/nodes-base/nodes/Files/ConvertToFile/actions/toJson.operation.ts +++ b/packages/nodes-base/nodes/Files/ConvertToFile/actions/toJson.operation.ts @@ -3,7 +3,7 @@ import { NodeOperationError } from 'n8n-workflow'; import { createBinaryFromJson } from '@utils/binary'; import { encodeDecodeOptions } from '@utils/descriptions'; -import { updateDisplayOptions } from '@utils/utilities'; +import { generatePairedItemData, updateDisplayOptions } from '@utils/utilities'; export const properties: INodeProperties[] = [ { @@ -92,6 +92,7 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData const mode = this.getNodeParameter('mode', 0, 'once') as string; if (mode === 'once') { + const pairedItem = generatePairedItemData(items.length); try { const options = this.getNodeParameter('options', 0, {}); const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0, 'data'); @@ -113,6 +114,7 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData binary: { [binaryPropertyName]: binaryData, }, + pairedItem, }; returnData = [newItem]; @@ -122,6 +124,7 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData json: { error: error.message, }, + pairedItem, }); } throw new NodeOperationError(this.getNode(), error); diff --git a/packages/nodes-base/nodes/Ftp/Ftp.node.ts b/packages/nodes-base/nodes/Ftp/Ftp.node.ts index a5d34e4a14..682eee343c 100644 --- a/packages/nodes-base/nodes/Ftp/Ftp.node.ts +++ b/packages/nodes-base/nodes/Ftp/Ftp.node.ts @@ -19,7 +19,7 @@ import type { Readable } from 'stream'; import { pipeline } from 'stream/promises'; import { file as tmpFile } from 'tmp-promise'; -import { formatPrivateKey } from '@utils/utilities'; +import { formatPrivateKey, generatePairedItemData } from '@utils/utilities'; interface ReturnFtpItem { type: string; @@ -550,7 +550,9 @@ export class Ftp implements INodeType { } } catch (error) { if (this.continueOnFail()) { - return [[{ json: { error: error.message } }]]; + const pairedItem = generatePairedItemData(items.length); + + return [[{ json: { error: error.message }, pairedItem }]]; } throw error; } 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 527bb6edec..160360ede3 100644 --- a/packages/nodes-base/nodes/Google/BigQuery/v1/GoogleBigQueryV1.node.ts +++ b/packages/nodes-base/nodes/Google/BigQuery/v1/GoogleBigQueryV1.node.ts @@ -16,6 +16,7 @@ import { oldVersionNotice } from '@utils/descriptions'; import { googleApiRequest, googleApiRequestAllItems, simplify } from './GenericFunctions'; import { recordFields, recordOperations } from './RecordDescription'; +import { generatePairedItemData } from '../../../../utils/utilities'; const versionDescription: INodeTypeDescription = { displayName: 'Google BigQuery', @@ -194,6 +195,8 @@ export class GoogleBigQueryV1 implements INodeType { body.rows = rows; + const itemData = generatePairedItemData(items.length); + try { responseData = await googleApiRequest.call( this, @@ -202,11 +205,17 @@ export class GoogleBigQueryV1 implements INodeType { body, ); - const executionData = this.helpers.returnJsonArray(responseData as IDataObject[]); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData as IDataObject[]), + { itemData }, + ); returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { - const executionErrorData = this.helpers.returnJsonArray({ error: error.message }); + const executionErrorData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: error.message }), + { itemData }, + ); returnData.push(...executionErrorData); } throw new NodeApiError(this.getNode(), error as JsonObject, { itemIndex: 0 }); 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 1b0fed5435..c0c197d96b 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 @@ -7,7 +7,7 @@ import type { 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'; @@ -227,6 +227,7 @@ export async function execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); - + const itemData = generatePairedItemData(items.length); const returnData: INodeExecutionData[] = []; let responseData; @@ -135,7 +136,7 @@ export class GoogleFirebaseCloudFirestore implements INodeType { if (nodeVersion >= 1.1) { itemsLength = items.length; } else { - fallbackPairedItems = []; + fallbackPairedItems = generatePairedItemData(items.length); } if (resource === 'document') { @@ -171,7 +172,10 @@ export class GoogleFirebaseCloudFirestore implements INodeType { .filter((el: IDataObject) => !!el); } - const executionData = this.helpers.returnJsonArray(responseData as IDataObject[]); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData as IDataObject[]), + { itemData }, + ); returnData.push(...executionData); } else if (operation === 'create') { diff --git a/packages/nodes-base/nodes/Google/Sheet/test/v2/node/delete.test.ts b/packages/nodes-base/nodes/Google/Sheet/test/v2/node/delete.test.ts index 988065baa9..12652f34a3 100644 --- a/packages/nodes-base/nodes/Google/Sheet/test/v2/node/delete.test.ts +++ b/packages/nodes-base/nodes/Google/Sheet/test/v2/node/delete.test.ts @@ -11,7 +11,10 @@ describe('Google Sheet - Delete', () => { mockExecuteFunctions = { getInputData: jest.fn().mockReturnValue([{ json: {} }]), getNodeParameter: jest.fn(), - } as Partial; + helpers: { + constructExecutionMetaData: jest.fn((data) => ({ json: data })), + }, + } as unknown as Partial; mockSheet = { spreadsheetBatchUpdate: jest.fn(), @@ -137,6 +140,9 @@ describe('Google Sheet - Delete', () => { if (param === 'numberToDelete') return 1; return null; }) as unknown as IExecuteFunctions['getNodeParameter']; + mockExecuteFunctions.helpers = { + constructExecutionMetaData: jest.fn((data) => data), + } as unknown as IExecuteFunctions['helpers']; const result = await execute.call( mockExecuteFunctions as IExecuteFunctions, 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 dfb7da8c79..6b218c1d6a 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v1/GoogleSheetsV1.node.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v1/GoogleSheetsV1.node.ts @@ -23,6 +23,7 @@ import type { } from './GoogleSheet'; import { GoogleSheet } from './GoogleSheet'; import { versionDescription } from './versionDescription'; +import { generatePairedItemData } from '../../../../utils/utilities'; import { getGoogleAccessToken } from '../../GenericFunctions'; export class GoogleSheetsV1 implements INodeType { @@ -293,9 +294,12 @@ export class GoogleSheetsV1 implements INodeType { returnData = []; } + const pairedItem = generatePairedItemData(items.length); + const lookupOutput = returnData.map((item) => { return { json: item, + pairedItem, }; }); 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 680da7c62e..4d5479795c 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 @@ -1,6 +1,6 @@ import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; -import { wrapData } from '../../../../../../utils/utilities'; +import { generatePairedItemData, wrapData } from '../../../../../../utils/utilities'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; import type { SheetProperties } from '../../helpers/GoogleSheets.types'; import { getColumnNumber, untilSheetSelected } from '../../helpers/GoogleSheets.utils'; @@ -166,7 +166,10 @@ export async function execute( await sheet.spreadsheetBatchUpdate(requests); } - const returnData = wrapData({ 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/Hubspot/V2/HubspotV2.node.ts b/packages/nodes-base/nodes/Hubspot/V2/HubspotV2.node.ts index bcc9601938..865e4ed04c 100644 --- a/packages/nodes-base/nodes/Hubspot/V2/HubspotV2.node.ts +++ b/packages/nodes-base/nodes/Hubspot/V2/HubspotV2.node.ts @@ -38,6 +38,7 @@ import { validateCredentials, } from './GenericFunctions'; import { ticketFields, ticketOperations } from './TicketDescription'; +import { generatePairedItemData } from '../../../utils/utilities'; export class HubspotV2 implements INodeType { description: INodeTypeDescription; @@ -1184,7 +1185,12 @@ export class HubspotV2 implements INodeType { ); } - const executionData = this.helpers.returnJsonArray(responseData as IDataObject[]); + const itemData = generatePairedItemData(items.length); + + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData as IDataObject[]), + { itemData }, + ); returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { diff --git a/packages/nodes-base/nodes/Kafka/Kafka.node.ts b/packages/nodes-base/nodes/Kafka/Kafka.node.ts index bf59a5fb22..71b0d40fea 100644 --- a/packages/nodes-base/nodes/Kafka/Kafka.node.ts +++ b/packages/nodes-base/nodes/Kafka/Kafka.node.ts @@ -14,6 +14,8 @@ import type { } from 'n8n-workflow'; import { ApplicationError, NodeConnectionType, NodeOperationError } from 'n8n-workflow'; +import { generatePairedItemData } from '../../utils/utilities'; + export class Kafka implements INodeType { description: INodeTypeDescription = { displayName: 'Kafka', @@ -258,6 +260,7 @@ export class Kafka implements INodeType { async execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const itemData = generatePairedItemData(items.length); const length = items.length; @@ -397,12 +400,15 @@ export class Kafka implements INodeType { await producer.disconnect(); - const executionData = this.helpers.returnJsonArray(responseData); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData), + { itemData }, + ); return [executionData]; } catch (error) { if (this.continueOnFail()) { - return [[{ json: { error: error.message } }]]; + return [[{ json: { error: error.message }, pairedItem: itemData }]]; } else { throw error; } 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 48e4c90ac8..53d7764dfc 100644 --- a/packages/nodes-base/nodes/Microsoft/Excel/v1/MicrosoftExcelV1.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Excel/v1/MicrosoftExcelV1.node.ts @@ -21,6 +21,7 @@ import { import { tableFields, tableOperations } from './TableDescription'; import { workbookFields, workbookOperations } from './WorkbookDescription'; import { worksheetFields, worksheetOperations } from './WorksheetDescription'; +import { generatePairedItemData } from '../../../../utils/utilities'; const versionDescription: INodeTypeDescription = { displayName: 'Microsoft Excel', @@ -172,6 +173,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 = {}; @@ -243,12 +245,18 @@ export class MicrosoftExcelV1 implements INodeType { { 'workbook-session-id': id }, ); - const executionData = this.helpers.returnJsonArray(responseData as IDataObject[]); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData as IDataObject[]), + { itemData }, + ); returnData.push(...executionData); } catch (error) { if (this.continueOnFail()) { - const executionErrorData = this.helpers.returnJsonArray({ error: error.message }); + const executionErrorData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: error.message }), + { itemData }, + ); returnData.push(...executionErrorData); } else { throw error; 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 a037baefe7..11e345c28b 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 @@ -5,7 +5,7 @@ import type { 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'; @@ -274,7 +274,11 @@ export async function execute( ); } catch (error) { if (this.continueOnFail()) { - const executionErrorData = this.helpers.returnJsonArray({ error: error.message }); + const itemData = generatePairedItemData(this.getInputData().length); + const executionErrorData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: error.message }), + { itemData }, + ); returnData.push(...executionErrorData); } else { throw error; 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 a1ba1a5d0b..b21a6b039e 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 @@ -6,7 +6,7 @@ import type { } 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 { @@ -375,7 +375,11 @@ export async function execute( } } catch (error) { if (this.continueOnFail()) { - const executionErrorData = this.helpers.returnJsonArray({ error: error.message }); + const itemData = generatePairedItemData(this.getInputData().length); + const executionErrorData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: error.message }), + { itemData }, + ); returnData.push(...executionErrorData); } else { throw error; 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 5821304d26..c27feae6d5 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 @@ -6,7 +6,7 @@ import type { } 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 { @@ -382,7 +382,11 @@ export async function execute( ); } catch (error) { if (this.continueOnFail()) { - const executionErrorData = this.helpers.returnJsonArray({ error: error.message }); + const itemData = generatePairedItemData(this.getInputData().length); + const executionErrorData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: error.message }), + { itemData }, + ); returnData.push(...executionErrorData); } else { throw error; 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 a71c390ecd..12151a471a 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 { wrapData } from '@utils/utilities'; +import { generatePairedItemData, wrapData } from '@utils/utilities'; import type { ExcelResponse, SheetData, UpdateSummary } from './interfaces'; @@ -62,7 +62,11 @@ export function prepareOutput( returnData.push(...executionData); } } else { - const executionData = wrapData({ [config.dataProperty || 'data']: responseData }); + const itemData = generatePairedItemData(this.getInputData().length); + const executionData = this.helpers.constructExecutionMetaData( + wrapData({ [config.dataProperty || 'data']: responseData }), + { 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 8df2314cae..0112c0f8bb 100644 --- a/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts @@ -12,7 +12,7 @@ import { NodeConnectionType, } from 'n8n-workflow'; -import { flatten, getResolvables } from '@utils/utilities'; +import { flatten, generatePairedItemData, getResolvables } from '@utils/utilities'; import { configurePool, @@ -343,7 +343,12 @@ export class MicrosoftSql implements INodeType { responseData = await deleteOperation(tables, pool); } - returnData = this.helpers.returnJsonArray(responseData); + const itemData = generatePairedItemData(items.length); + + returnData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData), + { itemData }, + ); } catch (error) { if (this.continueOnFail()) { responseData = items; diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index 274ae50173..349e56fd43 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -28,6 +28,7 @@ import { } from './GenericFunctions'; import type { IMongoParametricCredentials } from './mongoDb.types'; import { nodeProperties } from './MongoDbProperties'; +import { generatePairedItemData } from '../../utils/utilities'; export class MongoDb implements INodeType { description: INodeTypeDescription = { @@ -117,7 +118,7 @@ export class MongoDb implements INodeType { if (nodeVersion >= 1.1) { itemsLength = items.length; } else { - fallbackPairedItems = []; + fallbackPairedItems = generatePairedItemData(items.length); } if (operation === 'aggregate') { @@ -234,6 +235,7 @@ export class MongoDb implements INodeType { } if (operation === 'findOneAndReplace') { + fallbackPairedItems = fallbackPairedItems ?? generatePairedItemData(items.length); const fields = prepareFields(this.getNodeParameter('fields', 0) as string); const useDotNotation = this.getNodeParameter('options.useDotNotation', 0, false) as boolean; const dateFields = prepareFields( @@ -268,10 +270,14 @@ export class MongoDb implements INodeType { } } - returnData = this.helpers.returnJsonArray(updateItems); + returnData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(updateItems), + { itemData: fallbackPairedItems }, + ); } if (operation === 'findOneAndUpdate') { + fallbackPairedItems = fallbackPairedItems ?? generatePairedItemData(items.length); const fields = prepareFields(this.getNodeParameter('fields', 0) as string); const useDotNotation = this.getNodeParameter('options.useDotNotation', 0, false) as boolean; const dateFields = prepareFields( @@ -306,10 +312,14 @@ export class MongoDb implements INodeType { } } - returnData = this.helpers.returnJsonArray(updateItems); + returnData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(updateItems), + { itemData: fallbackPairedItems }, + ); } if (operation === 'insert') { + fallbackPairedItems = fallbackPairedItems ?? generatePairedItemData(items.length); let responseData: IDataObject[] = []; try { // Prepare the data to insert and copy it to be returned @@ -340,10 +350,14 @@ export class MongoDb implements INodeType { } } - returnData = this.helpers.returnJsonArray(responseData); + returnData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData), + { itemData: fallbackPairedItems }, + ); } if (operation === 'update') { + fallbackPairedItems = fallbackPairedItems ?? generatePairedItemData(items.length); const fields = prepareFields(this.getNodeParameter('fields', 0) as string); const useDotNotation = this.getNodeParameter('options.useDotNotation', 0, false) as boolean; const dateFields = prepareFields( @@ -378,7 +392,10 @@ export class MongoDb implements INodeType { } } - returnData = this.helpers.returnJsonArray(updateItems); + returnData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(updateItems), + { itemData: fallbackPairedItems }, + ); } await client.close(); 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 fff4d7d39f..86689890f2 100644 --- a/packages/nodes-base/nodes/Postgres/test/v2/runQueries.test.ts +++ b/packages/nodes-base/nodes/Postgres/test/v2/runQueries.test.ts @@ -49,7 +49,7 @@ describe('Test PostgresV2, runQueries', () => { expect(result).toBeDefined(); expect(result).toHaveLength(1); - expect(result).toEqual([{ json: { success: true }, pairedItem: undefined }]); + 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 67c8e74f3f..289c8fe351 100644 --- a/packages/nodes-base/nodes/Postgres/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Postgres/v2/helpers/utils.ts @@ -19,6 +19,7 @@ import type { SortRule, WhereClause, } from './interfaces'; +import { generatePairedItemData } from '../../../../utils/utilities'; export function isJSON(str: string) { try { @@ -251,15 +252,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 = undefined; + 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 433233805e..32d008e749 100644 --- a/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts +++ b/packages/nodes-base/nodes/QuickBase/QuickBase.node.ts @@ -19,6 +19,7 @@ import { } from './GenericFunctions'; import { recordFields, recordOperations } from './RecordDescription'; import { reportFields, reportOperations } from './ReportDescription'; +import { generatePairedItemData } from '../../utils/utilities'; export class QuickBase implements INodeType { description: INodeTypeDescription = { @@ -113,6 +114,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 = {}; @@ -290,7 +292,10 @@ export class QuickBase implements INodeType { } } - const executionData = this.helpers.returnJsonArray(responseData as IDataObject[]); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData as IDataObject[]), + { itemData }, + ); returnData.push(...executionData); } @@ -457,7 +462,10 @@ export class QuickBase implements INodeType { } } - const executionData = this.helpers.returnJsonArray(responseData as IDataObject[]); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData as IDataObject[]), + { itemData }, + ); returnData.push(...executionData); } @@ -537,7 +545,10 @@ export class QuickBase implements INodeType { } } - const executionData = this.helpers.returnJsonArray(responseData as IDataObject[]); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray(responseData as IDataObject[]), + { 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 e1c9fa1f70..d3687a6557 100644 --- a/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts +++ b/packages/nodes-base/nodes/ReadBinaryFiles/ReadBinaryFiles.node.ts @@ -7,6 +7,8 @@ import { type INodeTypeDescription, } from 'n8n-workflow'; +import { generatePairedItemData } from '../../utils/utilities'; + export class ReadBinaryFiles implements INodeType { description: INodeTypeDescription = { hidden: true, @@ -46,6 +48,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); @@ -57,6 +60,7 @@ export class ReadBinaryFiles implements INodeType { [dataPropertyName]: await this.helpers.prepareBinaryData(stream, filePath), }, json: {}, + pairedItem, }); } diff --git a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts index a765777288..b8038d9163 100644 --- a/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts +++ b/packages/nodes-base/nodes/RespondToWebhook/RespondToWebhook.node.ts @@ -21,7 +21,7 @@ import { } from 'n8n-workflow'; import type { Readable } from 'stream'; -import { formatPrivateKey } from '../../utils/utilities'; +import { formatPrivateKey, generatePairedItemData } from '../../utils/utilities'; export class RespondToWebhook implements INodeType { description: INodeTypeDescription = { @@ -443,7 +443,11 @@ export class RespondToWebhook implements INodeType { this.sendResponse(response); } catch (error) { if (this.continueOnFail()) { - const returnData = [{ json: { error: error.message } }]; + const itemData = generatePairedItemData(items.length); + const returnData = this.helpers.constructExecutionMetaData( + [{ json: { error: error.message } }], + { itemData }, + ); return [returnData]; } diff --git a/packages/nodes-base/nodes/RespondToWebhook/test/RespondToWebhook.test.ts b/packages/nodes-base/nodes/RespondToWebhook/test/RespondToWebhook.test.ts index e014512b60..797d1ea304 100644 --- a/packages/nodes-base/nodes/RespondToWebhook/test/RespondToWebhook.test.ts +++ b/packages/nodes-base/nodes/RespondToWebhook/test/RespondToWebhook.test.ts @@ -253,6 +253,7 @@ describe('RespondToWebhook Node', () => { [ { json: { error: 'The Response Data option "notSupportedRespondWith" is not supported!' }, + pairedItem: [{ item: 0 }], }, ], ]); diff --git a/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts b/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts index 8218414f26..74cebfb418 100644 --- a/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts +++ b/packages/nodes-base/nodes/RssFeedRead/RssFeedRead.node.ts @@ -4,12 +4,13 @@ import type { INodeExecutionData, INodeType, INodeTypeDescription, - IPairedItemData, } from 'n8n-workflow'; import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import Parser from 'rss-parser'; import { URL } from 'url'; +import { generatePairedItemData } from '../../utils/utilities'; + // Utility function function validateURL(url: string) { @@ -76,7 +77,7 @@ export class RssFeedRead implements INodeType { if (nodeVersion >= 1.1) { itemsLength = items.length; } else { - fallbackPairedItems = [] as IPairedItemData[]; + fallbackPairedItems = generatePairedItemData(items.length); } for (let i = 0; i < itemsLength; i++) { diff --git a/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts b/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts index 15659bee82..e575c756b1 100644 --- a/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts +++ b/packages/nodes-base/nodes/SpreadsheetFile/v1/SpreadsheetFileV1.node.ts @@ -22,7 +22,7 @@ import { } from 'xlsx'; import { oldVersionNotice } from '@utils/descriptions'; -import { flattenObject } from '@utils/utilities'; +import { flattenObject, generatePairedItemData } from '@utils/utilities'; import { operationProperty, @@ -58,6 +58,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); @@ -229,6 +230,7 @@ export class SpreadsheetFileV1 implements INodeType { const newItem: INodeExecutionData = { json: {}, binary: {}, + pairedItem, }; let fileName = `spreadsheet.${fileFormat}`; @@ -245,6 +247,7 @@ export class SpreadsheetFileV1 implements INodeType { json: { error: error.message, }, + pairedItem, }); } else { throw error; diff --git a/packages/nodes-base/nodes/SpreadsheetFile/v2/toFile.operation.ts b/packages/nodes-base/nodes/SpreadsheetFile/v2/toFile.operation.ts index cbab0bc414..d89d1e9c23 100644 --- a/packages/nodes-base/nodes/SpreadsheetFile/v2/toFile.operation.ts +++ b/packages/nodes-base/nodes/SpreadsheetFile/v2/toFile.operation.ts @@ -2,6 +2,7 @@ import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n import type { JsonToSpreadsheetBinaryFormat, JsonToSpreadsheetBinaryOptions } from '@utils/binary'; import { convertJsonToSpreadsheetBinary } from '@utils/binary'; +import { generatePairedItemData } from '@utils/utilities'; import { toFileOptions, toFileProperties } from '../description'; @@ -10,6 +11,8 @@ export const description: INodeProperties[] = [...toFileProperties, toFileOption export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) { const returnData: INodeExecutionData[] = []; + const pairedItem = generatePairedItemData(items.length); + try { const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0); const fileFormat = this.getNodeParameter('fileFormat', 0) as JsonToSpreadsheetBinaryFormat; @@ -22,6 +25,7 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData binary: { [binaryPropertyName]: binaryData, }, + pairedItem, }; returnData.push(newItem); @@ -31,6 +35,7 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData json: { error: error.message, }, + 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 1e8accffa3..76f7f3bc52 100644 --- a/packages/nodes-base/nodes/Stackby/Stackby.node.ts +++ b/packages/nodes-base/nodes/Stackby/Stackby.node.ts @@ -9,6 +9,7 @@ import { NodeConnectionType, 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 = { @@ -283,7 +284,11 @@ export class Stackby implements INodeType { ); } catch (error) { if (this.continueOnFail()) { - const executionErrorData = this.helpers.returnJsonArray({ error: error.message }); + const itemData = generatePairedItemData(items.length); + const executionErrorData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: error.message }), + { itemData }, + ); returnData.push(...executionErrorData); } else { throw error;