mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 06:34:05 -08:00
fix(Google BigQuery Node): Better error messages, transform timestamps (#9255)
Co-authored-by: Jonathan Bennetts <jonathan.bennetts@gmail.com>
This commit is contained in:
parent
e896889394
commit
7ff24f134b
|
@ -13,12 +13,13 @@ export class GoogleBigQuery extends VersionedNodeType {
|
||||||
group: ['input'],
|
group: ['input'],
|
||||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
description: 'Consume Google BigQuery API',
|
description: 'Consume Google BigQuery API',
|
||||||
defaultVersion: 2,
|
defaultVersion: 2.1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||||
1: new GoogleBigQueryV1(baseDescription),
|
1: new GoogleBigQueryV1(baseDescription),
|
||||||
2: new GoogleBigQueryV2(baseDescription),
|
2: new GoogleBigQueryV2(baseDescription),
|
||||||
|
2.1: new GoogleBigQueryV2(baseDescription),
|
||||||
};
|
};
|
||||||
|
|
||||||
super(nodeVersions, baseDescription);
|
super(nodeVersions, baseDescription);
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
import type { IDataObject, IExecuteFunctions, INode } from 'n8n-workflow';
|
||||||
import { constructExecutionMetaData } from 'n8n-core';
|
import { constructExecutionMetaData } from 'n8n-core';
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import { prepareOutput } from '../../../v2/helpers/utils';
|
import { prepareOutput } from '../../../v2/helpers/utils';
|
||||||
|
|
||||||
describe('Google BigQuery v2 Utils', () => {
|
describe('Google BigQuery v2 Utils', () => {
|
||||||
it('should prepareOutput', () => {
|
it('should prepareOutput', () => {
|
||||||
const thisArg = mock<IExecuteFunctions>({ helpers: mock({ constructExecutionMetaData }) });
|
const thisArg = mock<IExecuteFunctions>({
|
||||||
|
getNode: () => ({ typeVersion: 2.1 }) as INode,
|
||||||
|
helpers: mock({ constructExecutionMetaData }),
|
||||||
|
});
|
||||||
const response: IDataObject = {
|
const response: IDataObject = {
|
||||||
kind: 'bigquery#getQueryResultsResponse',
|
kind: 'bigquery#getQueryResultsResponse',
|
||||||
etag: 'e_tag',
|
etag: 'e_tag',
|
||||||
|
|
|
@ -249,10 +249,16 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
||||||
returnData.push(...executionErrorData);
|
returnData.push(...executionErrorData);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((error.message as string).includes('location')) {
|
if ((error.message as string).includes('location') || error.httpCode === '404') {
|
||||||
error.description =
|
error.description =
|
||||||
"Are you sure your table is in that region? You can specify the region using the 'Location' parameter from options.";
|
"Are you sure your table is in that region? You can specify the region using the 'Location' parameter from options.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error.httpCode === '403' && error.message.includes('Drive')) {
|
||||||
|
error.description =
|
||||||
|
'If your table(s) pull from a document in Google Drive, make sure that document is shared with your user';
|
||||||
|
}
|
||||||
|
|
||||||
throw new NodeOperationError(this.getNode(), error as Error, {
|
throw new NodeOperationError(this.getNode(), error as Error, {
|
||||||
itemIndex: i,
|
itemIndex: i,
|
||||||
description: error.description,
|
description: error.description,
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const versionDescription: INodeTypeDescription = {
|
||||||
name: 'googleBigQuery',
|
name: 'googleBigQuery',
|
||||||
icon: 'file:googleBigQuery.svg',
|
icon: 'file:googleBigQuery.svg',
|
||||||
group: ['input'],
|
group: ['input'],
|
||||||
version: 2,
|
version: [2, 2.1],
|
||||||
subtitle: '={{$parameter["operation"]}}',
|
subtitle: '={{$parameter["operation"]}}',
|
||||||
description: 'Consume Google BigQuery API',
|
description: 'Consume Google BigQuery API',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import type { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
import type { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||||
import { jsonParse, NodeOperationError } from 'n8n-workflow';
|
import { jsonParse, NodeOperationError } from 'n8n-workflow';
|
||||||
import type { SchemaField, TableRawData, TableSchema } from './interfaces';
|
import type { SchemaField, TableRawData, TableSchema } from './interfaces';
|
||||||
|
|
||||||
function getFieldValue(schemaField: SchemaField, field: IDataObject) {
|
function getFieldValue(schemaField: SchemaField, field: IDataObject, parseTimestamps = false) {
|
||||||
if (schemaField.type === 'RECORD') {
|
if (schemaField.type === 'RECORD') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
return simplify([field.v as TableRawData], schemaField.fields as unknown as SchemaField[]);
|
return simplify([field.v as TableRawData], schemaField.fields as unknown as SchemaField[]);
|
||||||
|
@ -12,6 +13,9 @@ function getFieldValue(schemaField: SchemaField, field: IDataObject) {
|
||||||
try {
|
try {
|
||||||
value = jsonParse(value as string);
|
value = jsonParse(value as string);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
} else if (schemaField.type === 'TIMESTAMP' && parseTimestamps) {
|
||||||
|
const dt = DateTime.fromSeconds(Number(value));
|
||||||
|
value = dt.isValid ? dt.toISO() : value;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -26,18 +30,27 @@ export function wrapData(data: IDataObject | IDataObject[]): INodeExecutionData[
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function simplify(data: TableRawData[], schema: SchemaField[], includeSchema = false) {
|
export function simplify(
|
||||||
|
data: TableRawData[],
|
||||||
|
schema: SchemaField[],
|
||||||
|
includeSchema = false,
|
||||||
|
parseTimestamps = false,
|
||||||
|
) {
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
for (const entry of data) {
|
for (const entry of data) {
|
||||||
const record: IDataObject = {};
|
const record: IDataObject = {};
|
||||||
|
|
||||||
for (const [index, field] of entry.f.entries()) {
|
for (const [index, field] of entry.f.entries()) {
|
||||||
if (schema[index].mode !== 'REPEATED') {
|
if (schema[index].mode !== 'REPEATED') {
|
||||||
record[schema[index].name] = getFieldValue(schema[index], field);
|
record[schema[index].name] = getFieldValue(schema[index], field, parseTimestamps);
|
||||||
} else {
|
} else {
|
||||||
record[schema[index].name] = (field.v as unknown as IDataObject[]).flatMap(
|
record[schema[index].name] = (field.v as unknown as IDataObject[]).flatMap(
|
||||||
(repeatedField) => {
|
(repeatedField) => {
|
||||||
return getFieldValue(schema[index], repeatedField as unknown as IDataObject);
|
return getFieldValue(
|
||||||
|
schema[index],
|
||||||
|
repeatedField as unknown as IDataObject,
|
||||||
|
parseTimestamps,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -68,12 +81,18 @@ export function prepareOutput(
|
||||||
responseData = response;
|
responseData = response;
|
||||||
} else {
|
} else {
|
||||||
const { rows, schema } = response;
|
const { rows, schema } = response;
|
||||||
|
const parseTimestamps = this.getNode().typeVersion >= 2.1;
|
||||||
|
|
||||||
if (rows !== undefined && schema !== undefined) {
|
if (rows !== undefined && schema !== undefined) {
|
||||||
const fields = (schema as TableSchema).fields;
|
const fields = (schema as TableSchema).fields;
|
||||||
responseData = rows;
|
responseData = rows;
|
||||||
|
|
||||||
responseData = simplify(responseData as TableRawData[], fields, includeSchema);
|
responseData = simplify(
|
||||||
|
responseData as TableRawData[],
|
||||||
|
fields,
|
||||||
|
includeSchema,
|
||||||
|
parseTimestamps,
|
||||||
|
);
|
||||||
} else if (schema && includeSchema) {
|
} else if (schema && includeSchema) {
|
||||||
responseData = { success: true, _schema: schema };
|
responseData = { success: true, _schema: schema };
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue