Refactor SWR mutation handling: replace Error with NetworkError for improved error management and add error handling in fetchers

master^2
Tudor Stanciu 2024-11-16 04:05:16 +02:00
parent 8f9c03f400
commit 82a4750c76
7 changed files with 63 additions and 19 deletions

View File

@ -14,7 +14,7 @@ import {
RestartMachine, RestartMachine,
ShutdownMachine ShutdownMachine
} from "types"; } from "types";
import { Key, mutationFetcher, useSWRMutation } from "units/swr"; import { Key, NetworkError, mutationFetcher, useSWRMutation } from "units/swr";
import { usePingTrigger } from "../hooks"; import { usePingTrigger } from "../hooks";
type Props = { type Props = {
@ -34,6 +34,7 @@ const MachineContainer: React.FC<Props> = ({ machine, viewMode }) => {
const manageActionResponse = useCallback( const manageActionResponse = useCallback(
(response: MachineActionResult) => { (response: MachineActionResult) => {
debugger;
addLog(`Success: ${response.success}. Status: ${response.status}`); addLog(`Success: ${response.success}. Status: ${response.status}`);
if (response.success) { if (response.success) {
blip.success(response.status); blip.success(response.status);
@ -48,20 +49,23 @@ const MachineContainer: React.FC<Props> = ({ machine, viewMode }) => {
onSuccess: manageActionResponse onSuccess: manageActionResponse
}); });
const { trigger: shutdownMachineTrigger } = useSWRMutation<MachineShutdown, Error, Key, ShutdownMachine>( const { trigger: shutdownMachineTrigger } = useSWRMutation<MachineShutdown, NetworkError, Key, ShutdownMachine>(
endpoints.network.machine.shutdown, endpoints.network.machine.shutdown,
mutationFetcher<ShutdownMachine>, mutationFetcher<ShutdownMachine>,
{ {
onError: err => blip.error(err.message), onError: err => {
debugger;
blip.error(err.message);
},
onSuccess: manageActionResponse onSuccess: manageActionResponse
} }
); );
const { trigger: restartMachineTrigger } = useSWRMutation<MachineRestarted, Error, Key, RestartMachine>( const { trigger: restartMachineTrigger } = useSWRMutation<MachineRestarted, NetworkError, Key, RestartMachine>(
endpoints.network.machine.restart, endpoints.network.machine.restart,
mutationFetcher<RestartMachine>, mutationFetcher<RestartMachine>,
{ {
onError: err => blip.error(err.message), onError: err => blip.error(err.serverError.message || err.message),
onSuccess: manageActionResponse onSuccess: manageActionResponse
} }
); );

View File

@ -7,7 +7,7 @@ import { msToMinAndSec } from "../../../../utils/time";
import { endpoints } from "../../../../utils/api"; import { endpoints } from "../../../../utils/api";
import { Machine, MachineWaked, WakeMachine } from "types"; import { Machine, MachineWaked, WakeMachine } from "types";
import { usePingTrigger } from "../../hooks"; import { usePingTrigger } from "../../hooks";
import { Key, mutationFetcher, useSWRMutation } from "units/swr"; import { Key, mutationFetcher, NetworkError, useSWRMutation } from "units/swr";
const initialState = { on: false }; const initialState = { on: false };
const defaultPingInterval = 1200000; //20 minutes const defaultPingInterval = 1200000; //20 minutes
@ -58,7 +58,7 @@ const WakeComponent: React.FC<Props> = ({ machine, addLog, disabled }) => {
} }
}); });
const { trigger: wakeMachineTrigger } = useSWRMutation<MachineWaked, Error, Key, WakeMachine>( const { trigger: wakeMachineTrigger } = useSWRMutation<MachineWaked, NetworkError, Key, WakeMachine>(
endpoints.network.machine.wake, endpoints.network.machine.wake,
mutationFetcher<WakeMachine>, mutationFetcher<WakeMachine>,
{ {

View File

@ -1,6 +1,6 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { MachinePinged, PingMachine } from "types"; 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 { blip } from "utils";
import { endpoints } from "utils/api"; import { endpoints } from "utils/api";
@ -12,7 +12,7 @@ const usePingTrigger = (options: PingTriggerOptions) => {
const { onSuccess } = options; const { onSuccess } = options;
const onError = useMemo(() => options.onError || ((error: Error) => blip.error(error.message)), [options.onError]); const onError = useMemo(() => options.onError || ((error: Error) => blip.error(error.message)), [options.onError]);
const { trigger: pingMachineTrigger } = useSWRMutation<MachinePinged, Error, Key, PingMachine>( const { trigger: pingMachineTrigger } = useSWRMutation<MachinePinged, NetworkError, Key, PingMachine>(
endpoints.network.machine.ping, endpoints.network.machine.ping,
mutationFetcher<PingMachine>, mutationFetcher<PingMachine>,
{ {

View File

@ -3,17 +3,21 @@ import CacheSettingsComponent from "./CacheSettingsComponent";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { endpoints } from "utils/api"; import { endpoints } from "utils/api";
import { blip } from "utils"; import { blip } from "utils";
import { useSWRMutation, mutationFetcher, Key } from "units/swr"; import { useSWRMutation, mutationFetcher, Key, NetworkError } from "units/swr";
const CacheSettingsContainer: React.FC = () => { const CacheSettingsContainer: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { trigger } = useSWRMutation<void, Error, Key, void>(endpoints.system.resetCache, mutationFetcher<void>, { const { trigger } = useSWRMutation<void, NetworkError, Key, void>(
onError: err => { endpoints.system.resetCache,
blip.error(err.message); mutationFetcher<void>,
}, {
onSuccess: () => blip.info(t("Settings.Cache.ResetInfo")) onError: err => {
}); blip.error(err.message);
},
onSuccess: () => blip.info(t("Settings.Cache.ResetInfo"))
}
);
const handleResetCache = useCallback(() => trigger(), [trigger]); const handleResetCache = useCallback(() => trigger(), [trigger]);

View File

@ -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;
}
}

View File

@ -1,5 +1,6 @@
import i18next from "i18next"; import i18next from "i18next";
import { acquire as fetchTuitioData } from "@flare/tuitio-client"; import { acquire as fetchTuitioData } from "@flare/tuitio-client";
import { NetworkError, ServerError } from "./errors";
const getHeaders = (): HeadersInit => { const getHeaders = (): HeadersInit => {
const { token } = fetchTuitioData(); const { token } = fetchTuitioData();
@ -20,18 +21,36 @@ const getHeaders = (): HeadersInit => {
return headers; 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<Command>(url: string, { arg }: { arg: Command }) { async function mutationFetcher<Command>(url: string, { arg }: { arg: Command }) {
const hasBody = arg !== undefined && arg !== null; const hasBody = arg !== undefined && arg !== null;
const headers = getHeaders(); const headers = getHeaders();
const body = hasBody ? JSON.stringify(arg) : undefined; const body = hasBody ? JSON.stringify(arg) : undefined;
return fetch(url, { const res = await fetch(url, {
method: "POST", method: "POST",
headers, headers,
body 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 }; export { fetcher, mutationFetcher };

View File

@ -3,6 +3,7 @@ import type { Key } from "swr";
import useSWRMutation from "swr/mutation"; import useSWRMutation from "swr/mutation";
export * from "./fetchers"; export * from "./fetchers";
export * from "./errors";
export { useSWR, useSWRMutation }; export { useSWR, useSWRMutation };
export type { Key }; export type { Key };