From 7d831e9e99fc943bcce4ef3ab2d8899cc96aa75f Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Thu, 6 Jun 2024 13:06:17 +0300 Subject: [PATCH 1/2] checkForSchemaChanges before append, update, upsert --- .../nodes/Google/Sheet/GoogleSheets.node.ts | 3 +- .../v2/actions/sheet/append.operation.ts | 21 +++++++++++++- .../actions/sheet/appendOrUpdate.operation.ts | 8 ++++- .../v2/actions/sheet/update.operation.ts | 9 +++++- .../Sheet/v2/actions/versionDescription.ts | 2 +- .../Sheet/v2/helpers/GoogleSheets.utils.ts | 29 +++++++++++++++++++ 6 files changed, 67 insertions(+), 5 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts index c29ae24cf8bfc..293cea5414333 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.3, + defaultVersion: 4.4, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Read, update and write data to Google Sheets', }; @@ -24,6 +24,7 @@ export class GoogleSheets extends VersionedNodeType { 4.1: new GoogleSheetsV2(baseDescription), 4.2: new GoogleSheetsV2(baseDescription), 4.3: new GoogleSheetsV2(baseDescription), + 4.4: 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 e5084f6ec0c09..a5bf334c7806b 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,9 +1,15 @@ -import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; +import { + type IExecuteFunctions, + type IDataObject, + type INodeExecutionData, + NodeOperationError, +} from 'n8n-workflow'; import type { SheetProperties, ValueInputOption } from '../../helpers/GoogleSheets.types'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; import { autoMapInputData, cellFormatDefault, + checkForSchemaChanges, mapFields, untilSheetSelected, } from '../../helpers/GoogleSheets.utils'; @@ -226,6 +232,19 @@ export async function execute( headerRow = locationDefine.headerRow as number; } + if (nodeVersion >= 4.4 && dataMode !== 'autoMapInputData') { + const sheetData = await sheet.getData(sheetName, 'FORMATTED_VALUE'); + + if (sheetData?.[headerRow - 1] === undefined) { + throw new NodeOperationError( + this.getNode(), + `Could not retrieve the column names from row ${headerRow}`, + ); + } + + checkForSchemaChanges(this, sheetData[headerRow - 1], nodeVersion); + } + let setData: IDataObject[] = []; if (dataMode === 'autoMapInputData') { 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 b39dc758a8450..68f75045a39bc 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,11 @@ import type { ValueRenderOption, } from '../../helpers/GoogleSheets.types'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; -import { cellFormatDefault, untilSheetSelected } from '../../helpers/GoogleSheets.utils'; +import { + cellFormatDefault, + checkForSchemaChanges, + untilSheetSelected, +} from '../../helpers/GoogleSheets.utils'; import { cellFormat, handlingExtraData, locationDefine } from './commonDescription'; export const description: SheetProperties = [ @@ -267,6 +271,8 @@ export async function execute( columnNames = sheetData[headerRow] ?? []; + checkForSchemaChanges(this, columnNames, nodeVersion); + const newColumns = new Set(); const columnsToMatchOn: string[] = 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 9799bd0c4fc06..977cb04d3406d 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,11 @@ import type { ValueRenderOption, } from '../../helpers/GoogleSheets.types'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; -import { cellFormatDefault, untilSheetSelected } from '../../helpers/GoogleSheets.utils'; +import { + cellFormatDefault, + checkForSchemaChanges, + untilSheetSelected, +} from '../../helpers/GoogleSheets.utils'; import { cellFormat, handlingExtraData, locationDefine } from './commonDescription'; export const description: SheetProperties = [ @@ -252,6 +256,9 @@ export async function execute( } columnNames = sheetData[headerRow]; + + checkForSchemaChanges(this, columnNames, nodeVersion); + const newColumns = new Set(); const columnsToMatchOn: string[] = 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 8bb6064f49a67..c18a6c75b863a 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, 4.1, 4.2, 4.3], + version: [3, 4, 4.1, 4.2, 4.3, 4.4], 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/GoogleSheets.utils.ts b/packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheets.utils.ts index 828a0fde77f5b..abc38ac7f48d9 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 @@ -334,3 +334,32 @@ export function cellFormatDefault(nodeVersion: number) { } return 'USER_ENTERED'; } + +export function checkForSchemaChanges( + ctx: IExecuteFunctions, + columnNames: string[], + nodeVersion: number, +) { + if (nodeVersion >= 4.4) { + const updatedColumnNames: Array<{ oldName: string; newName: string }> = []; + const schema = ctx.getNodeParameter('columns.schema', 0) as Array<{ id: string }>; + + for (const [columnIndex, columnName] of columnNames.entries()) { + const schemaEntry = schema[columnIndex]; + if (schemaEntry === undefined) break; + if (columnName !== schema[columnIndex].id) { + updatedColumnNames.push({ oldName: schema[columnIndex].id, newName: columnName }); + } + } + + if (updatedColumnNames.length) { + throw new NodeOperationError( + ctx.getNode(), + "Column names were updated after the node's setup", + { + description: `Refresh the columns list in the 'Column to Match On' parameter. Updated columns: ${updatedColumnNames.map((c) => `${c.oldName} -> ${c.newName}`).join(', ')}`, + }, + ); + } + } +} From 38bb5a205c4dacab7c88fe9828f42a5c6c7e4bdb Mon Sep 17 00:00:00 2001 From: Michael Kret Date: Thu, 6 Jun 2024 13:57:35 +0300 Subject: [PATCH 2/2] tests --- .../Google/Sheet/test/v2/utils/utils.test.ts | 46 ++++++++++++++++++- .../v2/actions/sheet/append.operation.ts | 5 +- .../actions/sheet/appendOrUpdate.operation.ts | 12 ++++- .../v2/actions/sheet/update.operation.ts | 12 ++++- .../Sheet/v2/helpers/GoogleSheets.utils.ts | 36 ++++++--------- 5 files changed, 84 insertions(+), 27 deletions(-) diff --git a/packages/nodes-base/nodes/Google/Sheet/test/v2/utils/utils.test.ts b/packages/nodes-base/nodes/Google/Sheet/test/v2/utils/utils.test.ts index 474c878dedb78..52ec8ae4c3fbc 100644 --- a/packages/nodes-base/nodes/Google/Sheet/test/v2/utils/utils.test.ts +++ b/packages/nodes-base/nodes/Google/Sheet/test/v2/utils/utils.test.ts @@ -1,8 +1,9 @@ -import type { IExecuteFunctions, INode } from 'n8n-workflow'; +import type { IExecuteFunctions, INode, ResourceMapperField } from 'n8n-workflow'; import { GoogleSheet } from '../../../v2/helpers/GoogleSheet'; import { addRowNumber, autoMapInputData, + checkForSchemaChanges, prepareSheetData, removeEmptyColumns, removeEmptyRows, @@ -400,3 +401,46 @@ describe('Test Google Sheets, lookupValues', () => { ]); }); }); + +describe('Test Google Sheets, checkForSchemaChanges', () => { + it('should not to throw error', async () => { + const node: INode = { + id: '1', + name: 'Google Sheets', + typeVersion: 4.4, + type: 'n8n-nodes-base.googleSheets', + position: [60, 760], + parameters: { + operation: 'append', + }, + }; + + expect(() => + checkForSchemaChanges(node, ['id', 'name', 'data'], [ + { id: 'id' }, + { id: 'name' }, + { id: 'data' }, + ] as ResourceMapperField[]), + ).not.toThrow(); + }); + it('should throw error when columns were renamed', async () => { + const node: INode = { + id: '1', + name: 'Google Sheets', + typeVersion: 4.4, + type: 'n8n-nodes-base.googleSheets', + position: [60, 760], + parameters: { + operation: 'append', + }, + }; + + expect(() => + checkForSchemaChanges(node, ['id', 'name', 'data'], [ + { id: 'id' }, + { id: 'name' }, + { id: 'text' }, + ] as ResourceMapperField[]), + ).toThrow("Column names were updated after the node's setup"); + }); +}); 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 a5bf334c7806b..14851bf7fa13a 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 @@ -3,6 +3,7 @@ import { type IDataObject, type INodeExecutionData, NodeOperationError, + type ResourceMapperField, } from 'n8n-workflow'; import type { SheetProperties, ValueInputOption } from '../../helpers/GoogleSheets.types'; import type { GoogleSheet } from '../../helpers/GoogleSheet'; @@ -233,6 +234,7 @@ export async function execute( } if (nodeVersion >= 4.4 && dataMode !== 'autoMapInputData') { + //not possible to refresh columns when mode is autoMapInputData const sheetData = await sheet.getData(sheetName, 'FORMATTED_VALUE'); if (sheetData?.[headerRow - 1] === undefined) { @@ -242,7 +244,8 @@ export async function execute( ); } - checkForSchemaChanges(this, sheetData[headerRow - 1], nodeVersion); + const schema = this.getNodeParameter('columns.schema', 0) as ResourceMapperField[]; + checkForSchemaChanges(this.getNode(), sheetData[headerRow - 1], schema); } let setData: IDataObject[] = []; 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 68f75045a39bc..b6b5b6a4ccda2 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 @@ -1,4 +1,9 @@ -import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; +import type { + IExecuteFunctions, + IDataObject, + INodeExecutionData, + ResourceMapperField, +} from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import type { ISheetUpdateData, @@ -271,7 +276,10 @@ export async function execute( columnNames = sheetData[headerRow] ?? []; - checkForSchemaChanges(this, columnNames, nodeVersion); + if (nodeVersion >= 4.4) { + const schema = this.getNodeParameter('columns.schema', 0) as ResourceMapperField[]; + checkForSchemaChanges(this.getNode(), columnNames, schema); + } const newColumns = new Set(); 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 977cb04d3406d..bf5fb0c142681 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 @@ -1,4 +1,9 @@ -import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; +import type { + IExecuteFunctions, + IDataObject, + INodeExecutionData, + ResourceMapperField, +} from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import type { ISheetUpdateData, @@ -257,7 +262,10 @@ export async function execute( columnNames = sheetData[headerRow]; - checkForSchemaChanges(this, columnNames, nodeVersion); + if (nodeVersion >= 4.4) { + const schema = this.getNodeParameter('columns.schema', 0) as ResourceMapperField[]; + checkForSchemaChanges(this.getNode(), columnNames, schema); + } const newColumns = new Set(); 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 abc38ac7f48d9..f2f39b5c4d9b1 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 @@ -5,6 +5,7 @@ import type { INodeListSearchItems, INodePropertyOptions, INode, + ResourceMapperField, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import type { GoogleSheet } from './GoogleSheet'; @@ -336,30 +337,23 @@ export function cellFormatDefault(nodeVersion: number) { } export function checkForSchemaChanges( - ctx: IExecuteFunctions, + node: INode, columnNames: string[], - nodeVersion: number, + schema: ResourceMapperField[], ) { - if (nodeVersion >= 4.4) { - const updatedColumnNames: Array<{ oldName: string; newName: string }> = []; - const schema = ctx.getNodeParameter('columns.schema', 0) as Array<{ id: string }>; - - for (const [columnIndex, columnName] of columnNames.entries()) { - const schemaEntry = schema[columnIndex]; - if (schemaEntry === undefined) break; - if (columnName !== schema[columnIndex].id) { - updatedColumnNames.push({ oldName: schema[columnIndex].id, newName: columnName }); - } - } + const updatedColumnNames: Array<{ oldName: string; newName: string }> = []; - if (updatedColumnNames.length) { - throw new NodeOperationError( - ctx.getNode(), - "Column names were updated after the node's setup", - { - description: `Refresh the columns list in the 'Column to Match On' parameter. Updated columns: ${updatedColumnNames.map((c) => `${c.oldName} -> ${c.newName}`).join(', ')}`, - }, - ); + for (const [columnIndex, columnName] of columnNames.entries()) { + const schemaEntry = schema[columnIndex]; + if (schemaEntry === undefined) break; + if (columnName !== schema[columnIndex].id) { + updatedColumnNames.push({ oldName: schema[columnIndex].id, newName: columnName }); } } + + if (updatedColumnNames.length) { + throw new NodeOperationError(node, "Column names were updated after the node's setup", { + description: `Refresh the columns list in the 'Column to Match On' parameter. Updated columns: ${updatedColumnNames.map((c) => `${c.oldName} -> ${c.newName}`).join(', ')}`, + }); + } }