From f12cb5604861137e4c5c8bd762d76396ad3aeba6 Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Fri, 12 Dec 2025 11:45:26 +0100 Subject: [PATCH 1/3] refactor: snackbar formatting with problem details Signed-off-by: Joris Mancini --- src/services/utils.ts | 20 +++++++------- src/translations/en/errorsEn.ts | 1 + src/translations/fr/errorsFr.ts | 2 ++ src/utils/error.ts | 27 ++++++++++++++----- .../{CustomError.ts => ProblemDetailError.ts} | 14 +++++++++- src/utils/types/index.ts | 2 +- 6 files changed, 47 insertions(+), 19 deletions(-) rename src/utils/types/{CustomError.ts => ProblemDetailError.ts} (77%) diff --git a/src/services/utils.ts b/src/services/utils.ts index 172cdf3a7..6de58e406 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -6,7 +6,7 @@ */ import { getUserToken } from '../redux/commonStore'; -import { CustomError } from '../utils/types/CustomError'; +import { ProblemDetailError } from '../utils/types/ProblemDetailError'; import { NetworkTimeoutError } from '../utils/types/NetworkTimeoutError'; const DEFAULT_TIMEOUT_MS = 50_000; @@ -56,20 +56,20 @@ const prepareRequest = (init: FetchInitWithTimeout | undefined, token?: string) return initWithSignal; }; -export const convertToCustomError = (response: string) => { - const errorJson = parseError(response); - if (errorJson?.businessErrorCode) { - return new CustomError( - `Server error: ${errorJson.detail}`, +export const convertToCustomError = (textError: string) => { + const errorJson = parseError(textError); + if (errorJson?.server && errorJson?.timestamp && errorJson?.traceId && errorJson?.detail) { + return new ProblemDetailError( + errorJson.detail, + errorJson.server, + new Date(errorJson.timestamp), + errorJson.traceId, errorJson.status, errorJson.businessErrorCode, errorJson.businessErrorValues ); } - if (errorJson?.detail) { - return new CustomError(`Server error: ${errorJson.detail}`, errorJson.status); - } - return new CustomError(errorJson); + return new Error(textError); }; const handleError = (response: Response) => { diff --git a/src/translations/en/errorsEn.ts b/src/translations/en/errorsEn.ts index 32a0d968c..1dbc107cc 100644 --- a/src/translations/en/errorsEn.ts +++ b/src/translations/en/errorsEn.ts @@ -7,4 +7,5 @@ export const errorsEn = { 'errors.network.timeout': 'The request timed out. Please try again.', + 'errors.technicalError': 'Server error: {message}\n (server: {serverName}, timestamp: {timestamp}, trace ID: {traceId})', }; diff --git a/src/translations/fr/errorsFr.ts b/src/translations/fr/errorsFr.ts index c40d24f15..589479bdd 100644 --- a/src/translations/fr/errorsFr.ts +++ b/src/translations/fr/errorsFr.ts @@ -7,4 +7,6 @@ export const errorsFr = { 'errors.network.timeout': 'La requête a expiré. Veuillez réessayer.', + 'errors.technicalError': + 'Erreur serveur: {message}\n (serveur: {serverName}, horodatage: {timestamp}, trace ID: {traceId})', }; diff --git a/src/utils/error.ts b/src/utils/error.ts index 2ba3fbec0..8d26350b8 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { SnackInputs, UseSnackMessageReturn } from '../hooks/useSnackMessage'; -import { CustomError, formatMessageValues } from './types/CustomError'; +import { ProblemDetailError, formatMessageValues } from './types/ProblemDetailError'; import { NetworkTimeoutError } from './types/NetworkTimeoutError'; export type HeaderSnackInputs = Pick; @@ -30,12 +30,25 @@ export function snackWithFallback( }); return; } - if (error instanceof CustomError && error.businessErrorCode) { - snackError({ - messageId: error.businessErrorCode, - messageValues: error.businessErrorValues ? formatMessageValues(error.businessErrorValues) : undefined, - ...headerInputs, - }); + if (error instanceof ProblemDetailError) { + if (error.businessErrorCode) { + snackError({ + messageId: error.businessErrorCode, + messageValues: error.businessErrorValues ? formatMessageValues(error.businessErrorValues) : undefined, + ...headerInputs, + }); + } else { + snackError({ + messageId: 'errors.technicalError', + messageValues: { + message: error.message, + serverName: error.serverName, + timestamp: error.timestamp.toLocaleString(), // It would require refactoring to adapt with GS language so we keep it like that for now + traceId: error.traceId, + }, + ...headerInputs, + }); + } } else { catchErrorHandler(error, (message) => { snackError({ diff --git a/src/utils/types/CustomError.ts b/src/utils/types/ProblemDetailError.ts similarity index 77% rename from src/utils/types/CustomError.ts rename to src/utils/types/ProblemDetailError.ts index ed3a530e6..ed686b016 100644 --- a/src/utils/types/CustomError.ts +++ b/src/utils/types/ProblemDetailError.ts @@ -4,7 +4,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export class CustomError extends Error { +export class ProblemDetailError extends Error { + serverName: string; + + timestamp: Date; + + traceId: string; + status?: number; businessErrorCode?: string; @@ -13,11 +19,17 @@ export class CustomError extends Error { constructor( message: string, + serverName: string, + timestamp: Date, + traceId: string, status?: number, businessErrorCode?: string, businessErrorValues?: Record ) { super(message); + this.serverName = serverName; + this.timestamp = timestamp; + this.traceId = traceId; this.status = status; this.businessErrorCode = businessErrorCode; this.businessErrorValues = businessErrorValues; diff --git a/src/utils/types/index.ts b/src/utils/types/index.ts index 7a3fa7aa1..47ef2b009 100644 --- a/src/utils/types/index.ts +++ b/src/utils/types/index.ts @@ -4,7 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export * from './CustomError'; +export * from './ProblemDetailError'; export * from './NetworkTimeoutError'; export * from './elementType'; export * from './equipmentType'; From 53124d9478112dcc7307e62e886b9fd86e48dac0 Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Fri, 12 Dec 2025 14:47:33 +0100 Subject: [PATCH 2/3] fix: add fallback on date parsing Signed-off-by: Joris Mancini --- src/services/utils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 6de58e406..b3d047661 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -59,10 +59,16 @@ const prepareRequest = (init: FetchInitWithTimeout | undefined, token?: string) export const convertToCustomError = (textError: string) => { const errorJson = parseError(textError); if (errorJson?.server && errorJson?.timestamp && errorJson?.traceId && errorJson?.detail) { + let date: Date = new Date(); // Fallback to current timestamp + try { + date = new Date(errorJson.timestamp); + } catch { + // Ignore + } return new ProblemDetailError( errorJson.detail, errorJson.server, - new Date(errorJson.timestamp), + date, errorJson.traceId, errorJson.status, errorJson.businessErrorCode, From 41dd47b65810919e21c3485308565a7bfedd7542 Mon Sep 17 00:00:00 2001 From: Joris Mancini Date: Fri, 12 Dec 2025 14:50:24 +0100 Subject: [PATCH 3/3] fix: prettier Signed-off-by: Joris Mancini --- src/translations/en/errorsEn.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/translations/en/errorsEn.ts b/src/translations/en/errorsEn.ts index 1dbc107cc..416ec7662 100644 --- a/src/translations/en/errorsEn.ts +++ b/src/translations/en/errorsEn.ts @@ -7,5 +7,6 @@ export const errorsEn = { 'errors.network.timeout': 'The request timed out. Please try again.', - 'errors.technicalError': 'Server error: {message}\n (server: {serverName}, timestamp: {timestamp}, trace ID: {traceId})', + 'errors.technicalError': + 'Server error: {message}\n (server: {serverName}, timestamp: {timestamp}, trace ID: {traceId})', };