feat(Google Sheets Node): Add "By Name" option to selector for Sheets (#8241)

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Elias Meire 2024-01-10 14:30:09 +01:00 committed by GitHub
parent e796e7f06d
commit dce28f9cb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 46 deletions

View file

@ -12,7 +12,7 @@ import { apiRequest } from './v2/transport';
import { sheetsSearch, spreadSheetsSearch } from './v2/methods/listSearch';
import { GoogleSheet } from './v2/helpers/GoogleSheet';
import { getSheetHeaderRowAndSkipEmpty } from './v2/methods/loadOptions';
import type { ValueRenderOption } from './v2/helpers/GoogleSheets.types';
import type { ResourceLocator, ValueRenderOption } from './v2/helpers/GoogleSheets.types';
import {
arrayOfArraysToJson,
@ -399,11 +399,21 @@ export class GoogleSheetsTrigger implements INodeType {
extractValue: true,
}) as string;
let sheetId = this.getNodeParameter('sheetName', undefined, {
const sheetWithinDocument = this.getNodeParameter('sheetName', undefined, {
extractValue: true,
}) as string;
const { mode: sheetMode } = this.getNodeParameter('sheetName', 0) as {
mode: ResourceLocator;
};
sheetId = sheetId === 'gid=0' ? '0' : sheetId;
const googleSheet = new GoogleSheet(documentId, this);
const { sheetId, title: sheetName } = await googleSheet.spreadsheetGetSheet(
this.getNode(),
sheetMode,
sheetWithinDocument,
);
const options = this.getNodeParameter('options') as IDataObject;
// If the documentId or sheetId changed, reset the workflow static data
if (
@ -417,13 +427,6 @@ export class GoogleSheetsTrigger implements INodeType {
workflowStaticData.lastIndexChecked = undefined;
}
const googleSheet = new GoogleSheet(documentId, this);
const sheetName: string = await googleSheet.spreadsheetGetSheetNameById(
this.getNode(),
sheetId,
);
const options = this.getNodeParameter('options') as IDataObject;
const previousRevision = workflowStaticData.lastRevision as number;
const previousRevisionLink = workflowStaticData.lastRevisionLink as string;

View file

@ -29,17 +29,25 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
const googleSheet = new GoogleSheet(spreadsheetId, this);
let sheetId = '';
let sheetName = '';
if (operation !== 'create') {
sheetId = this.getNodeParameter('sheetName', 0, undefined, {
const sheetWithinDocument = this.getNodeParameter('sheetName', 0, undefined, {
extractValue: true,
}) as string;
const { mode: sheetMode } = this.getNodeParameter('sheetName', 0) as {
mode: ResourceLocator;
};
const result = await googleSheet.spreadsheetGetSheet(
this.getNode(),
sheetMode,
sheetWithinDocument,
);
sheetId = result.sheetId.toString();
sheetName = result.title;
}
if (sheetId === 'gid=0') {
sheetId = '0';
}
let sheetName = '';
switch (operation) {
case 'create':
sheetName = spreadsheetId;
@ -50,8 +58,6 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
case 'remove':
sheetName = `${spreadsheetId}||${sheetId}`;
break;
default:
sheetName = await googleSheet.spreadsheetGetSheetNameById(this.getNode(), sheetId);
}
results = await sheet[googleSheets.operation].execute.call(

View file

@ -184,6 +184,12 @@ export const descriptions: INodeProperties[] = [
},
],
},
{
displayName: 'By Name',
name: 'name',
type: 'string',
placeholder: 'Sheet1',
},
],
displayOptions: {
show: {

View file

@ -1,24 +1,26 @@
import get from 'lodash/get';
import type {
IDataObject,
IExecuteFunctions,
ILoadOptionsFunctions,
IDataObject,
IPollFunctions,
INode,
IPollFunctions,
} from 'n8n-workflow';
import { ApplicationError, NodeOperationError } from 'n8n-workflow';
import { utils as xlsxUtils } from 'xlsx';
import get from 'lodash/get';
import { apiRequest } from '../transport';
import type {
ILookupValues,
ISheetUpdateData,
ResourceLocator,
SheetCellDecoded,
SheetRangeData,
SheetRangeDecoded,
SpreadSheetResponse,
ValueInputOption,
ValueRenderOption,
} from './GoogleSheets.types';
import { removeEmptyColumns } from './GoogleSheets.utils';
import { getSheetId, removeEmptyColumns } from './GoogleSheets.utils';
export class GoogleSheet {
id: string;
@ -116,32 +118,35 @@ export class GoogleSheet {
}
/**
* Returns the name of a sheet from a sheet id
* Returns the sheet within a spreadsheet based on name or ID
*/
async spreadsheetGetSheetNameById(node: INode, sheetId: string) {
async spreadsheetGetSheet(node: INode, mode: ResourceLocator, value: string) {
const query = {
fields: 'sheets.properties',
};
const response = await apiRequest.call(
const response = (await apiRequest.call(
this.executeFunctions,
'GET',
`/v4/spreadsheets/${this.id}`,
{},
query,
);
)) as SpreadSheetResponse;
const foundItem = response.sheets.find(
(item: { properties: { sheetId: number } }) => item.properties.sheetId === +sheetId,
);
const foundItem = response.sheets.find((item) => {
if (mode === 'name') return item.properties.title === value;
return item.properties.sheetId === getSheetId(value);
});
if (!foundItem?.properties?.title) {
throw new NodeOperationError(node, `Sheet with ID ${sheetId} not found`, {
level: 'warning',
});
throw new NodeOperationError(
node,
`Sheet with ${mode === 'name' ? 'name' : 'ID'} ${value} not found`,
{ level: 'warning' },
);
}
return foundItem.properties.title;
return foundItem.properties;
}
/**

View file

@ -62,12 +62,24 @@ export type GoogleSheetsSheet = Entity<GoogleSheetsMap, 'sheet'>;
export type SpreadSheetProperties = PropertiesOf<GoogleSheetsSpreadSheet>;
export type SheetProperties = PropertiesOf<GoogleSheetsSheet>;
export type ResourceLocator = 'id' | 'url' | 'list';
export type ResourceLocator = 'id' | 'url' | 'list' | 'name';
export const ResourceLocatorUiNames = {
id: 'By ID',
url: 'By URL',
list: 'From List',
name: 'By Name',
};
type SpreadSheetResponseSheet = {
properties: {
title: string;
sheetId: number;
};
};
export type SpreadSheetResponse = {
sheets: SpreadSheetResponseSheet[];
};
export type SheetCellDecoded = {

View file

@ -45,6 +45,11 @@ export function getSpreadsheetId(
return value;
}
export function getSheetId(value: string): number {
if (value === 'gid=0') return 0;
return parseInt(value);
}
// Convert number to Sheets / Excel column name
export function getColumnName(colNumber: number): string {
const baseChar = 'A'.charCodeAt(0);

View file

@ -47,15 +47,18 @@ export async function getSheetHeaderRow(
const spreadsheetId = getSpreadsheetId(this.getNode(), mode as ResourceLocator, value as string);
const sheet = new GoogleSheet(spreadsheetId, this);
let sheetWithinDocument = this.getNodeParameter('sheetName', undefined, {
const sheetWithinDocument = this.getNodeParameter('sheetName', undefined, {
extractValue: true,
}) as string;
const { mode: sheetMode } = this.getNodeParameter('sheetName', 0) as {
mode: ResourceLocator;
};
if (sheetWithinDocument === 'gid=0') {
sheetWithinDocument = '0';
}
const sheetName = await sheet.spreadsheetGetSheetNameById(this.getNode(), sheetWithinDocument);
const { title: sheetName } = await sheet.spreadsheetGetSheet(
this.getNode(),
sheetMode,
sheetWithinDocument,
);
const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE');
if (sheetData === undefined) {

View file

@ -20,15 +20,16 @@ export async function getMappingColumns(
const spreadsheetId = getSpreadsheetId(this.getNode(), mode as ResourceLocator, value as string);
const sheet = new GoogleSheet(spreadsheetId, this);
let sheetWithinDocument = this.getNodeParameter('sheetName', undefined, {
const sheetWithinDocument = this.getNodeParameter('sheetName', undefined, {
extractValue: true,
}) as string;
const { mode: sheetMode } = this.getNodeParameter('sheetName', 0) as { mode: ResourceLocator };
if (sheetWithinDocument === 'gid=0') {
sheetWithinDocument = '0';
}
const sheetName = await sheet.spreadsheetGetSheetNameById(this.getNode(), sheetWithinDocument);
const { title: sheetName } = await sheet.spreadsheetGetSheet(
this.getNode(),
sheetMode,
sheetWithinDocument,
);
const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE');
const columns = sheet.testFilter(sheetData || [], 0, 0).filter((col) => col !== '');