diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 51a53e0f7..8caab7feb 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -623,7 +623,7 @@ export const FETCH_ERROR_DESCRIPION = "Check if everything is working properly and try again."; export const TIMEOUT_ERROR_MESSAGE = - "Please wait a few seconds to server process your request."; + "Please wait a few moments while the server processes your request."; export const TIMEOUT_ERROR_DESCRIPION = "Server is busy."; export const SIGN_UP_SUCCESS = "Account created! Await admin activation. "; diff --git a/src/frontend/src/controllers/API/queries/health/use-get-health.ts b/src/frontend/src/controllers/API/queries/health/use-get-health.ts index c43ab488c..044cecc65 100644 --- a/src/frontend/src/controllers/API/queries/health/use-get-health.ts +++ b/src/frontend/src/controllers/API/queries/health/use-get-health.ts @@ -18,10 +18,14 @@ interface getHealthResponse { variables: string; } +interface getHealthParams { + enableInterval?: boolean; +} + export const useGetHealthQuery: useQueryFunctionType< - undefined, + getHealthParams, getHealthResponse -> = (options) => { +> = (params, options) => { const { query } = UseRequestProcessor(); const setHealthCheckTimeout = useUtilityStore( (state) => state.setHealthCheckTimeout, @@ -40,9 +44,13 @@ export const useGetHealthQuery: useQueryFunctionType< setTimeout(() => reject(createNewError503()), SERVER_HEALTH_INTERVAL), ); - const apiPromise = api.get<{ data: getHealthResponse }>("/health"); + const apiPromise = api.get("/health"); const response = await Promise.race([apiPromise, timeoutPromise]); - setHealthCheckTimeout(null); + setHealthCheckTimeout( + Object.values(response.data).some((value) => value !== "ok") + ? "serverDown" + : null, + ); return response.data; } catch (error) { const isServerBusy = @@ -60,7 +68,9 @@ export const useGetHealthQuery: useQueryFunctionType< const queryResult = query(["useGetHealthQuery"], getHealthFn, { placeholderData: keepPreviousData, - refetchInterval: REFETCH_SERVER_HEALTH_INTERVAL, + refetchInterval: params.enableInterval + ? REFETCH_SERVER_HEALTH_INTERVAL + : false, retry: false, ...options, }); diff --git a/src/frontend/src/customization/components/custom-loading-page.tsx b/src/frontend/src/customization/components/custom-loading-page.tsx new file mode 100644 index 000000000..021e65d71 --- /dev/null +++ b/src/frontend/src/customization/components/custom-loading-page.tsx @@ -0,0 +1,3 @@ +export function CustomLoadingPage() { + return <>; +} diff --git a/src/frontend/src/customization/hooks/use-primary-loading.ts b/src/frontend/src/customization/hooks/use-custom-primary-loading.ts similarity index 83% rename from src/frontend/src/customization/hooks/use-primary-loading.ts rename to src/frontend/src/customization/hooks/use-custom-primary-loading.ts index 4b3853cd0..95564a078 100644 --- a/src/frontend/src/customization/hooks/use-primary-loading.ts +++ b/src/frontend/src/customization/hooks/use-custom-primary-loading.ts @@ -1,7 +1,7 @@ import { UseRequestProcessor } from "@/controllers/API/services/request-processor"; import { useQueryFunctionType } from "@/types/api"; -export const usePrimaryLoading: useQueryFunctionType = ( +export const useCustomPrimaryLoading: useQueryFunctionType = ( options, ) => { const { query } = UseRequestProcessor(); diff --git a/src/frontend/src/pages/AppInitPage/index.tsx b/src/frontend/src/pages/AppInitPage/index.tsx index 80dab69ab..d46b717d2 100644 --- a/src/frontend/src/pages/AppInitPage/index.tsx +++ b/src/frontend/src/pages/AppInitPage/index.tsx @@ -1,7 +1,8 @@ import { useGetAutoLogin } from "@/controllers/API/queries/auth"; import { useGetConfig } from "@/controllers/API/queries/config/use-get-config"; import { useGetVersionQuery } from "@/controllers/API/queries/version"; -import { usePrimaryLoading } from "@/customization/hooks/use-primary-loading"; +import { CustomLoadingPage } from "@/customization/components/custom-loading-page"; +import { useCustomPrimaryLoading } from "@/customization/hooks/use-custom-primary-loading"; import { useDarkStore } from "@/stores/darkStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore"; import { useEffect } from "react"; @@ -13,7 +14,8 @@ export function AppInitPage() { const refreshStars = useDarkStore((state) => state.refreshStars); const isLoading = useFlowsManagerStore((state) => state.isLoading); - const { isFetched: isLoaded } = usePrimaryLoading(); + const { isFetched: isLoaded } = useCustomPrimaryLoading(); + const { isFetched } = useGetAutoLogin({ enabled: isLoaded }); useGetVersionQuery({ enabled: isFetched }); useGetConfig({ enabled: isFetched }); @@ -35,7 +37,11 @@ export function AppInitPage() { return ( //need parent component with width and height <> - {(isLoading || !isFetched) && } + {isLoaded ? ( + (isLoading || !isFetched) && + ) : ( + + )} {isFetched && } ); diff --git a/src/frontend/src/pages/AppWrapperPage/components/GenericErrorComponent/index.tsx b/src/frontend/src/pages/AppWrapperPage/components/GenericErrorComponent/index.tsx new file mode 100644 index 000000000..1eb730122 --- /dev/null +++ b/src/frontend/src/pages/AppWrapperPage/components/GenericErrorComponent/index.tsx @@ -0,0 +1,35 @@ +import FetchErrorComponent from "@/components/fetchErrorComponent"; +import TimeoutErrorComponent from "@/components/timeoutErrorComponent"; +import { + FETCH_ERROR_DESCRIPION, + FETCH_ERROR_MESSAGE, + TIMEOUT_ERROR_DESCRIPION, + TIMEOUT_ERROR_MESSAGE, +} from "@/constants/constants"; + +export function GenericErrorComponent({ healthCheckTimeout, fetching, retry }) { + switch (healthCheckTimeout) { + case "serverDown": + return ( + + ); + case "timeout": + return ( + + ); + default: + return <>; + } +} diff --git a/src/frontend/src/pages/AppWrapperPage/hooks/use-health-check.ts b/src/frontend/src/pages/AppWrapperPage/hooks/use-health-check.ts new file mode 100644 index 000000000..4c49b1fe1 --- /dev/null +++ b/src/frontend/src/pages/AppWrapperPage/hooks/use-health-check.ts @@ -0,0 +1,56 @@ +import { useGetHealthQuery } from "@/controllers/API/queries/health"; +import useFlowsManagerStore from "@/stores/flowsManagerStore"; +import useFlowStore from "@/stores/flowStore"; +import { useUtilityStore } from "@/stores/utilityStore"; +import { useIsFetching, useIsMutating } from "@tanstack/react-query"; +import { AxiosError } from "axios"; +import { useEffect, useState } from "react"; + +export function useHealthCheck() { + const healthCheckMaxRetries = useFlowsManagerStore( + (state) => state.healthCheckMaxRetries, + ); + + const healthCheckTimeout = useUtilityStore( + (state) => state.healthCheckTimeout, + ); + + const isMutating = useIsMutating(); + const isFetching = useIsFetching({ + predicate: (query) => query.queryKey[0] !== "useGetHealthQuery", + }); + const isBuilding = useFlowStore((state) => state.isBuilding); + + const disabled = isMutating || isFetching || isBuilding; + + const { + isFetching: fetchingHealth, + isError: isErrorHealth, + error, + refetch, + } = useGetHealthQuery({ enableInterval: !disabled }); + const [retryCount, setRetryCount] = useState(0); + + useEffect(() => { + const isServerBusy = + (error as AxiosError)?.response?.status === 503 || + (error as AxiosError)?.response?.status === 429; + + if (isServerBusy && isErrorHealth && !disabled) { + const maxRetries = healthCheckMaxRetries; + if (retryCount < maxRetries) { + const delay = Math.pow(2, retryCount) * 1000; + const timer = setTimeout(() => { + refetch(); + setRetryCount(retryCount + 1); + }, delay); + + return () => clearTimeout(timer); + } + } else { + setRetryCount(0); + } + }, [isErrorHealth, retryCount, refetch]); + + return { healthCheckTimeout, refetch, fetchingHealth }; +} diff --git a/src/frontend/src/pages/AppWrapperPage/index.tsx b/src/frontend/src/pages/AppWrapperPage/index.tsx index e28cb29a3..763210bf9 100644 --- a/src/frontend/src/pages/AppWrapperPage/index.tsx +++ b/src/frontend/src/pages/AppWrapperPage/index.tsx @@ -1,99 +1,13 @@ import AlertDisplayArea from "@/alerts/displayArea"; import CrashErrorComponent from "@/components/crashErrorComponent"; -import FetchErrorComponent from "@/components/fetchErrorComponent"; -import TimeoutErrorComponent from "@/components/timeoutErrorComponent"; -import { - FETCH_ERROR_DESCRIPION, - FETCH_ERROR_MESSAGE, - TIMEOUT_ERROR_DESCRIPION, - TIMEOUT_ERROR_MESSAGE, -} from "@/constants/constants"; -import { useGetHealthQuery } from "@/controllers/API/queries/health"; import { CustomHeader } from "@/customization/components/custom-header"; -import useFlowsManagerStore from "@/stores/flowsManagerStore"; -import { useUtilityStore } from "@/stores/utilityStore"; -import { AxiosError } from "axios"; -import { useEffect, useMemo, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { Outlet } from "react-router-dom"; +import { GenericErrorComponent } from "./components/GenericErrorComponent"; +import { useHealthCheck } from "./hooks/use-health-check"; export function AppWrapperPage() { - const healthCheckMaxRetries = useFlowsManagerStore( - (state) => state.healthCheckMaxRetries, - ); - - const healthCheckTimeout = useUtilityStore( - (state) => state.healthCheckTimeout, - ); - - const { - data: healthData, - isFetching: fetchingHealth, - isError: isErrorHealth, - error, - refetch, - } = useGetHealthQuery(); - - const isServerDown = - isErrorHealth || - (healthData && Object.values(healthData).some((value) => value !== "ok")) || - healthCheckTimeout === "serverDown"; - - const isTimeoutResponseServer = healthCheckTimeout === "timeout"; - - const [retryCount, setRetryCount] = useState(0); - - useEffect(() => { - const isServerBusy = - (error as AxiosError)?.response?.status === 503 || - (error as AxiosError)?.response?.status === 429; - - if (isServerBusy && isErrorHealth) { - const maxRetries = healthCheckMaxRetries; - if (retryCount < maxRetries) { - const delay = Math.pow(2, retryCount) * 1000; - const timer = setTimeout(() => { - refetch(); - setRetryCount(retryCount + 1); - }, delay); - - return () => clearTimeout(timer); - } - } else { - setRetryCount(0); - } - }, [isErrorHealth, retryCount, refetch]); - - const modalErrorComponent = useMemo(() => { - switch (healthCheckTimeout) { - case "serverDown": - return ( - { - refetch(); - }} - isLoadingHealth={fetchingHealth} - > - ); - case "timeout": - return ( - { - refetch(); - }} - isLoadingHealth={fetchingHealth} - > - ); - default: - return null; - } - }, [healthCheckTimeout, fetchingHealth]); + const { healthCheckTimeout, fetchingHealth, refetch } = useHealthCheck(); return (
@@ -105,7 +19,11 @@ export function AppWrapperPage() { FallbackComponent={CrashErrorComponent} > <> - {modalErrorComponent} +