From 82a4750c76fc3e35c8d4e721f3cb48c66ee4c363 Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Sat, 16 Nov 2024 04:05:16 +0200 Subject: [PATCH] Refactor SWR mutation handling: replace Error with NetworkError for improved error management and add error handling in fetchers --- .../machines/components/MachineContainer.tsx | 14 +++++++---- .../components/common/WakeComponent.tsx | 4 +-- .../features/machines/hooks/usePingTrigger.ts | 4 +-- .../system/CacheSettingsContainer.tsx | 18 +++++++------ frontend/src/units/swr/errors.ts | 16 ++++++++++++ frontend/src/units/swr/fetchers.ts | 25 ++++++++++++++++--- frontend/src/units/swr/index.ts | 1 + 7 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 frontend/src/units/swr/errors.ts diff --git a/frontend/src/features/machines/components/MachineContainer.tsx b/frontend/src/features/machines/components/MachineContainer.tsx index 3328a46..9eced98 100644 --- a/frontend/src/features/machines/components/MachineContainer.tsx +++ b/frontend/src/features/machines/components/MachineContainer.tsx @@ -14,7 +14,7 @@ import { RestartMachine, ShutdownMachine } from "types"; -import { Key, mutationFetcher, useSWRMutation } from "units/swr"; +import { Key, NetworkError, mutationFetcher, useSWRMutation } from "units/swr"; import { usePingTrigger } from "../hooks"; type Props = { @@ -34,6 +34,7 @@ const MachineContainer: React.FC = ({ machine, viewMode }) => { const manageActionResponse = useCallback( (response: MachineActionResult) => { + debugger; addLog(`Success: ${response.success}. Status: ${response.status}`); if (response.success) { blip.success(response.status); @@ -48,20 +49,23 @@ const MachineContainer: React.FC = ({ machine, viewMode }) => { onSuccess: manageActionResponse }); - const { trigger: shutdownMachineTrigger } = useSWRMutation( + const { trigger: shutdownMachineTrigger } = useSWRMutation( endpoints.network.machine.shutdown, mutationFetcher, { - onError: err => blip.error(err.message), + onError: err => { + debugger; + blip.error(err.message); + }, onSuccess: manageActionResponse } ); - const { trigger: restartMachineTrigger } = useSWRMutation( + const { trigger: restartMachineTrigger } = useSWRMutation( endpoints.network.machine.restart, mutationFetcher, { - onError: err => blip.error(err.message), + onError: err => blip.error(err.serverError.message || err.message), onSuccess: manageActionResponse } ); diff --git a/frontend/src/features/machines/components/common/WakeComponent.tsx b/frontend/src/features/machines/components/common/WakeComponent.tsx index 3f3843d..3e1aa9e 100644 --- a/frontend/src/features/machines/components/common/WakeComponent.tsx +++ b/frontend/src/features/machines/components/common/WakeComponent.tsx @@ -7,7 +7,7 @@ import { msToMinAndSec } from "../../../../utils/time"; import { endpoints } from "../../../../utils/api"; import { Machine, MachineWaked, WakeMachine } from "types"; import { usePingTrigger } from "../../hooks"; -import { Key, mutationFetcher, useSWRMutation } from "units/swr"; +import { Key, mutationFetcher, NetworkError, useSWRMutation } from "units/swr"; const initialState = { on: false }; const defaultPingInterval = 1200000; //20 minutes @@ -58,7 +58,7 @@ const WakeComponent: React.FC = ({ machine, addLog, disabled }) => { } }); - const { trigger: wakeMachineTrigger } = useSWRMutation( + const { trigger: wakeMachineTrigger } = useSWRMutation( endpoints.network.machine.wake, mutationFetcher, { diff --git a/frontend/src/features/machines/hooks/usePingTrigger.ts b/frontend/src/features/machines/hooks/usePingTrigger.ts index 110c787..c9264ea 100644 --- a/frontend/src/features/machines/hooks/usePingTrigger.ts +++ b/frontend/src/features/machines/hooks/usePingTrigger.ts @@ -1,6 +1,6 @@ import { useMemo } from "react"; import { MachinePinged, PingMachine } from "types"; -import { Key, mutationFetcher, useSWRMutation } from "units/swr"; +import { Key, mutationFetcher, NetworkError, useSWRMutation } from "units/swr"; import { blip } from "utils"; import { endpoints } from "utils/api"; @@ -12,7 +12,7 @@ const usePingTrigger = (options: PingTriggerOptions) => { const { onSuccess } = options; const onError = useMemo(() => options.onError || ((error: Error) => blip.error(error.message)), [options.onError]); - const { trigger: pingMachineTrigger } = useSWRMutation( + const { trigger: pingMachineTrigger } = useSWRMutation( endpoints.network.machine.ping, mutationFetcher, { diff --git a/frontend/src/features/settings/system/CacheSettingsContainer.tsx b/frontend/src/features/settings/system/CacheSettingsContainer.tsx index a423589..2ee1ee1 100644 --- a/frontend/src/features/settings/system/CacheSettingsContainer.tsx +++ b/frontend/src/features/settings/system/CacheSettingsContainer.tsx @@ -3,17 +3,21 @@ import CacheSettingsComponent from "./CacheSettingsComponent"; import { useTranslation } from "react-i18next"; import { endpoints } from "utils/api"; import { blip } from "utils"; -import { useSWRMutation, mutationFetcher, Key } from "units/swr"; +import { useSWRMutation, mutationFetcher, Key, NetworkError } from "units/swr"; const CacheSettingsContainer: React.FC = () => { const { t } = useTranslation(); - const { trigger } = useSWRMutation(endpoints.system.resetCache, mutationFetcher, { - onError: err => { - blip.error(err.message); - }, - onSuccess: () => blip.info(t("Settings.Cache.ResetInfo")) - }); + const { trigger } = useSWRMutation( + endpoints.system.resetCache, + mutationFetcher, + { + onError: err => { + blip.error(err.message); + }, + onSuccess: () => blip.info(t("Settings.Cache.ResetInfo")) + } + ); const handleResetCache = useCallback(() => trigger(), [trigger]); diff --git a/frontend/src/units/swr/errors.ts b/frontend/src/units/swr/errors.ts new file mode 100644 index 0000000..368b2d4 --- /dev/null +++ b/frontend/src/units/swr/errors.ts @@ -0,0 +1,16 @@ +export type ServerError = { + title: string; + status: number; + message: string | null; +}; + +export class NetworkError extends Error { + status: number; + serverError: ServerError; + + constructor(message: string, status: number, serverError: ServerError) { + super(message); + this.status = status; + this.serverError = serverError; + } +} diff --git a/frontend/src/units/swr/fetchers.ts b/frontend/src/units/swr/fetchers.ts index 23f7bd9..bec9af1 100644 --- a/frontend/src/units/swr/fetchers.ts +++ b/frontend/src/units/swr/fetchers.ts @@ -1,5 +1,6 @@ import i18next from "i18next"; import { acquire as fetchTuitioData } from "@flare/tuitio-client"; +import { NetworkError, ServerError } from "./errors"; const getHeaders = (): HeadersInit => { const { token } = fetchTuitioData(); @@ -20,18 +21,36 @@ const getHeaders = (): HeadersInit => { return headers; }; -const fetcher = (url: string) => fetch(url, { method: "GET", headers: getHeaders() }).then(res => res.json()); +const fetcher = async (url: string) => { + const res = await fetch(url, { method: "GET", headers: getHeaders() }); + + if (!res.ok) { + const serverError = (await res.json()) as ServerError; + const error = new NetworkError("An error occurred while fetching the data.", res.status, serverError); + throw error; + } + + return res.json(); +}; async function mutationFetcher(url: string, { arg }: { arg: Command }) { const hasBody = arg !== undefined && arg !== null; const headers = getHeaders(); const body = hasBody ? JSON.stringify(arg) : undefined; - return fetch(url, { + const res = await fetch(url, { method: "POST", headers, body - }).then(res => res.json()); + }); + + if (!res.ok) { + const serverError = (await res.json()) as ServerError; + const error = new NetworkError("An error occurred while mutating the data.", res.status, serverError); + throw error; + } + + return res.json(); } export { fetcher, mutationFetcher }; diff --git a/frontend/src/units/swr/index.ts b/frontend/src/units/swr/index.ts index 80fe9fd..f5d3e5c 100644 --- a/frontend/src/units/swr/index.ts +++ b/frontend/src/units/swr/index.ts @@ -3,6 +3,7 @@ import type { Key } from "swr"; import useSWRMutation from "swr/mutation"; export * from "./fetchers"; +export * from "./errors"; export { useSWR, useSWRMutation }; export type { Key };