From 741c7da8b1a3f2824613d2a4c4880423c8be91cb Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Thu, 1 Dec 2022 10:39:03 +0200 Subject: [PATCH] fix(Google Sheets Node): Fix for append operation if no empty rows in sheet --- .../nodes/Google/Sheet/v2/actions/router.ts | 30 ++-- .../v2/actions/sheet/append.operation.ts | 7 + .../actions/sheet/appendOrUpdate.operation.ts | 31 ++-- .../v2/actions/sheet/update.operation.ts | 29 ++-- .../Google/Sheet/v2/helpers/GoogleSheet.ts | 37 +++++ .../Google/Sheet/v2/methods/listSearch.ts | 134 ++++++++---------- .../Google/Sheet/v2/methods/loadOptions.ts | 104 +++++++------- .../nodes/Google/Sheet/v2/transport/index.ts | 10 +- 8 files changed, 211 insertions(+), 171 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/router.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/router.ts index 3066f383bc..74034493b7 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/router.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/router.ts @@ -24,15 +24,15 @@ export async function router(this: IExecuteFunctions): Promise { const items = this.getInputData(); const dataMode = this.getNodeParameter('dataMode', 0) as string; @@ -176,6 +177,12 @@ export async function execute( setData = mapFields.call(this, items.length); } + if (setData.length === 0) { + return []; + } else { + await sheet.appendEmptyRowsOrColumns(sheetId, 1, 0); + } + await sheet.appendSheetData( setData, sheetName, diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts index 22dd84eb7f..d877714718 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/appendOrUpdate.operation.ts @@ -160,6 +160,7 @@ export async function execute( this: IExecuteFunctions, sheet: GoogleSheet, sheetName: string, + sheetId: string, ): Promise { const items = this.getInputData(); const valueInputMode = this.getNodeParameter('options.cellFormat', 0, 'RAW') as ValueInputOption; @@ -248,23 +249,22 @@ export async function execute( } else { const valueToMatchOn = this.getNodeParameter('valueToMatchOn', i) as string; - const fields = (this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]).reduce( - (acc, entry) => { - if (entry.column === 'newColumn') { - const columnName = entry.columnName as string; + const fields = ( + (this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]) || [] + ).reduce((acc, entry) => { + if (entry.column === 'newColumn') { + const columnName = entry.columnName as string; - if (columnNames.includes(columnName) === false) { - newColumns.add(columnName); - } - - acc[columnName] = entry.fieldValue as string; - } else { - acc[entry.column as string] = entry.fieldValue as string; + if (columnNames.includes(columnName) === false) { + newColumns.add(columnName); } - return acc; - }, - {} as IDataObject, - ); + + acc[columnName] = entry.fieldValue as string; + } else { + acc[entry.column as string] = entry.fieldValue as string; + } + return acc; + }, {} as IDataObject); fields[columnToMatchOn] = valueToMatchOn; @@ -300,6 +300,7 @@ export async function execute( await sheet.batchUpdate(updateData, valueInputMode); } if (appendData.length) { + await sheet.appendEmptyRowsOrColumns(sheetId, 1, 0); const lastRow = sheetData.length + 1; await sheet.appendSheetData( appendData, diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts index d02557a454..326e9804b9 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/update.operation.ts @@ -247,23 +247,22 @@ export async function execute( } else { const valueToMatchOn = this.getNodeParameter('valueToMatchOn', i) as string; - const fields = (this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]).reduce( - (acc, entry) => { - if (entry.column === 'newColumn') { - const columnName = entry.columnName as string; + const fields = ( + (this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]) || [] + ).reduce((acc, entry) => { + if (entry.column === 'newColumn') { + const columnName = entry.columnName as string; - if (columnNames.includes(columnName) === false) { - newColumns.add(columnName); - } - - acc[columnName] = entry.fieldValue as string; - } else { - acc[entry.column as string] = entry.fieldValue as string; + if (columnNames.includes(columnName) === false) { + newColumns.add(columnName); } - return acc; - }, - {} as IDataObject, - ); + + acc[columnName] = entry.fieldValue as string; + } else { + acc[entry.column as string] = entry.fieldValue as string; + } + return acc; + }, {} as IDataObject); fields[columnToMatchOn] = valueToMatchOn; diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts index 96ed5d7d29..9c0f0c5263 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts @@ -187,6 +187,43 @@ export class GoogleSheet { return response; } + async appendEmptyRowsOrColumns(sheetId: string, rowsToAdd = 1, columnsToAdd = 1) { + const requests: IDataObject[] = []; + + if (rowsToAdd > 0) { + requests.push({ + appendDimension: { + sheetId, + dimension: 'ROWS', + length: rowsToAdd, + }, + }); + } + + if (columnsToAdd > 0) { + requests.push({ + appendDimension: { + sheetId, + dimension: 'COLUMNS', + length: columnsToAdd, + }, + }); + } + + if (requests.length === 0) { + throw new Error('Must specify at least one column or row to add'); + } + + const response = await apiRequest.call( + this.executeFunctions, + 'POST', + `/v4/spreadsheets/${this.id}:batchUpdate`, + { requests }, + ); + + return response; + } + /** * Appends the cell values */ diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/methods/listSearch.ts b/packages/nodes-base/nodes/Google/Sheet/v2/methods/listSearch.ts index b495646e77..e2c31890b7 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/methods/listSearch.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/methods/listSearch.ts @@ -6,91 +6,83 @@ import { NodeOperationError, } from 'n8n-workflow'; import { ResourceLocator } from '../helpers/GoogleSheets.types'; -import { getSpreadsheetId, sortLoadOptions } from '../helpers/GoogleSheets.utils'; -import { apiRequest, apiRequestAllItems } from '../transport'; +import { getSpreadsheetId } from '../helpers/GoogleSheets.utils'; +import { apiRequest } from '../transport'; export async function spreadSheetsSearch( this: ILoadOptionsFunctions, filter?: string, + paginationToken?: string, ): Promise { - try { - const returnData: INodeListSearchItems[] = []; - const query: string[] = []; - if (filter) { - query.push(`name contains '${filter.replace("'", "\\'")}'`); - } - query.push("mimeType = 'application/vnd.google-apps.spreadsheet'"); - const qs = { - pageSize: 50, - orderBy: 'modifiedTime desc', - fields: 'nextPageToken, files(id, name, webViewLink)', - q: query.join(' and '), - includeItemsFromAllDrives: true, - supportsAllDrives: true, - }; - - const sheets = await apiRequestAllItems.call( - this, - 'files', - 'GET', - '', - {}, - qs, - 'https://www.googleapis.com/drive/v3/files', - ); - for (const sheet of sheets) { - returnData.push({ - name: sheet.name as string, - value: sheet.id as string, - url: sheet.webViewLink as string, - }); - } - return { results: sortLoadOptions(returnData) }; - } catch (error) { - return { results: [] }; + const query: string[] = []; + if (filter) { + query.push(`name contains '${filter.replace("'", "\\'")}'`); } + query.push("mimeType = 'application/vnd.google-apps.spreadsheet'"); + + const qs = { + q: query.join(' and '), + pageToken: (paginationToken as string) || undefined, + fields: 'nextPageToken, files(id, name, webViewLink)', + orderBy: 'name_natural', + includeItemsFromAllDrives: true, + supportsAllDrives: true, + }; + + const res = await apiRequest.call( + this, + 'GET', + '', + {}, + qs, + 'https://www.googleapis.com/drive/v3/files', + ); + return { + results: res.files.map((sheet: IDataObject) => ({ + name: sheet.name as string, + value: sheet.id as string, + url: sheet.webViewLink as string, + })), + paginationToken: res.nextPageToken, + }; } export async function sheetsSearch( this: ILoadOptionsFunctions, _filter?: string, ): Promise { - try { - const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject; - const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string); + const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject; + const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string); - const query = { - fields: 'sheets.properties', - }; + const query = { + fields: 'sheets.properties', + }; - const responseData = await apiRequest.call( - this, - 'GET', - `/v4/spreadsheets/${spreadsheetId}`, - {}, - query, - ); + const responseData = await apiRequest.call( + this, + 'GET', + `/v4/spreadsheets/${spreadsheetId}`, + {}, + query, + ); - if (responseData === undefined) { - throw new NodeOperationError(this.getNode(), 'No data got returned'); - } - - const returnData: INodeListSearchItems[] = []; - for (const sheet of responseData.sheets!) { - if (sheet.properties!.sheetType !== 'GRID') { - continue; - } - - returnData.push({ - name: sheet.properties!.title as string, - value: (sheet.properties!.sheetId as number) || 'gid=0', - //prettier-ignore - url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit#gid=${sheet.properties!.sheetId}`, - }); - } - - return { results: returnData }; - } catch (error) { - return { results: [] }; + if (responseData === undefined) { + throw new NodeOperationError(this.getNode(), 'No data got returned'); } + + const returnData: INodeListSearchItems[] = []; + for (const sheet of responseData.sheets!) { + if (sheet.properties!.sheetType !== 'GRID') { + continue; + } + + returnData.push({ + name: sheet.properties!.title as string, + value: (sheet.properties!.sheetId as number) || 'gid=0', + //prettier-ignore + url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit#gid=${sheet.properties!.sheetId}`, + }); + } + + return { results: returnData }; } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/methods/loadOptions.ts b/packages/nodes-base/nodes/Google/Sheet/v2/methods/loadOptions.ts index 8f737f40e4..84de3ad324 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/methods/loadOptions.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/methods/loadOptions.ts @@ -9,73 +9,65 @@ import { getSpreadsheetId } from '../helpers/GoogleSheets.utils'; import { ResourceLocator } from '../helpers/GoogleSheets.types'; export async function getSheets(this: ILoadOptionsFunctions): Promise { - try { - const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject; - const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string); + const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject; + const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string); - const sheet = new GoogleSheet(spreadsheetId, this); - const responseData = await sheet.spreadsheetGetSheets(); + const sheet = new GoogleSheet(spreadsheetId, this); + const responseData = await sheet.spreadsheetGetSheets(); - if (responseData === undefined) { - throw new NodeOperationError(this.getNode(), 'No data got returned'); - } - - const returnData: INodePropertyOptions[] = []; - for (const sheet of responseData.sheets!) { - if (sheet.properties!.sheetType !== 'GRID') { - continue; - } - - returnData.push({ - name: sheet.properties!.title as string, - value: sheet.properties!.sheetId as unknown as string, - }); - } - - return returnData; - } catch (error) { - return []; + if (responseData === undefined) { + throw new NodeOperationError(this.getNode(), 'No data got returned'); } + + const returnData: INodePropertyOptions[] = []; + for (const sheet of responseData.sheets!) { + if (sheet.properties!.sheetType !== 'GRID') { + continue; + } + + returnData.push({ + name: sheet.properties!.title as string, + value: sheet.properties!.sheetId as unknown as string, + }); + } + + return returnData; } export async function getSheetHeaderRow( this: ILoadOptionsFunctions, ): Promise { - try { - const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject; - const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string); + const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject; + const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string); - const sheet = new GoogleSheet(spreadsheetId, this); - let sheetWithinDocument = this.getNodeParameter('sheetName', undefined, { - extractValue: true, - }) as string; + const sheet = new GoogleSheet(spreadsheetId, this); + let sheetWithinDocument = this.getNodeParameter('sheetName', undefined, { + extractValue: true, + }) as string; - if (sheetWithinDocument === 'gid=0') { - sheetWithinDocument = '0'; - } - - const sheetName = await sheet.spreadsheetGetSheetNameById(sheetWithinDocument); - const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE'); - - if (sheetData === undefined) { - throw new NodeOperationError(this.getNode(), 'No data got returned'); - } - - const columns = sheet.testFilter(sheetData, 0, 0); - - const returnData: INodePropertyOptions[] = []; - - for (const column of columns) { - returnData.push({ - name: column as unknown as string, - value: column as unknown as string, - }); - } - - return returnData; - } catch (error) { - return []; + if (sheetWithinDocument === 'gid=0') { + sheetWithinDocument = '0'; } + + const sheetName = await sheet.spreadsheetGetSheetNameById(sheetWithinDocument); + const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE'); + + if (sheetData === undefined) { + throw new NodeOperationError(this.getNode(), 'No data got returned'); + } + + const columns = sheet.testFilter(sheetData, 0, 0); + + const returnData: INodePropertyOptions[] = []; + + for (const column of columns) { + returnData.push({ + name: column as unknown as string, + value: column as unknown as string, + }); + } + + return returnData; } export async function getSheetHeaderRowAndAddColumn( diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/transport/index.ts b/packages/nodes-base/nodes/Google/Sheet/v2/transport/index.ts index e1a7dea878..515ddb0eb2 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/transport/index.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/transport/index.ts @@ -52,11 +52,10 @@ export async function apiRequest( ); options.headers!.Authorization = `Bearer ${access_token}`; - //@ts-ignore - return await this.helpers.request(options); + + return await this.helpers.request!(options); } else { - //@ts-ignore - return await this.helpers.requestOAuth2.call(this, 'googleSheetsOAuth2Api', options); + return await this.helpers.requestOAuth2!.call(this, 'googleSheetsOAuth2Api', options); } } catch (error) { if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') { @@ -151,6 +150,5 @@ export function getAccessToken( json: true, }; - //@ts-ignore - return this.helpers.request(options); + return this.helpers.request!(options); }