2022-01-19 07:39:39 +00:00
|
|
|
import { ipcRenderer } from 'electron';
|
2022-01-24 06:44:11 +00:00
|
|
|
import frappe, { t } from 'frappe';
|
2022-01-24 09:04:37 +00:00
|
|
|
import {
|
|
|
|
DuplicateEntryError,
|
|
|
|
LinkValidationError,
|
|
|
|
MandatoryError,
|
|
|
|
ValidationError,
|
|
|
|
} from 'frappe/common/errors';
|
|
|
|
import BaseDocument from 'frappe/model/document';
|
2022-01-24 11:19:34 +00:00
|
|
|
import { IPC_ACTIONS, IPC_MESSAGES } from './messages';
|
2022-01-19 10:59:13 +00:00
|
|
|
import { showMessageDialog, showToast } from './utils';
|
2022-01-18 13:03:16 +00:00
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
interface ErrorLog {
|
|
|
|
name: string;
|
|
|
|
message: string;
|
|
|
|
stack?: string;
|
2022-01-24 11:19:34 +00:00
|
|
|
more?: object;
|
2022-01-24 09:04:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function shouldNotStore(error: Error) {
|
2022-01-18 13:03:16 +00:00
|
|
|
return [MandatoryError, ValidationError].some(
|
|
|
|
(errorClass) => error instanceof errorClass
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-28 08:03:07 +00:00
|
|
|
async function reportError(errorLogObj: ErrorLog) {
|
|
|
|
if (!errorLogObj.stack) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const body = {
|
|
|
|
error_name: errorLogObj.name,
|
|
|
|
message: errorLogObj.message,
|
|
|
|
stack: errorLogObj.stack,
|
|
|
|
more: JSON.stringify(errorLogObj.more ?? {}),
|
|
|
|
};
|
|
|
|
ipcRenderer.invoke(IPC_ACTIONS.SEND_ERROR, JSON.stringify(body));
|
2022-01-19 10:59:13 +00:00
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
function getToastProps(errorLogObj: ErrorLog) {
|
2022-01-19 11:13:50 +00:00
|
|
|
const props = {
|
2022-01-24 06:44:11 +00:00
|
|
|
message: t`Error: ` + errorLogObj.name,
|
2022-01-19 11:13:50 +00:00
|
|
|
type: 'error',
|
|
|
|
};
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
// @ts-ignore
|
2022-01-19 11:13:50 +00:00
|
|
|
if (!frappe.SystemSettings.autoReportErrors) {
|
|
|
|
Object.assign(props, {
|
2022-01-24 06:44:11 +00:00
|
|
|
actionText: t`Report Error`,
|
2022-01-19 11:13:50 +00:00
|
|
|
action: () => {
|
|
|
|
reportError(errorLogObj);
|
2022-01-25 09:33:17 +00:00
|
|
|
reportIssue(errorLogObj);
|
2022-01-19 11:13:50 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export function handleError(
|
|
|
|
shouldLog: boolean,
|
|
|
|
error: Error,
|
|
|
|
more: object = {}
|
|
|
|
) {
|
2022-01-18 13:03:16 +00:00
|
|
|
if (shouldLog) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldNotStore(error)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { name, stack, message } = error;
|
2022-01-24 09:04:37 +00:00
|
|
|
const errorLogObj: ErrorLog = { name, stack, message, more };
|
2022-01-19 11:13:50 +00:00
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
// @ts-ignore
|
2022-01-18 13:03:16 +00:00
|
|
|
frappe.errorLog.push(errorLogObj);
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
// @ts-ignore
|
2022-01-19 11:13:50 +00:00
|
|
|
if (frappe.SystemSettings.autoReportErrors) {
|
|
|
|
reportError(errorLogObj);
|
2022-01-28 13:07:51 +00:00
|
|
|
} else {
|
|
|
|
showToast(getToastProps(errorLogObj));
|
2022-01-19 11:13:50 +00:00
|
|
|
}
|
2022-01-18 13:03:16 +00:00
|
|
|
}
|
2022-01-19 06:14:12 +00:00
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export function getErrorMessage(e: Error, doc?: BaseDocument): string {
|
2022-01-24 06:44:11 +00:00
|
|
|
let errorMessage = e.message || t`An error occurred.`;
|
2022-01-24 09:04:37 +00:00
|
|
|
|
|
|
|
const { doctype, name }: { doctype?: unknown; name?: unknown } = doc ?? {};
|
|
|
|
const canElaborate = !!(doctype && name);
|
|
|
|
|
|
|
|
if (e instanceof LinkValidationError && canElaborate) {
|
2022-01-24 06:44:11 +00:00
|
|
|
errorMessage = t`${doctype} ${name} is linked with existing records.`;
|
2022-01-24 09:04:37 +00:00
|
|
|
} else if (e instanceof DuplicateEntryError && canElaborate) {
|
2022-01-24 06:44:11 +00:00
|
|
|
errorMessage = t`${doctype} ${name} already exists.`;
|
2022-01-19 07:39:39 +00:00
|
|
|
}
|
2022-01-24 09:04:37 +00:00
|
|
|
|
2022-01-19 07:39:39 +00:00
|
|
|
return errorMessage;
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export function handleErrorWithDialog(error: Error, doc?: BaseDocument) {
|
|
|
|
const errorMessage = getErrorMessage(error, doc);
|
2022-01-19 07:39:39 +00:00
|
|
|
handleError(false, error, { errorMessage, doc });
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
showMessageDialog({ message: error.name, description: errorMessage });
|
2022-01-19 07:39:39 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export async function showErrorDialog(title?: string, content?: string) {
|
2022-01-19 07:39:39 +00:00
|
|
|
// To be used for show stopper errors
|
2022-01-24 06:44:11 +00:00
|
|
|
title ??= t`Error`;
|
|
|
|
content ??= t`Something has gone terribly wrong. Please check the console and raise an issue.`;
|
2022-01-19 07:39:39 +00:00
|
|
|
|
|
|
|
await ipcRenderer.invoke(IPC_ACTIONS.SHOW_ERROR, { title, content });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrapper Functions
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export function getErrorHandled(func: Function) {
|
|
|
|
return async function errorHandled(...args: unknown[]) {
|
2022-01-19 06:14:12 +00:00
|
|
|
try {
|
|
|
|
return await func(...args);
|
|
|
|
} catch (error) {
|
2022-01-24 09:04:37 +00:00
|
|
|
handleError(false, error as Error, {
|
2022-01-19 06:14:12 +00:00
|
|
|
functionName: func.name,
|
|
|
|
functionArgs: args,
|
|
|
|
});
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-24 09:04:37 +00:00
|
|
|
export function getErrorHandledSync(func: Function) {
|
|
|
|
return function errorHandledSync(...args: unknown[]) {
|
2022-01-19 06:14:12 +00:00
|
|
|
try {
|
|
|
|
return func(...args);
|
|
|
|
} catch (error) {
|
2022-01-24 09:04:37 +00:00
|
|
|
handleError(false, error as Error, {
|
2022-01-19 06:14:12 +00:00
|
|
|
functionName: func.name,
|
|
|
|
functionArgs: args,
|
|
|
|
});
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2022-01-24 11:19:34 +00:00
|
|
|
|
2022-01-25 09:33:17 +00:00
|
|
|
function getIssueUrlQuery(errorLogObj?: ErrorLog): string {
|
2022-01-24 11:19:34 +00:00
|
|
|
const baseUrl = 'https://github.com/frappe/books/issues/new?labels=bug';
|
|
|
|
|
2022-01-25 09:33:17 +00:00
|
|
|
const body = ['<h2>Description</h2>', 'Add some description...', ''];
|
|
|
|
|
|
|
|
if (errorLogObj) {
|
|
|
|
body.push(
|
|
|
|
'<h2>Error Info</h2>',
|
|
|
|
'',
|
|
|
|
`**Error**: _${errorLogObj.name}: ${errorLogObj.message}_`,
|
|
|
|
''
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (errorLogObj?.stack) {
|
2022-01-24 11:19:34 +00:00
|
|
|
body.push('**Stack**:', '```', errorLogObj.stack, '```', '');
|
|
|
|
}
|
|
|
|
|
2022-01-25 09:33:17 +00:00
|
|
|
const { fullPath } = (errorLogObj?.more as { fullPath?: string }) ?? {};
|
2022-01-24 11:19:34 +00:00
|
|
|
if (fullPath) {
|
|
|
|
body.push(`**Path**: \`${fullPath}\``);
|
|
|
|
}
|
|
|
|
|
|
|
|
const url = [baseUrl, `body=${body.join('\n')}`].join('&');
|
|
|
|
return encodeURI(url);
|
|
|
|
}
|
|
|
|
|
2022-01-25 09:33:17 +00:00
|
|
|
export function reportIssue(errorLogObj?: ErrorLog) {
|
2022-01-24 11:19:34 +00:00
|
|
|
const urlQuery = getIssueUrlQuery(errorLogObj);
|
|
|
|
ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, urlQuery);
|
|
|
|
}
|