From f5cbd9af478dfe37c07398494f6d11182ec2d075 Mon Sep 17 00:00:00 2001 From: Smith Date: Thu, 26 Oct 2023 15:51:22 -0700 Subject: [PATCH] psp-7099 prevent infinite refresh loop of acq files when user is not authorized or file does not exist. --- .../acquisition/AcquisitionContainer.tsx | 15 +++++++++--- .../acquisition/AcquisitionView.test.tsx | 1 + .../acquisition/AcquisitionView.tsx | 10 ++++++++ .../repositories/useAcquisitionProvider.ts | 8 +++++-- source/frontend/src/utils/axiosUtils.ts | 23 +++++++++++++++++++ 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx index c29dd5e79a..fea457b7c5 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx @@ -67,7 +67,11 @@ export const AcquisitionContainer: React.FunctionComponent('Failed to update Acquisition File'); const { - getAcquisitionFile: { execute: retrieveAcquisitionFile, loading: loadingAcquisitionFile }, + getAcquisitionFile: { + execute: retrieveAcquisitionFile, + loading: loadingAcquisitionFile, + error, + }, updateAcquisitionProperties, getAcquisitionProperties: { execute: retrieveAcquisitionFileProperties, @@ -121,6 +125,10 @@ export const AcquisitionContainer: React.FunctionComponent { var retrieved = await retrieveAcquisitionFile(acquisitionFileId); + if (retrieved === undefined) { + return; + } + // retrieve related entities (ie properties, checklist items) in parallel const acquisitionPropertiesTask = retrieveAcquisitionFileProperties(acquisitionFileId); const acquisitionChecklistTask = retrieveAcquisitionFileChecklist(acquisitionFileId); @@ -130,9 +138,9 @@ export const AcquisitionContainer: React.FunctionComponent ); }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx index b293c0a46f..7bc7dd8613 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx @@ -74,6 +74,7 @@ const DEFAULT_PROPS: IAcquisitionViewProps = { }, formikRef: React.createRef(), isFormValid: true, + error: undefined, }; const history = createMemoryHistory(); diff --git a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx index eefbde3863..95b632a8ad 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx @@ -1,3 +1,4 @@ +import { AxiosError } from 'axios'; import { FormikProps } from 'formik'; import React, { useContext } from 'react'; import { @@ -16,6 +17,7 @@ import GenericModal from '@/components/common/GenericModal'; import { FileTypes } from '@/constants'; import FileLayout from '@/features/mapSideBar/layout/FileLayout'; import MapSideBarLayout from '@/features/mapSideBar/layout/MapSideBarLayout'; +import { IApiError } from '@/interfaces/IApiError'; import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; import { Api_File } from '@/models/api/File'; import { stripTrailingSlash } from '@/utils'; @@ -48,6 +50,7 @@ export interface IAcquisitionViewProps { setContainerState: React.Dispatch>; formikRef: React.RefObject>; isFormValid: boolean; + error: AxiosError | undefined; } export const AcquisitionView: React.FunctionComponent = ({ @@ -66,6 +69,7 @@ export const AcquisitionView: React.FunctionComponent = ( setContainerState, formikRef, isFormValid, + error, }) => { // match for the current route const location = useLocation(); @@ -158,6 +162,12 @@ export const AcquisitionView: React.FunctionComponent = ( } bodyComponent={ + {error && ( + + Failed to load Acquisition File. Check the detailed error in the top right for + more details. + + )} { [getAcquisitionFile], ), requestName: 'RetrieveAcquisitionFile', - onError: useAxiosErrorHandler('Failed to load Acquisition File'), + onError: useAxiosErrorHandlerWithAuthorization('Failed to load Acquisition File'), }); const getLastUpdatedBy = useApiRequestWrapper< diff --git a/source/frontend/src/utils/axiosUtils.ts b/source/frontend/src/utils/axiosUtils.ts index c1b122ef09..72faeaeb87 100644 --- a/source/frontend/src/utils/axiosUtils.ts +++ b/source/frontend/src/utils/axiosUtils.ts @@ -1,5 +1,6 @@ import axios, { AxiosError } from 'axios'; import { useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; import { IApiError } from '@/interfaces/IApiError'; @@ -34,6 +35,28 @@ export function useAxiosErrorHandler(message = 'Network error. Check responses a ); } +/** + * Provides default boilerplate applicable to handling most common axios request errors. + * @param axiosError The request error object + */ +export function useAxiosErrorHandlerWithAuthorization( + message = 'Network error. Check responses and try again.', +) { + const history = useHistory(); + return useCallback( + (axiosError: AxiosError) => { + if (axiosError?.response?.status === 400) { + toast.error(axiosError?.response.data.error, { autoClose: 10000 }); + } else if (axiosError?.response?.status === 403) { + history.push('/forbidden'); + } else { + toast.error(message); + } + }, + [history, message], + ); +} + export function useAxiosErrorHandlerWithConfirmation( needsUserAction: (userOverrideCode: UserOverrideCode | null, message: string | null) => void, message = 'Network error. Check responses and try again.',