From d8531a53b9a256095157129b64adde7749aa84c9 Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Tue, 17 Oct 2023 18:41:30 +0300 Subject: [PATCH] fix(Google Sheets Node): Tweaks (#7357) Github issue / Community forum post (link here to close automatically): --- .../nodes/Google/Sheet/GoogleSheets.node.ts | 3 +- .../v2/actions/sheet/append.operation.ts | 19 +- .../actions/sheet/appendOrUpdate.operation.ts | 27 +- .../v2/actions/sheet/commonDescription.ts | 488 +++++++++--------- .../Sheet/v2/actions/sheet/read.operation.ts | 4 +- .../v2/actions/sheet/update.operation.ts | 61 ++- .../Sheet/v2/actions/versionDescription.ts | 2 +- .../Google/Sheet/v2/helpers/GoogleSheet.ts | 49 +- .../Sheet/v2/helpers/GoogleSheets.utils.ts | 7 + .../Sheet/v2/methods/resourceMapping.ts | 40 +- 10 files changed, 405 insertions(+), 295 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts index 6a7fe72c9f1af..3c5fd6ad49800 100644 --- a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts @@ -11,7 +11,7 @@ export class GoogleSheets extends VersionedNodeType { name: 'googleSheets', icon: 'file:googleSheets.svg', group: ['input', 'output'], - defaultVersion: 4, + defaultVersion: 4.1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Read, update and write data to Google Sheets', }; @@ -21,6 +21,7 @@ export class GoogleSheets extends VersionedNodeType { 2: new GoogleSheetsV1(baseDescription), 3: new GoogleSheetsV2(baseDescription), 4: new GoogleSheetsV2(baseDescription), + 4.1: new GoogleSheetsV2(baseDescription), }; super(nodeVersions, baseDescription); diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts index 0ae45163ca52a..6508cee1f3ec9 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/append.operation.ts @@ -1,7 +1,12 @@ import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; import type { SheetProperties, ValueInputOption } from '../../helpers/GoogleSheets.types'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; -import { autoMapInputData, mapFields, untilSheetSelected } from '../../helpers/GoogleSheets.utils'; +import { + autoMapInputData, + cellFormatDefault, + mapFields, + untilSheetSelected, +} from '../../helpers/GoogleSheets.utils'; import { cellFormat, handlingExtraData } from './commonDescription'; export const description: SheetProperties = [ @@ -131,7 +136,7 @@ export const description: SheetProperties = [ show: { resource: ['sheet'], operation: ['append'], - '@version': [4], + '@version': [4, 4.1], }, hide: { ...untilSheetSelected, @@ -154,7 +159,7 @@ export const description: SheetProperties = [ }, }, options: [ - ...cellFormat, + cellFormat, { displayName: 'Data Location on Sheet', name: 'locationDefine', @@ -181,7 +186,11 @@ export const description: SheetProperties = [ }, ], }, - ...handlingExtraData, + handlingExtraData, + { + ...handlingExtraData, + displayOptions: { show: { '/columns.mappingMode': ['autoMapInputData'] } }, + }, ], }, ]; @@ -227,7 +236,7 @@ export async function execute( setData, sheetName, headerRow, - (options.cellFormat as ValueInputOption) || 'RAW', + (options.cellFormat as ValueInputOption) || cellFormatDefault(nodeVersion), false, ); 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 0f5ab62f401b5..459bb85e6fe93 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 @@ -7,7 +7,7 @@ import type { } from '../../helpers/GoogleSheets.types'; import { NodeOperationError } from 'n8n-workflow'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; -import { untilSheetSelected } from '../../helpers/GoogleSheets.utils'; +import { cellFormatDefault, untilSheetSelected } from '../../helpers/GoogleSheets.utils'; import { cellFormat, handlingExtraData, locationDefine } from './commonDescription'; export const description: SheetProperties = [ @@ -172,7 +172,7 @@ export const description: SheetProperties = [ show: { resource: ['sheet'], operation: ['appendOrUpdate'], - '@version': [4], + '@version': [4, 4.1], }, hide: { ...untilSheetSelected, @@ -194,7 +194,15 @@ export const description: SheetProperties = [ ...untilSheetSelected, }, }, - options: [...cellFormat, ...locationDefine, ...handlingExtraData], + options: [ + cellFormat, + locationDefine, + handlingExtraData, + { + ...handlingExtraData, + displayOptions: { show: { '/columns.mappingMode': ['autoMapInputData'] } }, + }, + ], }, ]; @@ -205,9 +213,16 @@ export async function execute( sheetId: string, ): Promise { const items = this.getInputData(); - const valueInputMode = this.getNodeParameter('options.cellFormat', 0, 'RAW') as ValueInputOption; + const nodeVersion = this.getNode().typeVersion; + const range = `${sheetName}!A:Z`; + const valueInputMode = this.getNodeParameter( + 'options.cellFormat', + 0, + cellFormatDefault(nodeVersion), + ) as ValueInputOption; + const options = this.getNodeParameter('options', 0, {}); const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption; @@ -238,7 +253,7 @@ export async function execute( } columnNames = sheetData[headerRow]; - const nodeVersion = this.getNode().typeVersion; + const newColumns = new Set(); const columnsToMatchOn: string[] = @@ -346,7 +361,7 @@ export async function execute( await sheet.updateRows( sheetName, [columnNames.concat([...newColumns])], - (options.cellFormat as ValueInputOption) || 'RAW', + (options.cellFormat as ValueInputOption) || cellFormatDefault(nodeVersion), headerRow + 1, ); } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/commonDescription.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/commonDescription.ts index 726745fde1eb7..66fdd1166c9d2 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/commonDescription.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/commonDescription.ts @@ -1,270 +1,260 @@ import type { INodeProperties } from 'n8n-workflow'; -export const dataLocationOnSheet: INodeProperties[] = [ - { - displayName: 'Data Location on Sheet', - name: 'dataLocationOnSheet', - type: 'fixedCollection', - placeholder: 'Select Range', - default: { values: { rangeDefinition: 'detectAutomatically' } }, - options: [ - { - displayName: 'Values', - name: 'values', - values: [ - { - displayName: 'Range Definition', - name: 'rangeDefinition', - type: 'options', - options: [ - { - name: 'Detect Automatically', - value: 'detectAutomatically', - description: 'Automatically detect the data range', - }, - { - name: 'Specify Range (A1 Notation)', - value: 'specifyRangeA1', - description: 'Manually specify the data range', - }, - { - name: 'Specify Range (Rows)', - value: 'specifyRange', - description: 'Manually specify the data range', - }, - ], - default: '', - }, - { - displayName: 'Read Rows Until', - name: 'readRowsUntil', - type: 'options', - default: 'lastRowInSheet', - options: [ - { - name: 'First Empty Row', - value: 'firstEmptyRow', - }, - { - name: 'Last Row In Sheet', - value: 'lastRowInSheet', - }, - ], - displayOptions: { - show: { - rangeDefinition: ['detectAutomatically'], - }, +export const dataLocationOnSheet: INodeProperties = { + displayName: 'Data Location on Sheet', + name: 'dataLocationOnSheet', + type: 'fixedCollection', + placeholder: 'Select Range', + default: { values: { rangeDefinition: 'detectAutomatically' } }, + options: [ + { + displayName: 'Values', + name: 'values', + values: [ + { + displayName: 'Range Definition', + name: 'rangeDefinition', + type: 'options', + options: [ + { + name: 'Detect Automatically', + value: 'detectAutomatically', + description: 'Automatically detect the data range', }, - }, - { - displayName: 'Header Row', - name: 'headerRow', - type: 'number', - typeOptions: { - minValue: 1, + { + name: 'Specify Range (A1 Notation)', + value: 'specifyRangeA1', + description: 'Manually specify the data range', }, - default: 1, - description: "Index is relative to the set 'Range', first row index is 1", - hint: 'Index of the row which contains the column names', - displayOptions: { - show: { - rangeDefinition: ['specifyRange'], - }, + { + name: 'Specify Range (Rows)', + value: 'specifyRange', + description: 'Manually specify the data range', }, - }, - { - displayName: 'First Data Row', - name: 'firstDataRow', - type: 'number', - typeOptions: { - minValue: 1, + ], + default: '', + }, + { + displayName: 'Read Rows Until', + name: 'readRowsUntil', + type: 'options', + default: 'lastRowInSheet', + options: [ + { + name: 'First Empty Row', + value: 'firstEmptyRow', + }, + { + name: 'Last Row In Sheet', + value: 'lastRowInSheet', }, - default: 2, - description: "Index is relative to the set 'Range', first row index is 1", - hint: 'Index of first row which contains the actual data', - displayOptions: { - show: { - rangeDefinition: ['specifyRange'], - }, + ], + displayOptions: { + show: { + rangeDefinition: ['detectAutomatically'], }, }, - { - displayName: 'Range', - name: 'range', - type: 'string', - default: '', - placeholder: 'A:Z', - description: - 'The table range to read from or to append data to. See the Google documentation for the details.', - hint: 'You can specify both the rows and the columns, e.g. C4:E7', - displayOptions: { - show: { - rangeDefinition: ['specifyRangeA1'], - }, + }, + { + displayName: 'Header Row', + name: 'headerRow', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 1, + description: "Index is relative to the set 'Range', first row index is 1", + hint: 'Index of the row which contains the column names', + displayOptions: { + show: { + rangeDefinition: ['specifyRange'], }, }, - ], - }, - ], - }, -]; - -export const locationDefine: INodeProperties[] = [ - { - displayName: 'Data Location on Sheet', - name: 'locationDefine', - type: 'fixedCollection', - placeholder: 'Select Range', - default: { values: {} }, - options: [ - { - displayName: 'Values', - name: 'values', - values: [ - { - displayName: 'Header Row', - name: 'headerRow', - type: 'number', - typeOptions: { - minValue: 1, + }, + { + displayName: 'First Data Row', + name: 'firstDataRow', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 2, + description: "Index is relative to the set 'Range', first row index is 1", + hint: 'Index of first row which contains the actual data', + displayOptions: { + show: { + rangeDefinition: ['specifyRange'], }, - default: 1, - description: "Index is relative to the set 'Range', first row index is 1", - hint: 'Index of the row which contains the column names', }, - { - displayName: 'First Data Row', - name: 'firstDataRow', - type: 'number', - typeOptions: { - minValue: 1, + }, + { + displayName: 'Range', + name: 'range', + type: 'string', + default: '', + placeholder: 'A:Z', + description: + 'The table range to read from or to append data to. See the Google documentation for the details.', + hint: 'You can specify both the rows and the columns, e.g. C4:E7', + displayOptions: { + show: { + rangeDefinition: ['specifyRangeA1'], }, - default: 2, - description: "Index is relative to the set 'Range', first row index is 1", - hint: 'Index of first row which contains the actual data', }, - ], - }, - ], - }, -]; + }, + ], + }, + ], +}; -export const outputFormatting: INodeProperties[] = [ - { - displayName: 'Output Formatting', - name: 'outputFormatting', - type: 'fixedCollection', - placeholder: 'Add Formatting', - default: { values: { general: 'UNFORMATTED_VALUE', date: 'FORMATTED_STRING' } }, - options: [ - { - displayName: 'Values', - name: 'values', - values: [ - { - displayName: 'General Formatting', - name: 'general', - type: 'options', - options: [ - { - // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased - name: 'Values (unformatted)', - value: 'UNFORMATTED_VALUE', - description: - 'Numbers stay as numbers, but any currency signs or special formatting is lost', - }, - { - // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased - name: 'Values (formatted)', - value: 'FORMATTED_VALUE', - description: - 'Numbers are turned to text, and displayed as in Google Sheets (e.g. with commas or currency signs)', - }, - { - name: 'Formulas', - value: 'FORMULA', - }, - ], - default: '', - description: 'Determines how values should be rendered in the output', +export const locationDefine: INodeProperties = { + displayName: 'Data Location on Sheet', + name: 'locationDefine', + type: 'fixedCollection', + placeholder: 'Select Range', + default: { values: {} }, + options: [ + { + displayName: 'Values', + name: 'values', + values: [ + { + displayName: 'Header Row', + name: 'headerRow', + type: 'number', + typeOptions: { + minValue: 1, }, - { - displayName: 'Date Formatting', - name: 'date', - type: 'options', - default: '', - options: [ - { - name: 'Formatted Text', - value: 'FORMATTED_STRING', - description: "As displayed in Google Sheets, e.g. '01/01/2022'", - }, - { - name: 'Serial Number', - value: 'SERIAL_NUMBER', - description: 'A number representing the number of days since Dec 30, 1899', - }, - ], + default: 1, + description: "Index is relative to the set 'Range', first row index is 1", + hint: 'Index of the row which contains the column names', + }, + { + displayName: 'First Data Row', + name: 'firstDataRow', + type: 'number', + typeOptions: { + minValue: 1, }, - ], - }, - ], - }, -]; + default: 2, + description: "Index is relative to the set 'Range', first row index is 1", + hint: 'Index of first row which contains the actual data', + }, + ], + }, + ], +}; -export const cellFormat: INodeProperties[] = [ - { - displayName: 'Cell Format', - name: 'cellFormat', - type: 'options', - options: [ - { - // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased - name: 'Let n8n format', - value: 'RAW', - description: 'Cells have the same types as the input data', - }, - { - // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased - name: 'Let Google Sheets format', - value: 'USER_ENTERED', - description: 'Cells are styled as if you typed the values into Google Sheets directly', - }, - ], - default: 'RAW', - description: 'Determines how data should be interpreted', - }, -]; +export const outputFormatting: INodeProperties = { + displayName: 'Output Formatting', + name: 'outputFormatting', + type: 'fixedCollection', + placeholder: 'Add Formatting', + default: { values: { general: 'UNFORMATTED_VALUE', date: 'FORMATTED_STRING' } }, + options: [ + { + displayName: 'Values', + name: 'values', + values: [ + { + displayName: 'General Formatting', + name: 'general', + type: 'options', + options: [ + { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + name: 'Values (unformatted)', + value: 'UNFORMATTED_VALUE', + description: + 'Numbers stay as numbers, but any currency signs or special formatting is lost', + }, + { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + name: 'Values (formatted)', + value: 'FORMATTED_VALUE', + description: + 'Numbers are turned to text, and displayed as in Google Sheets (e.g. with commas or currency signs)', + }, + { + name: 'Formulas', + value: 'FORMULA', + }, + ], + default: '', + description: 'Determines how values should be rendered in the output', + }, + { + displayName: 'Date Formatting', + name: 'date', + type: 'options', + default: '', + options: [ + { + name: 'Formatted Text', + value: 'FORMATTED_STRING', + description: "As displayed in Google Sheets, e.g. '01/01/2022'", + }, + { + name: 'Serial Number', + value: 'SERIAL_NUMBER', + description: 'A number representing the number of days since Dec 30, 1899', + }, + ], + }, + ], + }, + ], +}; -export const handlingExtraData: INodeProperties[] = [ - { - // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased - displayName: 'Handling extra fields in input', - name: 'handlingExtraData', - type: 'options', - options: [ - { - name: 'Insert in New Column(s)', - value: 'insertInNewColumn', - description: 'Create a new column for extra data', - }, - { - name: 'Ignore Them', - value: 'ignoreIt', - description: 'Ignore extra data', - }, - { - name: 'Error', - value: 'error', - description: 'Throw an error', - }, - ], - displayOptions: { - show: { - '/dataMode': ['autoMapInputData'], - }, +export const cellFormat: INodeProperties = { + displayName: 'Cell Format', + name: 'cellFormat', + type: 'options', + options: [ + { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + name: 'Let Google Sheets format', + value: 'USER_ENTERED', + description: 'Cells are styled as if you typed the values into Google Sheets directly', + }, + { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + name: 'Let n8n format', + value: 'RAW', + description: 'Cells have the same types as the input data', + }, + ], + default: 'USER_ENTERED', + description: 'Determines how data should be interpreted', +}; + +export const handlingExtraData: INodeProperties = { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + displayName: 'Handling extra fields in input', + name: 'handlingExtraData', + type: 'options', + options: [ + { + name: 'Insert in New Column(s)', + value: 'insertInNewColumn', + description: 'Create a new column for extra data', + }, + { + name: 'Ignore Them', + value: 'ignoreIt', + description: 'Ignore extra data', + }, + { + name: 'Error', + value: 'error', + description: 'Throw an error', + }, + ], + displayOptions: { + show: { + '/dataMode': ['autoMapInputData'], }, - default: 'insertInNewColumn', - description: "What do to with fields that don't match any columns in the Google Sheet", }, -]; + default: 'insertInNewColumn', + description: "What do to with fields that don't match any columns in the Google Sheet", +}; diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts index 626bfbcdc5a0d..dd27d615e286a 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/sheet/read.operation.ts @@ -80,8 +80,8 @@ export const description: SheetProperties = [ }, }, options: [ - ...dataLocationOnSheet, - ...outputFormatting, + dataLocationOnSheet, + outputFormatting, { displayName: 'When Filter Has Multiple Matches', name: 'returnAllMatches', 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 ffea82f5ad0cc..f851a8a1c07a4 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 @@ -7,7 +7,7 @@ import type { } from '../../helpers/GoogleSheets.types'; import { NodeOperationError } from 'n8n-workflow'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; -import { untilSheetSelected } from '../../helpers/GoogleSheets.utils'; +import { cellFormatDefault, untilSheetSelected } from '../../helpers/GoogleSheets.utils'; import { cellFormat, handlingExtraData, locationDefine } from './commonDescription'; export const description: SheetProperties = [ @@ -172,7 +172,7 @@ export const description: SheetProperties = [ show: { resource: ['sheet'], operation: ['update'], - '@version': [4], + '@version': [4, 4.1], }, hide: { ...untilSheetSelected, @@ -194,7 +194,15 @@ export const description: SheetProperties = [ ...untilSheetSelected, }, }, - options: [...cellFormat, ...locationDefine, ...handlingExtraData], + options: [ + cellFormat, + locationDefine, + handlingExtraData, + { + ...handlingExtraData, + displayOptions: { show: { '/columns.mappingMode': ['autoMapInputData'] } }, + }, + ], }, ]; @@ -204,17 +212,22 @@ export async function execute( sheetName: string, ): Promise { const items = this.getInputData(); - const valueInputMode = this.getNodeParameter('options.cellFormat', 0, 'RAW') as ValueInputOption; + const nodeVersion = this.getNode().typeVersion; + const range = `${sheetName}!A:Z`; + const valueInputMode = this.getNodeParameter( + 'options.cellFormat', + 0, + cellFormatDefault(nodeVersion), + ) as ValueInputOption; + const options = this.getNodeParameter('options', 0, {}); const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption; const locationDefineOptions = (options.locationDefine as IDataObject)?.values as IDataObject; - const nodeVersion = this.getNode().typeVersion; - let headerRow = 0; let firstDataRow = 1; @@ -254,6 +267,7 @@ export async function execute( // TODO: Add support for multiple columns to match on in the next overhaul const keyIndex = columnNames.indexOf(columnsToMatchOn[0]); + //not used when updating row const columnValues = await sheet.getColumnValues( range, keyIndex, @@ -276,7 +290,7 @@ export async function execute( if (handlingExtraDataOption === 'ignoreIt') { data.push(items[i].json); } - if (handlingExtraDataOption === 'error') { + if (handlingExtraDataOption === 'error' && columnsToMatchOn[0] !== 'row_number') { Object.keys(items[i].json).forEach((key) => { if (!columnNames.includes(key)) { throw new NodeOperationError(this.getNode(), 'Unexpected fields in node input', { @@ -287,7 +301,7 @@ export async function execute( }); data.push(items[i].json); } - if (handlingExtraDataOption === 'insertInNewColumn') { + if (handlingExtraDataOption === 'insertInNewColumn' && columnsToMatchOn[0] !== 'row_number') { Object.keys(items[i].json).forEach((key) => { if (!columnNames.includes(key)) { newColumns.add(key); @@ -350,22 +364,29 @@ export async function execute( await sheet.updateRows( sheetName, [columnNames.concat([...newColumns])], - (options.cellFormat as ValueInputOption) || 'RAW', + (options.cellFormat as ValueInputOption) || cellFormatDefault(nodeVersion), headerRow + 1, ); } - const preparedData = await sheet.prepareDataForUpdateOrUpsert( - data, - columnsToMatchOn[0], - range, - headerRow, - firstDataRow, - valueRenderMode, - false, - [columnNames.concat([...newColumns])], - columnValues, - ); + let preparedData; + if (columnsToMatchOn[0] === 'row_number') { + preparedData = sheet.prepareDataForUpdatingByRowNumber(data, range, [ + columnNames.concat([...newColumns]), + ]); + } else { + preparedData = await sheet.prepareDataForUpdateOrUpsert( + data, + columnsToMatchOn[0], + range, + headerRow, + firstDataRow, + valueRenderMode, + false, + [columnNames.concat([...newColumns])], + columnValues, + ); + } updateData.push(...preparedData.updateData); } diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/actions/versionDescription.ts b/packages/nodes-base/nodes/Google/Sheet/v2/actions/versionDescription.ts index a10c1f768d2a0..170ecb50f53d9 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/actions/versionDescription.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/actions/versionDescription.ts @@ -9,7 +9,7 @@ export const versionDescription: INodeTypeDescription = { name: 'googleSheets', icon: 'file:googleSheets.svg', group: ['input', 'output'], - version: [3, 4], + version: [3, 4, 4.1], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Read, update and write data to Google Sheets', defaults: { 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 de145996826ee..9d0ed5be82305 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts @@ -5,9 +5,9 @@ import type { IPollFunctions, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; -import { apiRequest } from '../transport'; import { utils as xlsxUtils } from 'xlsx'; import get from 'lodash/get'; +import { apiRequest } from '../transport'; import type { ILookupValues, ISheetUpdateData, @@ -553,6 +553,53 @@ export class GoogleSheet { return { updateData, appendData }; } + /** + * Updates data in a sheet + * + * @param {IDataObject[]} inputData Data to update Sheet with + * @param {string} range The range to look for data + * @param {number} dataStartRowIndex Index of the first row which contains data + * @param {string[][]} columnNamesList The column names to use + * @returns {Promise} + * @memberof GoogleSheet + */ + prepareDataForUpdatingByRowNumber( + inputData: IDataObject[], + range: string, + columnNamesList: string[][], + ) { + const decodedRange = this.getDecodedSheetRange(range); + const columnNames = columnNamesList[0]; + const updateData: ISheetUpdateData[] = []; + + for (const item of inputData) { + const updateRowIndex = item.row_number as number; + + for (const name of columnNames) { + if (name === 'row_number') continue; + if (item[name] === undefined || item[name] === null) continue; + + const columnToUpdate = this.getColumnWithOffset( + decodedRange.start?.column || 'A', + columnNames.indexOf(name), + ); + + let updateValue = item[name] as string; + if (typeof updateValue === 'object') { + try { + updateValue = JSON.stringify(updateValue); + } catch (error) {} + } + updateData.push({ + range: `${decodedRange.name}!${columnToUpdate}${updateRowIndex}`, + values: [[updateValue]], + }); + } + } + + return { updateData }; + } + /** * Looks for a specific value in a column and if it gets found it returns the whole row * diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheets.utils.ts b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheets.utils.ts index 0ca0e2445a68c..bea92aa442603 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheets.utils.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheets.utils.ts @@ -315,3 +315,10 @@ export function sortLoadOptions(data: INodePropertyOptions[] | INodeListSearchIt return returnData; } + +export function cellFormatDefault(nodeVersion: number) { + if (nodeVersion < 4.1) { + return 'RAW'; + } + return 'USER_ENTERED'; +} diff --git a/packages/nodes-base/nodes/Google/Sheet/v2/methods/resourceMapping.ts b/packages/nodes-base/nodes/Google/Sheet/v2/methods/resourceMapping.ts index a5ebbaff3bfdd..11c7ed8bb263f 100644 --- a/packages/nodes-base/nodes/Google/Sheet/v2/methods/resourceMapping.ts +++ b/packages/nodes-base/nodes/Google/Sheet/v2/methods/resourceMapping.ts @@ -1,6 +1,11 @@ -import type { IDataObject, ILoadOptionsFunctions, ResourceMapperFields } from 'n8n-workflow'; +import type { + IDataObject, + ILoadOptionsFunctions, + ResourceMapperField, + ResourceMapperFields, +} from 'n8n-workflow'; import { GoogleSheet } from '../helpers/GoogleSheet'; -import type { ResourceLocator } from '../helpers/GoogleSheets.types'; +import { ROW_NUMBER, type ResourceLocator } from '../helpers/GoogleSheets.types'; import { getSpreadsheetId } from '../helpers/GoogleSheets.utils'; export async function getMappingColumns( @@ -22,17 +27,32 @@ export async function getMappingColumns( const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE'); const columns = sheet.testFilter(sheetData || [], 0, 0).filter((col) => col !== ''); - const columnData: ResourceMapperFields = { - fields: columns.map((col) => ({ - id: col, - displayName: col, + + const fields: ResourceMapperField[] = columns.map((col) => ({ + id: col, + displayName: col, + required: false, + defaultMatch: col === 'id', + display: true, + type: 'string', + canBeUsedToMatch: true, + })); + + const operation = this.getNodeParameter('operation', 0) as string; + + if (operation === 'update') { + fields.push({ + id: ROW_NUMBER, + displayName: ROW_NUMBER, required: false, - defaultMatch: col === 'id', + defaultMatch: false, display: true, type: 'string', canBeUsedToMatch: true, - })), - }; + readOnly: true, + removed: true, + }); + } - return columnData; + return { fields }; }