fix(Google Sheets Node): Check for column names changes before upsert, append, update (#9649)

This commit is contained in:
Michael Kret 2024-06-20 16:19:16 +03:00 committed by GitHub
parent cdc2f9e7d3
commit 223488f190
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 127 additions and 8 deletions

View file

@ -11,7 +11,7 @@ export class GoogleSheets extends VersionedNodeType {
name: 'googleSheets', name: 'googleSheets',
icon: 'file:googleSheets.svg', icon: 'file:googleSheets.svg',
group: ['input', 'output'], group: ['input', 'output'],
defaultVersion: 4.3, defaultVersion: 4.4,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Read, update and write data to Google Sheets', description: 'Read, update and write data to Google Sheets',
}; };
@ -24,6 +24,7 @@ export class GoogleSheets extends VersionedNodeType {
4.1: new GoogleSheetsV2(baseDescription), 4.1: new GoogleSheetsV2(baseDescription),
4.2: new GoogleSheetsV2(baseDescription), 4.2: new GoogleSheetsV2(baseDescription),
4.3: new GoogleSheetsV2(baseDescription), 4.3: new GoogleSheetsV2(baseDescription),
4.4: new GoogleSheetsV2(baseDescription),
}; };
super(nodeVersions, baseDescription); super(nodeVersions, baseDescription);

View file

@ -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 { GoogleSheet } from '../../../v2/helpers/GoogleSheet';
import { import {
addRowNumber, addRowNumber,
autoMapInputData, autoMapInputData,
checkForSchemaChanges,
prepareSheetData, prepareSheetData,
removeEmptyColumns, removeEmptyColumns,
removeEmptyRows, 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");
});
});

View file

@ -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 { SheetProperties, ValueInputOption } from '../../helpers/GoogleSheets.types';
import type { GoogleSheet } from '../../helpers/GoogleSheet'; import type { GoogleSheet } from '../../helpers/GoogleSheet';
import { import {
autoMapInputData, autoMapInputData,
cellFormatDefault, cellFormatDefault,
checkForSchemaChanges,
mapFields, mapFields,
untilSheetSelected, untilSheetSelected,
} from '../../helpers/GoogleSheets.utils'; } from '../../helpers/GoogleSheets.utils';
@ -226,6 +233,21 @@ export async function execute(
headerRow = locationDefine.headerRow as number; 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[] = []; let setData: IDataObject[] = [];
if (dataMode === 'autoMapInputData') { if (dataMode === 'autoMapInputData') {

View file

@ -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 { NodeOperationError } from 'n8n-workflow';
import type { import type {
ISheetUpdateData, ISheetUpdateData,
@ -7,7 +12,11 @@ import type {
ValueRenderOption, ValueRenderOption,
} from '../../helpers/GoogleSheets.types'; } from '../../helpers/GoogleSheets.types';
import type { GoogleSheet } from '../../helpers/GoogleSheet'; 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'; import { cellFormat, handlingExtraData, locationDefine } from './commonDescription';
export const description: SheetProperties = [ export const description: SheetProperties = [
@ -267,6 +276,11 @@ export async function execute(
columnNames = sheetData[headerRow] ?? []; 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<string>(); const newColumns = new Set<string>();
const columnsToMatchOn: string[] = const columnsToMatchOn: string[] =

View file

@ -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 { NodeOperationError } from 'n8n-workflow';
import type { import type {
ISheetUpdateData, ISheetUpdateData,
@ -7,7 +12,11 @@ import type {
ValueRenderOption, ValueRenderOption,
} from '../../helpers/GoogleSheets.types'; } from '../../helpers/GoogleSheets.types';
import type { GoogleSheet } from '../../helpers/GoogleSheet'; 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'; import { cellFormat, handlingExtraData, locationDefine } from './commonDescription';
export const description: SheetProperties = [ export const description: SheetProperties = [
@ -252,6 +261,12 @@ export async function execute(
} }
columnNames = sheetData[headerRow]; 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<string>(); const newColumns = new Set<string>();
const columnsToMatchOn: string[] = const columnsToMatchOn: string[] =

View file

@ -9,7 +9,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'googleSheets', name: 'googleSheets',
icon: 'file:googleSheets.svg', icon: 'file:googleSheets.svg',
group: ['input', 'output'], 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"]}}', subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Read, update and write data to Google Sheets', description: 'Read, update and write data to Google Sheets',
defaults: { defaults: {

View file

@ -5,6 +5,7 @@ import type {
INodeListSearchItems, INodeListSearchItems,
INodePropertyOptions, INodePropertyOptions,
INode, INode,
ResourceMapperField,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import type { GoogleSheet } from './GoogleSheet'; import type { GoogleSheet } from './GoogleSheet';
@ -334,3 +335,25 @@ export function cellFormatDefault(nodeVersion: number) {
} }
return 'USER_ENTERED'; 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(', ')}`,
});
}
}