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

View file

@ -72,7 +72,7 @@ export class PermissionChecker {
throw new NodeOperationError(nodeToFlag, 'Node has no access to credential', {
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) {
throw new NodeOperationError(node, 'Node uses invalid 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 {
severity: Severity;
constructor(credentialId: string, credentialType: string) {
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(
node,
`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(
node,
`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(
node,
`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(
node,
`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
if (!node.credentials) {
throw new NodeOperationError(node, 'Node does not have any credentials set!', {
severity: 'warning',
level: 'warning',
});
}
if (!node.credentials[type]) {
throw new NodeOperationError(
node,
`Node does not have any credentials set for "${type}"!`,
{ severity: 'warning' },
{ level: 'warning' },
);
}
} else {
@ -2903,7 +2903,7 @@ const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions =>
throw error.code === 'ENOENT'
? new NodeOperationError(node, error, {
message: `The file "${String(filePath)}" could not be accessed.`,
severity: 'warning',
level: 'warning',
})
: error;
}
@ -2911,7 +2911,7 @@ const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions =>
const allowedPaths = getAllowedPaths();
const message = allowedPaths.length ? ` Allowed paths: ${allowedPaths.join(', ')}` : '';
throw new NodeOperationError(node, `Access to the file is not allowed.${message}`, {
severity: 'warning',
level: 'warning',
});
}
return createReadStream(filePath);
@ -2924,7 +2924,7 @@ const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions =>
async writeContentToFile(filePath, content, flag) {
if (isFilePathBlocked(filePath as string)) {
throw new NodeOperationError(node, `The file "${String(filePath)}" is not writable.`, {
severity: 'warning',
level: 'warning',
});
}
return fsWriteFile(filePath, content, { encoding: 'binary', flag });

View file

@ -119,7 +119,7 @@ export async function viewSearch(
if (!tableData) {
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) {
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) {
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) {
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(
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.`,
{ severity: 'warning' },
{ level: 'warning' },
);
}

View file

@ -169,7 +169,7 @@ export class FacebookLeadAdsTrigger implements INodeType {
throw new NodeOperationError(
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.`,
{ severity: 'warning' },
{ level: 'warning' },
);
}

View file

@ -529,7 +529,7 @@ export class GithubTrigger implements INodeType {
throw new NodeOperationError(
this.getNode(),
'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(
this.getNode(),
'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) {
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(
node,
`Can not get sheet '${ResourceLocatorUiNames[documentIdType]}' with a value of '${value}'`,
{ severity: 'warning' },
{ level: 'warning' },
);
}
if (documentIdType === 'url') {

View file

@ -64,7 +64,7 @@ export async function slackApiRequest(
{
description:
'Hint: Upgrade to a Slack plan that includes the functionality you want to use.',
severity: 'warning',
level: 'warning',
},
);
} else if (response.error === 'missing_scope') {
@ -73,7 +73,7 @@ export async function slackApiRequest(
'Your Slack credential is missing required Oauth Scopes',
{
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'
| 'EMAIL_CONFIRMATION';
export type Severity = 'warning' | 'error';
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';
interface ExecutionBaseErrorOptions {
@ -19,8 +19,6 @@ export abstract class ExecutionBaseError extends ApplicationError {
lineNumber: number | undefined;
severity: Severity = 'error';
functionality: Functionality = 'regular';
constructor(message: string, { cause, errorResponse }: ExecutionBaseErrorOptions = {}) {

View file

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

View file

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

View file

@ -19,7 +19,7 @@ export class NodeOperationError extends NodeError {
super(node, error);
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;
this.description = options.description;
this.context.runIndex = options.runIndex;

View file

@ -4,7 +4,7 @@ export class WebhookPathTakenError extends WorkflowActivationError {
constructor(nodeName: string, cause?: Error) {
super(
`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 type { ApplicationError } from './application.error';
interface WorkflowActivationErrorOptions {
cause?: Error;
node?: INode;
severity?: Severity;
level?: ApplicationError['level'];
workflowId?: string;
}
@ -18,7 +19,7 @@ export class WorkflowActivationError extends ExecutionBaseError {
constructor(
message: string,
{ cause, node, severity, workflowId }: WorkflowActivationErrorOptions = {},
{ cause, node, level, workflowId }: WorkflowActivationErrorOptions = {},
) {
let error = cause as Error;
if (cause instanceof ExecutionBaseError) {
@ -31,6 +32,6 @@ export class WorkflowActivationError extends ExecutionBaseError {
this.node = node;
this.workflowId = workflowId;
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) {
super(message, { cause: undefined });
this.severity = 'warning';
this.level = 'warning';
this.name = this.constructor.name;
if (description) this.description = description;
this.node = node;