From 93e17621e0afba4847b9ef0a1e33073dda924bc2 Mon Sep 17 00:00:00 2001 From: Desmond Howard Date: Thu, 12 Oct 2023 12:51:44 -0700 Subject: [PATCH 1/2] use token when calling /maintenanceStatus --- webapp/src/components/views/BackendProbe.tsx | 23 ++++++++----------- .../src/libs/services/MaintenanceService.ts | 22 ++++++++++++++++++ 2 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 webapp/src/libs/services/MaintenanceService.ts diff --git a/webapp/src/components/views/BackendProbe.tsx b/webapp/src/components/views/BackendProbe.tsx index e4911e202..1dd811ec3 100644 --- a/webapp/src/components/views/BackendProbe.tsx +++ b/webapp/src/components/views/BackendProbe.tsx @@ -1,10 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. +import { useMsal } from '@azure/msal-react'; import { Body1, Spinner, Title3 } from '@fluentui/react-components'; import { FC, useEffect, useMemo, useState } from 'react'; import { renderApp } from '../../index'; import { AuthHelper } from '../../libs/auth/AuthHelper'; import { BackendServiceUrl } from '../../libs/services/BaseService'; +import { MaintenanceService, MaintenanceStatus } from '../../libs/services/MaintenanceService'; import { useAppDispatch, useAppSelector } from '../../redux/app/hooks'; import { RootState } from '../../redux/app/store'; import { setMaintenance } from '../../redux/features/app/appSlice'; @@ -14,20 +16,15 @@ interface IData { onBackendFound: () => void; } -interface IMaintenance { - title: string | null; - message: string | null; - note: string | null | undefined; -} - export const BackendProbe: FC = ({ onBackendFound }) => { const classes = useSharedClasses(); const dispatch = useAppDispatch(); const { isMaintenance } = useAppSelector((state: RootState) => state.app); const healthUrl = useMemo(() => new URL('healthz', BackendServiceUrl), []); - const migrationUrl = useMemo(() => new URL('maintenanceStatus', BackendServiceUrl), []); + const maintenanceService = useMemo(() => new MaintenanceService(), []); + const { instance, inProgress } = useMsal(); - const [model, setModel] = useState(null); + const [model, setModel] = useState(null); useEffect(() => { const timer = setInterval(() => { @@ -49,12 +46,12 @@ export const BackendProbe: FC = ({ onBackendFound }) => { } }; - const fetchMaintenanceAsync = () => - fetch(migrationUrl) - .then((response) => response.json()) + const fetchMaintenanceAsync = async () => + maintenanceService + .getMaintenanceStatus(await AuthHelper.getSKaaSAccessToken(instance, inProgress)) .then((data) => { // Body has payload. This means the app is in maintenance - setModel(data as IMaintenance); + setModel(data); return true; }) .catch((e: any) => { @@ -80,7 +77,7 @@ export const BackendProbe: FC = ({ onBackendFound }) => { return () => { clearInterval(timer); }; - }, [dispatch, healthUrl, migrationUrl, onBackendFound]); + }, [dispatch, healthUrl, maintenanceService, onBackendFound, instance, inProgress]); return ( <> diff --git a/webapp/src/libs/services/MaintenanceService.ts b/webapp/src/libs/services/MaintenanceService.ts new file mode 100644 index 000000000..4328e171a --- /dev/null +++ b/webapp/src/libs/services/MaintenanceService.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { BaseService } from './BaseService'; + +export interface MaintenanceStatus { + title: string | null; + message: string | null; + note: string | null | undefined; +} + +export class MaintenanceService extends BaseService { + public getMaintenanceStatus = async (accessToken: string) => { + const result = await this.getResponseAsync( + { + commandPath: 'maintenanceStatus', + }, + accessToken, + ); + + return result; + }; +} From 702ff935a0639c9d31a4d5c8f5a91562fbbb7787 Mon Sep 17 00:00:00 2001 From: Desmond Howard Date: Thu, 12 Oct 2023 14:49:10 -0700 Subject: [PATCH 2/2] remove /healthz probe --- webapp/src/components/views/BackendProbe.tsx | 55 ++++++++------------ webapp/src/libs/services/BaseService.ts | 8 +-- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/webapp/src/components/views/BackendProbe.tsx b/webapp/src/components/views/BackendProbe.tsx index 1dd811ec3..a7db1ab72 100644 --- a/webapp/src/components/views/BackendProbe.tsx +++ b/webapp/src/components/views/BackendProbe.tsx @@ -5,7 +5,7 @@ import { Body1, Spinner, Title3 } from '@fluentui/react-components'; import { FC, useEffect, useMemo, useState } from 'react'; import { renderApp } from '../../index'; import { AuthHelper } from '../../libs/auth/AuthHelper'; -import { BackendServiceUrl } from '../../libs/services/BaseService'; +import { BackendServiceUrl, NetworkErrorMessage } from '../../libs/services/BaseService'; import { MaintenanceService, MaintenanceStatus } from '../../libs/services/MaintenanceService'; import { useAppDispatch, useAppSelector } from '../../redux/app/hooks'; import { RootState } from '../../redux/app/store'; @@ -20,7 +20,6 @@ export const BackendProbe: FC = ({ onBackendFound }) => { const classes = useSharedClasses(); const dispatch = useAppDispatch(); const { isMaintenance } = useAppSelector((state: RootState) => state.app); - const healthUrl = useMemo(() => new URL('healthz', BackendServiceUrl), []); const maintenanceService = useMemo(() => new MaintenanceService(), []); const { instance, inProgress } = useMsal(); @@ -38,46 +37,34 @@ export const BackendProbe: FC = ({ onBackendFound }) => { } }; - const fetchHealthAsync = async () => { - const result = await fetch(healthUrl); + AuthHelper.getSKaaSAccessToken(instance, inProgress) + .then((token) => + maintenanceService + .getMaintenanceStatus(token) + .then((data) => { + // Body has payload. This means the app is in maintenance + setModel(data); + }) + .catch((e: any) => { + if (e instanceof Error && e.message.includes(NetworkErrorMessage)) { + // a network error was encountered, so we should probe until we find the backend: + return; + } - if (result.ok) { - onBackendFoundWithAuthCheck(); - } - }; - - const fetchMaintenanceAsync = async () => - maintenanceService - .getMaintenanceStatus(await AuthHelper.getSKaaSAccessToken(instance, inProgress)) - .then((data) => { - // Body has payload. This means the app is in maintenance - setModel(data); - return true; - }) - .catch((e: any) => { - if (e instanceof TypeError) { - // fetch() will reject with a TypeError when a network error is encountered - // this means the backend is not found and we need to probe. - return true; - } - - // JSON Exception since response has no body. This means app is not in maintenance. - dispatch(setMaintenance(false)); - onBackendFoundWithAuthCheck(); - return false; - }); - - fetchMaintenanceAsync() - .then((shouldProbe) => (shouldProbe ? fetchHealthAsync() : Promise.resolve())) + // JSON Exception since response has no body. This means app is not in maintenance. + dispatch(setMaintenance(false)); + onBackendFoundWithAuthCheck(); + }), + ) .catch(() => { - // Ignore - this page is just a probe, so we don't need to show any errors if backend is not found + // Ignore - we'll retry on the next interval }); }, 3000); return () => { clearInterval(timer); }; - }, [dispatch, healthUrl, maintenanceService, onBackendFound, instance, inProgress]); + }, [dispatch, maintenanceService, onBackendFound, instance, inProgress]); return ( <> diff --git a/webapp/src/libs/services/BaseService.ts b/webapp/src/libs/services/BaseService.ts index d596e4e16..1200b6c5f 100644 --- a/webapp/src/libs/services/BaseService.ts +++ b/webapp/src/libs/services/BaseService.ts @@ -13,6 +13,7 @@ interface ServiceRequest { const noResponseBodyStatusCodes = [202, 204]; export const BackendServiceUrl = process.env.REACT_APP_BACKEND_URI ?? window.origin; +export const NetworkErrorMessage = '\n\nPlease check that your backend is running and that it is accessible by the app'; export class BaseService { constructor(protected readonly serviceUrl: string = BackendServiceUrl) {} @@ -76,13 +77,12 @@ export class BaseService { return (noResponseBodyStatusCodes.includes(response.status) ? {} : await response.json()) as T; } catch (e: any) { - let additionalErrorMsg = ''; + let isNetworkError = false; if (e instanceof TypeError) { // fetch() will reject with a TypeError when a network error is encountered. - additionalErrorMsg = - '\n\nPlease check that your backend is running and that it is accessible by the app'; + isNetworkError = true; } - throw Object.assign(new Error(`${e as string} ${additionalErrorMsg}`)); + throw Object.assign(new Error(`${e as string} ${isNetworkError ? NetworkErrorMessage : ''}`)); } }; }