fix(Google Sheets Node): Allow column reorder and insertion without erroring (#30621)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dawid Myslak 2026-05-20 11:05:59 +02:00 committed by GitHub
parent 78e4bcbbdf
commit 85f5221312
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 54 additions and 46 deletions

View File

@ -473,18 +473,18 @@ 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',
},
};
const node: INode = {
id: '1',
name: 'Google Sheets',
typeVersion: 4.4,
type: 'n8n-nodes-base.googleSheets',
position: [60, 760],
parameters: {
operation: 'append',
},
};
it('should not throw when columns match exactly', () => {
expect(() =>
checkForSchemaChanges(node, ['id', 'name', 'data'], [
{ id: 'id' },
@ -493,18 +493,8 @@ describe('Test Google Sheets, checkForSchemaChanges', () => {
] 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',
},
};
it('should throw when a schema column is missing from the sheet', () => {
expect(() =>
checkForSchemaChanges(node, ['id', 'name', 'data'], [
{ id: 'id' },
@ -514,18 +504,7 @@ describe('Test Google Sheets, checkForSchemaChanges', () => {
).toThrow("Column names were updated after the node's setup");
});
it('should filter out empty columns without throwing an error', async () => {
const node: INode = {
id: '1',
name: 'Google Sheets',
typeVersion: 4.4,
type: 'n8n-nodes-base.googleSheets',
position: [60, 760],
parameters: {
operation: 'append',
},
};
it('should filter out empty columns without throwing', () => {
expect(() =>
checkForSchemaChanges(node, ['', '', 'id', 'name', 'data'], [
{ id: 'id' },
@ -534,6 +513,42 @@ describe('Test Google Sheets, checkForSchemaChanges', () => {
] as ResourceMapperField[]),
).not.toThrow();
});
it('should not throw when columns are reordered', () => {
expect(() =>
checkForSchemaChanges(node, ['data', 'id', 'name'], [
{ id: 'id' },
{ id: 'name' },
{ id: 'data' },
] as ResourceMapperField[]),
).not.toThrow();
});
it('should not throw when new columns are inserted', () => {
expect(() =>
checkForSchemaChanges(node, ['id', 'owner_email', 'name', 'data'], [
{ id: 'id' },
{ id: 'name' },
{ id: 'data' },
] as ResourceMapperField[]),
).not.toThrow();
});
it('should throw and list only the missing columns', () => {
try {
checkForSchemaChanges(node, ['id', 'name'], [
{ id: 'id' },
{ id: 'name' },
{ id: 'data' },
] as ResourceMapperField[]);
fail('Expected checkForSchemaChanges to throw');
} catch (error) {
expect(error.message).toBe("Column names were updated after the node's setup");
expect(error.description).toBe(
"Refresh the columns list in the 'Column to Match On' parameter. Missing columns: data",
);
}
});
});
describe('Test Google Sheets, getSpreadsheetId', () => {

View File

@ -360,26 +360,19 @@ export function checkForSchemaChanges(
columnNames: string[],
schema: ResourceMapperField[],
) {
const updatedColumnNames: Array<{ oldName: string; newName: string }> = [];
// RMC filters out empty columns so do the same here
columnNames = columnNames.filter((col) => col !== '');
const liveColumns = new Set(columnNames.filter((col) => col !== ''));
// if sheet does not contain ROW_NUMBER ignore it as data come from read rows operation
const schemaColumns = columnNames.includes(ROW_NUMBER)
const schemaColumns = liveColumns.has(ROW_NUMBER)
? schema.map((s) => s.id)
: schema.filter((s) => s.id !== ROW_NUMBER).map((s) => s.id);
for (const [columnIndex, columnName] of columnNames.entries()) {
const schemaEntry = schemaColumns[columnIndex];
if (schemaEntry === undefined) break;
if (columnName !== schemaEntry) {
updatedColumnNames.push({ oldName: schemaEntry, newName: columnName });
}
}
const missingColumns = schemaColumns.filter((col) => !liveColumns.has(col));
if (updatedColumnNames.length) {
if (missingColumns.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(', ')}`,
description: `Refresh the columns list in the 'Column to Match On' parameter. Missing columns: ${missingColumns.join(', ')}`,
});
}
}