Skip to content

Commit ef9d4ab

Browse files
fix: Update operations to run per item (#8967)
Co-authored-by: Elias Meire <[email protected]>
1 parent 870412f commit ef9d4ab

File tree

12 files changed

+600
-431
lines changed

12 files changed

+600
-431
lines changed

packages/nodes-base/credentials/CodaApi.credentials.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
1+
import type { ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
22

33
export class CodaApi implements ICredentialType {
44
name = 'codaApi';
@@ -16,4 +16,13 @@ export class CodaApi implements ICredentialType {
1616
default: '',
1717
},
1818
];
19+
20+
test: ICredentialTestRequest = {
21+
request: {
22+
baseURL: 'https://coda.io/apis/v1/whoami',
23+
headers: {
24+
Authorization: '=Bearer {{$credentials.accessToken}}',
25+
},
26+
},
27+
};
1928
}

packages/nodes-base/nodes/Airtable/Airtable.node.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ export class Airtable extends VersionedNodeType {
1212
icon: 'file:airtable.svg',
1313
group: ['input'],
1414
description: 'Read, update, write and delete data from Airtable',
15-
defaultVersion: 2,
15+
defaultVersion: 2.1,
1616
};
1717

1818
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1919
1: new AirtableV1(baseDescription),
2020
2: new AirtableV2(baseDescription),
21+
2.1: new AirtableV2(baseDescription),
2122
};
2223

2324
super(nodeVersions, baseDescription);

packages/nodes-base/nodes/Airtable/v2/actions/record/search.operation.ts

Lines changed: 69 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -149,74 +149,90 @@ export async function execute(
149149
base: string,
150150
table: string,
151151
): Promise<INodeExecutionData[]> {
152-
let returnData: INodeExecutionData[] = [];
153-
154-
const body: IDataObject = {};
155-
const qs: IDataObject = {};
152+
const returnData: INodeExecutionData[] = [];
153+
const nodeVersion = this.getNode().typeVersion;
156154

157155
const endpoint = `${base}/${table}`;
158156

159-
try {
160-
const returnAll = this.getNodeParameter('returnAll', 0);
161-
const options = this.getNodeParameter('options', 0, {});
162-
const sort = this.getNodeParameter('sort', 0, {}) as IDataObject;
163-
const filterByFormula = this.getNodeParameter('filterByFormula', 0) as string;
157+
let itemsLength = items.length ? 1 : 0;
158+
let fallbackPairedItems;
164159

165-
if (filterByFormula) {
166-
qs.filterByFormula = filterByFormula;
167-
}
160+
if (nodeVersion >= 2.1) {
161+
itemsLength = items.length;
162+
} else {
163+
fallbackPairedItems = generatePairedItemData(items.length);
164+
}
168165

169-
if (options.fields) {
170-
if (typeof options.fields === 'string') {
171-
qs.fields = options.fields.split(',').map((field) => field.trim());
172-
} else {
173-
qs.fields = options.fields as string[];
166+
for (let i = 0; i < itemsLength; i++) {
167+
try {
168+
const returnAll = this.getNodeParameter('returnAll', i);
169+
const options = this.getNodeParameter('options', i, {});
170+
const sort = this.getNodeParameter('sort', i, {}) as IDataObject;
171+
const filterByFormula = this.getNodeParameter('filterByFormula', i) as string;
172+
173+
const body: IDataObject = {};
174+
const qs: IDataObject = {};
175+
176+
if (filterByFormula) {
177+
qs.filterByFormula = filterByFormula;
174178
}
175-
}
176179

177-
if (sort.property) {
178-
qs.sort = sort.property;
179-
}
180+
if (options.fields) {
181+
if (typeof options.fields === 'string') {
182+
qs.fields = options.fields.split(',').map((field) => field.trim());
183+
} else {
184+
qs.fields = options.fields as string[];
185+
}
186+
}
180187

181-
if (options.view) {
182-
qs.view = (options.view as IDataObject).value as string;
183-
}
188+
if (sort.property) {
189+
qs.sort = sort.property;
190+
}
184191

185-
let responseData;
192+
if (options.view) {
193+
qs.view = (options.view as IDataObject).value as string;
194+
}
186195

187-
if (returnAll) {
188-
responseData = await apiRequestAllItems.call(this, 'GET', endpoint, body, qs);
189-
} else {
190-
qs.maxRecords = this.getNodeParameter('limit', 0);
191-
responseData = await apiRequest.call(this, 'GET', endpoint, body, qs);
192-
}
196+
let responseData;
193197

194-
returnData = responseData.records as INodeExecutionData[];
198+
if (returnAll) {
199+
responseData = await apiRequestAllItems.call(this, 'GET', endpoint, body, qs);
200+
} else {
201+
qs.maxRecords = this.getNodeParameter('limit', i);
202+
responseData = await apiRequest.call(this, 'GET', endpoint, body, qs);
203+
}
195204

196-
if (options.downloadFields) {
197-
const pairedItem = generatePairedItemData(items.length);
198-
return await downloadRecordAttachments.call(
199-
this,
200-
responseData.records as IRecord[],
201-
options.downloadFields as string[],
202-
pairedItem,
203-
);
204-
}
205+
if (options.downloadFields) {
206+
const itemWithAttachments = await downloadRecordAttachments.call(
207+
this,
208+
responseData.records as IRecord[],
209+
options.downloadFields as string[],
210+
fallbackPairedItems || [{ item: i }],
211+
);
212+
returnData.push(...itemWithAttachments);
213+
continue;
214+
}
215+
216+
let records = responseData.records;
217+
218+
records = (records as IDataObject[]).map((record) => ({
219+
json: flattenOutput(record),
220+
})) as INodeExecutionData[];
205221

206-
returnData = returnData.map((record) => ({
207-
json: flattenOutput(record as IDataObject),
208-
}));
222+
const itemData = fallbackPairedItems || [{ item: i }];
209223

210-
const itemData = generatePairedItemData(items.length);
224+
const executionData = this.helpers.constructExecutionMetaData(records, {
225+
itemData,
226+
});
211227

212-
returnData = this.helpers.constructExecutionMetaData(returnData, {
213-
itemData,
214-
});
215-
} catch (error) {
216-
if (this.continueOnFail()) {
217-
returnData.push({ json: { message: error.message, error } });
218-
} else {
219-
throw error;
228+
returnData.push(...executionData);
229+
} catch (error) {
230+
if (this.continueOnFail()) {
231+
returnData.push({ json: { message: error.message, error }, pairedItem: { item: i } });
232+
continue;
233+
} else {
234+
throw error;
235+
}
220236
}
221237
}
222238

packages/nodes-base/nodes/Airtable/v2/actions/versionDescription.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const versionDescription: INodeTypeDescription = {
99
name: 'airtable',
1010
icon: 'file:airtable.svg',
1111
group: ['input'],
12-
version: 2,
12+
version: [2, 2.1],
1313
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
1414
description: 'Read, update, write and delete data from Airtable',
1515
defaults: {

0 commit comments

Comments
 (0)