Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove /healthz call + use token when calling /maintenanceStatus #495

Merged
merged 2 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 26 additions & 42 deletions webapp/src/components/views/BackendProbe.tsx
Original file line number Diff line number Diff line change
@@ -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 { 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';
import { setMaintenance } from '../../redux/features/app/appSlice';
Expand All @@ -14,20 +16,14 @@ interface IData {
onBackendFound: () => void;
}

interface IMaintenance {
title: string | null;
message: string | null;
note: string | null | undefined;
}

export const BackendProbe: FC<IData> = ({ 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<IMaintenance | null>(null);
const [model, setModel] = useState<MaintenanceStatus | null>(null);

useEffect(() => {
const timer = setInterval(() => {
Expand All @@ -41,46 +37,34 @@ export const BackendProbe: FC<IData> = ({ onBackendFound }) => {
}
};

const fetchHealthAsync = async () => {
const result = await fetch(healthUrl);

if (result.ok) {
onBackendFoundWithAuthCheck();
}
};

const fetchMaintenanceAsync = () =>
fetch(migrationUrl)
.then((response) => response.json())
.then((data) => {
// Body has payload. This means the app is in maintenance
setModel(data as IMaintenance);
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;
});
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;
}

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, migrationUrl, onBackendFound]);
}, [dispatch, maintenanceService, onBackendFound, instance, inProgress]);

return (
<>
Expand Down
8 changes: 4 additions & 4 deletions webapp/src/libs/services/BaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down Expand Up @@ -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 : ''}`));
}
};
}
22 changes: 22 additions & 0 deletions webapp/src/libs/services/MaintenanceService.ts
Original file line number Diff line number Diff line change
@@ -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<MaintenanceStatus>(
{
commandPath: 'maintenanceStatus',
},
accessToken,
);

return result;
};
}