feat(editor): Improve errors in output panel (#8644)

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Giulio Andreini
2024-03-07 17:08:01 +01:00
committed by GitHub
parent 6e2aa405fc
commit 5301323906
38 changed files with 772 additions and 287 deletions

View File

@@ -3,10 +3,12 @@ import type {
INodeExecutionData,
INodeProperties,
IExecuteFunctions,
NodeApiError,
} from 'n8n-workflow';
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
import { apiRequest } from '../../transport';
import { baseRLC } from '../common.descriptions';
import { processAirtableError } from '../../helpers/utils';
const properties: INodeProperties[] = [
{
@@ -45,6 +47,7 @@ export async function execute(
returnData.push(...executionData);
} catch (error) {
error = processAirtableError(error as NodeApiError, undefined, i);
if (this.continueOnFail()) {
returnData.push({ json: { error: error.message } });
continue;

View File

@@ -3,11 +3,12 @@ import type {
INodeExecutionData,
INodeProperties,
IExecuteFunctions,
NodeApiError,
} from 'n8n-workflow';
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
import { apiRequest } from '../../transport';
import { insertUpdateOptions } from '../common.descriptions';
import { removeIgnored } from '../../helpers/utils';
import { processAirtableError, removeIgnored } from '../../helpers/utils';
const properties: INodeProperties[] = [
{
@@ -85,6 +86,7 @@ export async function execute(
returnData.push(...executionData);
} catch (error) {
error = processAirtableError(error as NodeApiError, undefined, i);
if (this.continueOnFail()) {
returnData.push({ json: { message: error.message, error } });
continue;

View File

@@ -54,7 +54,7 @@ export async function execute(
returnData.push(...executionData);
} catch (error) {
error = processAirtableError(error as NodeApiError, id);
error = processAirtableError(error as NodeApiError, id, i);
if (this.continueOnFail()) {
returnData.push({ json: { error: error.message } });
continue;

View File

@@ -90,7 +90,7 @@ export async function execute(
returnData.push(...executionData);
} catch (error) {
error = processAirtableError(error as NodeApiError, id);
error = processAirtableError(error as NodeApiError, id, i);
if (this.continueOnFail()) {
returnData.push({ json: { error: error.message } });
continue;

View File

@@ -137,7 +137,7 @@ export async function execute(
returnData.push(...executionData);
} catch (error) {
error = processAirtableError(error as NodeApiError, recordId);
error = processAirtableError(error as NodeApiError, recordId, i);
if (this.continueOnFail()) {
returnData.push({ json: { message: error.message, error } });
continue;

View File

@@ -3,10 +3,11 @@ import type {
INodeExecutionData,
INodeProperties,
IExecuteFunctions,
NodeApiError,
} from 'n8n-workflow';
import { updateDisplayOptions, wrapData } from '../../../../../utils/utilities';
import { apiRequest, apiRequestAllItems, batchUpdate } from '../../transport';
import { removeIgnored } from '../../helpers/utils';
import { processAirtableError, removeIgnored } from '../../helpers/utils';
import type { UpdateRecord } from '../../helpers/interfaces';
import { insertUpdateOptions } from '../common.descriptions';
@@ -146,6 +147,7 @@ export async function execute(
returnData.push(...executionData);
} catch (error) {
error = processAirtableError(error as NodeApiError, undefined, i);
if (this.continueOnFail()) {
returnData.push({ json: { message: error.message, error } });
continue;

View File

@@ -1,5 +1,6 @@
import { ApplicationError, type IDataObject, type NodeApiError } from 'n8n-workflow';
import type { UpdateRecord } from './interfaces';
import set from 'lodash/set';
export function removeIgnored(data: IDataObject, ignore: string | string[]) {
if (ignore) {
@@ -66,13 +67,18 @@ export function findMatches(
}
}
export function processAirtableError(error: NodeApiError, id?: string) {
export function processAirtableError(error: NodeApiError, id?: string, itemIndex?: number) {
if (error.description === 'NOT_FOUND' && id) {
error.description = `${id} is not a valid Record ID`;
}
if (error.description?.includes('You must provide an array of up to 10 record objects') && id) {
error.description = `${id} is not a valid Record ID`;
}
if (itemIndex !== undefined) {
set(error, 'context.itemIndex', itemIndex);
}
return error;
}

View File

@@ -37,6 +37,7 @@ export async function calApiRequest(
try {
return await this.helpers.httpRequestWithAuthentication.call(this, 'calApi', options);
} catch (error) {
if (error instanceof NodeApiError) throw error;
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}

View File

@@ -13,6 +13,8 @@ import { PythonSandbox } from './PythonSandbox';
import { getSandboxContext } from './Sandbox';
import { standardizeOutput } from './utils';
import set from 'lodash/set';
const { CODE_ENABLE_STDOUT } = process.env;
export class Code implements INodeType {
@@ -133,7 +135,10 @@ export class Code implements INodeType {
try {
items = (await sandbox.runCodeAllItems()) as INodeExecutionData[];
} catch (error) {
if (!this.continueOnFail()) throw error;
if (!this.continueOnFail()) {
set(error, 'node', node);
throw error;
}
items = [{ json: { error: error.message } }];
}
@@ -158,7 +163,10 @@ export class Code implements INodeType {
try {
result = await sandbox.runCodeEachItem();
} catch (error) {
if (!this.continueOnFail()) throw error;
if (!this.continueOnFail()) {
set(error, 'node', node);
throw error;
}
returnData.push({
json: { error: error.message },
pairedItem: {

View File

@@ -86,6 +86,8 @@ export class FilterV2 implements INodeType {
"Try to change the operator, switch ON the option 'Less Strict Type Validation', or change the type with an expression",
);
}
set(error, 'context.itemIndex', itemIndex);
set(error, 'node', this.getNode());
throw error;
}

View File

@@ -57,31 +57,13 @@ export async function googleApiRequest(
);
}
} catch (error) {
if (error instanceof NodeApiError) throw error;
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
error.statusCode = '401';
}
const apiError = new NodeApiError(
this.getNode(),
{
reason: error.error,
} as JsonObject,
{ httpCode: String(error.statusCode) },
);
if (
apiError.message &&
apiError.description &&
(apiError.message.toLowerCase().includes('bad request') ||
apiError.message.toLowerCase().includes('forbidden') ||
apiError.message.toUpperCase().includes('UNKNOWN ERROR'))
) {
const message = apiError.message;
apiError.message = apiError.description;
apiError.description = message;
}
throw apiError;
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}

View File

@@ -1,4 +1,4 @@
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
import { type IExecuteFunctions, type IDataObject, type INodeExecutionData } from 'n8n-workflow';
import { GoogleSheet } from '../helpers/GoogleSheet';
import { getSpreadsheetId } from '../helpers/GoogleSheets.utils';
import type { GoogleSheets, ResourceLocator } from '../helpers/GoogleSheets.types';
@@ -72,20 +72,11 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
if (results?.length) {
operationResult = operationResult.concat(results);
}
} catch (err) {
} catch (error) {
if (this.continueOnFail()) {
operationResult.push({ json: this.getInputData(0)[0].json, error: err });
operationResult.push({ json: this.getInputData(0)[0].json, error });
} 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;
throw error;
}
}

View File

@@ -5,7 +5,7 @@ import { wrapData } from '../../../../../../utils/utilities';
export async function execute(
this: IExecuteFunctions,
sheet: GoogleSheet,
_sheet: GoogleSheet,
sheetName: string,
): Promise<INodeExecutionData[]> {
const returnData: INodeExecutionData[] = [];

View File

@@ -139,11 +139,8 @@ export class GoogleSheet {
});
if (!foundItem?.properties?.title) {
throw new NodeOperationError(
node,
`Sheet with ${mode === 'name' ? 'name' : 'ID'} ${value} not found`,
{ level: 'warning' },
);
const error = new Error(`Sheet with ${mode === 'name' ? 'name' : 'ID'} ${value} not found`);
throw new NodeOperationError(node, error, { level: 'warning' });
}
return foundItem.properties;

View File

@@ -9,6 +9,7 @@ import type {
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { getGoogleAccessToken } from '../../../GenericFunctions';
import set from 'lodash/set';
export async function apiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
@@ -62,11 +63,15 @@ export async function apiRequest(
error.statusCode = '401';
}
if (error.message.includes('PERMISSION_DENIED')) {
const message = `Missing permissions for Google Sheet, ${error.message}}`;
const details = error.description ? ` Details of the error: ${error.description}.` : '';
const description = `Please check that the account you're using has the right permissions. (If you're trying to modify the sheet, you'll need edit access.)${details}`;
throw new NodeApiError(this.getNode(), error as JsonObject, { message, description });
if (error instanceof NodeApiError) {
if (error.message.includes('PERMISSION_DENIED')) {
const details = error.description ? ` Details of the error: ${error.description}.` : '';
const description = `Please check that the account you're using has the right permissions. (If you're trying to modify the sheet, you'll need edit access.)${details}`;
set(error, 'description', description);
}
throw error;
}
throw new NodeApiError(this.getNode(), error as JsonObject);

View File

@@ -34,6 +34,7 @@ import {
sanitizeUiMessage,
} from '../GenericFunctions';
import { keysToLowercase } from '@utils/utilities';
import set from 'lodash/set';
function toText<T>(data: T) {
if (typeof data === 'object' && data !== null) {
@@ -1255,6 +1256,7 @@ export class HttpRequestV3 implements INodeType {
requestInterval: number;
};
const sanitazedRequests: IDataObject[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (authentication === 'genericCredentialType') {
genericCredentialType = this.getNodeParameter('genericAuthType', 0) as string;
@@ -1627,8 +1629,11 @@ export class HttpRequestV3 implements INodeType {
'application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7';
}
}
try {
this.sendMessageToUI(sanitizeUiMessage(requestOptions, authDataKeys));
const sanitazedRequestOptions = sanitizeUiMessage(requestOptions, authDataKeys);
this.sendMessageToUI(sanitazedRequestOptions);
sanitazedRequests.push(sanitazedRequestOptions);
} catch (e) {}
if (pagination && pagination.paginationMode !== 'off') {
@@ -1770,7 +1775,9 @@ export class HttpRequestV3 implements INodeType {
if (autoDetectResponseFormat && responseData.reason.error instanceof Buffer) {
responseData.reason.error = Buffer.from(responseData.reason.error as Buffer).toString();
}
throw new NodeApiError(this.getNode(), responseData as JsonObject, { itemIndex });
const error = new NodeApiError(this.getNode(), responseData as JsonObject, { itemIndex });
set(error, 'context.request', sanitazedRequests[itemIndex]);
throw error;
} else {
removeCircularRefs(responseData.reason as JsonObject);
// Return the actual reason as error

View File

@@ -15,7 +15,7 @@ import type {
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import { snakeCase } from 'change-case';
import { generatePairedItemData } from '../../../utils/utilities';
@@ -47,6 +47,8 @@ import type { IForm } from './FormInterface';
import type { IAssociation, IDeal } from './DealInterface';
import set from 'lodash/set';
export class HubspotV2 implements INodeType {
description: INodeTypeDescription;
@@ -3058,21 +3060,17 @@ export class HubspotV2 implements INodeType {
error.cause.error?.validationResults &&
error.cause.error.validationResults[0].error === 'INVALID_EMAIL'
) {
throw new NodeOperationError(
this.getNode(),
error.cause.error.validationResults[0].message as string,
);
const message = error.cause.error.validationResults[0].message as string;
set(error, 'message', message);
}
if (error.cause.error?.message !== 'The resource you are requesting could not be found') {
if (error.httpCode === '404' && error.description === 'resource not found') {
throw new NodeOperationError(
this.getNode(),
`${error.node.parameters.resource} #${
error.node.parameters[`${error.node.parameters.resource}Id`].value
} could not be found. Check your ${error.node.parameters.resource} ID is correct`,
);
const message = `${error.node.parameters.resource} #${
error.node.parameters[`${error.node.parameters.resource}Id`].value
} could not be found. Check your ${error.node.parameters.resource} ID is correct`;
set(error, 'message', message);
}
throw new NodeOperationError(this.getNode(), error as string);
}
if (this.continueOnFail()) {
returnData.push({
@@ -3081,6 +3079,9 @@ export class HubspotV2 implements INodeType {
});
continue;
}
if (error instanceof NodeApiError) {
set(error, 'context.itemIndex', i);
}
throw error;
}
}

View File

@@ -86,6 +86,8 @@ export class IfV2 implements INodeType {
"Try to change the operator, switch ON the option 'Less Strict Type Validation', or change the type with an expression",
);
}
set(error, 'context.itemIndex', itemIndex);
set(error, 'node', this.getNode());
throw error;
}

View File

@@ -11,6 +11,7 @@ import type {
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import { capitalize } from '@utils/utilities';
import set from 'lodash/set';
const configuredOutputs = (parameters: INodeParameters) => {
const mode = parameters.mode as string;
@@ -351,6 +352,8 @@ export class SwitchV3 implements INodeType {
error.description =
"Try to change the operator, switch ON the option 'Less Strict Type Validation', or change the type with an expression";
}
set(error, 'context.itemIndex', itemIndex);
set(error, 'node', this.getNode());
throw error;
}

View File

@@ -28,6 +28,7 @@ export async function uprocApiRequest(
try {
return await this.helpers.httpRequestWithAuthentication.call(this, 'uprocApi', options);
} catch (error) {
if (error instanceof NodeApiError) throw error;
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}