mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-26 05:04:05 -08:00
fix(Google Sheets Node): Tweaks (#7357)
Github issue / Community forum post (link here to close automatically):
This commit is contained in:
parent
a2d2e3dda7
commit
d8531a53b9
|
@ -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,
|
defaultVersion: 4.1,
|
||||||
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',
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@ export class GoogleSheets extends VersionedNodeType {
|
||||||
2: new GoogleSheetsV1(baseDescription),
|
2: new GoogleSheetsV1(baseDescription),
|
||||||
3: new GoogleSheetsV2(baseDescription),
|
3: new GoogleSheetsV2(baseDescription),
|
||||||
4: new GoogleSheetsV2(baseDescription),
|
4: new GoogleSheetsV2(baseDescription),
|
||||||
|
4.1: new GoogleSheetsV2(baseDescription),
|
||||||
};
|
};
|
||||||
|
|
||||||
super(nodeVersions, baseDescription);
|
super(nodeVersions, baseDescription);
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
import type { IExecuteFunctions, IDataObject, INodeExecutionData } 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 { autoMapInputData, mapFields, untilSheetSelected } from '../../helpers/GoogleSheets.utils';
|
import {
|
||||||
|
autoMapInputData,
|
||||||
|
cellFormatDefault,
|
||||||
|
mapFields,
|
||||||
|
untilSheetSelected,
|
||||||
|
} from '../../helpers/GoogleSheets.utils';
|
||||||
import { cellFormat, handlingExtraData } from './commonDescription';
|
import { cellFormat, handlingExtraData } from './commonDescription';
|
||||||
|
|
||||||
export const description: SheetProperties = [
|
export const description: SheetProperties = [
|
||||||
|
@ -131,7 +136,7 @@ export const description: SheetProperties = [
|
||||||
show: {
|
show: {
|
||||||
resource: ['sheet'],
|
resource: ['sheet'],
|
||||||
operation: ['append'],
|
operation: ['append'],
|
||||||
'@version': [4],
|
'@version': [4, 4.1],
|
||||||
},
|
},
|
||||||
hide: {
|
hide: {
|
||||||
...untilSheetSelected,
|
...untilSheetSelected,
|
||||||
|
@ -154,7 +159,7 @@ export const description: SheetProperties = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
...cellFormat,
|
cellFormat,
|
||||||
{
|
{
|
||||||
displayName: 'Data Location on Sheet',
|
displayName: 'Data Location on Sheet',
|
||||||
name: 'locationDefine',
|
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,
|
setData,
|
||||||
sheetName,
|
sheetName,
|
||||||
headerRow,
|
headerRow,
|
||||||
(options.cellFormat as ValueInputOption) || 'RAW',
|
(options.cellFormat as ValueInputOption) || cellFormatDefault(nodeVersion),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
} from '../../helpers/GoogleSheets.types';
|
} from '../../helpers/GoogleSheets.types';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
import type { GoogleSheet } from '../../helpers/GoogleSheet';
|
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';
|
import { cellFormat, handlingExtraData, locationDefine } from './commonDescription';
|
||||||
|
|
||||||
export const description: SheetProperties = [
|
export const description: SheetProperties = [
|
||||||
|
@ -172,7 +172,7 @@ export const description: SheetProperties = [
|
||||||
show: {
|
show: {
|
||||||
resource: ['sheet'],
|
resource: ['sheet'],
|
||||||
operation: ['appendOrUpdate'],
|
operation: ['appendOrUpdate'],
|
||||||
'@version': [4],
|
'@version': [4, 4.1],
|
||||||
},
|
},
|
||||||
hide: {
|
hide: {
|
||||||
...untilSheetSelected,
|
...untilSheetSelected,
|
||||||
|
@ -194,7 +194,15 @@ export const description: SheetProperties = [
|
||||||
...untilSheetSelected,
|
...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,
|
sheetId: string,
|
||||||
): Promise<INodeExecutionData[]> {
|
): Promise<INodeExecutionData[]> {
|
||||||
const items = this.getInputData();
|
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 range = `${sheetName}!A:Z`;
|
||||||
|
|
||||||
|
const valueInputMode = this.getNodeParameter(
|
||||||
|
'options.cellFormat',
|
||||||
|
0,
|
||||||
|
cellFormatDefault(nodeVersion),
|
||||||
|
) as ValueInputOption;
|
||||||
|
|
||||||
const options = this.getNodeParameter('options', 0, {});
|
const options = this.getNodeParameter('options', 0, {});
|
||||||
|
|
||||||
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
|
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
|
||||||
|
@ -238,7 +253,7 @@ export async function execute(
|
||||||
}
|
}
|
||||||
|
|
||||||
columnNames = sheetData[headerRow];
|
columnNames = sheetData[headerRow];
|
||||||
const nodeVersion = this.getNode().typeVersion;
|
|
||||||
const newColumns = new Set<string>();
|
const newColumns = new Set<string>();
|
||||||
|
|
||||||
const columnsToMatchOn: string[] =
|
const columnsToMatchOn: string[] =
|
||||||
|
@ -346,7 +361,7 @@ export async function execute(
|
||||||
await sheet.updateRows(
|
await sheet.updateRows(
|
||||||
sheetName,
|
sheetName,
|
||||||
[columnNames.concat([...newColumns])],
|
[columnNames.concat([...newColumns])],
|
||||||
(options.cellFormat as ValueInputOption) || 'RAW',
|
(options.cellFormat as ValueInputOption) || cellFormatDefault(nodeVersion),
|
||||||
headerRow + 1,
|
headerRow + 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,270 +1,260 @@
|
||||||
import type { INodeProperties } from 'n8n-workflow';
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
export const dataLocationOnSheet: INodeProperties[] = [
|
export const dataLocationOnSheet: INodeProperties = {
|
||||||
{
|
displayName: 'Data Location on Sheet',
|
||||||
displayName: 'Data Location on Sheet',
|
name: 'dataLocationOnSheet',
|
||||||
name: 'dataLocationOnSheet',
|
type: 'fixedCollection',
|
||||||
type: 'fixedCollection',
|
placeholder: 'Select Range',
|
||||||
placeholder: 'Select Range',
|
default: { values: { rangeDefinition: 'detectAutomatically' } },
|
||||||
default: { values: { rangeDefinition: 'detectAutomatically' } },
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
displayName: 'Values',
|
||||||
displayName: 'Values',
|
name: 'values',
|
||||||
name: 'values',
|
values: [
|
||||||
values: [
|
{
|
||||||
{
|
displayName: 'Range Definition',
|
||||||
displayName: 'Range Definition',
|
name: 'rangeDefinition',
|
||||||
name: 'rangeDefinition',
|
type: 'options',
|
||||||
type: 'options',
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
name: 'Detect Automatically',
|
||||||
name: 'Detect Automatically',
|
value: 'detectAutomatically',
|
||||||
value: 'detectAutomatically',
|
description: 'Automatically detect the data range',
|
||||||
description: 'Automatically detect the data range',
|
},
|
||||||
},
|
{
|
||||||
{
|
name: 'Specify Range (A1 Notation)',
|
||||||
name: 'Specify Range (A1 Notation)',
|
value: 'specifyRangeA1',
|
||||||
value: 'specifyRangeA1',
|
description: 'Manually specify the data range',
|
||||||
description: 'Manually specify the data range',
|
},
|
||||||
},
|
{
|
||||||
{
|
name: 'Specify Range (Rows)',
|
||||||
name: 'Specify Range (Rows)',
|
value: 'specifyRange',
|
||||||
value: 'specifyRange',
|
description: 'Manually specify the data range',
|
||||||
description: 'Manually specify the data range',
|
},
|
||||||
},
|
],
|
||||||
],
|
default: '',
|
||||||
default: '',
|
},
|
||||||
},
|
{
|
||||||
{
|
displayName: 'Read Rows Until',
|
||||||
displayName: 'Read Rows Until',
|
name: 'readRowsUntil',
|
||||||
name: 'readRowsUntil',
|
type: 'options',
|
||||||
type: 'options',
|
default: 'lastRowInSheet',
|
||||||
default: 'lastRowInSheet',
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
name: 'First Empty Row',
|
||||||
name: 'First Empty Row',
|
value: 'firstEmptyRow',
|
||||||
value: 'firstEmptyRow',
|
},
|
||||||
},
|
{
|
||||||
{
|
name: 'Last Row In Sheet',
|
||||||
name: 'Last Row In Sheet',
|
value: 'lastRowInSheet',
|
||||||
value: 'lastRowInSheet',
|
},
|
||||||
},
|
],
|
||||||
],
|
displayOptions: {
|
||||||
displayOptions: {
|
show: {
|
||||||
show: {
|
rangeDefinition: ['detectAutomatically'],
|
||||||
rangeDefinition: ['detectAutomatically'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
displayName: 'Header Row',
|
{
|
||||||
name: 'headerRow',
|
displayName: 'Header Row',
|
||||||
type: 'number',
|
name: 'headerRow',
|
||||||
typeOptions: {
|
type: 'number',
|
||||||
minValue: 1,
|
typeOptions: {
|
||||||
},
|
minValue: 1,
|
||||||
default: 1,
|
},
|
||||||
description: "Index is relative to the set 'Range', first row index is 1",
|
default: 1,
|
||||||
hint: 'Index of the row which contains the column names',
|
description: "Index is relative to the set 'Range', first row index is 1",
|
||||||
displayOptions: {
|
hint: 'Index of the row which contains the column names',
|
||||||
show: {
|
displayOptions: {
|
||||||
rangeDefinition: ['specifyRange'],
|
show: {
|
||||||
},
|
rangeDefinition: ['specifyRange'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
displayName: 'First Data Row',
|
{
|
||||||
name: 'firstDataRow',
|
displayName: 'First Data Row',
|
||||||
type: 'number',
|
name: 'firstDataRow',
|
||||||
typeOptions: {
|
type: 'number',
|
||||||
minValue: 1,
|
typeOptions: {
|
||||||
},
|
minValue: 1,
|
||||||
default: 2,
|
},
|
||||||
description: "Index is relative to the set 'Range', first row index is 1",
|
default: 2,
|
||||||
hint: 'Index of first row which contains the actual data',
|
description: "Index is relative to the set 'Range', first row index is 1",
|
||||||
displayOptions: {
|
hint: 'Index of first row which contains the actual data',
|
||||||
show: {
|
displayOptions: {
|
||||||
rangeDefinition: ['specifyRange'],
|
show: {
|
||||||
},
|
rangeDefinition: ['specifyRange'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
displayName: 'Range',
|
{
|
||||||
name: 'range',
|
displayName: 'Range',
|
||||||
type: 'string',
|
name: 'range',
|
||||||
default: '',
|
type: 'string',
|
||||||
placeholder: 'A:Z',
|
default: '',
|
||||||
description:
|
placeholder: 'A:Z',
|
||||||
'The table range to read from or to append data to. See the Google <a href="https://developers.google.com/sheets/api/guides/values#writing">documentation</a> for the details.',
|
description:
|
||||||
hint: 'You can specify both the rows and the columns, e.g. C4:E7',
|
'The table range to read from or to append data to. See the Google <a href="https://developers.google.com/sheets/api/guides/values#writing">documentation</a> for the details.',
|
||||||
displayOptions: {
|
hint: 'You can specify both the rows and the columns, e.g. C4:E7',
|
||||||
show: {
|
displayOptions: {
|
||||||
rangeDefinition: ['specifyRangeA1'],
|
show: {
|
||||||
},
|
rangeDefinition: ['specifyRangeA1'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
];
|
};
|
||||||
|
|
||||||
export const locationDefine: INodeProperties[] = [
|
export const locationDefine: INodeProperties = {
|
||||||
{
|
displayName: 'Data Location on Sheet',
|
||||||
displayName: 'Data Location on Sheet',
|
name: 'locationDefine',
|
||||||
name: 'locationDefine',
|
type: 'fixedCollection',
|
||||||
type: 'fixedCollection',
|
placeholder: 'Select Range',
|
||||||
placeholder: 'Select Range',
|
default: { values: {} },
|
||||||
default: { values: {} },
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
displayName: 'Values',
|
||||||
displayName: 'Values',
|
name: 'values',
|
||||||
name: 'values',
|
values: [
|
||||||
values: [
|
{
|
||||||
{
|
displayName: 'Header Row',
|
||||||
displayName: 'Header Row',
|
name: 'headerRow',
|
||||||
name: 'headerRow',
|
type: 'number',
|
||||||
type: 'number',
|
typeOptions: {
|
||||||
typeOptions: {
|
minValue: 1,
|
||||||
minValue: 1,
|
},
|
||||||
},
|
default: 1,
|
||||||
default: 1,
|
description: "Index is relative to the set 'Range', first row index is 1",
|
||||||
description: "Index is relative to the set 'Range', first row index is 1",
|
hint: 'Index of the row which contains the column names',
|
||||||
hint: 'Index of the row which contains the column names',
|
},
|
||||||
},
|
{
|
||||||
{
|
displayName: 'First Data Row',
|
||||||
displayName: 'First Data Row',
|
name: 'firstDataRow',
|
||||||
name: 'firstDataRow',
|
type: 'number',
|
||||||
type: 'number',
|
typeOptions: {
|
||||||
typeOptions: {
|
minValue: 1,
|
||||||
minValue: 1,
|
},
|
||||||
},
|
default: 2,
|
||||||
default: 2,
|
description: "Index is relative to the set 'Range', first row index is 1",
|
||||||
description: "Index is relative to the set 'Range', first row index is 1",
|
hint: 'Index of first row which contains the actual data',
|
||||||
hint: 'Index of first row which contains the actual data',
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
],
|
};
|
||||||
},
|
|
||||||
];
|
export const outputFormatting: INodeProperties = {
|
||||||
|
displayName: 'Output Formatting',
|
||||||
export const outputFormatting: INodeProperties[] = [
|
name: 'outputFormatting',
|
||||||
{
|
type: 'fixedCollection',
|
||||||
displayName: 'Output Formatting',
|
placeholder: 'Add Formatting',
|
||||||
name: 'outputFormatting',
|
default: { values: { general: 'UNFORMATTED_VALUE', date: 'FORMATTED_STRING' } },
|
||||||
type: 'fixedCollection',
|
options: [
|
||||||
placeholder: 'Add Formatting',
|
{
|
||||||
default: { values: { general: 'UNFORMATTED_VALUE', date: 'FORMATTED_STRING' } },
|
displayName: 'Values',
|
||||||
options: [
|
name: 'values',
|
||||||
{
|
values: [
|
||||||
displayName: 'Values',
|
{
|
||||||
name: 'values',
|
displayName: 'General Formatting',
|
||||||
values: [
|
name: 'general',
|
||||||
{
|
type: 'options',
|
||||||
displayName: 'General Formatting',
|
options: [
|
||||||
name: 'general',
|
{
|
||||||
type: 'options',
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
options: [
|
name: 'Values (unformatted)',
|
||||||
{
|
value: 'UNFORMATTED_VALUE',
|
||||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
description:
|
||||||
name: 'Values (unformatted)',
|
'Numbers stay as numbers, but any currency signs or special formatting is lost',
|
||||||
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',
|
||||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
description:
|
||||||
name: 'Values (formatted)',
|
'Numbers are turned to text, and displayed as in Google Sheets (e.g. with commas or currency signs)',
|
||||||
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',
|
||||||
{
|
},
|
||||||
name: 'Formulas',
|
],
|
||||||
value: 'FORMULA',
|
default: '',
|
||||||
},
|
description: 'Determines how values should be rendered in the output',
|
||||||
],
|
},
|
||||||
default: '',
|
{
|
||||||
description: 'Determines how values should be rendered in the output',
|
displayName: 'Date Formatting',
|
||||||
},
|
name: 'date',
|
||||||
{
|
type: 'options',
|
||||||
displayName: 'Date Formatting',
|
default: '',
|
||||||
name: 'date',
|
options: [
|
||||||
type: 'options',
|
{
|
||||||
default: '',
|
name: 'Formatted Text',
|
||||||
options: [
|
value: 'FORMATTED_STRING',
|
||||||
{
|
description: "As displayed in Google Sheets, e.g. '01/01/2022'",
|
||||||
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',
|
||||||
name: 'Serial Number',
|
},
|
||||||
value: 'SERIAL_NUMBER',
|
],
|
||||||
description: 'A number representing the number of days since Dec 30, 1899',
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
],
|
};
|
||||||
},
|
|
||||||
],
|
export const cellFormat: INodeProperties = {
|
||||||
},
|
displayName: 'Cell Format',
|
||||||
];
|
name: 'cellFormat',
|
||||||
|
type: 'options',
|
||||||
export const cellFormat: INodeProperties[] = [
|
options: [
|
||||||
{
|
{
|
||||||
displayName: 'Cell Format',
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
name: 'cellFormat',
|
name: 'Let Google Sheets format',
|
||||||
type: 'options',
|
value: 'USER_ENTERED',
|
||||||
options: [
|
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',
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
value: 'RAW',
|
name: 'Let n8n format',
|
||||||
description: 'Cells have the same types as the input data',
|
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',
|
default: 'USER_ENTERED',
|
||||||
value: 'USER_ENTERED',
|
description: 'Determines how data should be interpreted',
|
||||||
description: 'Cells are styled as if you typed the values into Google Sheets directly',
|
};
|
||||||
},
|
|
||||||
],
|
export const handlingExtraData: INodeProperties = {
|
||||||
default: 'RAW',
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
description: 'Determines how data should be interpreted',
|
displayName: 'Handling extra fields in input',
|
||||||
},
|
name: 'handlingExtraData',
|
||||||
];
|
type: 'options',
|
||||||
|
options: [
|
||||||
export const handlingExtraData: INodeProperties[] = [
|
{
|
||||||
{
|
name: 'Insert in New Column(s)',
|
||||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
value: 'insertInNewColumn',
|
||||||
displayName: 'Handling extra fields in input',
|
description: 'Create a new column for extra data',
|
||||||
name: 'handlingExtraData',
|
},
|
||||||
type: 'options',
|
{
|
||||||
options: [
|
name: 'Ignore Them',
|
||||||
{
|
value: 'ignoreIt',
|
||||||
name: 'Insert in New Column(s)',
|
description: 'Ignore extra data',
|
||||||
value: 'insertInNewColumn',
|
},
|
||||||
description: 'Create a new column for extra data',
|
{
|
||||||
},
|
name: 'Error',
|
||||||
{
|
value: 'error',
|
||||||
name: 'Ignore Them',
|
description: 'Throw an error',
|
||||||
value: 'ignoreIt',
|
},
|
||||||
description: 'Ignore extra data',
|
],
|
||||||
},
|
displayOptions: {
|
||||||
{
|
show: {
|
||||||
name: 'Error',
|
'/dataMode': ['autoMapInputData'],
|
||||||
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",
|
||||||
|
};
|
||||||
|
|
|
@ -80,8 +80,8 @@ export const description: SheetProperties = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
...dataLocationOnSheet,
|
dataLocationOnSheet,
|
||||||
...outputFormatting,
|
outputFormatting,
|
||||||
{
|
{
|
||||||
displayName: 'When Filter Has Multiple Matches',
|
displayName: 'When Filter Has Multiple Matches',
|
||||||
name: 'returnAllMatches',
|
name: 'returnAllMatches',
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
} from '../../helpers/GoogleSheets.types';
|
} from '../../helpers/GoogleSheets.types';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
import type { GoogleSheet } from '../../helpers/GoogleSheet';
|
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';
|
import { cellFormat, handlingExtraData, locationDefine } from './commonDescription';
|
||||||
|
|
||||||
export const description: SheetProperties = [
|
export const description: SheetProperties = [
|
||||||
|
@ -172,7 +172,7 @@ export const description: SheetProperties = [
|
||||||
show: {
|
show: {
|
||||||
resource: ['sheet'],
|
resource: ['sheet'],
|
||||||
operation: ['update'],
|
operation: ['update'],
|
||||||
'@version': [4],
|
'@version': [4, 4.1],
|
||||||
},
|
},
|
||||||
hide: {
|
hide: {
|
||||||
...untilSheetSelected,
|
...untilSheetSelected,
|
||||||
|
@ -194,7 +194,15 @@ export const description: SheetProperties = [
|
||||||
...untilSheetSelected,
|
...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,
|
sheetName: string,
|
||||||
): Promise<INodeExecutionData[]> {
|
): Promise<INodeExecutionData[]> {
|
||||||
const items = this.getInputData();
|
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 range = `${sheetName}!A:Z`;
|
||||||
|
|
||||||
|
const valueInputMode = this.getNodeParameter(
|
||||||
|
'options.cellFormat',
|
||||||
|
0,
|
||||||
|
cellFormatDefault(nodeVersion),
|
||||||
|
) as ValueInputOption;
|
||||||
|
|
||||||
const options = this.getNodeParameter('options', 0, {});
|
const options = this.getNodeParameter('options', 0, {});
|
||||||
|
|
||||||
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
|
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
|
||||||
|
|
||||||
const locationDefineOptions = (options.locationDefine as IDataObject)?.values as IDataObject;
|
const locationDefineOptions = (options.locationDefine as IDataObject)?.values as IDataObject;
|
||||||
|
|
||||||
const nodeVersion = this.getNode().typeVersion;
|
|
||||||
|
|
||||||
let headerRow = 0;
|
let headerRow = 0;
|
||||||
let firstDataRow = 1;
|
let firstDataRow = 1;
|
||||||
|
|
||||||
|
@ -254,6 +267,7 @@ export async function execute(
|
||||||
// TODO: Add support for multiple columns to match on in the next overhaul
|
// TODO: Add support for multiple columns to match on in the next overhaul
|
||||||
const keyIndex = columnNames.indexOf(columnsToMatchOn[0]);
|
const keyIndex = columnNames.indexOf(columnsToMatchOn[0]);
|
||||||
|
|
||||||
|
//not used when updating row
|
||||||
const columnValues = await sheet.getColumnValues(
|
const columnValues = await sheet.getColumnValues(
|
||||||
range,
|
range,
|
||||||
keyIndex,
|
keyIndex,
|
||||||
|
@ -276,7 +290,7 @@ export async function execute(
|
||||||
if (handlingExtraDataOption === 'ignoreIt') {
|
if (handlingExtraDataOption === 'ignoreIt') {
|
||||||
data.push(items[i].json);
|
data.push(items[i].json);
|
||||||
}
|
}
|
||||||
if (handlingExtraDataOption === 'error') {
|
if (handlingExtraDataOption === 'error' && columnsToMatchOn[0] !== 'row_number') {
|
||||||
Object.keys(items[i].json).forEach((key) => {
|
Object.keys(items[i].json).forEach((key) => {
|
||||||
if (!columnNames.includes(key)) {
|
if (!columnNames.includes(key)) {
|
||||||
throw new NodeOperationError(this.getNode(), 'Unexpected fields in node input', {
|
throw new NodeOperationError(this.getNode(), 'Unexpected fields in node input', {
|
||||||
|
@ -287,7 +301,7 @@ export async function execute(
|
||||||
});
|
});
|
||||||
data.push(items[i].json);
|
data.push(items[i].json);
|
||||||
}
|
}
|
||||||
if (handlingExtraDataOption === 'insertInNewColumn') {
|
if (handlingExtraDataOption === 'insertInNewColumn' && columnsToMatchOn[0] !== 'row_number') {
|
||||||
Object.keys(items[i].json).forEach((key) => {
|
Object.keys(items[i].json).forEach((key) => {
|
||||||
if (!columnNames.includes(key)) {
|
if (!columnNames.includes(key)) {
|
||||||
newColumns.add(key);
|
newColumns.add(key);
|
||||||
|
@ -350,22 +364,29 @@ export async function execute(
|
||||||
await sheet.updateRows(
|
await sheet.updateRows(
|
||||||
sheetName,
|
sheetName,
|
||||||
[columnNames.concat([...newColumns])],
|
[columnNames.concat([...newColumns])],
|
||||||
(options.cellFormat as ValueInputOption) || 'RAW',
|
(options.cellFormat as ValueInputOption) || cellFormatDefault(nodeVersion),
|
||||||
headerRow + 1,
|
headerRow + 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const preparedData = await sheet.prepareDataForUpdateOrUpsert(
|
let preparedData;
|
||||||
data,
|
if (columnsToMatchOn[0] === 'row_number') {
|
||||||
columnsToMatchOn[0],
|
preparedData = sheet.prepareDataForUpdatingByRowNumber(data, range, [
|
||||||
range,
|
columnNames.concat([...newColumns]),
|
||||||
headerRow,
|
]);
|
||||||
firstDataRow,
|
} else {
|
||||||
valueRenderMode,
|
preparedData = await sheet.prepareDataForUpdateOrUpsert(
|
||||||
false,
|
data,
|
||||||
[columnNames.concat([...newColumns])],
|
columnsToMatchOn[0],
|
||||||
columnValues,
|
range,
|
||||||
);
|
headerRow,
|
||||||
|
firstDataRow,
|
||||||
|
valueRenderMode,
|
||||||
|
false,
|
||||||
|
[columnNames.concat([...newColumns])],
|
||||||
|
columnValues,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
updateData.push(...preparedData.updateData);
|
updateData.push(...preparedData.updateData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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],
|
version: [3, 4, 4.1],
|
||||||
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: {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import type {
|
||||||
IPollFunctions,
|
IPollFunctions,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
import { apiRequest } from '../transport';
|
|
||||||
import { utils as xlsxUtils } from 'xlsx';
|
import { utils as xlsxUtils } from 'xlsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
import { apiRequest } from '../transport';
|
||||||
import type {
|
import type {
|
||||||
ILookupValues,
|
ILookupValues,
|
||||||
ISheetUpdateData,
|
ISheetUpdateData,
|
||||||
|
@ -553,6 +553,53 @@ export class GoogleSheet {
|
||||||
return { updateData, appendData };
|
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<string[][]>}
|
||||||
|
* @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
|
* Looks for a specific value in a column and if it gets found it returns the whole row
|
||||||
*
|
*
|
||||||
|
|
|
@ -315,3 +315,10 @@ export function sortLoadOptions(data: INodePropertyOptions[] | INodeListSearchIt
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cellFormatDefault(nodeVersion: number) {
|
||||||
|
if (nodeVersion < 4.1) {
|
||||||
|
return 'RAW';
|
||||||
|
}
|
||||||
|
return 'USER_ENTERED';
|
||||||
|
}
|
||||||
|
|
|
@ -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 { 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';
|
import { getSpreadsheetId } from '../helpers/GoogleSheets.utils';
|
||||||
|
|
||||||
export async function getMappingColumns(
|
export async function getMappingColumns(
|
||||||
|
@ -22,17 +27,32 @@ export async function getMappingColumns(
|
||||||
const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE');
|
const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE');
|
||||||
|
|
||||||
const columns = sheet.testFilter(sheetData || [], 0, 0).filter((col) => col !== '');
|
const columns = sheet.testFilter(sheetData || [], 0, 0).filter((col) => col !== '');
|
||||||
const columnData: ResourceMapperFields = {
|
|
||||||
fields: columns.map((col) => ({
|
const fields: ResourceMapperField[] = columns.map((col) => ({
|
||||||
id: col,
|
id: col,
|
||||||
displayName: 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,
|
required: false,
|
||||||
defaultMatch: col === 'id',
|
defaultMatch: false,
|
||||||
display: true,
|
display: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
canBeUsedToMatch: true,
|
canBeUsedToMatch: true,
|
||||||
})),
|
readOnly: true,
|
||||||
};
|
removed: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return columnData;
|
return { fields };
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue