From 2a802256cab9edf14f58f2fdd83f12c28fa4398b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 30 Nov 2019 23:55:22 +0100 Subject: [PATCH] :sparkles: Add delete operation to GoogleSheets-Node --- .../nodes-base/nodes/Google/GoogleSheet.ts | 52 ++++- .../nodes/Google/GoogleSheets.node.ts | 205 +++++++++++++++++- 2 files changed, 254 insertions(+), 3 deletions(-) diff --git a/packages/nodes-base/nodes/Google/GoogleSheet.ts b/packages/nodes-base/nodes/Google/GoogleSheet.ts index d2eb887b9..9a30c683a 100644 --- a/packages/nodes-base/nodes/Google/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/GoogleSheet.ts @@ -1,5 +1,5 @@ import { IDataObject } from 'n8n-workflow'; -import { google } from 'googleapis'; +import { google, sheets_v4 } from 'googleapis'; import { JWT } from 'google-auth-library'; import { getAuthenticationClient } from './GoogleApi'; @@ -24,6 +24,18 @@ export interface ILookupValues { lookupValue: string; } +export interface IToDeleteRange { + amount: number; + startIndex: number; + sheetId: number; +} + +export interface IToDelete { + [key: string]: IToDeleteRange[] | undefined; + columns?: IToDeleteRange[]; + rows?: IToDeleteRange[]; +} + export type ValueInputOption = 'RAW' | 'USER_ENTERED'; export type ValueRenderOption = 'FORMATTED_VALUE' | 'FORMULA' | 'UNFORMATTED_VALUE'; @@ -85,6 +97,44 @@ export class GoogleSheet { } + /** + * Returns the sheets in a Spreadsheet + */ + async spreadsheetGetSheets() { + const client = await this.getAuthenticationClient(); + + const response = await Sheets.spreadsheets.get( + { + auth: client, + spreadsheetId: this.id, + fields: 'sheets.properties' + } + ); + + return response.data; + } + + + /** + * Sets values in one or more ranges of a spreadsheet. + */ + async spreadsheetBatchUpdate(requests: sheets_v4.Schema$Request[]) { // tslint:disable-line:no-any + const client = await this.getAuthenticationClient(); + + const response = await Sheets.spreadsheets.batchUpdate( + { + auth: client, + spreadsheetId: this.id, + requestBody: { + requests, + }, + } + ); + + return response.data; + } + + /** * Sets the cell values */ diff --git a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts index be80ae375..36f59a267 100644 --- a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts @@ -1,7 +1,11 @@ +import { sheets_v4 } from 'googleapis'; + import { IExecuteFunctions } from 'n8n-core'; import { IDataObject, + ILoadOptionsFunctions, INodeExecutionData, + INodePropertyOptions, INodeType, INodeTypeDescription, } from 'n8n-workflow'; @@ -11,6 +15,7 @@ import { IGoogleAuthCredentials, ILookupValues, ISheetUpdateData, + IToDelete, ValueInputOption, ValueRenderOption, } from './GoogleSheet'; @@ -52,6 +57,11 @@ export class GoogleSheets implements INodeType { value: 'clear', description: 'Clears data from a Sheet', }, + { + name: 'Delete', + value: 'delete', + description: 'Delete columns and rows from a Sheet', + }, { name: 'Lookup', value: 'lookup', @@ -87,11 +97,120 @@ export class GoogleSheets implements INodeType { displayName: 'Range', name: 'range', type: 'string', + displayOptions: { + hide: { + operation: [ + 'delete' + ], + }, + }, default: 'A:F', required: true, description: 'The table range to read from or to append data to. See the Google documentation for the details.
If it contains multiple sheets it can also be
added like this: "MySheet!A:F"', }, + + // ---------------------------------- + // Delete + // ---------------------------------- + { + displayName: 'To Delete', + name: 'toDelete', + placeholder: 'Add Columns/Rows to delete', + description: 'Deletes colums and rows from a sheet.', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'delete' + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Columns', + name: 'columns', + values: [ + { + displayName: 'Sheet', + name: 'sheetId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getSheets', + }, + options: [], + default: '', + required: true, + description: 'The sheet to delete columns from', + }, + { + displayName: 'Start Index', + name: 'startIndex', + type: 'number', + typeOptions: { + minValue: 0, + }, + default: 0, + description: 'The start index (0 based and inclusive) of column to delete.', + }, + { + displayName: 'Amount', + name: 'amount', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + description: 'Number of columns to delete.', + }, + ] + }, + { + displayName: 'Rows', + name: 'rows', + values: [ + { + displayName: 'Sheet', + name: 'sheetId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getSheets', + }, + options: [], + default: '', + required: true, + description: 'The sheet to delete columns from', + }, + { + displayName: 'Start Index', + name: 'startIndex', + type: 'number', + typeOptions: { + minValue: 0, + }, + default: 0, + description: 'The start index (0 based and inclusive) of row to delete.', + }, + { + displayName: 'Amount', + name: 'amount', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + description: 'Number of rows to delete.', + }, + ] + }, + ], + }, + + // ---------------------------------- // Read // ---------------------------------- @@ -178,6 +297,7 @@ export class GoogleSheets implements INodeType { operation: [ 'append', 'clear', + 'delete', ], rawData: [ true @@ -201,6 +321,7 @@ export class GoogleSheets implements INodeType { hide: { operation: [ 'clear', + 'delete', ], rawData: [ true @@ -401,6 +522,48 @@ export class GoogleSheets implements INodeType { }; + methods = { + loadOptions: { + // Get all the sheets in a Spreadsheet + async getSheets(this: ILoadOptionsFunctions): Promise { + const spreadsheetId = this.getCurrentNodeParameter('sheetId') as string; + + const credentials = this.getCredentials('googleApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const googleCredentials = { + email: credentials.email, + privateKey: credentials.privateKey, + } as IGoogleAuthCredentials; + + const sheet = new GoogleSheet(spreadsheetId, googleCredentials); + const responseData = await sheet.spreadsheetGetSheets(); + + if (responseData === undefined) { + throw new Error('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; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { const spreadsheetId = this.getNodeParameter('sheetId', 0) as string; const credentials = this.getCredentials('googleApi'); @@ -416,10 +579,13 @@ export class GoogleSheets implements INodeType { const sheet = new GoogleSheet(spreadsheetId, googleCredentials); - const range = this.getNodeParameter('range', 0) as string; - const operation = this.getNodeParameter('operation', 0) as string; + let range = ''; + if (operation !== 'delete') { + range = this.getNodeParameter('range', 0) as string; + } + const options = this.getNodeParameter('options', 0, {}) as IDataObject; const valueInputMode = (options.valueInputMode || 'RAW') as ValueInputOption; @@ -452,6 +618,41 @@ export class GoogleSheets implements INodeType { await sheet.clearData(range); + const items = this.getInputData(); + return this.prepareOutputData(items); + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + const requests: sheets_v4.Schema$Request[] = []; + + const toDelete = this.getNodeParameter('toDelete', 0) as IToDelete; + + const deletePropertyToDimensions: IDataObject = { + 'columns': 'COLUMNS', + 'rows': 'ROWS', + }; + + for (const propertyName of Object.keys(deletePropertyToDimensions)) { + if (toDelete[propertyName] !== undefined) { + toDelete[propertyName]!.forEach(range => { + requests.push({ + deleteDimension: { + range: { + sheetId: range.sheetId, + dimension: deletePropertyToDimensions[propertyName] as string, + startIndex: range.startIndex, + endIndex: range.startIndex + range.amount, + } + } + }); + }); + } + } + + const data = await sheet.spreadsheetBatchUpdate(requests); + const items = this.getInputData(); return this.prepareOutputData(items); } else if (operation === 'lookup') {