Skip to content

Commit 53272cb

Browse files
committed
Added draft error handler
1 parent c55f45a commit 53272cb

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-0
lines changed

library/src/utils/ErrorHandler.tsx

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
import { type AxiosError, isAxiosError } from "axios"
2+
import { Button, showErrorFlag } from "../components"
3+
4+
// in the handler function the returned boolean states if after the handler the error handling is done
5+
6+
// this comes usually from generated/index.ts
7+
export interface DomainError {
8+
error?: string
9+
/** @format int32 */
10+
httpStatusCode?: number
11+
id?:
12+
| "UNKNOWN_ERROR"
13+
| "JIRA_CLIENT_ERROR"
14+
| "INSIGHT_CLIENT_ERROR"
15+
| "CONFIGURATION_ERROR"
16+
| "TICKET_COLLISION"
17+
| "TICKET_MAX_BOOKING_DURATION_EXCEEDED"
18+
| "TICKET_MAX_BOOKING_AMOUNT_EXCEEDED"
19+
| "TICKET_NOT_FOUND"
20+
| "TICKET_INVALID_DATA"
21+
| "NO_CREATE_PERMISSION"
22+
| "NO_EDIT_PERMISSION"
23+
| "NO_DELETE_PERMISSION"
24+
| "WEEKEND_DISABLED"
25+
| "PAST_DISABLED"
26+
| "PROJECT_NOT_FOUND"
27+
| "PROJECT_EXISTS"
28+
| "ALREADY_EXISTS"
29+
| "OBJECT_NOT_FOUND"
30+
| "FAILED_VALIDATION"
31+
information?: string
32+
message?: string
33+
stackTrace?: string
34+
}
35+
36+
// this is to suppress the error flags in case of certain errors
37+
type SpecialErrorCases = Partial<
38+
Record<
39+
NonNullable<DomainError["id"]>,
40+
(err: Error) => boolean // true states that the error handling is done and no error flag should be shown
41+
>
42+
>
43+
44+
/*const ErrorHandlerFunctions: Partial<
45+
Record<
46+
NonNullable<DomainError["id"]>,
47+
((err: Error, queryClient: QueryClient) => boolean) | undefined
48+
>
49+
> = {
50+
TICKET_COLLISION: () => true, // true states that the error handling is done and no error flag should be shown
51+
} as const*/
52+
53+
export function isDomainError(err: unknown): err is DomainError {
54+
return !!(
55+
(err as DomainError).id !== undefined ||
56+
(err as DomainError).information ||
57+
(err as DomainError).stackTrace ||
58+
(err as DomainError).message ||
59+
(err as DomainError).error
60+
)
61+
}
62+
63+
export class ErrorHandler {
64+
private static instance: ErrorHandler | undefined
65+
//queryClient: QueryClient | undefined
66+
67+
specialErrorCases: SpecialErrorCases | undefined
68+
69+
private constructor(specialErrorCases?: SpecialErrorCases) {
70+
this.specialErrorCases = specialErrorCases
71+
}
72+
73+
/*static setQueryClient(queryClient: QueryClient) {
74+
ErrorHandler.getHandler().queryClient = queryClient
75+
}*/
76+
77+
static getHandler() {
78+
if (!ErrorHandler.instance) {
79+
ErrorHandler.instance = new ErrorHandler()
80+
}
81+
return ErrorHandler.instance
82+
}
83+
84+
static handleError(error: unknown | Error | AxiosError, caller?: string) {
85+
ErrorHandler.getHandler().handleError(error, caller)
86+
}
87+
88+
handleError(error: unknown | Error | AxiosError, caller?: string) {
89+
/*if (!this.queryClient) {
90+
console.error("No queryClient set yet in error handler")
91+
return
92+
}*/
93+
console.error(`${caller ? `${caller} - ` : ""}response error: ${error}`)
94+
if (isAxiosError(error)) {
95+
// logged out
96+
if (error.response?.status === 401) {
97+
window.location.href = "/login.jsp"
98+
return
99+
}
100+
101+
let errorObject = error.response?.data
102+
if (!errorObject) {
103+
showErrorFlag({
104+
title: "Unbekannter Fehlertyp",
105+
autoClose: false,
106+
description: `${error.name}: ${error.message}`,
107+
})
108+
return
109+
}
110+
if (typeof errorObject === "string") {
111+
try {
112+
errorObject = JSON.parse(errorObject)
113+
} catch (e) {
114+
console.error(
115+
`${
116+
caller ? `${caller} - ` : ""
117+
}response error was not in error format: ${e}`,
118+
error.response,
119+
)
120+
showErrorFlag({
121+
title: "Fehler",
122+
description: error.response?.data,
123+
autoClose: false,
124+
})
125+
}
126+
}
127+
128+
if (!isDomainError(errorObject)) {
129+
showErrorFlag({
130+
title: "Unbekannter Fehlertyp",
131+
autoClose: false,
132+
description: `${error.name}: ${error.message}`,
133+
})
134+
return
135+
}
136+
137+
if (errorObject.id !== undefined) {
138+
const handler = this.specialErrorCases?.[errorObject.id]
139+
if (handler?.(error)) {
140+
return
141+
}
142+
}
143+
144+
const { beforeJSON, extractedJSON, afterJSON } =
145+
extractJSONObjectFromString(errorObject?.message)
146+
147+
let objectKey = ""
148+
if (extractedJSON) {
149+
try {
150+
const parsed = JSON.parse(extractedJSON) as { key?: string }
151+
objectKey = parsed.key ?? ""
152+
} catch (e) {
153+
console.error(
154+
"ErrorHandler - Error parsing extracted JSON: ",
155+
e,
156+
extractedJSON,
157+
)
158+
}
159+
}
160+
161+
if (errorObject.stackTrace) {
162+
console.error(
163+
`${caller ? `${caller} - ` : ""}response error stack trace: ${errorObject.stackTrace}`,
164+
)
165+
}
166+
showErrorFlag({
167+
title: "Fehler",
168+
autoClose: false,
169+
description: (
170+
<div className="max-h-96 overflow-auto flex flex-col gap-6">
171+
{!extractedJSON && errorObject.message && (
172+
<div
173+
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
174+
dangerouslySetInnerHTML={{
175+
__html: errorObject.message,
176+
}}
177+
/>
178+
)}
179+
{extractedJSON && (
180+
<div>
181+
<p>{beforeJSON}</p>
182+
{objectKey && (
183+
<a
184+
href={`/browse/${objectKey}`}
185+
target="_blank"
186+
rel="noreferrer"
187+
>
188+
{objectKey}
189+
</a>
190+
)}
191+
<p>
192+
<Button
193+
onClick={() => {
194+
openTab(
195+
new Blob([extractedJSON], {
196+
type: "application/json",
197+
}),
198+
)
199+
}}
200+
>
201+
Debug-Daten
202+
</Button>
203+
</p>
204+
205+
{afterJSON && <p>{afterJSON}</p>}
206+
</div>
207+
)}
208+
{errorObject.stackTrace && (
209+
<div>
210+
<Button
211+
onClick={() => {
212+
openTab(
213+
new Blob(
214+
[
215+
errorObject.stackTrace ??
216+
"no stacktrace",
217+
],
218+
{
219+
type: "text/plain",
220+
},
221+
),
222+
)
223+
}}
224+
>
225+
Stacktrace
226+
</Button>
227+
</div>
228+
)}
229+
{errorObject.information && (
230+
<div
231+
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
232+
dangerouslySetInnerHTML={{
233+
__html:
234+
errorObject.information ??
235+
"No Error Information",
236+
}}
237+
/>
238+
)}
239+
</div>
240+
),
241+
})
242+
} else if (error instanceof Error) {
243+
showErrorFlag({
244+
title: error.name ?? "Unbekannter Fehler",
245+
autoClose: false,
246+
description: error.message ?? "Wenden Sie sich an den Admin",
247+
})
248+
} else if (typeof error === "string") {
249+
showErrorFlag({
250+
title: "Unbekannter Fehler",
251+
autoClose: false,
252+
description: error,
253+
})
254+
} else {
255+
showErrorFlag({
256+
title: "Unbekannter Fehler",
257+
autoClose: false,
258+
description: JSON.stringify(error),
259+
})
260+
}
261+
}
262+
}
263+
264+
function openTab(blob: Blob) {
265+
const url = URL.createObjectURL(blob)
266+
const newTab = window.open(url, "_blank")
267+
URL.revokeObjectURL(url)
268+
if (!newTab) {
269+
alert(
270+
"The new tab was blocked by the browser. Please enable pop-ups for this site.",
271+
)
272+
}
273+
}
274+
275+
function extractJSONObjectFromString(str: string | undefined) {
276+
if (!str) {
277+
return {
278+
beforeJSON: "No Error Message",
279+
extractedJSON: undefined,
280+
afterJSON: undefined,
281+
}
282+
}
283+
try {
284+
const matches = str.match(/([\s\S]*?)(\{[\s\S]+})([\s\S]*)/)
285+
if (matches) {
286+
const beforeJSON = matches[1] ?? ""
287+
const extractedJSON = JSON.stringify(
288+
JSON.parse(matches[2]),
289+
null,
290+
" ",
291+
) // format the json
292+
const afterJSON = matches[3] ?? ""
293+
return { beforeJSON, extractedJSON, afterJSON }
294+
}
295+
} catch (error) {
296+
return { beforeJSON: str }
297+
}
298+
return { beforeJSON: str }
299+
}

library/src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ export type { TimeType, DateTimeType, DateType }
1515
import { isDateTimeType, isDateType, isTimeType } from "./DateUtils"
1616
export { isTimeType, isDateTimeType, isDateType }
1717
export * as DateUtils from "./DateUtils"
18+
19+
export * from "./ErrorHandler"

0 commit comments

Comments
 (0)