mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-23 11:44:06 -08:00
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:
parent
dc1f14b0be
commit
1d46983b24
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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' },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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' },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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' },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2381,5 +2381,4 @@ export type BannerName =
|
|||
| 'NON_PRODUCTION_LICENSE'
|
||||
| 'EMAIL_CONFIRMATION';
|
||||
|
||||
export type Severity = 'warning' | 'error';
|
||||
export type Functionality = 'regular' | 'configuration-node';
|
||||
|
|
|
@ -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 = {}) {
|
||||
|
|
|
@ -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'];
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue