From df075ff181d4958d7f721f05748092c20574ac7c Mon Sep 17 00:00:00 2001 From: Smith Date: Thu, 26 Oct 2023 15:51:22 -0700 Subject: [PATCH 1/2] 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.', From 051aa3365c857d919d1f8c53524d81a789745234 Mon Sep 17 00:00:00 2001 From: devinleighsmith Date: Mon, 30 Oct 2023 10:34:15 -0700 Subject: [PATCH 2/2] increase test coverage. --- .../mapSideBar/acquisition/AcquisitionView.test.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx index 7bc7dd8613..f8ac7c6efb 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx @@ -235,4 +235,13 @@ describe('AcquisitionView component', () => { expect(tab).toBeVisible(); expect(tab).toHaveClass('active'); }); + + it(`should display an error message when the error prop is set.`, async () => { + const { getByText } = await act(() => setup({ ...DEFAULT_PROPS, error: {} } as any)); + expect( + getByText( + 'Failed to load Acquisition File. Check the detailed error in the top right for more details.', + ), + ).toBeVisible(); + }); });