2
0
mirror of https://github.com/frappe/books.git synced 2024-12-25 20:11:15 +00:00
books/src/errorHandling.ts

204 lines
5.0 KiB
TypeScript
Raw Normal View History

import { ipcRenderer } from 'electron';
2022-04-20 06:38:47 +00:00
import { t } from 'fyo';
import { ConfigKeys } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
2022-04-20 06:38:47 +00:00
import { TelemetrySetting } from 'fyo/telemetry/types';
import {
DuplicateEntryError,
LinkValidationError,
MandatoryError,
ValidationError,
} from 'fyo/utils/errors';
import { ErrorLog } from 'fyo/utils/types';
2022-03-31 07:33:58 +00:00
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
2022-04-20 06:38:47 +00:00
import { fyo } from './initFyo';
import { ToastOptions } from './utils/types';
import { showMessageDialog, showToast } from './utils/ui';
2022-01-18 13:03:16 +00:00
function getCanLog(): boolean {
2022-04-20 06:38:47 +00:00
const telemetrySetting = fyo.config.get(ConfigKeys.Telemetry);
2022-03-10 10:31:55 +00:00
return telemetrySetting !== TelemetrySetting.dontLogAnything;
}
function shouldNotStore(error: Error) {
2022-01-18 13:03:16 +00:00
return [MandatoryError, ValidationError].some(
(errorClass) => error instanceof errorClass
);
}
async function reportError(errorLogObj: ErrorLog, cb?: Function) {
if (!errorLogObj.stack) {
return;
}
const body = {
error_name: errorLogObj.name,
message: errorLogObj.message,
stack: errorLogObj.stack,
more: JSON.stringify(errorLogObj.more ?? {}),
};
2022-03-17 06:03:02 +00:00
2022-04-20 06:38:47 +00:00
if (fyo.store.isDevelopment) {
2022-03-17 06:03:02 +00:00
console.log('errorHandling');
console.log(body);
}
await ipcRenderer.invoke(IPC_ACTIONS.SEND_ERROR, JSON.stringify(body));
cb?.();
2022-01-19 10:59:13 +00:00
}
function getToastProps(errorLogObj: ErrorLog, canLog: boolean, cb?: Function) {
2022-04-20 06:38:47 +00:00
const props: ToastOptions = {
2022-01-24 06:44:11 +00:00
message: t`Error: ` + errorLogObj.name,
type: 'error',
};
// @ts-ignore
if (!canLog) {
Object.assign(props, {
2022-01-24 06:44:11 +00:00
actionText: t`Report Error`,
action: () => {
reportIssue(errorLogObj);
reportError(errorLogObj, cb);
},
});
}
return props;
}
2022-03-16 11:42:06 +00:00
export function getErrorLogObject(
error: Error,
more: Record<string, unknown>
): ErrorLog {
const { name, stack, message } = error;
const errorLogObj = { name, stack, message, more };
// @ts-ignore
2022-04-20 06:38:47 +00:00
fyo.errorLog.push(errorLogObj);
return errorLogObj;
}
export function handleError(
shouldLog: boolean,
error: Error,
2022-03-16 11:42:06 +00:00
more?: Record<string, unknown>,
cb?: Function
) {
2022-04-20 06:38:47 +00:00
fyo.telemetry.error(error.name);
2022-01-18 13:03:16 +00:00
if (shouldLog) {
console.error(error);
}
if (shouldNotStore(error)) {
return;
}
2022-03-16 11:42:06 +00:00
const errorLogObj = getErrorLogObject(error, more ?? {});
2022-01-18 13:03:16 +00:00
// @ts-ignore
const canLog = getCanLog();
if (canLog) {
reportError(errorLogObj, cb);
} else {
showToast(getToastProps(errorLogObj, canLog, cb));
}
2022-01-18 13:03:16 +00:00
}
export function getErrorMessage(e: Error, doc?: Doc): string {
2022-01-24 06:44:11 +00:00
let errorMessage = e.message || t`An error occurred.`;
const { schemaName, name }: { schemaName?: string; name?: string } =
doc ?? {};
const canElaborate = !!(schemaName && name);
if (e instanceof LinkValidationError && canElaborate) {
errorMessage = t`${schemaName} ${name} is linked with existing records.`;
} else if (e instanceof DuplicateEntryError && canElaborate) {
errorMessage = t`${schemaName} ${name} already exists.`;
}
return errorMessage;
}
export function handleErrorWithDialog(error: Error, doc?: Doc) {
const errorMessage = getErrorMessage(error, doc);
handleError(false, error, { errorMessage, doc });
2022-04-20 06:38:47 +00:00
showMessageDialog({ message: error.name, detail: errorMessage });
throw error;
}
export async function showErrorDialog(title?: string, content?: string) {
// 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.`;
await ipcRenderer.invoke(IPC_ACTIONS.SHOW_ERROR, { title, content });
}
// Wrapper Functions
export function getErrorHandled(func: Function) {
return async function errorHandled(...args: unknown[]) {
try {
return await func(...args);
} catch (error) {
handleError(false, error as Error, {
functionName: func.name,
functionArgs: args,
});
throw error;
}
};
}
export function getErrorHandledSync(func: Function) {
return function errorHandledSync(...args: unknown[]) {
try {
return func(...args);
} catch (error) {
handleError(false, error as Error, {
functionName: func.name,
functionArgs: args,
});
throw error;
}
};
}
function getIssueUrlQuery(errorLogObj?: ErrorLog): string {
const baseUrl = 'https://github.com/frappe/books/issues/new?labels=bug';
const body = ['<h2>Description</h2>', 'Add some description...', ''];
if (errorLogObj) {
body.push(
'<h2>Error Info</h2>',
'',
`**Error**: _${errorLogObj.name}: ${errorLogObj.message}_`,
''
);
}
if (errorLogObj?.stack) {
body.push('**Stack**:', '```', errorLogObj.stack, '```', '');
}
const { fullPath } = (errorLogObj?.more as { fullPath?: string }) ?? {};
if (fullPath) {
body.push(`**Path**: \`${fullPath}\``);
}
const url = [baseUrl, `body=${body.join('\n')}`].join('&');
return encodeURI(url);
}
export function reportIssue(errorLogObj?: ErrorLog) {
const urlQuery = getIssueUrlQuery(errorLogObj);
ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, urlQuery);
}