Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
helpers.report.tsx Maven / Gradle / Ivy
import merge from 'deepmerge';
import { T } from 'helpers/translator';
import { Notifier } from 'hooks/notification';
import React from 'react';
import Types from 'types/types';
import { ApolloError } from '@apollo/client';
import Notification, {
NotificationProps, NotificationType
} from '../components/common/Notification';
import { t } from '../utils/i18n';
import { assertType, assertTypes } from '../utils/utils';
// TODO rename Report to Problem
export const enum ReportCategory { FRONTEND = 1, BACKEND = 2, INPUT = 3, USER = 4}
export const reportCategoryName = { 1: 'FRONTEND', 2: 'BACKEND', 3: 'INPUT', 4: 'USER' } as const
export const enum ReportLevel {ERROR = 1, WARNING = 2, INFO = 3}
const ReportStatus = {
frontend : {
Whoops : 1,
NotSupported : 2,
Broken : 3,
Loading : 4,
NotAuthorized : 5,
Apollo : 6
},
backend : {
Error : 1,
Offline : 2
},
input : {
Error : 1,
loginError : 2
},
} as const
type ReportProps = {
level: ReportLevel // ReportLevel
category: ReportCategory // Report Category
status: ReportStatus // ReportStatus
verbose: string
options: ReportOptions
}
type ReportOptions = Partial<{
component: string
}>
type ReportStatus = number
/** A report is used to store an issue. A status code is stored, issue level, and more.
* A Report can be used to create a Notification, i.e., a visualisation of the issue that
* is presented to the user.
*
*/
class Report implements ReportProps {
options: ReportOptions
verbose: string
level: ReportLevel
status: ReportStatus
category: ReportCategory
categoryName: string
/**
* @param {ReportLevel} level - the report level (ERROR, WARNING or INFO)
* @param {ReportCategory} category - the report category (FRONTEND, BACKEND, USER)
* @param {number} status - the status code (a number, e.g. 2 means operation not supported)
* @param {object} options - additional information used for translation (an object containing e.g. a component name)
* @param {string} verbose - additional html or string content (additional text that is displayed)
*/
constructor(level: ReportLevel, category: ReportCategory, status: ReportStatus, options?: ReportOptions, verbose?: string) {
this.options = options || {}
this.level = level
this.status = status
this.verbose = verbose || ""
this.category = category
this.categoryName = reportCategoryName[category]
}
static error(category: ReportCategory, status: ReportStatus, options?: ReportOptions, message?: string) { return new Report(ReportLevel.ERROR, category, status, options, message) }
static warning(category: ReportCategory, status: ReportStatus, options?: ReportOptions, message?: string) { return new Report(ReportLevel.WARNING, category, status, options, message) }
static info(category: ReportCategory, status: ReportStatus, options?: ReportOptions, message?: string) { return new Report(ReportLevel.INFO, category, status, options, message) }
static get user() { return ReportCategory.USER }
static get frontend() { return ReportCategory.FRONTEND }
static get backend() { return ReportCategory.BACKEND }
static get input() { return ReportCategory.INPUT }
static get code() { return ReportStatus }
static isReport(obj: any) {
return obj !== null && obj instanceof Report
}
static isJsError(error: any) {
return Types.isObject(error) && (
error.name === "RangeError" || // a number is outside an allowable range of values
error.name === "ReferenceError" || // variable is undefined
error.name === "SyntaxError" || // syntax error occurs during parsing/compile time
error.name === "TypeError" || // occurs when an operation is performed on a wrong data type
error.name === "URIError" || // indicates that one of the global URI handling functions was used in a way that is incompatible with its definition.
error.name === "EvalError" || // identify errors when using the global eval() function.
error.name === "InternalError" || // This error occurs internally in the JS engine, especially when it has too much data to handle and the stack grows way over its critical limit."
error.name === "AggregateError" // an instance representing several errors wrapped in a single error when multiple errors need to be reported by an operation
)
}
static toSpecErrors(error: any) {
if (error instanceof ApolloError) {
const graphQLErrors = error.graphQLErrors || []
const errors = graphQLErrors.filter(error => error?.extensions?.classification == "FROM_SPEC")
return errors
}
else return []
}
/** create a suitable report given an error. */
static from(error: any, translator: T | undefined, args: Partial, processKey?: string) {
const t = (str: string) => {
if (translator) {
return translator.toError(processKey || "none", str, str)
} else {
str
}
}
function toReport(error: any, args: Partial) {
// create report
const r = Report.defaults.Whoops.with(args)
// prioritize spec errors
if (error instanceof ApolloError) {
const specErrors = Report.toSpecErrors(error)
if (specErrors.length > 0) {
const msg = specErrors[0].message
return Report.error(Report.user, 1, {}, t(msg));
}
}
if (error instanceof Error) {
// some reports need to be converted
if (args.category == Report.backend) {
if (error.message.match(/.*Invalid.*username.*password.*/i))
return Report.error(Report.input, Report.code.input.loginError)
else if (error.message.match(/failed to fetch.*/i))
return Report.error(Report.backend, Report.code.backend.Offline)
else if (error.message.match(/Internal Server Error.*/i))
return Report.error(Report.backend, Report.code.backend.Error)
else if (error.message.match(/exception while fetching.*/i))
return Report.error(Report.frontend, Report.code.frontend.Apollo, {},
t(error.message.replace(/exception while fetching(.*?):[\ ]*/i, "")))
else
return Report.error(Report.backend, Report.code.backend.Error, {}, t(error.message));
}
return new Report(r.level, r.category, r.status, r.options, t(error.message))
} else if (typeof error == 'string') {
return new Report(r.level, r.category, r.status, r.options, t(error))
} else
throw new Error("Cannot create report from provided data.")
}
// set defaults
const defaultArgs = { category: Report.frontend }
const nargs = merge.all([defaultArgs, args])
const report = toReport(error, nargs)
console.debug("From issue %o created %o", error.message, report.verboseMessage)
return report
}
static equals(a: any, b: any) {
if (Report.isReport(a) && Report.isReport(b))
return a.status === b.status && a.category === b.category && a.level === b.level
else
return false
}
addToNotifier(notifier: Notifier, overrideOptions = {}) {
const options = {
type: this.notificationType,
message: this.message,
details: this.verbose,
...Types.asObject(overrideOptions)
}
switch (options.type) {
case NotificationType.INFO:
notifier.info(options.message)
break
case NotificationType.WARNING:
notifier.warning(options.message)
break
case NotificationType.ERROR:
notifier.error(options.message)
break
case NotificationType.CLOUD:
default:
notifier.message(options.message)
break
}
}
with(args: Partial = {}) {
const ops = args?.options ? merge.all([this.options, args.options]) : this.options
return new Report(
args?.level || this.level,
args?.category || this.category,
args?.status || this.status,
ops,
args?.verbose == undefined ? this.verbose : args.verbose
)
}
toNotification(options: Partial = {}) {
const ops = Types.asObject(options)
const notificationDefaults = {
type: this.notificationType,
message: this.message,
details: this.verbose
}
return
}
get notificationType(): NotificationType {
if (this.category === ReportCategory.BACKEND)
return NotificationType.CLOUD
switch (this.level) {
case ReportLevel.ERROR: return NotificationType.ERROR
case ReportLevel.WARNING: return NotificationType.WARNING
case ReportLevel.INFO: return NotificationType.INFO
}
}
get code() { return "error." + this.categoryName + "." + this.status }
get verboseMessage() { return this.message?.replace(/\.+$/, "") + (this.verbose ? ": " + this.verbose : "") }
get message() {
if (this.category == Report.user) {
return this.verbose
} else {
// first convert component name to allow translation
const ops = this.options.component ? {...this.options, component: t('component.' + this.options.component) } : this.options
// then translate the error
const translation = t(this.code, ops)
if (this.status === undefined)
return "An unspecified error occured." // this option is for when translations have not been loaded.
else if (translation == this.code)
return "You are not authorized to view this page." // this option is for when translations have not been loaded.
else
return translation
}
}
get details() { return this.verbose } // TODO: this is not yet used
static defaults = {
Whoops: Report.error(ReportCategory.FRONTEND, Report.code.frontend.Whoops),
NotImplemented: Report.warning(ReportCategory.FRONTEND, Report.code.frontend.NotSupported),
NotAuthorized: Report.error(ReportCategory.FRONTEND, Report.code.frontend.NotAuthorized),
}
}
export default Report