Spaces:
Building
Building
Update flare-ui/src/app/services/error-handler.service.ts
Browse files
flare-ui/src/app/services/error-handler.service.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
// error-handler.service.ts
|
2 |
// Path: /flare-ui/src/app/services/error-handler.service.ts
|
3 |
|
4 |
import { ErrorHandler, Injectable, Injector } from '@angular/core';
|
@@ -22,172 +22,277 @@ export class GlobalErrorHandler implements ErrorHandler {
|
|
22 |
constructor(private injector: Injector) {}
|
23 |
|
24 |
handleError(error: Error | HttpErrorResponse): void {
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
}
|
38 |
}
|
39 |
|
40 |
private handleHttpError(error: HttpErrorResponse, snackBar: MatSnackBar, router: Router): void {
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
}
|
52 |
-
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
|
58 |
-
//
|
59 |
-
if (flareError
|
60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
}
|
62 |
-
return;
|
63 |
-
}
|
64 |
-
|
65 |
-
// Authentication error (401)
|
66 |
-
if (error.status === 401) {
|
67 |
-
snackBar.open(
|
68 |
-
'Your session has expired. Please login again.',
|
69 |
-
'Login',
|
70 |
-
{
|
71 |
-
duration: 5000,
|
72 |
-
panelClass: ['error-snackbar']
|
73 |
-
}
|
74 |
-
).onAction().subscribe(() => {
|
75 |
-
router.navigate(['/login']);
|
76 |
-
});
|
77 |
-
return;
|
78 |
-
}
|
79 |
-
|
80 |
-
// Validation error (422)
|
81 |
-
if (error.status === 422 && flareError.details) {
|
82 |
-
const fieldErrors = flareError.details
|
83 |
-
.map((d: any) => `${d.field}: ${d.message}`)
|
84 |
-
.join('\n');
|
85 |
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
94 |
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
|
|
|
|
|
117 |
snackBar.open(
|
118 |
-
|
119 |
'Close',
|
120 |
{
|
121 |
-
duration:
|
122 |
-
panelClass: ['error-snackbar'
|
123 |
}
|
124 |
);
|
125 |
-
|
|
|
|
|
126 |
}
|
127 |
-
|
128 |
-
// Generic HTTP error
|
129 |
-
snackBar.open(
|
130 |
-
flareError.message || `HTTP Error ${error.status}: ${error.statusText}`,
|
131 |
-
'Close',
|
132 |
-
{
|
133 |
-
duration: 6000,
|
134 |
-
panelClass: ['error-snackbar']
|
135 |
-
}
|
136 |
-
);
|
137 |
}
|
138 |
|
139 |
private handleClientError(error: Error, snackBar: MatSnackBar): void {
|
140 |
-
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
snackBar.open(
|
143 |
-
'
|
144 |
-
'
|
145 |
{
|
146 |
-
duration:
|
147 |
-
panelClass: ['error-snackbar'
|
148 |
}
|
149 |
).onAction().subscribe(() => {
|
150 |
window.location.reload();
|
151 |
});
|
152 |
-
|
|
|
|
|
153 |
}
|
154 |
-
|
155 |
-
|
|
|
156 |
snackBar.open(
|
157 |
-
'An
|
158 |
-
'
|
159 |
{
|
160 |
-
duration:
|
161 |
panelClass: ['error-snackbar']
|
162 |
}
|
163 |
-
)
|
164 |
-
window.location.reload();
|
165 |
-
});
|
166 |
}
|
167 |
}
|
168 |
|
169 |
// Error interceptor for consistent error format
|
170 |
-
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
|
171 |
import { Observable, throwError } from 'rxjs';
|
172 |
-
import { catchError } from 'rxjs/operators';
|
173 |
|
174 |
@Injectable()
|
175 |
export class ErrorInterceptor implements HttpInterceptor {
|
|
|
|
|
176 |
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
catchError((error: HttpErrorResponse) => {
|
179 |
// Log request details for debugging
|
180 |
console.error('HTTP Error:', {
|
181 |
url: req.url,
|
182 |
method: req.method,
|
183 |
status: error.status,
|
|
|
184 |
error: error.error,
|
185 |
-
requestId:
|
|
|
186 |
});
|
187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
// Re-throw to be handled by global error handler
|
189 |
-
return throwError(() =>
|
|
|
|
|
|
|
|
|
190 |
})
|
191 |
);
|
192 |
}
|
193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// error-handler.service.ts
|
2 |
// Path: /flare-ui/src/app/services/error-handler.service.ts
|
3 |
|
4 |
import { ErrorHandler, Injectable, Injector } from '@angular/core';
|
|
|
22 |
constructor(private injector: Injector) {}
|
23 |
|
24 |
handleError(error: Error | HttpErrorResponse): void {
|
25 |
+
try {
|
26 |
+
// Get services lazily to avoid circular dependency
|
27 |
+
const snackBar = this.injector.get(MatSnackBar);
|
28 |
+
const router = this.injector.get(Router);
|
29 |
+
|
30 |
+
console.error('Global error caught:', error);
|
31 |
+
|
32 |
+
// Handle HTTP errors
|
33 |
+
if (error instanceof HttpErrorResponse) {
|
34 |
+
this.handleHttpError(error, snackBar, router);
|
35 |
+
} else {
|
36 |
+
// Handle client-side errors
|
37 |
+
this.handleClientError(error, snackBar);
|
38 |
+
}
|
39 |
+
} catch (handlerError) {
|
40 |
+
// Fallback if error handler itself fails
|
41 |
+
console.error('Error in error handler:', handlerError);
|
42 |
+
console.error('Original error:', error);
|
43 |
}
|
44 |
}
|
45 |
|
46 |
private handleHttpError(error: HttpErrorResponse, snackBar: MatSnackBar, router: Router): void {
|
47 |
+
try {
|
48 |
+
const flareError = error.error as FlareError;
|
49 |
+
|
50 |
+
// Race condition error (409)
|
51 |
+
if (error.status === 409) {
|
52 |
+
const isRaceCondition = flareError?.error === 'RaceConditionError' ||
|
53 |
+
error.error?.type === 'race_condition';
|
54 |
+
|
55 |
+
if (isRaceCondition) {
|
56 |
+
const snackBarRef = snackBar.open(
|
57 |
+
flareError?.message || 'The data was modified by another user. Please refresh and try again.',
|
58 |
+
'Refresh',
|
59 |
+
{
|
60 |
+
duration: 0,
|
61 |
+
panelClass: ['error-snackbar', 'race-condition-snackbar']
|
62 |
+
}
|
63 |
+
);
|
64 |
+
|
65 |
+
snackBarRef.onAction().subscribe(() => {
|
66 |
+
window.location.reload();
|
67 |
+
});
|
68 |
+
|
69 |
+
// Show additional info if available
|
70 |
+
if (flareError?.details?.last_update_user) {
|
71 |
+
console.info(`Last updated by: ${flareError.details.last_update_user} at ${flareError.details.last_update_date}`);
|
72 |
+
}
|
73 |
+
return;
|
74 |
}
|
75 |
+
}
|
76 |
|
77 |
+
// Authentication error (401)
|
78 |
+
if (error.status === 401) {
|
79 |
+
snackBar.open(
|
80 |
+
'Your session has expired. Please login again.',
|
81 |
+
'Login',
|
82 |
+
{
|
83 |
+
duration: 5000,
|
84 |
+
panelClass: ['error-snackbar']
|
85 |
+
}
|
86 |
+
).onAction().subscribe(() => {
|
87 |
+
router.navigate(['/login']);
|
88 |
+
});
|
89 |
+
return;
|
90 |
+
}
|
91 |
|
92 |
+
// Validation error (422)
|
93 |
+
if (error.status === 422 && flareError?.details) {
|
94 |
+
const fieldErrors = Array.isArray(flareError.details)
|
95 |
+
? flareError.details.map((d: any) => `${d.field}: ${d.message}`).join('\n')
|
96 |
+
: 'Validation error occurred';
|
97 |
+
|
98 |
+
snackBar.open(
|
99 |
+
flareError.message || 'Validation failed. Please check your input.',
|
100 |
+
'Close',
|
101 |
+
{
|
102 |
+
duration: 8000,
|
103 |
+
panelClass: ['error-snackbar', 'validation-snackbar']
|
104 |
+
}
|
105 |
+
);
|
106 |
+
|
107 |
+
console.error('Validation errors:', flareError.details);
|
108 |
+
return;
|
109 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
|
111 |
+
// Not found error (404)
|
112 |
+
if (error.status === 404) {
|
113 |
+
snackBar.open(
|
114 |
+
flareError?.message || 'The requested resource was not found.',
|
115 |
+
'Close',
|
116 |
+
{
|
117 |
+
duration: 5000,
|
118 |
+
panelClass: ['error-snackbar']
|
119 |
+
}
|
120 |
+
);
|
121 |
+
return;
|
122 |
+
}
|
123 |
|
124 |
+
// Server errors (5xx)
|
125 |
+
if (error.status >= 500) {
|
126 |
+
const message = flareError?.message || 'A server error occurred. Please try again later.';
|
127 |
+
const requestId = flareError?.request_id || error.headers?.get('X-Request-ID');
|
128 |
+
|
129 |
+
snackBar.open(
|
130 |
+
requestId ? `${message} (Request ID: ${requestId})` : message,
|
131 |
+
'Close',
|
132 |
+
{
|
133 |
+
duration: 8000,
|
134 |
+
panelClass: ['error-snackbar', 'server-error-snackbar']
|
135 |
+
}
|
136 |
+
);
|
137 |
+
return;
|
138 |
+
}
|
139 |
+
|
140 |
+
// Network error (0 status usually indicates network issues)
|
141 |
+
if (error.status === 0) {
|
142 |
+
snackBar.open(
|
143 |
+
'Network connection error. Please check your internet connection.',
|
144 |
+
'Retry',
|
145 |
+
{
|
146 |
+
duration: 0,
|
147 |
+
panelClass: ['error-snackbar', 'network-error-snackbar']
|
148 |
+
}
|
149 |
+
).onAction().subscribe(() => {
|
150 |
+
window.location.reload();
|
151 |
+
});
|
152 |
+
return;
|
153 |
+
}
|
154 |
|
155 |
+
// Generic HTTP error
|
156 |
+
const errorMessage = flareError?.message || error.message || `HTTP Error ${error.status}: ${error.statusText}`;
|
157 |
snackBar.open(
|
158 |
+
errorMessage,
|
159 |
'Close',
|
160 |
{
|
161 |
+
duration: 6000,
|
162 |
+
panelClass: ['error-snackbar']
|
163 |
}
|
164 |
);
|
165 |
+
} catch (err) {
|
166 |
+
console.error('Error in handleHttpError:', err);
|
167 |
+
this.showGenericError(snackBar);
|
168 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
}
|
170 |
|
171 |
private handleClientError(error: Error, snackBar: MatSnackBar): void {
|
172 |
+
try {
|
173 |
+
// Check if it's a network error
|
174 |
+
if (error.message?.includes('NetworkError') || error.message?.includes('Failed to fetch')) {
|
175 |
+
snackBar.open(
|
176 |
+
'Network connection error. Please check your internet connection.',
|
177 |
+
'Retry',
|
178 |
+
{
|
179 |
+
duration: 0,
|
180 |
+
panelClass: ['error-snackbar', 'network-error-snackbar']
|
181 |
+
}
|
182 |
+
).onAction().subscribe(() => {
|
183 |
+
window.location.reload();
|
184 |
+
});
|
185 |
+
return;
|
186 |
+
}
|
187 |
+
|
188 |
+
// Check for specific Angular errors
|
189 |
+
if (error.name === 'HttpErrorResponse') {
|
190 |
+
// This might be an HTTP error that wasn't caught properly
|
191 |
+
this.handleHttpError(error as any, snackBar, this.injector.get(Router));
|
192 |
+
return;
|
193 |
+
}
|
194 |
+
|
195 |
+
// Generic client error
|
196 |
snackBar.open(
|
197 |
+
'An unexpected error occurred. Please refresh the page.',
|
198 |
+
'Refresh',
|
199 |
{
|
200 |
+
duration: 6000,
|
201 |
+
panelClass: ['error-snackbar']
|
202 |
}
|
203 |
).onAction().subscribe(() => {
|
204 |
window.location.reload();
|
205 |
});
|
206 |
+
} catch (err) {
|
207 |
+
console.error('Error in handleClientError:', err);
|
208 |
+
this.showGenericError(snackBar);
|
209 |
}
|
210 |
+
}
|
211 |
+
|
212 |
+
private showGenericError(snackBar: MatSnackBar): void {
|
213 |
snackBar.open(
|
214 |
+
'An error occurred. Please try again.',
|
215 |
+
'Close',
|
216 |
{
|
217 |
+
duration: 5000,
|
218 |
panelClass: ['error-snackbar']
|
219 |
}
|
220 |
+
);
|
|
|
|
|
221 |
}
|
222 |
}
|
223 |
|
224 |
// Error interceptor for consistent error format
|
225 |
+
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
|
226 |
import { Observable, throwError } from 'rxjs';
|
227 |
+
import { catchError, finalize } from 'rxjs/operators';
|
228 |
|
229 |
@Injectable()
|
230 |
export class ErrorInterceptor implements HttpInterceptor {
|
231 |
+
private activeRequests = new Map<string, AbortController>();
|
232 |
+
|
233 |
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
234 |
+
// Create abort controller for request cancellation
|
235 |
+
const requestId = this.generateRequestId();
|
236 |
+
const abortController = new AbortController();
|
237 |
+
this.activeRequests.set(requestId, abortController);
|
238 |
+
|
239 |
+
// Clone request with additional headers
|
240 |
+
const clonedReq = req.clone({
|
241 |
+
setHeaders: {
|
242 |
+
'X-Request-ID': requestId
|
243 |
+
}
|
244 |
+
});
|
245 |
+
|
246 |
+
return next.handle(clonedReq).pipe(
|
247 |
catchError((error: HttpErrorResponse) => {
|
248 |
// Log request details for debugging
|
249 |
console.error('HTTP Error:', {
|
250 |
url: req.url,
|
251 |
method: req.method,
|
252 |
status: error.status,
|
253 |
+
statusText: error.statusText,
|
254 |
error: error.error,
|
255 |
+
requestId: requestId,
|
256 |
+
headers: error.headers?.keys()
|
257 |
});
|
258 |
|
259 |
+
// Enhanced error object
|
260 |
+
const enhancedError = {
|
261 |
+
...error,
|
262 |
+
requestId: requestId,
|
263 |
+
timestamp: new Date().toISOString(),
|
264 |
+
url: req.url,
|
265 |
+
method: req.method
|
266 |
+
};
|
267 |
+
|
268 |
// Re-throw to be handled by global error handler
|
269 |
+
return throwError(() => enhancedError);
|
270 |
+
}),
|
271 |
+
finalize(() => {
|
272 |
+
// Clean up abort controller
|
273 |
+
this.activeRequests.delete(requestId);
|
274 |
})
|
275 |
);
|
276 |
}
|
277 |
+
|
278 |
+
private generateRequestId(): string {
|
279 |
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
280 |
+
}
|
281 |
+
|
282 |
+
// Method to cancel a specific request
|
283 |
+
cancelRequest(requestId: string): void {
|
284 |
+
const controller = this.activeRequests.get(requestId);
|
285 |
+
if (controller) {
|
286 |
+
controller.abort();
|
287 |
+
this.activeRequests.delete(requestId);
|
288 |
+
}
|
289 |
+
}
|
290 |
+
|
291 |
+
// Method to cancel all active requests
|
292 |
+
cancelAllRequests(): void {
|
293 |
+
this.activeRequests.forEach((controller) => {
|
294 |
+
controller.abort();
|
295 |
+
});
|
296 |
+
this.activeRequests.clear();
|
297 |
+
}
|
298 |
+
}
|