refactor: Unify severity and level for all application errors for Sentry (no-changelog) (#7956)

## Summary
Unify `severity` and `level` for all backend application errors for
Sentry

Follow-up to:
https://github.com/n8n-io/n8n/pull/7914#issuecomment-1840433542

...

#### How to test the change:
1. ...


## Issues fixed
Include links to Github issue or Community forum post or **Linear
ticket**:
> Important in order to close automatically and provide context to
reviewers

...


## Review / Merge checklist
- [ ] PR title and summary are descriptive. **Remember, the title
automatically goes into the changelog. Use `(no-changelog)` otherwise.**
([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md))
- [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up
ticket created.
- [ ] Tests included.
> A bug is not considered fixed, unless a test is added to prevent it
from happening again. A feature is not complete without tests.
  >
> *(internal)* You can use Slack commands to trigger [e2e
tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227)
or [deploy test
instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce)
or [deploy early access version on
Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e).
This commit is contained in:
Iván Ovejero 2023-12-07 16:57:02 +01:00 committed by GitHub
parent dc1f14b0be
commit 1d46983b24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 42 additions and 49 deletions

View file

@ -1,6 +1,6 @@
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import config from '@/config'; import config from '@/config';
import { ErrorReporterProxy, ApplicationError, ExecutionBaseError } from 'n8n-workflow'; import { ErrorReporterProxy, ApplicationError } from 'n8n-workflow';
let initialized = false; let initialized = false;
@ -39,9 +39,6 @@ export const initErrorHandling = async () => {
const seenErrors = new Set<string>(); const seenErrors = new Set<string>();
addGlobalEventProcessor((event, { originalException }) => { addGlobalEventProcessor((event, { originalException }) => {
if (originalException instanceof ExecutionBaseError && originalException.severity === 'warning')
return null;
if (originalException instanceof ApplicationError) { if (originalException instanceof ApplicationError) {
const { level, extra } = originalException; const { level, extra } = originalException;
if (level === 'warning') return null; if (level === 'warning') return null;

View file

@ -72,7 +72,7 @@ export class PermissionChecker {
throw new NodeOperationError(nodeToFlag, 'Node has no access to credential', { throw new NodeOperationError(nodeToFlag, 'Node has no access to credential', {
description: 'Please recreate the credential or ask its owner to share it with you.', description: 'Please recreate the credential or ask its owner to share it with you.',
severity: 'warning', level: 'warning',
}); });
} }
@ -151,7 +151,7 @@ export class PermissionChecker {
if (!cred.id) { if (!cred.id) {
throw new NodeOperationError(node, 'Node uses invalid credential', { throw new NodeOperationError(node, 'Node uses invalid credential', {
description: 'Please recreate the credential.', description: 'Please recreate the credential.',
severity: 'warning', level: 'warning',
}); });
} }

View file

@ -1,10 +1,8 @@
import { ApplicationError, type Severity } from 'n8n-workflow'; import { ApplicationError } from 'n8n-workflow';
export class CredentialNotFoundError extends ApplicationError { export class CredentialNotFoundError extends ApplicationError {
severity: Severity;
constructor(credentialId: string, credentialType: string) { constructor(credentialId: string, credentialType: string) {
super(`Credential with ID "${credentialId}" does not exist for type "${credentialType}".`); super(`Credential with ID "${credentialId}" does not exist for type "${credentialType}".`);
this.severity = 'warning'; this.level = 'warning';
} }
} }

View file

@ -1528,7 +1528,7 @@ export async function httpRequestWithAuthentication(
throw new NodeOperationError( throw new NodeOperationError(
node, node,
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`, `Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }
@ -1722,7 +1722,7 @@ export async function requestWithAuthentication(
throw new NodeOperationError( throw new NodeOperationError(
node, node,
`Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`, `Node "${node.name}" does not have any credentials of type "${credentialsType}" set!`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }
@ -1881,7 +1881,7 @@ export async function getCredentials(
throw new NodeOperationError( throw new NodeOperationError(
node, node,
`Node type "${node.type}" does not have any credentials defined!`, `Node type "${node.type}" does not have any credentials defined!`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }
@ -1892,7 +1892,7 @@ export async function getCredentials(
throw new NodeOperationError( throw new NodeOperationError(
node, node,
`Node type "${node.type}" does not have any credentials of type "${type}" defined!`, `Node type "${node.type}" does not have any credentials of type "${type}" defined!`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }
@ -1917,14 +1917,14 @@ export async function getCredentials(
// Credentials are required so error // Credentials are required so error
if (!node.credentials) { if (!node.credentials) {
throw new NodeOperationError(node, 'Node does not have any credentials set!', { throw new NodeOperationError(node, 'Node does not have any credentials set!', {
severity: 'warning', level: 'warning',
}); });
} }
if (!node.credentials[type]) { if (!node.credentials[type]) {
throw new NodeOperationError( throw new NodeOperationError(
node, node,
`Node does not have any credentials set for "${type}"!`, `Node does not have any credentials set for "${type}"!`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }
} else { } else {
@ -2903,7 +2903,7 @@ const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions =>
throw error.code === 'ENOENT' throw error.code === 'ENOENT'
? new NodeOperationError(node, error, { ? new NodeOperationError(node, error, {
message: `The file "${String(filePath)}" could not be accessed.`, message: `The file "${String(filePath)}" could not be accessed.`,
severity: 'warning', level: 'warning',
}) })
: error; : error;
} }
@ -2911,7 +2911,7 @@ const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions =>
const allowedPaths = getAllowedPaths(); const allowedPaths = getAllowedPaths();
const message = allowedPaths.length ? ` Allowed paths: ${allowedPaths.join(', ')}` : ''; const message = allowedPaths.length ? ` Allowed paths: ${allowedPaths.join(', ')}` : '';
throw new NodeOperationError(node, `Access to the file is not allowed.${message}`, { throw new NodeOperationError(node, `Access to the file is not allowed.${message}`, {
severity: 'warning', level: 'warning',
}); });
} }
return createReadStream(filePath); return createReadStream(filePath);
@ -2924,7 +2924,7 @@ const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions =>
async writeContentToFile(filePath, content, flag) { async writeContentToFile(filePath, content, flag) {
if (isFilePathBlocked(filePath as string)) { if (isFilePathBlocked(filePath as string)) {
throw new NodeOperationError(node, `The file "${String(filePath)}" is not writable.`, { throw new NodeOperationError(node, `The file "${String(filePath)}" is not writable.`, {
severity: 'warning', level: 'warning',
}); });
} }
return fsWriteFile(filePath, content, { encoding: 'binary', flag }); return fsWriteFile(filePath, content, { encoding: 'binary', flag });

View file

@ -119,7 +119,7 @@ export async function viewSearch(
if (!tableData) { if (!tableData) {
throw new NodeOperationError(this.getNode(), 'Table information could not be found!', { throw new NodeOperationError(this.getNode(), 'Table information could not be found!', {
severity: 'warning', level: 'warning',
}); });
} }

View file

@ -21,7 +21,7 @@ export async function getColumns(this: ILoadOptionsFunctions): Promise<INodeProp
if (!tableData) { if (!tableData) {
throw new NodeOperationError(this.getNode(), 'Table information could not be found!', { throw new NodeOperationError(this.getNode(), 'Table information could not be found!', {
severity: 'warning', level: 'warning',
}); });
} }
@ -82,7 +82,7 @@ export async function getAttachmentColumns(
if (!tableData) { if (!tableData) {
throw new NodeOperationError(this.getNode(), 'Table information could not be found!', { throw new NodeOperationError(this.getNode(), 'Table information could not be found!', {
severity: 'warning', level: 'warning',
}); });
} }

View file

@ -79,7 +79,7 @@ export async function getColumns(this: ILoadOptionsFunctions): Promise<ResourceM
if (!tableData) { if (!tableData) {
throw new NodeOperationError(this.getNode(), 'Table information could not be found!', { throw new NodeOperationError(this.getNode(), 'Table information could not be found!', {
severity: 'warning', level: 'warning',
}); });
} }

View file

@ -195,7 +195,7 @@ export class FacebookTrigger implements INodeType {
throw new NodeOperationError( throw new NodeOperationError(
this.getNode(), this.getNode(),
`The Facebook App ID ${appId} already has a webhook subscription. Delete it or use another App before executing the trigger. Due to Facebook API limitations, you can have just one trigger per App.`, `The Facebook App ID ${appId} already has a webhook subscription. Delete it or use another App before executing the trigger. Due to Facebook API limitations, you can have just one trigger per App.`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }

View file

@ -169,7 +169,7 @@ export class FacebookLeadAdsTrigger implements INodeType {
throw new NodeOperationError( throw new NodeOperationError(
this.getNode(), this.getNode(),
`The Facebook App ID ${appId} already has a webhook subscription. Delete it or use another App before executing the trigger. Due to Facebook API limitations, you can have just one trigger per App.`, `The Facebook App ID ${appId} already has a webhook subscription. Delete it or use another App before executing the trigger. Due to Facebook API limitations, you can have just one trigger per App.`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }

View file

@ -529,7 +529,7 @@ export class GithubTrigger implements INodeType {
throw new NodeOperationError( throw new NodeOperationError(
this.getNode(), this.getNode(),
'A webhook with the identical URL probably exists already. Please delete it manually on Github!', 'A webhook with the identical URL probably exists already. Please delete it manually on Github!',
{ severity: 'warning' }, { level: 'warning' },
); );
} }
@ -537,7 +537,7 @@ export class GithubTrigger implements INodeType {
throw new NodeOperationError( throw new NodeOperationError(
this.getNode(), this.getNode(),
'Check that the repository exists and that you have permission to create the webhooks this node requires', 'Check that the repository exists and that you have permission to create the webhooks this node requires',
{ severity: 'warning' }, { level: 'warning' },
); );
} }

View file

@ -137,7 +137,7 @@ export class GoogleSheet {
if (!foundItem?.properties?.title) { if (!foundItem?.properties?.title) {
throw new NodeOperationError(node, `Sheet with ID ${sheetId} not found`, { throw new NodeOperationError(node, `Sheet with ID ${sheetId} not found`, {
severity: 'warning', level: 'warning',
}); });
} }

View file

@ -28,7 +28,7 @@ export function getSpreadsheetId(
throw new NodeOperationError( throw new NodeOperationError(
node, node,
`Can not get sheet '${ResourceLocatorUiNames[documentIdType]}' with a value of '${value}'`, `Can not get sheet '${ResourceLocatorUiNames[documentIdType]}' with a value of '${value}'`,
{ severity: 'warning' }, { level: 'warning' },
); );
} }
if (documentIdType === 'url') { if (documentIdType === 'url') {

View file

@ -64,7 +64,7 @@ export async function slackApiRequest(
{ {
description: description:
'Hint: Upgrade to a Slack plan that includes the functionality you want to use.', 'Hint: Upgrade to a Slack plan that includes the functionality you want to use.',
severity: 'warning', level: 'warning',
}, },
); );
} else if (response.error === 'missing_scope') { } else if (response.error === 'missing_scope') {
@ -73,7 +73,7 @@ export async function slackApiRequest(
'Your Slack credential is missing required Oauth Scopes', 'Your Slack credential is missing required Oauth Scopes',
{ {
description: `Add the following scope(s) to your Slack App: ${response.needed}`, description: `Add the following scope(s) to your Slack App: ${response.needed}`,
severity: 'warning', level: 'warning',
}, },
); );
} }

View file

@ -2381,5 +2381,4 @@ export type BannerName =
| 'NON_PRODUCTION_LICENSE' | 'NON_PRODUCTION_LICENSE'
| 'EMAIL_CONFIRMATION'; | 'EMAIL_CONFIRMATION';
export type Severity = 'warning' | 'error';
export type Functionality = 'regular' | 'configuration-node'; export type Functionality = 'regular' | 'configuration-node';

View file

@ -1,4 +1,4 @@
import type { Functionality, IDataObject, JsonObject, Severity } from '../../Interfaces'; import type { Functionality, IDataObject, JsonObject } from '../../Interfaces';
import { ApplicationError } from '../application.error'; import { ApplicationError } from '../application.error';
interface ExecutionBaseErrorOptions { interface ExecutionBaseErrorOptions {
@ -19,8 +19,6 @@ export abstract class ExecutionBaseError extends ApplicationError {
lineNumber: number | undefined; lineNumber: number | undefined;
severity: Severity = 'error';
functionality: Functionality = 'regular'; functionality: Functionality = 'regular';
constructor(message: string, { cause, errorResponse }: ExecutionBaseErrorOptions = {}) { constructor(message: string, { cause, errorResponse }: ExecutionBaseErrorOptions = {}) {

View file

@ -7,7 +7,7 @@ export type ReportingOptions = {
} & Pick<Event, 'tags' | 'extra'>; } & Pick<Event, 'tags' | 'extra'>;
export class ApplicationError extends Error { export class ApplicationError extends Error {
readonly level: Level; level: Level;
readonly tags?: Event['tags']; readonly tags?: Event['tags'];

View file

@ -9,18 +9,18 @@ import type {
JsonObject, JsonObject,
IDataObject, IDataObject,
IStatusCodeMessages, IStatusCodeMessages,
Severity,
Functionality, Functionality,
} from '../Interfaces'; } from '../Interfaces';
import { NodeError } from './abstract/node.error'; import { NodeError } from './abstract/node.error';
import { removeCircularRefs } from '../utils'; import { removeCircularRefs } from '../utils';
import type { ReportingOptions } from './application.error';
export interface NodeOperationErrorOptions { export interface NodeOperationErrorOptions {
message?: string; message?: string;
description?: string; description?: string;
runIndex?: number; runIndex?: number;
itemIndex?: number; itemIndex?: number;
severity?: Severity; level?: ReportingOptions['level'];
messageMapping?: { [key: string]: string }; // allows to pass custom mapping for error messages scoped to a node messageMapping?: { [key: string]: string }; // allows to pass custom mapping for error messages scoped to a node
functionality?: Functionality; functionality?: Functionality;
} }
@ -120,7 +120,7 @@ export class NodeApiError extends NodeError {
parseXml, parseXml,
runIndex, runIndex,
itemIndex, itemIndex,
severity, level,
functionality, functionality,
messageMapping, messageMapping,
}: NodeApiErrorOptions = {}, }: NodeApiErrorOptions = {},
@ -174,10 +174,10 @@ export class NodeApiError extends NodeError {
this.findProperty(errorResponse, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES) ?? null; this.findProperty(errorResponse, ERROR_STATUS_PROPERTIES, ERROR_NESTING_PROPERTIES) ?? null;
} }
if (severity) { if (level) {
this.severity = severity; this.level = level;
} else if (this.httpCode?.charAt(0) !== '5') { } else if (this.httpCode?.charAt(0) !== '5') {
this.severity = 'warning'; this.level = 'warning';
} }
// set description of this error // set description of this error

View file

@ -19,7 +19,7 @@ export class NodeOperationError extends NodeError {
super(node, error); super(node, error);
if (options.message) this.message = options.message; if (options.message) this.message = options.message;
if (options.severity) this.severity = options.severity; if (options.level) this.level = options.level;
if (options.functionality) this.functionality = options.functionality; if (options.functionality) this.functionality = options.functionality;
this.description = options.description; this.description = options.description;
this.context.runIndex = options.runIndex; this.context.runIndex = options.runIndex;

View file

@ -4,7 +4,7 @@ export class WebhookPathTakenError extends WorkflowActivationError {
constructor(nodeName: string, cause?: Error) { constructor(nodeName: string, cause?: Error) {
super( super(
`The URL path that the "${nodeName}" node uses is already taken. Please change it to something else.`, `The URL path that the "${nodeName}" node uses is already taken. Please change it to something else.`,
{ severity: 'warning', cause }, { level: 'warning', cause },
); );
} }
} }

View file

@ -1,10 +1,11 @@
import type { INode, Severity } from '../Interfaces'; import type { INode } from '../Interfaces';
import { ExecutionBaseError } from './abstract/execution-base.error'; import { ExecutionBaseError } from './abstract/execution-base.error';
import type { ApplicationError } from './application.error';
interface WorkflowActivationErrorOptions { interface WorkflowActivationErrorOptions {
cause?: Error; cause?: Error;
node?: INode; node?: INode;
severity?: Severity; level?: ApplicationError['level'];
workflowId?: string; workflowId?: string;
} }
@ -18,7 +19,7 @@ export class WorkflowActivationError extends ExecutionBaseError {
constructor( constructor(
message: string, message: string,
{ cause, node, severity, workflowId }: WorkflowActivationErrorOptions = {}, { cause, node, level, workflowId }: WorkflowActivationErrorOptions = {},
) { ) {
let error = cause as Error; let error = cause as Error;
if (cause instanceof ExecutionBaseError) { if (cause instanceof ExecutionBaseError) {
@ -31,6 +32,6 @@ export class WorkflowActivationError extends ExecutionBaseError {
this.node = node; this.node = node;
this.workflowId = workflowId; this.workflowId = workflowId;
this.message = message; this.message = message;
if (severity) this.severity = severity; if (level) this.level = level;
} }
} }

View file

@ -15,7 +15,7 @@ export class WorkflowOperationError extends ExecutionBaseError {
constructor(message: string, node?: INode, description?: string) { constructor(message: string, node?: INode, description?: string) {
super(message, { cause: undefined }); super(message, { cause: undefined });
this.severity = 'warning'; this.level = 'warning';
this.name = this.constructor.name; this.name = this.constructor.name;
if (description) this.description = description; if (description) this.description = description;
this.node = node; this.node = node;