diff --git a/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts index c29ae24cf8..293cea5414 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/test/v2/utils/utils.test.ts b/packages/nodes-base/nodes/Google/Sheet/test/v2/utils/utils.test.ts index 474c878ded..52ec8ae4c3 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 e5084f6ec0..14851bf7fa 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,16 @@ -import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow'; +import { + type IExecuteFunctions, + type IDataObject, + type INodeExecutionData, + NodeOperationError, + type ResourceMapperField, +} 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 +233,21 @@ export async function execute( headerRow = locationDefine.headerRow as number; } + 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) { + throw new NodeOperationError( + this.getNode(), + `Could not retrieve the column names from row ${headerRow}`, + ); + } + + const schema = this.getNodeParameter('columns.schema', 0) as ResourceMapperField[]; + checkForSchemaChanges(this.getNode(), sheetData[headerRow - 1], schema); + } + 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 b39dc758a8..b6b5b6a4cc 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, @@ -7,7 +12,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 +276,11 @@ export async function execute( columnNames = sheetData[headerRow] ?? []; + if (nodeVersion >= 4.4) { + const schema = this.getNodeParameter('columns.schema', 0) as ResourceMapperField[]; + checkForSchemaChanges(this.getNode(), columnNames, schema); + } + 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 9799bd0c4f..bf5fb0c142 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, @@ -7,7 +12,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 +261,12 @@ export async function execute( } columnNames = sheetData[headerRow]; + + if (nodeVersion >= 4.4) { + const schema = this.getNodeParameter('columns.schema', 0) as ResourceMapperField[]; + checkForSchemaChanges(this.getNode(), columnNames, schema); + } + 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 8bb6064f49..c18a6c75b8 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 828a0fde77..f2f39b5c4d 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'; @@ -334,3 +335,25 @@ export function cellFormatDefault(nodeVersion: number) { } return 'USER_ENTERED'; } + +export function checkForSchemaChanges( + node: INode, + columnNames: string[], + schema: ResourceMapperField[], +) { + const updatedColumnNames: Array<{ oldName: string; newName: 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(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(', ')}`, + }); + } +}