fix: Update operations to run per item (#8967)

Co-authored-by: Elias Meire <elias@meire.dev>
This commit is contained in:
Michael Kret 2024-05-22 15:28:09 +03:00 committed by GitHub
parent 870412f093
commit ef9d4aba90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 611 additions and 442 deletions

View file

@ -1,4 +1,4 @@
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
import type { ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
export class CodaApi implements ICredentialType {
name = 'codaApi';
@ -16,4 +16,13 @@ export class CodaApi implements ICredentialType {
default: '',
},
];
test: ICredentialTestRequest = {
request: {
baseURL: 'https://coda.io/apis/v1/whoami',
headers: {
Authorization: '=Bearer {{$credentials.accessToken}}',
},
},
};
}

View file

@ -12,12 +12,13 @@ export class Airtable extends VersionedNodeType {
icon: 'file:airtable.svg',
group: ['input'],
description: 'Read, update, write and delete data from Airtable',
defaultVersion: 2,
defaultVersion: 2.1,
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new AirtableV1(baseDescription),
2: new AirtableV2(baseDescription),
2.1: new AirtableV2(baseDescription),
};
super(nodeVersions, baseDescription);

View file

@ -149,74 +149,90 @@ export async function execute(
base: string,
table: string,
): Promise<INodeExecutionData[]> {
let returnData: INodeExecutionData[] = [];
const body: IDataObject = {};
const qs: IDataObject = {};
const returnData: INodeExecutionData[] = [];
const nodeVersion = this.getNode().typeVersion;
const endpoint = `${base}/${table}`;
try {
const returnAll = this.getNodeParameter('returnAll', 0);
const options = this.getNodeParameter('options', 0, {});
const sort = this.getNodeParameter('sort', 0, {}) as IDataObject;
const filterByFormula = this.getNodeParameter('filterByFormula', 0) as string;
let itemsLength = items.length ? 1 : 0;
let fallbackPairedItems;
if (filterByFormula) {
qs.filterByFormula = filterByFormula;
}
if (nodeVersion >= 2.1) {
itemsLength = items.length;
} else {
fallbackPairedItems = generatePairedItemData(items.length);
}
if (options.fields) {
if (typeof options.fields === 'string') {
qs.fields = options.fields.split(',').map((field) => field.trim());
} else {
qs.fields = options.fields as string[];
for (let i = 0; i < itemsLength; i++) {
try {
const returnAll = this.getNodeParameter('returnAll', i);
const options = this.getNodeParameter('options', i, {});
const sort = this.getNodeParameter('sort', i, {}) as IDataObject;
const filterByFormula = this.getNodeParameter('filterByFormula', i) as string;
const body: IDataObject = {};
const qs: IDataObject = {};
if (filterByFormula) {
qs.filterByFormula = filterByFormula;
}
}
if (sort.property) {
qs.sort = sort.property;
}
if (options.fields) {
if (typeof options.fields === 'string') {
qs.fields = options.fields.split(',').map((field) => field.trim());
} else {
qs.fields = options.fields as string[];
}
}
if (options.view) {
qs.view = (options.view as IDataObject).value as string;
}
if (sort.property) {
qs.sort = sort.property;
}
let responseData;
if (options.view) {
qs.view = (options.view as IDataObject).value as string;
}
if (returnAll) {
responseData = await apiRequestAllItems.call(this, 'GET', endpoint, body, qs);
} else {
qs.maxRecords = this.getNodeParameter('limit', 0);
responseData = await apiRequest.call(this, 'GET', endpoint, body, qs);
}
let responseData;
returnData = responseData.records as INodeExecutionData[];
if (returnAll) {
responseData = await apiRequestAllItems.call(this, 'GET', endpoint, body, qs);
} else {
qs.maxRecords = this.getNodeParameter('limit', i);
responseData = await apiRequest.call(this, 'GET', endpoint, body, qs);
}
if (options.downloadFields) {
const pairedItem = generatePairedItemData(items.length);
return await downloadRecordAttachments.call(
this,
responseData.records as IRecord[],
options.downloadFields as string[],
pairedItem,
);
}
if (options.downloadFields) {
const itemWithAttachments = await downloadRecordAttachments.call(
this,
responseData.records as IRecord[],
options.downloadFields as string[],
fallbackPairedItems || [{ item: i }],
);
returnData.push(...itemWithAttachments);
continue;
}
returnData = returnData.map((record) => ({
json: flattenOutput(record as IDataObject),
}));
let records = responseData.records;
const itemData = generatePairedItemData(items.length);
records = (records as IDataObject[]).map((record) => ({
json: flattenOutput(record),
})) as INodeExecutionData[];
returnData = this.helpers.constructExecutionMetaData(returnData, {
itemData,
});
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { message: error.message, error } });
} else {
throw error;
const itemData = fallbackPairedItems || [{ item: i }];
const executionData = this.helpers.constructExecutionMetaData(records, {
itemData,
});
returnData.push(...executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { message: error.message, error }, pairedItem: { item: i } });
continue;
} else {
throw error;
}
}
}

View file

@ -9,7 +9,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'airtable',
icon: 'file:airtable.svg',
group: ['input'],
version: 2,
version: [2, 2.1],
subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
description: 'Read, update, write and delete data from Airtable',
defaults: {

View file

@ -21,7 +21,7 @@ export class Coda implements INodeType {
name: 'coda',
icon: 'file:coda.svg',
group: ['output'],
version: 1,
version: [1, 1.1],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Coda API',
defaults: {
@ -240,6 +240,7 @@ export class Coda implements INodeType {
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const nodeVersion = this.getNode().typeVersion;
const returnData: INodeExecutionData[] = [];
const items = this.getInputData();
let responseData;
@ -363,61 +364,83 @@ export class Coda implements INodeType {
}
// https://coda.io/developers/apis/v1beta1#operation/listRows
if (operation === 'getAllRows') {
const docId = this.getNodeParameter('docId', 0) as string;
const returnAll = this.getNodeParameter('returnAll', 0);
const tableId = this.getNodeParameter('tableId', 0) as string;
const options = this.getNodeParameter('options', 0);
const endpoint = `/docs/${docId}/tables/${tableId}/rows`;
if (options.useColumnNames === false) {
qs.useColumnNames = options.useColumnNames as boolean;
} else {
qs.useColumnNames = true;
}
if (options.valueFormat) {
qs.valueFormat = options.valueFormat as string;
}
if (options.sortBy) {
qs.sortBy = options.sortBy as string;
}
if (options.visibleOnly) {
qs.visibleOnly = options.visibleOnly as boolean;
}
if (options.query) {
qs.query = options.query as string;
}
try {
if (returnAll) {
responseData = await codaApiRequestAllItems.call(
this,
'items',
'GET',
endpoint,
{},
qs,
);
} else {
qs.limit = this.getNodeParameter('limit', 0);
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items;
}
} catch (error) {
if (this.continueOnFail()) {
return [this.helpers.returnJsonArray({ error: error.message })];
}
throw new NodeApiError(this.getNode(), error as JsonObject);
let itemsLength = items.length ? 1 : 0;
if (nodeVersion >= 1.1) {
itemsLength = items.length;
}
if (options.rawData === true) {
return [this.helpers.returnJsonArray(responseData as IDataObject[])];
} else {
for (const item of responseData) {
returnData.push({
id: item.id,
...item.values,
});
for (let i = 0; i < itemsLength; i++) {
const docId = this.getNodeParameter('docId', i) as string;
const returnAll = this.getNodeParameter('returnAll', i);
const tableId = this.getNodeParameter('tableId', i) as string;
const options = this.getNodeParameter('options', i);
const endpoint = `/docs/${docId}/tables/${tableId}/rows`;
if (options.useColumnNames === false) {
qs.useColumnNames = options.useColumnNames as boolean;
} else {
qs.useColumnNames = true;
}
if (options.valueFormat) {
qs.valueFormat = options.valueFormat as string;
}
if (options.sortBy) {
qs.sortBy = options.sortBy as string;
}
if (options.visibleOnly) {
qs.visibleOnly = options.visibleOnly as boolean;
}
if (options.query) {
qs.query = options.query as string;
}
try {
if (returnAll) {
responseData = await codaApiRequestAllItems.call(
this,
'items',
'GET',
endpoint,
{},
qs,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items;
}
if (options.rawData === true) {
for (const item of responseData) {
returnData.push({
json: item,
pairedItem: [{ item: i }],
});
}
} else {
for (const item of responseData) {
returnData.push({
json: {
id: item.id,
...item.values,
},
pairedItem: [{ item: i }],
});
}
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: [{ item: i }],
});
continue;
}
if (error instanceof NodeApiError) throw error;
throw new NodeApiError(this.getNode(), error as JsonObject);
}
return [this.helpers.returnJsonArray(returnData)];
}
return [returnData];
}
// https://coda.io/developers/apis/v1beta1#operation/deleteRows
if (operation === 'deleteRow') {
@ -630,15 +653,15 @@ export class Coda implements INodeType {
}
//https://coda.io/developers/apis/v1beta1#operation/listControls
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', 0);
qs.limit = this.getNodeParameter('limit', 0);
for (let i = 0; i < items.length; i++) {
try {
const returnAll = this.getNodeParameter('returnAll', 0);
const docId = this.getNodeParameter('docId', i) as string;
const endpoint = `/docs/${docId}/controls`;
if (returnAll) {
responseData = await codaApiRequestAllItems.call(this, 'items', 'GET', endpoint, {});
} else {
qs.limit = this.getNodeParameter('limit', 0);
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items;
}
@ -680,15 +703,15 @@ export class Coda implements INodeType {
}
//https://coda.io/developers/apis/v1beta1#operation/listViews
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', 0);
qs.limit = this.getNodeParameter('limit', 0);
for (let i = 0; i < items.length; i++) {
try {
const returnAll = this.getNodeParameter('returnAll', 0);
const docId = this.getNodeParameter('docId', i) as string;
const endpoint = `/docs/${docId}/tables?tableTypes=view`;
if (returnAll) {
responseData = await codaApiRequestAllItems.call(this, 'items', 'GET', endpoint, {});
} else {
qs.limit = this.getNodeParameter('limit', 0);
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items;
}
@ -712,58 +735,80 @@ export class Coda implements INodeType {
return [returnData];
}
if (operation === 'getAllViewRows') {
const docId = this.getNodeParameter('docId', 0) as string;
const returnAll = this.getNodeParameter('returnAll', 0);
const viewId = this.getNodeParameter('viewId', 0) as string;
const options = this.getNodeParameter('options', 0);
const endpoint = `/docs/${docId}/tables/${viewId}/rows`;
if (options.useColumnNames === false) {
qs.useColumnNames = options.useColumnNames as boolean;
} else {
qs.useColumnNames = true;
}
if (options.valueFormat) {
qs.valueFormat = options.valueFormat as string;
}
if (options.sortBy) {
qs.sortBy = options.sortBy as string;
}
if (options.query) {
qs.query = options.query as string;
}
try {
if (returnAll) {
responseData = await codaApiRequestAllItems.call(
this,
'items',
'GET',
endpoint,
{},
qs,
);
} else {
qs.limit = this.getNodeParameter('limit', 0);
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items;
}
} catch (error) {
if (this.continueOnFail()) {
return [this.helpers.returnJsonArray({ error: error.message })];
}
throw new NodeApiError(this.getNode(), error as JsonObject);
let itemsLength = items.length ? 1 : 0;
if (nodeVersion >= 1.1) {
itemsLength = items.length;
}
if (options.rawData === true) {
return [this.helpers.returnJsonArray(responseData as IDataObject[])];
} else {
for (const item of responseData) {
returnData.push({
id: item.id,
...item.values,
});
for (let i = 0; i < itemsLength; i++) {
const docId = this.getNodeParameter('docId', i) as string;
const returnAll = this.getNodeParameter('returnAll', i);
const viewId = this.getNodeParameter('viewId', i) as string;
const options = this.getNodeParameter('options', i);
const endpoint = `/docs/${docId}/tables/${viewId}/rows`;
if (options.useColumnNames === false) {
qs.useColumnNames = options.useColumnNames as boolean;
} else {
qs.useColumnNames = true;
}
if (options.valueFormat) {
qs.valueFormat = options.valueFormat as string;
}
if (options.sortBy) {
qs.sortBy = options.sortBy as string;
}
if (options.query) {
qs.query = options.query as string;
}
try {
if (returnAll) {
responseData = await codaApiRequestAllItems.call(
this,
'items',
'GET',
endpoint,
{},
qs,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items;
}
if (options.rawData === true) {
for (const item of responseData) {
returnData.push({
json: item,
pairedItem: [{ item: i }],
});
}
} else {
for (const item of responseData) {
returnData.push({
json: {
id: item.id,
...item.values,
},
pairedItem: [{ item: i }],
});
}
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: [{ item: i }],
});
continue;
}
if (error instanceof NodeApiError) throw error;
throw new NodeApiError(this.getNode(), error as JsonObject);
}
return [this.helpers.returnJsonArray(returnData)];
}
return [returnData];
}
//https://coda.io/developers/apis/v1beta1#operation/deleteViewRow
if (operation === 'deleteViewRow') {
@ -823,16 +868,16 @@ export class Coda implements INodeType {
return [returnData];
}
if (operation === 'getAllViewColumns') {
const returnAll = this.getNodeParameter('returnAll', 0);
qs.limit = this.getNodeParameter('limit', 0);
for (let i = 0; i < items.length; i++) {
try {
const returnAll = this.getNodeParameter('returnAll', 0);
const docId = this.getNodeParameter('docId', i) as string;
const viewId = this.getNodeParameter('viewId', i) as string;
const endpoint = `/docs/${docId}/tables/${viewId}/columns`;
if (returnAll) {
responseData = await codaApiRequestAllItems.call(this, 'items', 'GET', endpoint, {});
} else {
qs.limit = this.getNodeParameter('limit', 0);
responseData = await codaApiRequest.call(this, 'GET', endpoint, {}, qs);
responseData = responseData.items;
}

View file

@ -27,12 +27,13 @@ export const tableOperations: INodeProperties[] = [
{
name: 'Get All Columns',
value: 'getAllColumns',
description: 'Get all columns in a table',
action: 'Get all columns',
},
{
name: 'Get All Rows',
value: 'getAllRows',
description: 'Get all the rows',
description: 'Get all rows in a table',
action: 'Get all rows',
},
{

View file

@ -28,7 +28,7 @@ export class GoogleFirebaseCloudFirestore implements INodeType {
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
icon: 'file:googleFirebaseCloudFirestore.png',
group: ['input'],
version: 1,
version: [1, 1.1],
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
description: 'Interact with Google Firebase - Cloud Firestore API',
defaults: {
@ -94,15 +94,27 @@ export class GoogleFirebaseCloudFirestore implements INodeType {
const itemData = generatePairedItemData(items.length);
const returnData: INodeExecutionData[] = [];
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
const nodeVersion = this.getNode().typeVersion;
let itemsLength = items.length ? 1 : 0;
let fallbackPairedItems;
if (nodeVersion >= 1.1) {
itemsLength = items.length;
} else {
fallbackPairedItems = generatePairedItemData(items.length);
}
if (resource === 'document') {
if (operation === 'get') {
const projectId = this.getNodeParameter('projectId', 0) as string;
const database = this.getNodeParameter('database', 0) as string;
const simple = this.getNodeParameter('simple', 0) as boolean;
const documentList = items.map((item: IDataObject, i: number) => {
const documentList = items.map((_: IDataObject, i: number) => {
const collection = this.getNodeParameter('collection', i) as string;
const documentId = this.getNodeParameter('documentId', i) as string;
return `projects/${projectId}/databases/${database}/documents/${collection}/${documentId}`;
@ -179,49 +191,64 @@ export class GoogleFirebaseCloudFirestore implements INodeType {
}),
);
} else if (operation === 'getAll') {
const projectId = this.getNodeParameter('projectId', 0) as string;
const database = this.getNodeParameter('database', 0) as string;
const collection = this.getNodeParameter('collection', 0) as string;
const returnAll = this.getNodeParameter('returnAll', 0);
const simple = this.getNodeParameter('simple', 0) as boolean;
for (let i = 0; i < itemsLength; i++) {
try {
const projectId = this.getNodeParameter('projectId', i) as string;
const database = this.getNodeParameter('database', i) as string;
const collection = this.getNodeParameter('collection', i) as string;
const returnAll = this.getNodeParameter('returnAll', i);
const simple = this.getNodeParameter('simple', i) as boolean;
if (returnAll) {
responseData = await googleApiRequestAllItems.call(
this,
'documents',
'GET',
`/${projectId}/databases/${database}/documents/${collection}`,
);
} else {
const limit = this.getNodeParameter('limit', 0);
const getAllResponse = (await googleApiRequest.call(
this,
'GET',
`/${projectId}/databases/${database}/documents/${collection}`,
{},
{ pageSize: limit },
)) as IDataObject;
responseData = getAllResponse.documents;
if (returnAll) {
responseData = await googleApiRequestAllItems.call(
this,
'documents',
'GET',
`/${projectId}/databases/${database}/documents/${collection}`,
);
} else {
const limit = this.getNodeParameter('limit', i);
const getAllResponse = (await googleApiRequest.call(
this,
'GET',
`/${projectId}/databases/${database}/documents/${collection}`,
{},
{ pageSize: limit },
)) as IDataObject;
responseData = getAllResponse.documents;
}
responseData = responseData.map((element: IDataObject) => {
element.id = (element.name as string).split('/').pop();
return element;
});
if (simple) {
responseData = responseData.map((element: IDataObject) =>
fullDocumentToJson(element),
);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: fallbackPairedItems ?? [{ item: i }] },
);
returnData.push(...executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: fallbackPairedItems ?? [{ item: i }],
});
continue;
}
throw error;
}
}
responseData = responseData.map((element: IDataObject) => {
element.id = (element.name as string).split('/').pop();
return element;
});
if (simple) {
responseData = responseData.map((element: IDataObject) => fullDocumentToJson(element));
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData },
);
returnData.push(...executionData);
} else if (operation === 'delete') {
await Promise.all(
items.map(async (item: IDataObject, i: number) => {
items.map(async (_: IDataObject, i: number) => {
const projectId = this.getNodeParameter('projectId', i) as string;
const database = this.getNodeParameter('database', i) as string;
const collection = this.getNodeParameter('collection', i) as string;
@ -295,44 +322,13 @@ export class GoogleFirebaseCloudFirestore implements INodeType {
returnData.push(...executionData);
}
// } else if (operation === 'update') {
// const projectId = this.getNodeParameter('projectId', 0) as string;
// const database = this.getNodeParameter('database', 0) as string;
// const simple = this.getNodeParameter('simple', 0) as boolean;
// await Promise.all(items.map(async (item: IDataObject, i: number) => {
// const collection = this.getNodeParameter('collection', i) as string;
// const updateKey = this.getNodeParameter('updateKey', i) as string;
// // @ts-ignore
// const documentId = item['json'][updateKey] as string;
// const columns = this.getNodeParameter('columns', i) as string;
// const columnList = columns.split(',').map(column => column.trim()) as string[];
// const document = {};
// columnList.map(column => {
// // @ts-ignore
// document[column] = item['json'].hasOwnProperty(column) ? jsonToDocument(item['json'][column]) : jsonToDocument(null);
// });
// responseData = await googleApiRequest.call(
// this,
// 'PATCH',
// `/${projectId}/databases/${database}/documents/${collection}/${documentId}`,
// { fields: document },
// { [`updateMask.fieldPaths`]: columnList },
// );
// if (simple === false) {
// returnData.push(responseData);
// } else {
// returnData.push(fullDocumentToJson(responseData as IDataObject));
// }
// }));
} else if (operation === 'query') {
const projectId = this.getNodeParameter('projectId', 0) as string;
const database = this.getNodeParameter('database', 0) as string;
const simple = this.getNodeParameter('simple', 0) as boolean;
await Promise.all(
items.map(async (item: IDataObject, i: number) => {
items.map(async (_: IDataObject, i: number) => {
const query = this.getNodeParameter('query', i) as string;
responseData = await googleApiRequest.call(
this,
@ -369,38 +365,51 @@ export class GoogleFirebaseCloudFirestore implements INodeType {
}
} else if (resource === 'collection') {
if (operation === 'getAll') {
const projectId = this.getNodeParameter('projectId', 0) as string;
const database = this.getNodeParameter('database', 0) as string;
const returnAll = this.getNodeParameter('returnAll', 0);
for (let i = 0; i < itemsLength; i++) {
try {
const projectId = this.getNodeParameter('projectId', i) as string;
const database = this.getNodeParameter('database', i) as string;
const returnAll = this.getNodeParameter('returnAll', i);
if (returnAll) {
const getAllResponse = await googleApiRequestAllItems.call(
this,
'collectionIds',
'POST',
`/${projectId}/databases/${database}/documents:listCollectionIds`,
);
// @ts-ignore
responseData = getAllResponse.map((o) => ({ name: o }));
} else {
const limit = this.getNodeParameter('limit', 0);
const getAllResponse = (await googleApiRequest.call(
this,
'POST',
`/${projectId}/databases/${database}/documents:listCollectionIds`,
{},
{ pageSize: limit },
)) as IDataObject;
// @ts-ignore
responseData = getAllResponse.collectionIds.map((o) => ({ name: o }));
if (returnAll) {
const getAllResponse = await googleApiRequestAllItems.call(
this,
'collectionIds',
'POST',
`/${projectId}/databases/${database}/documents:listCollectionIds`,
);
// @ts-ignore
responseData = getAllResponse.map((o) => ({ name: o }));
} else {
const limit = this.getNodeParameter('limit', i);
const getAllResponse = (await googleApiRequest.call(
this,
'POST',
`/${projectId}/databases/${database}/documents:listCollectionIds`,
{},
{ pageSize: limit },
)) as IDataObject;
// @ts-ignore
responseData = getAllResponse.collectionIds.map((o) => ({ name: o }));
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: fallbackPairedItems ?? [{ item: i }] },
);
returnData.push(...executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: fallbackPairedItems ?? [{ item: i }],
});
continue;
}
throw error;
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData },
);
returnData.push(...executionData);
}
}

View file

@ -69,7 +69,6 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
// const spreadsheetId = this.getNodeParameter('spreadsheetId', i) as string;
const documentId = this.getNodeParameter('documentId', i, undefined, {
extractValue: true,
}) as string;

View file

@ -10,7 +10,6 @@ import type {
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import type { ITables } from './interfaces';
@ -30,7 +29,7 @@ export class MicrosoftSql implements INodeType {
name: 'microsoftSql',
icon: 'file:mssql.svg',
group: ['input'],
version: 1,
version: [1, 1.1],
description: 'Get, add and update data in Microsoft SQL',
defaults: {
name: 'Microsoft SQL',
@ -250,10 +249,50 @@ export class MicrosoftSql implements INodeType {
await pool.connect();
let responseData: IDataObject | IDataObject[] = [];
let returnData: INodeExecutionData[] = [];
const items = this.getInputData();
const operation = this.getNodeParameter('operation', 0);
const nodeVersion = this.getNode().typeVersion;
if (operation === 'executeQuery' && nodeVersion >= 1.1) {
for (let i = 0; i < items.length; i++) {
try {
let rawQuery = this.getNodeParameter('query', i) as string;
for (const resolvable of getResolvables(rawQuery)) {
rawQuery = rawQuery.replace(
resolvable,
this.evaluateExpression(resolvable, i) as string,
);
}
const { recordsets }: IResult<any[]> = await pool.request().query(rawQuery);
const result: IDataObject[] = recordsets.length > 1 ? flatten(recordsets) : recordsets[0];
for (const entry of result) {
returnData.push({
json: entry,
pairedItem: [{ item: i }],
});
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: [{ item: i }],
});
continue;
}
await pool.close();
throw error;
}
}
await pool.close();
return [returnData];
}
try {
if (operation === 'executeQuery') {
let rawQuery = this.getNodeParameter('query', 0) as string;
@ -267,15 +306,19 @@ export class MicrosoftSql implements INodeType {
const result = recordsets.length > 1 ? flatten(recordsets) : recordsets[0];
responseData = result;
} else if (operation === 'insert') {
}
if (operation === 'insert') {
const tables = createTableStruct(this.getNodeParameter, items);
await insertOperation(tables, pool);
responseData = items;
} else if (operation === 'update') {
}
if (operation === 'update') {
const updateKeys = items.map(
(item, index) => this.getNodeParameter('updateKey', index) as string,
(_, index) => this.getNodeParameter('updateKey', index) as string,
);
const tables = createTableStruct(
@ -288,7 +331,9 @@ export class MicrosoftSql implements INodeType {
await updateOperation(tables, pool);
responseData = items;
} else if (operation === 'delete') {
}
if (operation === 'delete') {
const tables = items.reduce((acc, item, index) => {
const table = this.getNodeParameter('table', index) as string;
const deleteKey = this.getNodeParameter('deleteKey', index) as string;
@ -303,13 +348,14 @@ export class MicrosoftSql implements INodeType {
}, {} as ITables);
responseData = await deleteOperation(tables, pool);
} else {
await pool.close();
throw new NodeOperationError(
this.getNode(),
`The operation "${operation}" is not supported!`,
);
}
const itemData = generatePairedItemData(items.length);
returnData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData),
{ itemData },
);
} catch (error) {
if (this.continueOnFail()) {
responseData = items;
@ -322,13 +368,6 @@ export class MicrosoftSql implements INodeType {
// shuts down the connection pool associated with the db object to allow the process to finish
await pool.close();
const itemData = generatePairedItemData(items.length);
const returnItems = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData),
{ itemData },
);
return [returnItems];
return [returnData];
}
}

View file

@ -132,15 +132,17 @@ export function prepareFields(fields: string) {
.filter((field) => !!field);
}
export function stringifyObjectIDs(items: IDataObject[]) {
export function stringifyObjectIDs(items: INodeExecutionData[]) {
items.forEach((item) => {
if (item._id instanceof ObjectId) {
item._id = item._id.toString();
item.json._id = item._id.toString();
}
if (item.id instanceof ObjectId) {
item.id = item.id.toString();
item.json.id = item.id.toString();
}
});
return items;
}
export async function connectMongoClient(connectionString: string, credentials: IDataObject = {}) {

View file

@ -9,7 +9,7 @@ import type {
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import { ApplicationError, NodeOperationError } from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow';
import type {
FindOneAndReplaceOptions,
@ -38,7 +38,7 @@ export class MongoDb implements INodeType {
name: 'mongoDb',
icon: 'file:mongodb.svg',
group: ['input'],
version: 1,
version: [1, 1.1],
description: 'Find, insert and update documents in MongoDB',
defaults: {
name: 'MongoDB',
@ -108,101 +108,126 @@ export class MongoDb implements INodeType {
const mdb = client.db(database);
let responseData: IDataObject | IDataObject[] = [];
let returnData: INodeExecutionData[] = [];
const items = this.getInputData();
const operation = this.getNodeParameter('operation', 0);
const nodeVersion = this.getNode().typeVersion;
let itemsLength = items.length ? 1 : 0;
let fallbackPairedItems;
if (nodeVersion >= 1.1) {
itemsLength = items.length;
} else {
fallbackPairedItems = generatePairedItemData(items.length);
}
if (operation === 'aggregate') {
// ----------------------------------
// aggregate
// ----------------------------------
for (let i = 0; i < itemsLength; i++) {
try {
const queryParameter = JSON.parse(
this.getNodeParameter('query', i) as string,
) as IDataObject;
try {
const queryParameter = JSON.parse(
this.getNodeParameter('query', 0) as string,
) as IDataObject;
if (queryParameter._id && typeof queryParameter._id === 'string') {
queryParameter._id = new ObjectId(queryParameter._id);
}
if (queryParameter._id && typeof queryParameter._id === 'string') {
queryParameter._id = new ObjectId(queryParameter._id);
}
const query = mdb
.collection(this.getNodeParameter('collection', i) as string)
.aggregate(queryParameter as unknown as Document[]);
const query = mdb
.collection(this.getNodeParameter('collection', 0) as string)
.aggregate(queryParameter as unknown as Document[]);
responseData = await query.toArray();
} catch (error) {
if (this.continueOnFail()) {
responseData = [{ error: (error as JsonObject).message }];
} else {
for (const entry of await query.toArray()) {
returnData.push({ json: entry, pairedItem: fallbackPairedItems ?? [{ item: i }] });
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: (error as JsonObject).message },
pairedItem: fallbackPairedItems ?? [{ item: i }],
});
continue;
}
throw error;
}
}
} else if (operation === 'delete') {
// ----------------------------------
// delete
// ----------------------------------
}
try {
const { deletedCount } = await mdb
.collection(this.getNodeParameter('collection', 0) as string)
.deleteMany(JSON.parse(this.getNodeParameter('query', 0) as string) as Document);
if (operation === 'delete') {
for (let i = 0; i < itemsLength; i++) {
try {
const { deletedCount } = await mdb
.collection(this.getNodeParameter('collection', i) as string)
.deleteMany(JSON.parse(this.getNodeParameter('query', i) as string) as Document);
responseData = [{ deletedCount }];
} catch (error) {
if (this.continueOnFail()) {
responseData = [{ error: (error as JsonObject).message }];
} else {
returnData.push({
json: { deletedCount },
pairedItem: fallbackPairedItems ?? [{ item: i }],
});
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: (error as JsonObject).message },
pairedItem: fallbackPairedItems ?? [{ item: i }],
});
continue;
}
throw error;
}
}
} else if (operation === 'find') {
// ----------------------------------
// find
// ----------------------------------
}
try {
const queryParameter = JSON.parse(
this.getNodeParameter('query', 0) as string,
) as IDataObject;
if (operation === 'find') {
for (let i = 0; i < itemsLength; i++) {
try {
const queryParameter = JSON.parse(
this.getNodeParameter('query', i) as string,
) as IDataObject;
if (queryParameter._id && typeof queryParameter._id === 'string') {
queryParameter._id = new ObjectId(queryParameter._id);
}
if (queryParameter._id && typeof queryParameter._id === 'string') {
queryParameter._id = new ObjectId(queryParameter._id);
}
let query = mdb
.collection(this.getNodeParameter('collection', 0) as string)
.find(queryParameter as unknown as Document);
let query = mdb
.collection(this.getNodeParameter('collection', i) as string)
.find(queryParameter as unknown as Document);
const options = this.getNodeParameter('options', 0);
const limit = options.limit as number;
const skip = options.skip as number;
const sort = options.sort && (JSON.parse(options.sort as string) as Sort);
if (skip > 0) {
query = query.skip(skip);
}
if (limit > 0) {
query = query.limit(limit);
}
if (sort && Object.keys(sort).length !== 0 && sort.constructor === Object) {
query = query.sort(sort);
}
const queryResult = await query.toArray();
const options = this.getNodeParameter('options', i);
const limit = options.limit as number;
const skip = options.skip as number;
const sort = options.sort && (JSON.parse(options.sort as string) as Sort);
responseData = queryResult;
} catch (error) {
if (this.continueOnFail()) {
responseData = [{ error: (error as JsonObject).message }];
} else {
if (skip > 0) {
query = query.skip(skip);
}
if (limit > 0) {
query = query.limit(limit);
}
if (sort && Object.keys(sort).length !== 0 && sort.constructor === Object) {
query = query.sort(sort);
}
const queryResult = await query.toArray();
for (const entry of queryResult) {
returnData.push({ json: entry, pairedItem: fallbackPairedItems ?? [{ item: i }] });
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: (error as JsonObject).message },
pairedItem: fallbackPairedItems ?? [{ item: i }],
});
continue;
}
throw error;
}
}
} else if (operation === 'findOneAndReplace') {
// ----------------------------------
// findOneAndReplace
// ----------------------------------
}
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(
@ -237,12 +262,14 @@ export class MongoDb implements INodeType {
}
}
responseData = updateItems;
} else if (operation === 'findOneAndUpdate') {
// ----------------------------------
// findOneAndUpdate
// ----------------------------------
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(
@ -277,11 +304,15 @@ export class MongoDb implements INodeType {
}
}
responseData = updateItems;
} else if (operation === 'insert') {
// ----------------------------------
// insert
// ----------------------------------
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
const fields = prepareFields(this.getNodeParameter('fields', 0) as string);
@ -310,11 +341,15 @@ export class MongoDb implements INodeType {
throw error;
}
}
} else if (operation === 'update') {
// ----------------------------------
// update
// ----------------------------------
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(
@ -349,30 +384,14 @@ export class MongoDb implements INodeType {
}
}
responseData = updateItems;
} else {
if (this.continueOnFail()) {
responseData = [{ error: `The operation "${operation}" is not supported!` }];
} else {
throw new NodeOperationError(
this.getNode(),
`The operation "${operation}" is not supported!`,
{ itemIndex: 0 },
);
}
returnData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(updateItems),
{ itemData: fallbackPairedItems },
);
}
await client.close();
stringifyObjectIDs(responseData);
const itemData = generatePairedItemData(items.length);
const returnItems = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData),
{ itemData },
);
return [returnItems];
return [stringifyObjectIDs(returnData)];
}
}

View file

@ -28,7 +28,7 @@ export class RssFeedRead implements INodeType {
name: 'rssFeedRead',
icon: 'fa:rss',
group: ['input'],
version: 1,
version: [1, 1.1],
description: 'Reads data from an RSS Feed',
defaults: {
name: 'RSS Read',
@ -65,59 +65,88 @@ export class RssFeedRead implements INodeType {
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const pairedItem = generatePairedItemData(this.getInputData().length);
const returnData: INodeExecutionData[] = [];
const nodeVersion = this.getNode().typeVersion;
const items = this.getInputData();
try {
const url = this.getNodeParameter('url', 0) as string;
const options = this.getNodeParameter('options', 0);
const ignoreSSL = Boolean(options.ignoreSSL);
let itemsLength = items.length ? 1 : 0;
let fallbackPairedItems;
if (!url) {
throw new NodeOperationError(this.getNode(), 'The parameter "URL" has to be set!');
}
if (nodeVersion >= 1.1) {
itemsLength = items.length;
} else {
fallbackPairedItems = generatePairedItemData(items.length);
}
if (!validateURL(url)) {
throw new NodeOperationError(this.getNode(), 'The provided "URL" is not valid!');
}
const parser = new Parser({
requestOptions: {
rejectUnauthorized: !ignoreSSL,
},
});
let feed: Parser.Output<IDataObject>;
for (let i = 0; i < itemsLength; i++) {
try {
feed = await parser.parseURL(url);
} catch (error) {
if (error.code === 'ECONNREFUSED') {
throw new NodeOperationError(
this.getNode(),
`It was not possible to connect to the URL. Please make sure the URL "${url}" it is valid!`,
);
const url = this.getNodeParameter('url', i) as string;
const options = this.getNodeParameter('options', i);
const ignoreSSL = Boolean(options.ignoreSSL);
if (!url) {
throw new NodeOperationError(this.getNode(), 'The parameter "URL" has to be set!', {
itemIndex: i,
});
}
throw new NodeOperationError(this.getNode(), error as Error);
}
const returnData: INodeExecutionData[] = [];
// For now we just take the items and ignore everything else
if (feed.items) {
feed.items.forEach((item) => {
returnData.push({
json: item,
pairedItem,
if (!validateURL(url)) {
throw new NodeOperationError(this.getNode(), 'The provided "URL" is not valid!', {
itemIndex: i,
});
});
}
}
return [returnData];
} catch (error) {
if (this.continueOnFail()) {
return [[{ json: { error: error.message }, pairedItem }]];
const parser = new Parser({
requestOptions: {
rejectUnauthorized: !ignoreSSL,
},
});
let feed: Parser.Output<IDataObject>;
try {
feed = await parser.parseURL(url);
} catch (error) {
if (error.code === 'ECONNREFUSED') {
throw new NodeOperationError(
this.getNode(),
`It was not possible to connect to the URL. Please make sure the URL "${url}" it is valid!`,
{
itemIndex: i,
},
);
}
throw new NodeOperationError(this.getNode(), error as Error, {
itemIndex: i,
});
}
// For now we just take the items and ignore everything else
if (feed.items) {
const feedItems = (feed.items as IDataObject[]).map((item) => ({
json: item,
})) as INodeExecutionData[];
const itemData = fallbackPairedItems || [{ item: i }];
const executionData = this.helpers.constructExecutionMetaData(feedItems, {
itemData,
});
returnData.push(...executionData);
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: fallbackPairedItems || [{ item: i }],
});
continue;
}
throw error;
}
throw error;
}
return [returnData];
}
}