mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 12:44:07 -08:00
fix(Google Sheets Node): Fix for append operation if no empty rows in sheet
This commit is contained in:
parent
6d4e959884
commit
741c7da8b1
|
@ -24,15 +24,15 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
|||
|
||||
const googleSheet = new GoogleSheet(spreadsheetId, this);
|
||||
|
||||
let sheetWithinDocument = '';
|
||||
let sheetId = '';
|
||||
if (operation !== 'create') {
|
||||
sheetWithinDocument = this.getNodeParameter('sheetName', 0, undefined, {
|
||||
sheetId = this.getNodeParameter('sheetName', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
}
|
||||
|
||||
if (sheetWithinDocument === 'gid=0') {
|
||||
sheetWithinDocument = '0';
|
||||
if (sheetId === 'gid=0') {
|
||||
sheetId = '0';
|
||||
}
|
||||
|
||||
let sheetName = '';
|
||||
|
@ -41,17 +41,22 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
|||
sheetName = spreadsheetId;
|
||||
break;
|
||||
case 'delete':
|
||||
sheetName = sheetWithinDocument;
|
||||
sheetName = sheetId;
|
||||
break;
|
||||
case 'remove':
|
||||
sheetName = `${spreadsheetId}||${sheetWithinDocument}`;
|
||||
sheetName = `${spreadsheetId}||${sheetId}`;
|
||||
break;
|
||||
default:
|
||||
sheetName = await googleSheet.spreadsheetGetSheetNameById(sheetWithinDocument);
|
||||
sheetName = await googleSheet.spreadsheetGetSheetNameById(sheetId);
|
||||
}
|
||||
|
||||
operationResult.push(
|
||||
...(await sheet[googleSheets.operation].execute.call(this, googleSheet, sheetName)),
|
||||
...(await sheet[googleSheets.operation].execute.call(
|
||||
this,
|
||||
googleSheet,
|
||||
sheetName,
|
||||
sheetId,
|
||||
)),
|
||||
);
|
||||
} else if (googleSheets.resource === 'spreadsheet') {
|
||||
operationResult.push(...(await spreadsheet[googleSheets.operation].execute.call(this)));
|
||||
|
@ -60,6 +65,15 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
|||
if (this.continueOnFail()) {
|
||||
operationResult.push({ json: this.getInputData(0)[0].json, error: err });
|
||||
} else {
|
||||
if (
|
||||
err.message &&
|
||||
(err.message.toLowerCase().includes('bad request') ||
|
||||
err.message.toLowerCase().includes('uknown error')) &&
|
||||
err.description
|
||||
) {
|
||||
err.message = err.description;
|
||||
err.description = undefined;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ export async function execute(
|
|||
this: IExecuteFunctions,
|
||||
sheet: GoogleSheet,
|
||||
sheetName: string,
|
||||
sheetId: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const items = this.getInputData();
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
@ -176,6 +177,12 @@ export async function execute(
|
|||
setData = mapFields.call(this, items.length);
|
||||
}
|
||||
|
||||
if (setData.length === 0) {
|
||||
return [];
|
||||
} else {
|
||||
await sheet.appendEmptyRowsOrColumns(sheetId, 1, 0);
|
||||
}
|
||||
|
||||
await sheet.appendSheetData(
|
||||
setData,
|
||||
sheetName,
|
||||
|
|
|
@ -160,6 +160,7 @@ export async function execute(
|
|||
this: IExecuteFunctions,
|
||||
sheet: GoogleSheet,
|
||||
sheetName: string,
|
||||
sheetId: string,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const items = this.getInputData();
|
||||
const valueInputMode = this.getNodeParameter('options.cellFormat', 0, 'RAW') as ValueInputOption;
|
||||
|
@ -248,23 +249,22 @@ export async function execute(
|
|||
} else {
|
||||
const valueToMatchOn = this.getNodeParameter('valueToMatchOn', i) as string;
|
||||
|
||||
const fields = (this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]).reduce(
|
||||
(acc, entry) => {
|
||||
if (entry.column === 'newColumn') {
|
||||
const columnName = entry.columnName as string;
|
||||
const fields = (
|
||||
(this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]) || []
|
||||
).reduce((acc, entry) => {
|
||||
if (entry.column === 'newColumn') {
|
||||
const columnName = entry.columnName as string;
|
||||
|
||||
if (columnNames.includes(columnName) === false) {
|
||||
newColumns.add(columnName);
|
||||
}
|
||||
|
||||
acc[columnName] = entry.fieldValue as string;
|
||||
} else {
|
||||
acc[entry.column as string] = entry.fieldValue as string;
|
||||
if (columnNames.includes(columnName) === false) {
|
||||
newColumns.add(columnName);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as IDataObject,
|
||||
);
|
||||
|
||||
acc[columnName] = entry.fieldValue as string;
|
||||
} else {
|
||||
acc[entry.column as string] = entry.fieldValue as string;
|
||||
}
|
||||
return acc;
|
||||
}, {} as IDataObject);
|
||||
|
||||
fields[columnToMatchOn] = valueToMatchOn;
|
||||
|
||||
|
@ -300,6 +300,7 @@ export async function execute(
|
|||
await sheet.batchUpdate(updateData, valueInputMode);
|
||||
}
|
||||
if (appendData.length) {
|
||||
await sheet.appendEmptyRowsOrColumns(sheetId, 1, 0);
|
||||
const lastRow = sheetData.length + 1;
|
||||
await sheet.appendSheetData(
|
||||
appendData,
|
||||
|
|
|
@ -247,23 +247,22 @@ export async function execute(
|
|||
} else {
|
||||
const valueToMatchOn = this.getNodeParameter('valueToMatchOn', i) as string;
|
||||
|
||||
const fields = (this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]).reduce(
|
||||
(acc, entry) => {
|
||||
if (entry.column === 'newColumn') {
|
||||
const columnName = entry.columnName as string;
|
||||
const fields = (
|
||||
(this.getNodeParameter('fieldsUi.values', i, {}) as IDataObject[]) || []
|
||||
).reduce((acc, entry) => {
|
||||
if (entry.column === 'newColumn') {
|
||||
const columnName = entry.columnName as string;
|
||||
|
||||
if (columnNames.includes(columnName) === false) {
|
||||
newColumns.add(columnName);
|
||||
}
|
||||
|
||||
acc[columnName] = entry.fieldValue as string;
|
||||
} else {
|
||||
acc[entry.column as string] = entry.fieldValue as string;
|
||||
if (columnNames.includes(columnName) === false) {
|
||||
newColumns.add(columnName);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as IDataObject,
|
||||
);
|
||||
|
||||
acc[columnName] = entry.fieldValue as string;
|
||||
} else {
|
||||
acc[entry.column as string] = entry.fieldValue as string;
|
||||
}
|
||||
return acc;
|
||||
}, {} as IDataObject);
|
||||
|
||||
fields[columnToMatchOn] = valueToMatchOn;
|
||||
|
||||
|
|
|
@ -187,6 +187,43 @@ export class GoogleSheet {
|
|||
return response;
|
||||
}
|
||||
|
||||
async appendEmptyRowsOrColumns(sheetId: string, rowsToAdd = 1, columnsToAdd = 1) {
|
||||
const requests: IDataObject[] = [];
|
||||
|
||||
if (rowsToAdd > 0) {
|
||||
requests.push({
|
||||
appendDimension: {
|
||||
sheetId,
|
||||
dimension: 'ROWS',
|
||||
length: rowsToAdd,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (columnsToAdd > 0) {
|
||||
requests.push({
|
||||
appendDimension: {
|
||||
sheetId,
|
||||
dimension: 'COLUMNS',
|
||||
length: columnsToAdd,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (requests.length === 0) {
|
||||
throw new Error('Must specify at least one column or row to add');
|
||||
}
|
||||
|
||||
const response = await apiRequest.call(
|
||||
this.executeFunctions,
|
||||
'POST',
|
||||
`/v4/spreadsheets/${this.id}:batchUpdate`,
|
||||
{ requests },
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the cell values
|
||||
*/
|
||||
|
|
|
@ -6,91 +6,83 @@ import {
|
|||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
import { ResourceLocator } from '../helpers/GoogleSheets.types';
|
||||
import { getSpreadsheetId, sortLoadOptions } from '../helpers/GoogleSheets.utils';
|
||||
import { apiRequest, apiRequestAllItems } from '../transport';
|
||||
import { getSpreadsheetId } from '../helpers/GoogleSheets.utils';
|
||||
import { apiRequest } from '../transport';
|
||||
|
||||
export async function spreadSheetsSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
try {
|
||||
const returnData: INodeListSearchItems[] = [];
|
||||
const query: string[] = [];
|
||||
if (filter) {
|
||||
query.push(`name contains '${filter.replace("'", "\\'")}'`);
|
||||
}
|
||||
query.push("mimeType = 'application/vnd.google-apps.spreadsheet'");
|
||||
const qs = {
|
||||
pageSize: 50,
|
||||
orderBy: 'modifiedTime desc',
|
||||
fields: 'nextPageToken, files(id, name, webViewLink)',
|
||||
q: query.join(' and '),
|
||||
includeItemsFromAllDrives: true,
|
||||
supportsAllDrives: true,
|
||||
};
|
||||
|
||||
const sheets = await apiRequestAllItems.call(
|
||||
this,
|
||||
'files',
|
||||
'GET',
|
||||
'',
|
||||
{},
|
||||
qs,
|
||||
'https://www.googleapis.com/drive/v3/files',
|
||||
);
|
||||
for (const sheet of sheets) {
|
||||
returnData.push({
|
||||
name: sheet.name as string,
|
||||
value: sheet.id as string,
|
||||
url: sheet.webViewLink as string,
|
||||
});
|
||||
}
|
||||
return { results: sortLoadOptions(returnData) };
|
||||
} catch (error) {
|
||||
return { results: [] };
|
||||
const query: string[] = [];
|
||||
if (filter) {
|
||||
query.push(`name contains '${filter.replace("'", "\\'")}'`);
|
||||
}
|
||||
query.push("mimeType = 'application/vnd.google-apps.spreadsheet'");
|
||||
|
||||
const qs = {
|
||||
q: query.join(' and '),
|
||||
pageToken: (paginationToken as string) || undefined,
|
||||
fields: 'nextPageToken, files(id, name, webViewLink)',
|
||||
orderBy: 'name_natural',
|
||||
includeItemsFromAllDrives: true,
|
||||
supportsAllDrives: true,
|
||||
};
|
||||
|
||||
const res = await apiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
{},
|
||||
qs,
|
||||
'https://www.googleapis.com/drive/v3/files',
|
||||
);
|
||||
return {
|
||||
results: res.files.map((sheet: IDataObject) => ({
|
||||
name: sheet.name as string,
|
||||
value: sheet.id as string,
|
||||
url: sheet.webViewLink as string,
|
||||
})),
|
||||
paginationToken: res.nextPageToken,
|
||||
};
|
||||
}
|
||||
|
||||
export async function sheetsSearch(
|
||||
this: ILoadOptionsFunctions,
|
||||
_filter?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
try {
|
||||
const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject;
|
||||
const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string);
|
||||
const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject;
|
||||
const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string);
|
||||
|
||||
const query = {
|
||||
fields: 'sheets.properties',
|
||||
};
|
||||
const query = {
|
||||
fields: 'sheets.properties',
|
||||
};
|
||||
|
||||
const responseData = await apiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/v4/spreadsheets/${spreadsheetId}`,
|
||||
{},
|
||||
query,
|
||||
);
|
||||
const responseData = await apiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/v4/spreadsheets/${spreadsheetId}`,
|
||||
{},
|
||||
query,
|
||||
);
|
||||
|
||||
if (responseData === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||
}
|
||||
|
||||
const returnData: INodeListSearchItems[] = [];
|
||||
for (const sheet of responseData.sheets!) {
|
||||
if (sheet.properties!.sheetType !== 'GRID') {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
name: sheet.properties!.title as string,
|
||||
value: (sheet.properties!.sheetId as number) || 'gid=0',
|
||||
//prettier-ignore
|
||||
url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit#gid=${sheet.properties!.sheetId}`,
|
||||
});
|
||||
}
|
||||
|
||||
return { results: returnData };
|
||||
} catch (error) {
|
||||
return { results: [] };
|
||||
if (responseData === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||
}
|
||||
|
||||
const returnData: INodeListSearchItems[] = [];
|
||||
for (const sheet of responseData.sheets!) {
|
||||
if (sheet.properties!.sheetType !== 'GRID') {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
name: sheet.properties!.title as string,
|
||||
value: (sheet.properties!.sheetId as number) || 'gid=0',
|
||||
//prettier-ignore
|
||||
url: `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit#gid=${sheet.properties!.sheetId}`,
|
||||
});
|
||||
}
|
||||
|
||||
return { results: returnData };
|
||||
}
|
||||
|
|
|
@ -9,73 +9,65 @@ import { getSpreadsheetId } from '../helpers/GoogleSheets.utils';
|
|||
import { ResourceLocator } from '../helpers/GoogleSheets.types';
|
||||
|
||||
export async function getSheets(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
try {
|
||||
const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject;
|
||||
const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string);
|
||||
const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject;
|
||||
const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string);
|
||||
|
||||
const sheet = new GoogleSheet(spreadsheetId, this);
|
||||
const responseData = await sheet.spreadsheetGetSheets();
|
||||
const sheet = new GoogleSheet(spreadsheetId, this);
|
||||
const responseData = await sheet.spreadsheetGetSheets();
|
||||
|
||||
if (responseData === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
for (const sheet of responseData.sheets!) {
|
||||
if (sheet.properties!.sheetType !== 'GRID') {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
name: sheet.properties!.title as string,
|
||||
value: sheet.properties!.sheetId as unknown as string,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
} catch (error) {
|
||||
return [];
|
||||
if (responseData === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
for (const sheet of responseData.sheets!) {
|
||||
if (sheet.properties!.sheetType !== 'GRID') {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
name: sheet.properties!.title as string,
|
||||
value: sheet.properties!.sheetId as unknown as string,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getSheetHeaderRow(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
try {
|
||||
const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject;
|
||||
const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string);
|
||||
const { mode, value } = this.getNodeParameter('documentId', 0) as IDataObject;
|
||||
const spreadsheetId = getSpreadsheetId(mode as ResourceLocator, value as string);
|
||||
|
||||
const sheet = new GoogleSheet(spreadsheetId, this);
|
||||
let sheetWithinDocument = this.getNodeParameter('sheetName', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
const sheet = new GoogleSheet(spreadsheetId, this);
|
||||
let sheetWithinDocument = this.getNodeParameter('sheetName', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
if (sheetWithinDocument === 'gid=0') {
|
||||
sheetWithinDocument = '0';
|
||||
}
|
||||
|
||||
const sheetName = await sheet.spreadsheetGetSheetNameById(sheetWithinDocument);
|
||||
const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE');
|
||||
|
||||
if (sheetData === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||
}
|
||||
|
||||
const columns = sheet.testFilter(sheetData, 0, 0);
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
for (const column of columns) {
|
||||
returnData.push({
|
||||
name: column as unknown as string,
|
||||
value: column as unknown as string,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
} catch (error) {
|
||||
return [];
|
||||
if (sheetWithinDocument === 'gid=0') {
|
||||
sheetWithinDocument = '0';
|
||||
}
|
||||
|
||||
const sheetName = await sheet.spreadsheetGetSheetNameById(sheetWithinDocument);
|
||||
const sheetData = await sheet.getData(`${sheetName}!1:1`, 'FORMATTED_VALUE');
|
||||
|
||||
if (sheetData === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No data got returned');
|
||||
}
|
||||
|
||||
const columns = sheet.testFilter(sheetData, 0, 0);
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
for (const column of columns) {
|
||||
returnData.push({
|
||||
name: column as unknown as string,
|
||||
value: column as unknown as string,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getSheetHeaderRowAndAddColumn(
|
||||
|
|
|
@ -52,11 +52,10 @@ export async function apiRequest(
|
|||
);
|
||||
|
||||
options.headers!.Authorization = `Bearer ${access_token}`;
|
||||
//@ts-ignore
|
||||
return await this.helpers.request(options);
|
||||
|
||||
return await this.helpers.request!(options);
|
||||
} else {
|
||||
//@ts-ignore
|
||||
return await this.helpers.requestOAuth2.call(this, 'googleSheetsOAuth2Api', options);
|
||||
return await this.helpers.requestOAuth2!.call(this, 'googleSheetsOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
||||
|
@ -151,6 +150,5 @@ export function getAccessToken(
|
|||
json: true,
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
return this.helpers.request(options);
|
||||
return this.helpers.request!(options);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue