From d5dc0a00709e1780eb071985d15d85411f710fc3 Mon Sep 17 00:00:00 2001 From: "sp.wack" <83104063+amanape@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:25:31 +0400 Subject: [PATCH] feat: Better error message handling (#6502) --- frontend/src/query-client-config.ts | 6 +++--- frontend/src/types/react-query.d.ts | 8 ++++++++ frontend/src/utils/render-toast-if-error.ts | 19 +++++++++++++++++++ frontend/src/utils/type-guards.ts | 9 +++++++++ openhands/server/routes/settings.py | 10 ++++++---- 5 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 frontend/src/types/react-query.d.ts create mode 100644 frontend/src/utils/render-toast-if-error.ts create mode 100644 frontend/src/utils/type-guards.ts diff --git a/frontend/src/query-client-config.ts b/frontend/src/query-client-config.ts index 2dba240ddac54..cfb847e27d1e1 100644 --- a/frontend/src/query-client-config.ts +++ b/frontend/src/query-client-config.ts @@ -1,5 +1,5 @@ import { QueryClientConfig, QueryCache } from "@tanstack/react-query"; -import toast from "react-hot-toast"; +import { renderToastIfError } from "./utils/render-toast-if-error"; const QUERY_KEYS_TO_IGNORE = ["authenticated", "hosts"]; @@ -7,7 +7,7 @@ export const queryClientConfig: QueryClientConfig = { queryCache: new QueryCache({ onError: (error, query) => { if (!QUERY_KEYS_TO_IGNORE.some((key) => query.queryKey.includes(key))) { - toast.error(error.message); + renderToastIfError(error); } }, }), @@ -18,7 +18,7 @@ export const queryClientConfig: QueryClientConfig = { }, mutations: { onError: (error) => { - toast.error(error.message); + renderToastIfError(error); }, }, }, diff --git a/frontend/src/types/react-query.d.ts b/frontend/src/types/react-query.d.ts new file mode 100644 index 0000000000000..830a95a340b3e --- /dev/null +++ b/frontend/src/types/react-query.d.ts @@ -0,0 +1,8 @@ +import "@tanstack/react-query"; +import type { AxiosError } from "axios"; + +declare module "@tanstack/react-query" { + interface Register { + defaultError: AxiosError; + } +} diff --git a/frontend/src/utils/render-toast-if-error.ts b/frontend/src/utils/render-toast-if-error.ts new file mode 100644 index 0000000000000..08a4d7444518a --- /dev/null +++ b/frontend/src/utils/render-toast-if-error.ts @@ -0,0 +1,19 @@ +import { AxiosError } from "axios"; +import toast from "react-hot-toast"; +import { isAxiosErrorWithResponse } from "./type-guards"; + +/** + * Renders a toast with the error message from an Axios error + * @param error The error to render a toast for + */ +export const renderToastIfError = (error: AxiosError) => { + let errorMessage: string | null = null; + + if (isAxiosErrorWithResponse(error) && error.response?.data.error) { + errorMessage = error.response?.data.error; + } else { + errorMessage = error.message; + } + + toast.error(errorMessage || "An error occurred"); +}; diff --git a/frontend/src/utils/type-guards.ts b/frontend/src/utils/type-guards.ts new file mode 100644 index 0000000000000..93b4b4a655a42 --- /dev/null +++ b/frontend/src/utils/type-guards.ts @@ -0,0 +1,9 @@ +import { AxiosError } from "axios"; + +export const isAxiosErrorWithResponse = ( + error: AxiosError, +): error is AxiosError<{ error: string }> => + typeof error.response?.data === "object" && + error.response?.data !== null && + "error" in error.response.data && + typeof error.response?.data?.error === "string"; diff --git a/openhands/server/routes/settings.py b/openhands/server/routes/settings.py index 0c39e0243e275..c1dcfa5074cf2 100644 --- a/openhands/server/routes/settings.py +++ b/openhands/server/routes/settings.py @@ -66,7 +66,9 @@ async def store_settings( logger.warning(f'Invalid GitHub token: {e}') return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, - content={'error': 'Invalid GitHub token'}, + content={ + 'error': 'Invalid GitHub token. Please make sure it is valid.' + }, ) try: @@ -102,10 +104,10 @@ async def store_settings( await settings_store.store(settings) return response except Exception as e: - logger.warning(f'Invalid token: {e}') + logger.warning(f'Something went wrong storing settings: {e}') return JSONResponse( - status_code=status.HTTP_401_UNAUTHORIZED, - content={'error': 'Invalid token'}, + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content={'error': 'Something went wrong storing settings'}, )