From 6259343760c79eb5cb35299bbc8a757cb731123b Mon Sep 17 00:00:00 2001 From: Louise Davies Date: Wed, 25 Sep 2024 10:29:24 +0100 Subject: [PATCH 1/3] Add queryRetries setting to all plugins that configures the react-query query retry behaviour --- packages/datagateway-common/src/api/cart.tsx | 3 +- .../src/api/dataPublications.tsx | 7 +- .../datagateway-common/src/api/datafiles.tsx | 6 +- .../datagateway-common/src/api/datasets.tsx | 7 +- .../src/api/facilityCycles.tsx | 6 +- packages/datagateway-common/src/api/index.tsx | 14 +++- .../src/api/instruments.tsx | 6 +- .../src/api/investigations.tsx | 7 +- .../datagateway-common/src/api/lucene.tsx | 3 +- .../src/api/retryICATErrors.test.ts | 68 +++++++++++++++++-- .../src/api/retryICATErrors.ts | 31 ++++++++- packages/datagateway-common/src/index.tsx | 2 +- .../datagateway-common/src/setupTests.tsx | 6 +- .../src/state/actions/actions.types.tsx | 5 ++ .../src/state/actions/index.tsx | 11 +++ .../src/state/app.types.tsx | 1 + .../state/reducers/dgcommon.reducer.test.tsx | 10 ++- .../src/state/reducers/dgcommon.reducer.tsx | 13 ++++ .../datagateway-dataview/src/App.test.tsx | 68 ++++++++++++++++++- packages/datagateway-dataview/src/App.tsx | 24 ++++++- .../src/page/breadcrumbs.component.tsx | 11 ++- packages/datagateway-dataview/src/settings.ts | 1 + .../src/state/actions/actions.test.tsx | 18 +++-- .../src/state/actions/index.tsx | 10 ++- .../src/views/roleSelector.component.tsx | 3 +- .../datagateway-download/src/App.test.tsx | 48 ++++++++++++- packages/datagateway-download/src/App.tsx | 22 +++++- .../src/ConfigProvider.tsx | 1 + .../src/downloadApiHooks.ts | 28 ++++++-- packages/datagateway-search/src/App.test.tsx | 63 ++++++++++++++++- packages/datagateway-search/src/App.tsx | 24 ++++++- packages/datagateway-search/src/settings.ts | 1 + .../src/state/actions/actions.test.tsx | 18 +++-- .../src/state/actions/index.tsx | 10 ++- 34 files changed, 506 insertions(+), 50 deletions(-) diff --git a/packages/datagateway-common/src/api/cart.tsx b/packages/datagateway-common/src/api/cart.tsx index 9cd4b5b3f..264b769a9 100644 --- a/packages/datagateway-common/src/api/cart.tsx +++ b/packages/datagateway-common/src/api/cart.tsx @@ -11,7 +11,7 @@ import { useMutation, UseMutationResult, } from 'react-query'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; export const fetchDownloadCart = (config: { facilityName: string; @@ -57,6 +57,7 @@ export const useCart = (): UseQueryResult => { const facilityName = useSelector( (state: StateType) => state.dgcommon.facilityName ); + const retryICATErrors = useRetryICATErrors(); return useQuery( 'cart', () => diff --git a/packages/datagateway-common/src/api/dataPublications.tsx b/packages/datagateway-common/src/api/dataPublications.tsx index 3d2ba5f0b..c5d94a41f 100644 --- a/packages/datagateway-common/src/api/dataPublications.tsx +++ b/packages/datagateway-common/src/api/dataPublications.tsx @@ -15,7 +15,7 @@ import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { getApiParams, parseSearchToQuery } from '.'; import { StateType } from '..'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; const fetchDataPublications = ( apiUrl: string, @@ -61,6 +61,7 @@ export const useDataPublicationsPaginated = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort, page, results } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< DataPublication[], @@ -118,6 +119,7 @@ export const useDataPublicationsInfinite = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useInfiniteQuery( [ @@ -154,6 +156,7 @@ export const useDataPublication = ( > ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery( ['dataPublication', dataPublicationId], @@ -203,6 +206,7 @@ export const useDataPublications = ( additionalFilters: AdditionalFilters ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery< DataPublication[], @@ -259,6 +263,7 @@ export const useDataPublicationCount = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< number, diff --git a/packages/datagateway-common/src/api/datafiles.tsx b/packages/datagateway-common/src/api/datafiles.tsx index 0616297a3..cf9488a1a 100644 --- a/packages/datagateway-common/src/api/datafiles.tsx +++ b/packages/datagateway-common/src/api/datafiles.tsx @@ -18,7 +18,7 @@ import type { UseQueryOptions, } from 'react-query'; import { useQuery, useInfiniteQuery } from 'react-query'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; export const fetchDatafiles = ( apiUrl: string, @@ -62,6 +62,7 @@ export const useDatafilesPaginated = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort, page, results } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< Datafile[], @@ -114,6 +115,7 @@ export const useDatafilesInfinite = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useInfiniteQuery( ['datafile', { sort: JSON.stringify(sort), filters }, additionalFilters], // need to stringify sort as property order is important! @@ -167,6 +169,7 @@ export const useDatafileCount = ( const location = useLocation(); const filters = parseSearchToQuery(location.search).filters; + const retryICATErrors = useRetryICATErrors(); return useQuery< number, @@ -223,6 +226,7 @@ export const useDatafileDetails = ( > ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery< Datafile, diff --git a/packages/datagateway-common/src/api/datasets.tsx b/packages/datagateway-common/src/api/datasets.tsx index 156e29ace..ec09e751b 100644 --- a/packages/datagateway-common/src/api/datasets.tsx +++ b/packages/datagateway-common/src/api/datasets.tsx @@ -18,7 +18,7 @@ import { useInfiniteQuery, UseInfiniteQueryResult, } from 'react-query'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; export const fetchDatasets = ( apiUrl: string, @@ -60,6 +60,7 @@ export const useDataset = ( additionalFilters?: AdditionalFilters ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery< Dataset[], @@ -95,6 +96,7 @@ export const useDatasetsPaginated = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort, page, results } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< Dataset[], @@ -147,6 +149,7 @@ export const useDatasetsInfinite = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useInfiniteQuery( ['dataset', { sort: JSON.stringify(sort), filters }, additionalFilters], // need to stringify sort as property order is important! @@ -199,6 +202,7 @@ export const useDatasetCount = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const filters = parseSearchToQuery(location.search).filters; + const retryICATErrors = useRetryICATErrors(); return useQuery< number, @@ -242,6 +246,7 @@ export const useDatasetDetails = ( datasetId: number ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery( ['datasetDetails', datasetId], diff --git a/packages/datagateway-common/src/api/facilityCycles.tsx b/packages/datagateway-common/src/api/facilityCycles.tsx index 60d17f28a..aa4274690 100644 --- a/packages/datagateway-common/src/api/facilityCycles.tsx +++ b/packages/datagateway-common/src/api/facilityCycles.tsx @@ -13,7 +13,7 @@ import { useInfiniteQuery, UseInfiniteQueryResult, } from 'react-query'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; const fetchFacilityCycles = ( apiUrl: string, @@ -79,6 +79,7 @@ export const useAllFacilityCycles = ( enabled?: boolean ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery( 'facilityCycle', @@ -100,6 +101,7 @@ export const useFacilityCyclesPaginated = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort, page, results } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< FacilityCycle[], @@ -157,6 +159,7 @@ export const useFacilityCyclesInfinite = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useInfiniteQuery( ['facilityCycle', instrumentId, { sort: JSON.stringify(sort), filters }], @@ -220,6 +223,7 @@ export const useFacilityCycleCount = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< number, diff --git a/packages/datagateway-common/src/api/index.tsx b/packages/datagateway-common/src/api/index.tsx index aaa18126b..e4faf4188 100644 --- a/packages/datagateway-common/src/api/index.tsx +++ b/packages/datagateway-common/src/api/index.tsx @@ -24,7 +24,7 @@ import { useSelector } from 'react-redux'; import { StateType } from '../state/app.types'; import format from 'date-fns/format'; import { isValid } from 'date-fns'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; export * from './cart'; export * from './facilityCycles'; @@ -678,6 +678,7 @@ export const useIds = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< number[], @@ -758,6 +759,7 @@ export const useCustomFilter = ( }[] ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery< string[], @@ -842,6 +844,7 @@ export const useCustomFilterCount = ( }[] ): UseQueryResult[] => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); const queryConfigs: UseQueryOptions< number, @@ -891,7 +894,14 @@ export const useCustomFilterCount = ( staleTime: Infinity, }; }); - }, [apiUrl, entityType, filterIds, filterKey, additionalFilters]); + }, [ + filterIds, + entityType, + filterKey, + additionalFilters, + retryICATErrors, + apiUrl, + ]); // useQueries doesn't allow us to specify type info, so ignore this line // since we strongly type the queries object anyway diff --git a/packages/datagateway-common/src/api/instruments.tsx b/packages/datagateway-common/src/api/instruments.tsx index fec924921..5cbd14fd4 100644 --- a/packages/datagateway-common/src/api/instruments.tsx +++ b/packages/datagateway-common/src/api/instruments.tsx @@ -18,7 +18,7 @@ import { useInfiniteQuery, UseInfiniteQueryResult, } from 'react-query'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; const fetchInstruments = ( apiUrl: string, @@ -62,6 +62,7 @@ export const useInstrumentsPaginated = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort, page, results } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< Instrument[], @@ -112,6 +113,7 @@ export const useInstrumentsInfinite = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useInfiniteQuery( ['instrument', { sort: JSON.stringify(sort), filters }], // need to stringify sort as property order is important! @@ -155,6 +157,7 @@ export const useInstrumentCount = (): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< number, @@ -198,6 +201,7 @@ export const useInstrumentDetails = ( instrumentId: number ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery( ['instrumentDetails', instrumentId], diff --git a/packages/datagateway-common/src/api/investigations.tsx b/packages/datagateway-common/src/api/investigations.tsx index 83384c310..fdf2b9462 100644 --- a/packages/datagateway-common/src/api/investigations.tsx +++ b/packages/datagateway-common/src/api/investigations.tsx @@ -18,7 +18,7 @@ import { useQuery, UseQueryResult, } from 'react-query'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; export const fetchInvestigations = ( apiUrl: string, @@ -61,6 +61,7 @@ export const useInvestigation = ( additionalFilters?: AdditionalFilters ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery< Investigation[], @@ -97,6 +98,7 @@ export const useInvestigationsPaginated = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort, page, results } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useQuery< Investigation[], @@ -158,6 +160,7 @@ export const useInvestigationsInfinite = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const { filters, sort } = parseSearchToQuery(location.search); + const retryICATErrors = useRetryICATErrors(); return useInfiniteQuery( [ @@ -216,6 +219,7 @@ export const useInvestigationCount = ( const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); const location = useLocation(); const filters = parseSearchToQuery(location.search).filters; + const retryICATErrors = useRetryICATErrors(); return useQuery< number, @@ -267,6 +271,7 @@ export const useInvestigationDetails = ( investigationId: number ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery( ['investigationDetails', investigationId], diff --git a/packages/datagateway-common/src/api/lucene.tsx b/packages/datagateway-common/src/api/lucene.tsx index a15901b4d..8f1943554 100644 --- a/packages/datagateway-common/src/api/lucene.tsx +++ b/packages/datagateway-common/src/api/lucene.tsx @@ -19,7 +19,7 @@ import { } from '../app.types'; import handleICATError from '../handleICATError'; import { readSciGatewayToken } from '../parseTokens'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; interface QueryParameters { target: string; @@ -352,6 +352,7 @@ export const useLuceneSearchInfinite = ( {} ); } + const retryICATErrors = useRetryICATErrors(); return useInfiniteQuery( ['search', datasearchType, luceneParams], diff --git a/packages/datagateway-common/src/api/retryICATErrors.test.ts b/packages/datagateway-common/src/api/retryICATErrors.test.ts index 49ba4974e..97f5724a3 100644 --- a/packages/datagateway-common/src/api/retryICATErrors.test.ts +++ b/packages/datagateway-common/src/api/retryICATErrors.test.ts @@ -1,11 +1,21 @@ import { AxiosError } from 'axios'; -import retryICATErrors from './retryICATErrors'; +import { useRetryICATErrors } from './retryICATErrors'; +import { renderHook } from '@testing-library/react-hooks'; +import { createReactQueryWrapper } from '../setupTests'; +import { QueryClient } from 'react-query'; // have to unmock here as we mock "globally" in setupTests.tsx jest.unmock('./retryICATErrors'); describe('retryICATErrors', () => { let error: AxiosError; + const testQueryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 0, + }, + }, + }); beforeEach(() => { error = { @@ -26,7 +36,15 @@ describe('retryICATErrors', () => { it('returns false if error code is 403', () => { error.response.status = 403; + + const { + result: { current: retryICATErrors }, + } = renderHook(() => useRetryICATErrors(), { + wrapper: createReactQueryWrapper(undefined, testQueryClient), + }); + const result = retryICATErrors(0, error); + expect(result).toBe(false); }); @@ -34,6 +52,12 @@ describe('retryICATErrors', () => { error.response.data = { message: 'Session id: test has expired', }; + const { + result: { current: retryICATErrors }, + } = renderHook(() => useRetryICATErrors(), { + wrapper: createReactQueryWrapper(undefined, testQueryClient), + }); + let result = retryICATErrors(0, error); expect(result).toBe(false); @@ -43,19 +67,53 @@ describe('retryICATErrors', () => { expect(result).toBe(false); }); - it('returns false if failureCount is 3 or greater', () => { - let result = retryICATErrors(3, error); + it('returns false if failureCount is greater than or equal to retry', () => { + testQueryClient.setDefaultOptions({ queries: { retry: 1 } }); + const { + result: { current: retryICATErrors }, + } = renderHook(() => useRetryICATErrors(), { + wrapper: createReactQueryWrapper(undefined, testQueryClient), + }); + + let result = retryICATErrors(1, error); expect(result).toBe(false); - result = retryICATErrors(4, error); + result = retryICATErrors(2, error); expect(result).toBe(false); }); - it('returns true if non-auth error and failureCount is less than 3', () => { + it('returns true if non-auth error and failureCount is less than retry', () => { + testQueryClient.setDefaultOptions({ queries: { retry: 2 } }); + + const { + result: { current: retryICATErrors }, + } = renderHook(() => useRetryICATErrors(), { + wrapper: createReactQueryWrapper(undefined, testQueryClient), + }); + let result = retryICATErrors(0, error); expect(result).toBe(true); + result = retryICATErrors(1, error); + expect(result).toBe(true); + result = retryICATErrors(2, error); + expect(result).toBe(false); + }); + + it('defaults to a query retry of 3 if retry is not set', () => { + testQueryClient.setDefaultOptions({ queries: { retry: undefined } }); + + const { + result: { current: retryICATErrors }, + } = renderHook(() => useRetryICATErrors(), { + wrapper: createReactQueryWrapper(undefined, testQueryClient), + }); + + let result = retryICATErrors(2, error); expect(result).toBe(true); + + result = retryICATErrors(3, error); + expect(result).toBe(false); }); }); diff --git a/packages/datagateway-common/src/api/retryICATErrors.ts b/packages/datagateway-common/src/api/retryICATErrors.ts index c5bb04b6b..26056ddd3 100644 --- a/packages/datagateway-common/src/api/retryICATErrors.ts +++ b/packages/datagateway-common/src/api/retryICATErrors.ts @@ -1,6 +1,18 @@ import { AxiosError } from 'axios'; +import { useQueryClient } from 'react-query'; -const retryICATErrors = (failureCount: number, error: AxiosError): boolean => { +export const createRetryICATErrors = ( + retries: number +): ((failureCount: number, error: AxiosError) => boolean) => { + return (failureCount: number, error: AxiosError) => + baseRetryICATErrors(failureCount, error, retries); +}; + +const baseRetryICATErrors = ( + failureCount: number, + error: AxiosError, + retries: number +): boolean => { const message = (error as AxiosError<{ message?: string }>).response?.data?.message ?? error.message; @@ -8,10 +20,23 @@ const retryICATErrors = (failureCount: number, error: AxiosError): boolean => { error.response?.status === 403 || // TopCAT doesn't set 403 for session ID failure, so detect by looking at the message message.toUpperCase().includes('SESSION') || - failureCount >= 3 + failureCount >= retries ) return false; return true; }; -export default retryICATErrors; +const useRetryICATErrors = (): (( + failureCount: number, + error: AxiosError +) => boolean) => { + const queryClient = useQueryClient(); + const opts = queryClient.getDefaultOptions(); + // TODO: do we want to be more elegant in handling other types of retry... + const retries = + typeof opts.queries?.retry === 'number' ? opts.queries.retry : 3; + + return createRetryICATErrors(retries); +}; + +export { useRetryICATErrors }; diff --git a/packages/datagateway-common/src/index.tsx b/packages/datagateway-common/src/index.tsx index f147234f8..19c3f238d 100644 --- a/packages/datagateway-common/src/index.tsx +++ b/packages/datagateway-common/src/index.tsx @@ -53,7 +53,7 @@ export type DGCommonState = StateType; export * from './parseTokens'; export { default as handleICATError } from './handleICATError'; -export { default as retryICATErrors } from './api/retryICATErrors'; +export * from './api/retryICATErrors'; export { default as ArrowTooltip, diff --git a/packages/datagateway-common/src/setupTests.tsx b/packages/datagateway-common/src/setupTests.tsx index 39088846a..8ae41eb75 100644 --- a/packages/datagateway-common/src/setupTests.tsx +++ b/packages/datagateway-common/src/setupTests.tsx @@ -94,9 +94,9 @@ export const createTestQueryClient = (): QueryClient => }); export const createReactQueryWrapper = ( - history: History = createMemoryHistory() + history: History = createMemoryHistory(), + queryClient: QueryClient = createTestQueryClient() ): WrapperComponent => { - const testQueryClient = createTestQueryClient(); const state = { dgcommon: { ...initialState, @@ -115,7 +115,7 @@ export const createReactQueryWrapper = ( const wrapper: WrapperComponent = ({ children }) => ( - + {children} diff --git a/packages/datagateway-common/src/state/actions/actions.types.tsx b/packages/datagateway-common/src/state/actions/actions.types.tsx index 4f3d4e6d6..9a633c991 100644 --- a/packages/datagateway-common/src/state/actions/actions.types.tsx +++ b/packages/datagateway-common/src/state/actions/actions.types.tsx @@ -33,6 +33,7 @@ export const BroadcastSignOutType = `${CustomFrontendMessageType}:signout`; export const ConfigureFacilityNameType = 'datagateway_common:configure_facility_name'; export const ConfigureURLsType = 'datagateway_common:configure_urls'; +export const ConfigureQueryRetriesType = 'datagateway_common:configure_retries'; export const SortTableType = 'datagateway_common:sort_table'; export const FilterTableType = 'datagateway_common:filter_table'; @@ -272,6 +273,10 @@ export interface ConfigureUrlsPayload { urls: URLs; } +export interface ConfigureQueryRetriesPayload { + queryRetries?: number; +} + export interface URLs { idsUrl: string; apiUrl: string; diff --git a/packages/datagateway-common/src/state/actions/index.tsx b/packages/datagateway-common/src/state/actions/index.tsx index 793f2f9f5..8db2598fe 100644 --- a/packages/datagateway-common/src/state/actions/index.tsx +++ b/packages/datagateway-common/src/state/actions/index.tsx @@ -2,6 +2,8 @@ import { ActionType } from '../app.types'; import { ConfigureFacilityNamePayload, ConfigureFacilityNameType, + ConfigureQueryRetriesPayload, + ConfigureQueryRetriesType, ConfigureUrlsPayload, ConfigureURLsType, URLs, @@ -22,3 +24,12 @@ export const loadUrls = (urls: URLs): ActionType => ({ urls, }, }); + +export const loadQueryRetries = ( + queryRetries?: number +): ActionType => ({ + type: ConfigureQueryRetriesType, + payload: { + queryRetries, + }, +}); diff --git a/packages/datagateway-common/src/state/app.types.tsx b/packages/datagateway-common/src/state/app.types.tsx index bf3f51794..340669ec8 100644 --- a/packages/datagateway-common/src/state/app.types.tsx +++ b/packages/datagateway-common/src/state/app.types.tsx @@ -18,6 +18,7 @@ import type { URLs } from './actions/actions.types'; export interface DGCommonState { facilityName: string; urls: URLs; + queryRetries?: number; isisDatafileDetailsPanel: Record< Datafile['id'], { diff --git a/packages/datagateway-common/src/state/reducers/dgcommon.reducer.test.tsx b/packages/datagateway-common/src/state/reducers/dgcommon.reducer.test.tsx index 8652b489c..40dcf3d06 100644 --- a/packages/datagateway-common/src/state/reducers/dgcommon.reducer.test.tsx +++ b/packages/datagateway-common/src/state/reducers/dgcommon.reducer.test.tsx @@ -1,6 +1,6 @@ import DGCommonReducer, { initialState } from './dgcommon.reducer'; import { DGCommonState } from '../app.types'; -import { loadFacilityName, loadUrls } from '../actions'; +import { loadFacilityName, loadQueryRetries, loadUrls } from '../actions'; describe('DGCommon reducer', () => { let state: DGCommonState; @@ -36,4 +36,12 @@ describe('DGCommon reducer', () => { expect(updatedState.urls.apiUrl).toEqual('test'); }); + + it('should set query retry property when configure query retry action is sent', () => { + expect(state.queryRetries).toEqual(undefined); + + const updatedState = DGCommonReducer(state, loadQueryRetries(1)); + + expect(updatedState.queryRetries).toEqual(1); + }); }); diff --git a/packages/datagateway-common/src/state/reducers/dgcommon.reducer.tsx b/packages/datagateway-common/src/state/reducers/dgcommon.reducer.tsx index 2bba8f89f..4870e2889 100644 --- a/packages/datagateway-common/src/state/reducers/dgcommon.reducer.tsx +++ b/packages/datagateway-common/src/state/reducers/dgcommon.reducer.tsx @@ -1,6 +1,8 @@ import { ConfigureFacilityNamePayload, ConfigureFacilityNameType, + ConfigureQueryRetriesPayload, + ConfigureQueryRetriesType, ConfigureUrlsPayload, ConfigureURLsType, DlsDatasetDetailsPanelChangeTabPayload, @@ -55,6 +57,16 @@ export function handleConfigureUrls( }; } +export function handleConfigureQueryRetries( + state: DGCommonState, + payload: ConfigureQueryRetriesPayload +): DGCommonState { + return { + ...state, + queryRetries: payload.queryRetries, + }; +} + export function changeIsisDatafileDetailsPanelTab( state: DGCommonState, payload: IsisDatafileDetailsPanelChangeTabPayload @@ -154,6 +166,7 @@ export function changeDlsVisitDetailsPanelTab( const dGCommonReducer = createReducer(initialState, { [ConfigureFacilityNameType]: handleConfigureFacilityName, [ConfigureURLsType]: handleConfigureUrls, + [ConfigureQueryRetriesType]: handleConfigureQueryRetries, [IsisDatafileDetailsPanelChangeTabType]: changeIsisDatafileDetailsPanelTab, [IsisDatasetDetailsPanelChangeTabType]: changeIsisDatasetDetailsPanelTab, [IsisInstrumentDetailsPanelChangeTabType]: diff --git a/packages/datagateway-dataview/src/App.test.tsx b/packages/datagateway-dataview/src/App.test.tsx index 8cdc7ddd4..f20f7d3d0 100644 --- a/packages/datagateway-dataview/src/App.test.tsx +++ b/packages/datagateway-dataview/src/App.test.tsx @@ -1,9 +1,22 @@ import * as React from 'react'; -import App from './App'; +import App, { QueryClientSettingUpdater } from './App'; import log from 'loglevel'; -import { render, screen, waitFor } from '@testing-library/react'; +import { + render, + screen, + waitFor, + type RenderResult, +} from '@testing-library/react'; import PageContainer from './page/pageContainer.component'; import { configureApp, settingsLoaded } from './state/actions'; +import { StateType } from './state/app.types'; +import { Provider } from 'react-redux'; +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { dGCommonInitialState } from 'datagateway-common'; +import { initialState as dgDataViewInitialState } from './state/reducers/dgdataview.reducer'; +import { createLocation } from 'history'; jest .mock('loglevel') @@ -79,3 +92,54 @@ describe('App', () => { expect(await screen.findByText('app.error')).toBeInTheDocument(); }); }); + +describe('QueryClientSettingUpdater', () => { + const initialState: StateType = { + dgcommon: dGCommonInitialState, + dgdataview: dgDataViewInitialState, + router: { + action: 'POP', + location: { ...createLocation('/'), query: {} }, + }, + }; + const renderComponent = ( + state: StateType = initialState, + queryClient = new QueryClient() + ): RenderResult => { + const mockStore = configureStore([thunk]); + function Wrapper({ + children, + }: React.PropsWithChildren): JSX.Element { + return ( + + + {children} + + + ); + } + return render(, { + wrapper: Wrapper, + }); + }; + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it('syncs retry setting to query client when it updates', async () => { + const queryClient = new QueryClient({ + // set random other option to check it doesn't get overridden + defaultOptions: { queries: { staleTime: 300000 } }, + }); + const { rerender } = renderComponent(initialState, queryClient); + + initialState.dgcommon.queryRetries = 0; + + rerender(); + + expect(queryClient.getDefaultOptions()).toEqual({ + queries: { staleTime: 300000, retry: 0 }, + }); + }); +}); diff --git a/packages/datagateway-dataview/src/App.tsx b/packages/datagateway-dataview/src/App.tsx index ccd84b7f1..47925eace 100644 --- a/packages/datagateway-dataview/src/App.tsx +++ b/packages/datagateway-dataview/src/App.tsx @@ -17,7 +17,7 @@ import { import log from 'loglevel'; import React from 'react'; import { Translation } from 'react-i18next'; -import { batch, connect, Provider } from 'react-redux'; +import { batch, connect, Provider, useSelector } from 'react-redux'; import { AnyAction, applyMiddleware, compose, createStore, Store } from 'redux'; import { createLogger } from 'redux-logger'; import thunk, { ThunkDispatch } from 'redux-thunk'; @@ -106,6 +106,27 @@ const queryClient = new QueryClient({ }, }); +export const QueryClientSettingUpdater: React.FC<{ + queryClient: QueryClient; +}> = (props) => { + const { queryClient } = props; + const queryRetries = useSelector( + (state: StateType) => state.dgcommon.queryRetries + ); + + React.useEffect(() => { + if (typeof queryRetries !== 'undefined') { + const opts = queryClient.getDefaultOptions(); + queryClient.setDefaultOptions({ + ...opts, + queries: { ...opts.queries, retry: queryRetries }, + }); + } + }, [queryClient, queryRetries]); + + return null; +}; + document.addEventListener(MicroFrontendId, (e) => { const action = (e as CustomEvent).detail; if (action.type === BroadcastSignOutType) { @@ -184,6 +205,7 @@ class App extends React.Component { + state.dgdataview.breadcrumbSettings ); + const retryICATErrors = useRetryICATErrors(); const queryConfigs = React.useMemo(() => { const queryConfigs: UseQueryOptions< @@ -187,7 +188,13 @@ const useEntityInformation = ( } return queryConfigs; - }, [currentPathnames, landingPageEntities, breadcrumbSettings, apiUrl]); + }, [ + currentPathnames, + landingPageEntities, + breadcrumbSettings, + retryICATErrors, + apiUrl, + ]); // useQueries doesn't allow us to specify type info, so ignore this line // since we strongly type the queries object anyway diff --git a/packages/datagateway-dataview/src/settings.ts b/packages/datagateway-dataview/src/settings.ts index 1f183b890..2018c639c 100644 --- a/packages/datagateway-dataview/src/settings.ts +++ b/packages/datagateway-dataview/src/settings.ts @@ -7,6 +7,7 @@ export interface DataviewSettings { downloadApiUrl: string; idsUrl: string; icatUrl: string; + queryRetries?: number; selectAllSetting?: boolean; facilityImageURL?: string; features?: never; diff --git a/packages/datagateway-dataview/src/state/actions/actions.test.tsx b/packages/datagateway-dataview/src/state/actions/actions.test.tsx index c1f1f6967..de59fbba3 100644 --- a/packages/datagateway-dataview/src/state/actions/actions.test.tsx +++ b/packages/datagateway-dataview/src/state/actions/actions.test.tsx @@ -16,7 +16,12 @@ import { ConfigureFacilityImageSettingType, } from './actions.types'; import { actions, resetActions, dispatch, getState } from '../../setupTests'; -import { loadUrls, loadFacilityName } from 'datagateway-common'; +import { + loadUrls, + loadFacilityName, + loadQueryRetries, + ConfigureQueryRetriesType, +} from 'datagateway-common'; jest.mock('loglevel'); @@ -90,13 +95,14 @@ describe('Actions', () => { }); }); - it('settings are loaded and facilityName, loadFeatureSwitches, loadUrls, loadBreadcrumbSettings, loadSelectAllSetting and settingsLoaded actions are sent', async () => { + it('settings are loaded and facilityName, loadFeatureSwitches, loadUrls, loadQueryRetries, loadBreadcrumbSettings, loadSelectAllSetting and settingsLoaded actions are sent', async () => { mockSettingsGetter.mockReturnValue({ facilityName: 'Generic', facilityImageURL: 'test-image.jpg', features: {}, idsUrl: 'ids', apiUrl: 'api', + retries: 1, breadcrumbs: [ { matchEntity: 'test', @@ -117,7 +123,7 @@ describe('Actions', () => { const asyncAction = configureApp(); await asyncAction(dispatch, getState, null); - expect(actions.length).toEqual(8); + expect(actions.length).toEqual(9); expect(actions).toContainEqual(loadFacilityName('Generic')); expect(actions).toContainEqual(loadFacilityImageSetting('test-image.jpg')); expect(actions).toContainEqual(loadFeatureSwitches({})); @@ -142,9 +148,10 @@ describe('Actions', () => { expect(actions).toContainEqual( loadPluginHostSetting('http://localhost:3000/') ); + expect(actions).toContainEqual(loadQueryRetries(1)); }); - it("doesn't send loadSelectAllSetting, loadBreadcrumbSettings, loadPluginHostSetting, loadFacilityImageSetting and loadFeatureSwitches actions when they're not defined", async () => { + it("doesn't send loadQueryRetries, loadSelectAllSetting, loadBreadcrumbSettings, loadPluginHostSetting, loadFacilityImageSetting and loadFeatureSwitches actions when they're not defined", async () => { mockSettingsGetter.mockReturnValue({ facilityName: 'Generic', idsUrl: 'ids', @@ -171,6 +178,9 @@ describe('Actions', () => { expect( actions.every(({ type }) => type !== ConfigureFacilityImageSettingType) ).toBe(true); + expect( + actions.every(({ type }) => type !== ConfigureQueryRetriesType) + ).toBe(true); expect(actions).toContainEqual(settingsLoaded()); }); diff --git a/packages/datagateway-dataview/src/state/actions/index.tsx b/packages/datagateway-dataview/src/state/actions/index.tsx index 0e93bee90..61604fb48 100644 --- a/packages/datagateway-dataview/src/state/actions/index.tsx +++ b/packages/datagateway-dataview/src/state/actions/index.tsx @@ -14,7 +14,11 @@ import { ConfigureFacilityImageSettingPayload, ConfigureFacilityImageSettingType, } from './actions.types'; -import { loadUrls, loadFacilityName } from 'datagateway-common'; +import { + loadUrls, + loadFacilityName, + loadQueryRetries, +} from 'datagateway-common'; import { Action } from 'redux'; import { settings } from '../../settings'; @@ -87,6 +91,10 @@ export const configureApp = (): ThunkResult> => { }) ); + if (settingsResult?.['queryRetries'] !== undefined) { + dispatch(loadQueryRetries(settingsResult['queryRetries'])); + } + // Dispatch the action to load the breadcrumb settings (optional settings). if (settingsResult?.['breadcrumbs'] !== undefined) { dispatch(loadBreadcrumbSettings(settingsResult['breadcrumbs'])); diff --git a/packages/datagateway-dataview/src/views/roleSelector.component.tsx b/packages/datagateway-dataview/src/views/roleSelector.component.tsx index d6a22979d..75fbe90e5 100644 --- a/packages/datagateway-dataview/src/views/roleSelector.component.tsx +++ b/packages/datagateway-dataview/src/views/roleSelector.component.tsx @@ -11,7 +11,7 @@ import { InvestigationUser, parseSearchToQuery, readSciGatewayToken, - retryICATErrors, + useRetryICATErrors, StateType, usePushFilter, } from 'datagateway-common'; @@ -48,6 +48,7 @@ export const useRoles = ( username: string ): UseQueryResult => { const apiUrl = useSelector((state: StateType) => state.dgcommon.urls.apiUrl); + const retryICATErrors = useRetryICATErrors(); return useQuery( ['roles', username], diff --git a/packages/datagateway-download/src/App.test.tsx b/packages/datagateway-download/src/App.test.tsx index b2256b55e..870539dde 100644 --- a/packages/datagateway-download/src/App.test.tsx +++ b/packages/datagateway-download/src/App.test.tsx @@ -1,9 +1,12 @@ -import { render } from '@testing-library/react'; +import { RenderResult, render } from '@testing-library/react'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { act } from 'react-dom/test-utils'; -import App, { ErrorFallback } from './App'; +import App, { ErrorFallback, QueryClientSettingUpdater } from './App'; import { flushPromises } from './setupTests'; +import { mockedSettings } from './testData'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { DownloadSettingsContext } from './ConfigProvider'; jest.mock('loglevel'); jest.mock('./ConfigProvider'); @@ -28,3 +31,44 @@ describe('ErrorFallback', () => { expect(asFragment()).toMatchSnapshot(); }); }); + +describe('QueryClientSettingUpdater', () => { + let settings = mockedSettings; + const renderComponent = (queryClient = new QueryClient()): RenderResult => { + function Wrapper({ + children, + }: React.PropsWithChildren): JSX.Element { + return ( + + + {children} + + + ); + } + return render(, { + wrapper: Wrapper, + }); + }; + + beforeEach(() => { + jest.restoreAllMocks(); + settings = mockedSettings; + }); + + it('syncs retry setting to query client when it updates', async () => { + const queryClient = new QueryClient({ + // set random other option to check it doesn't get overridden + defaultOptions: { queries: { staleTime: 300000 } }, + }); + const { rerender } = renderComponent(queryClient); + + settings.queryRetries = 0; + + rerender(); + + expect(queryClient.getDefaultOptions()).toEqual({ + queries: { staleTime: 300000, retry: 0 }, + }); + }); +}); diff --git a/packages/datagateway-download/src/App.tsx b/packages/datagateway-download/src/App.tsx index 949eb9d34..0dbfc7c28 100644 --- a/packages/datagateway-download/src/App.tsx +++ b/packages/datagateway-download/src/App.tsx @@ -9,7 +9,7 @@ import { QueryClient, QueryClientProvider } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom'; -import ConfigProvider from './ConfigProvider'; +import ConfigProvider, { DownloadSettingsContext } from './ConfigProvider'; import DOIGenerationForm from './DOIGenerationForm/DOIGenerationForm.component'; import AdminDownloadStatusTable from './downloadStatus/adminDownloadStatusTable.component'; @@ -24,6 +24,25 @@ const queryClient = new QueryClient({ }, }); +export const QueryClientSettingUpdater: React.FC<{ + queryClient: QueryClient; +}> = (props) => { + const { queryClient } = props; + const { queryRetries } = React.useContext(DownloadSettingsContext); + + React.useEffect(() => { + if (typeof queryRetries !== 'undefined') { + const opts = queryClient.getDefaultOptions(); + queryClient.setDefaultOptions({ + ...opts, + queries: { ...opts.queries, retry: queryRetries }, + }); + } + }, [queryClient, queryRetries]); + + return null; +}; + /** * A fallback component when the app fails to render. */ @@ -80,6 +99,7 @@ class App extends Component { + Finished loading diff --git a/packages/datagateway-download/src/ConfigProvider.tsx b/packages/datagateway-download/src/ConfigProvider.tsx index f5c7ee468..942fdd643 100644 --- a/packages/datagateway-download/src/ConfigProvider.tsx +++ b/packages/datagateway-download/src/ConfigProvider.tsx @@ -17,6 +17,7 @@ export interface DownloadSettings { idsUrl: string; doiMinterUrl?: string; dataCiteUrl?: string; + queryRetries?: number; fileCountMax?: number; totalSizeMax?: number; diff --git a/packages/datagateway-download/src/downloadApiHooks.ts b/packages/datagateway-download/src/downloadApiHooks.ts index 16087535c..f5d447cee 100644 --- a/packages/datagateway-download/src/downloadApiHooks.ts +++ b/packages/datagateway-download/src/downloadApiHooks.ts @@ -11,7 +11,7 @@ import { handleICATError, MicroFrontendId, NotificationType, - retryICATErrors, + useRetryICATErrors, } from 'datagateway-common'; import log from 'loglevel'; import pLimit from 'p-limit'; @@ -105,6 +105,7 @@ type RollbackFunction = () => void; export const useCart = (): UseQueryResult => { const settings = React.useContext(DownloadSettingsContext); const { facilityName, downloadApiUrl } = settings; + const retryICATErrors = useRetryICATErrors(); return useQuery( QueryKey.CART, () => @@ -189,6 +190,8 @@ export const useRemoveEntityFromCart = (): UseMutationResult< export const useIsTwoLevel = (): UseQueryResult => { const settings = React.useContext(DownloadSettingsContext); const { idsUrl } = settings; + const retryICATErrors = useRetryICATErrors(); + return useQuery('isTwoLevel', () => getIsTwoLevel({ idsUrl }), { onError: (error) => { handleICATError(error); @@ -261,6 +264,7 @@ export const useFileSizesAndCounts = ( ): UseQueryResult[] => { const settings = React.useContext(DownloadSettingsContext); const { facilityName, apiUrl, downloadApiUrl } = settings; + const retryICATErrors = useRetryICATErrors(); const queryConfigs: { queryKey: [string, number]; @@ -287,7 +291,7 @@ export const useFileSizesAndCounts = ( }; }) : []; - }, [data, facilityName, apiUrl, downloadApiUrl]); + }, [data, retryICATErrors, facilityName, apiUrl, downloadApiUrl]); return useQueries(queryConfigs); }; @@ -320,6 +324,7 @@ export const useDownload = ({ >): UseQueryResult => { // Load the download settings for use. const downloadSettings = React.useContext(DownloadSettingsContext); + const retryICATErrors = useRetryICATErrors(); return useQuery( [QueryKey.DOWNLOAD, id], @@ -351,6 +356,7 @@ export const useDownloads = ( ): UseQueryResult => { // Load the download settings for use. const downloadSettings = React.useContext(DownloadSettingsContext); + const retryICATErrors = useRetryICATErrors(); return useQuery( QueryKey.DOWNLOADS, @@ -763,6 +769,10 @@ export const useIsCartMintable = ( > => { const settings = React.useContext(DownloadSettingsContext); const { doiMinterUrl } = settings; + const queryClient = useQueryClient(); + const opts = queryClient.getDefaultOptions(); + const retries = + typeof opts?.queries?.retry === 'number' ? opts.queries.retry : 3; return useQuery( ['ismintable', cart], @@ -794,7 +804,7 @@ export const useIsCartMintable = ( retry: (failureCount, error) => { // if we get 403 we know this is an legit response from the backend so don't bother retrying // all other errors use default retry behaviour - if (error.response?.status === 403 || failureCount >= 3) { + if (error.response?.status === 403 || failureCount >= retries) { return false; } else { return true; @@ -876,6 +886,10 @@ export const useCheckUser = ( username: string ): UseQueryResult => { const settings = React.useContext(DownloadSettingsContext); + const queryClient = useQueryClient(); + const opts = queryClient.getDefaultOptions(); + const retries = + typeof opts?.queries?.retry === 'number' ? opts.queries.retry : 3; return useQuery( ['checkUser', username], @@ -908,7 +922,7 @@ export const useCheckUser = ( error.response?.status === 404 || // email is invalid - don't retry as this is correct response from the server error.response?.status === 422 || - failureCount >= 3 + failureCount >= retries ) return false; return true; @@ -929,13 +943,17 @@ export const useCheckDOI = ( doi: string ): UseQueryResult => { const settings = React.useContext(DownloadSettingsContext); + const queryClient = useQueryClient(); + const opts = queryClient.getDefaultOptions(); + const retries = + typeof opts?.queries?.retry === 'number' ? opts.queries.retry : 3; return useQuery(['checkDOI', doi], () => fetchDOI(doi, settings), { retry: (failureCount: number, error: AxiosError) => { if ( // DOI is invalid - don't retry as this is a correct response from the server error.response?.status === 404 || - failureCount >= 3 + failureCount >= retries ) return false; return true; diff --git a/packages/datagateway-search/src/App.test.tsx b/packages/datagateway-search/src/App.test.tsx index 76b626f48..fca4d2003 100644 --- a/packages/datagateway-search/src/App.test.tsx +++ b/packages/datagateway-search/src/App.test.tsx @@ -1,9 +1,17 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './App'; +import App, { QueryClientSettingUpdater } from './App'; import log from 'loglevel'; -import { render, screen, waitFor } from '@testing-library/react'; +import { RenderResult, render, screen, waitFor } from '@testing-library/react'; import { configureApp, settingsLoaded } from './state/actions'; +import { dGCommonInitialState } from 'datagateway-common'; +import { createLocation } from 'history'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { Provider } from 'react-redux'; +import thunk from 'redux-thunk'; +import configureStore from 'redux-mock-store'; +import { initialState as dgSearchInitialState } from './state/reducers/dgsearch.reducer'; +import { StateType } from './state/app.types'; jest.mock('loglevel').mock('./state/actions', () => ({ ...jest.requireActual('./state/actions'), @@ -70,3 +78,54 @@ describe('App', () => { expect(await screen.findByText('app.error')).toBeInTheDocument(); }); }); + +describe('QueryClientSettingUpdater', () => { + const initialState: StateType = { + dgcommon: dGCommonInitialState, + dgsearch: dgSearchInitialState, + router: { + action: 'POP', + location: { ...createLocation('/'), query: {} }, + }, + }; + const renderComponent = ( + state: StateType = initialState, + queryClient = new QueryClient() + ): RenderResult => { + const mockStore = configureStore([thunk]); + function Wrapper({ + children, + }: React.PropsWithChildren): JSX.Element { + return ( + + + {children} + + + ); + } + return render(, { + wrapper: Wrapper, + }); + }; + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it('syncs retry setting to query client when it updates', async () => { + const queryClient = new QueryClient({ + // set random other option to check it doesn't get overridden + defaultOptions: { queries: { staleTime: 300000 } }, + }); + const { rerender } = renderComponent(initialState, queryClient); + + initialState.dgcommon.queryRetries = 0; + + rerender(); + + expect(queryClient.getDefaultOptions()).toEqual({ + queries: { staleTime: 300000, retry: 0 }, + }); + }); +}); diff --git a/packages/datagateway-search/src/App.tsx b/packages/datagateway-search/src/App.tsx index 89b44063b..7e3ea47a5 100644 --- a/packages/datagateway-search/src/App.tsx +++ b/packages/datagateway-search/src/App.tsx @@ -16,7 +16,7 @@ import { } from 'history'; import log from 'loglevel'; import React from 'react'; -import { batch, connect, Provider } from 'react-redux'; +import { batch, connect, Provider, useSelector } from 'react-redux'; import { AnyAction, applyMiddleware, compose, createStore, Store } from 'redux'; import { createLogger } from 'redux-logger'; import thunk, { ThunkDispatch } from 'redux-thunk'; @@ -99,6 +99,27 @@ const queryClient = new QueryClient({ }, }); +export const QueryClientSettingUpdater: React.FC<{ + queryClient: QueryClient; +}> = (props) => { + const { queryClient } = props; + const queryRetries = useSelector( + (state: StateType) => state.dgcommon.queryRetries + ); + + React.useEffect(() => { + if (typeof queryRetries !== 'undefined') { + const opts = queryClient.getDefaultOptions(); + queryClient.setDefaultOptions({ + ...opts, + queries: { ...opts.queries, retry: queryRetries }, + }); + } + }, [queryClient, queryRetries]); + + return null; +}; + document.addEventListener(MicroFrontendId, (e) => { const action = (e as CustomEvent).detail; if (action.type === BroadcastSignOutType) { @@ -175,6 +196,7 @@ class App extends React.Component { + ({ @@ -50,13 +55,14 @@ describe('Actions', () => { }); }); - it('settings are loaded and facilityName, loadUrls, loadSelectAllSetting, loadSearchableEntitites, loadMaxNumResults and settingsLoaded actions are sent', async () => { + it('settings are loaded and facilityName, loadUrls, loadQueryRetries, loadSelectAllSetting, loadSearchableEntitites, loadMaxNumResults and settingsLoaded actions are sent', async () => { mockSettingsGetter.mockReturnValue({ facilityName: 'Generic', idsUrl: 'ids', apiUrl: 'api', downloadApiUrl: 'download-api', icatUrl: 'icat', + retries: 0, selectAllSetting: false, searchableEntities: ['investigation', 'dataset', 'datafile'], maxNumResults: 150, @@ -64,7 +70,7 @@ describe('Actions', () => { const asyncAction = configureApp(); await asyncAction(dispatch, getState, null); - expect(actions.length).toEqual(6); + expect(actions.length).toEqual(7); expect(actions).toContainEqual(loadFacilityName('Generic')); expect(actions).toContainEqual( loadUrls({ @@ -79,11 +85,12 @@ describe('Actions', () => { loadSearchableEntitites(['investigation', 'dataset', 'datafile']) ); expect(actions).toContainEqual(loadMaxNumResults(150)); + expect(actions).toContainEqual(loadQueryRetries(0)); expect(actions).toContainEqual(settingsLoaded()); }); - it("doesn't send loadSelectAllSetting, loadSearchableEntitites and loadMaxNumResults actions when they're not defined", async () => { + it("doesn't send loadQueryRetries, loadSelectAllSetting, loadSearchableEntitites and loadMaxNumResults actions when they're not defined", async () => { mockSettingsGetter.mockReturnValue({ facilityName: 'Generic', idsUrl: 'ids', @@ -104,6 +111,9 @@ describe('Actions', () => { expect( actions.every(({ type }) => type !== ConfigureMaxNumResultsType) ).toBe(true); + expect( + actions.every(({ type }) => type !== ConfigureQueryRetriesType) + ).toBe(true); expect(actions).toContainEqual(settingsLoaded()); }); diff --git a/packages/datagateway-search/src/state/actions/index.tsx b/packages/datagateway-search/src/state/actions/index.tsx index a0c5f2d8d..e8747a338 100644 --- a/packages/datagateway-search/src/state/actions/index.tsx +++ b/packages/datagateway-search/src/state/actions/index.tsx @@ -10,7 +10,11 @@ import { ConfigureMinNumResultsPayload, ConfigureMinNumResultsType, } from './actions.types'; -import { loadUrls, loadFacilityName } from 'datagateway-common'; +import { + loadUrls, + loadFacilityName, + loadQueryRetries, +} from 'datagateway-common'; import { Action } from 'redux'; import { settings } from '../../settings'; @@ -69,6 +73,10 @@ export const configureApp = (): ThunkResult> => { }) ); + if (settingsResult?.['queryRetries'] !== undefined) { + dispatch(loadQueryRetries(settingsResult['queryRetries'])); + } + if (settingsResult?.['selectAllSetting'] !== undefined) { dispatch(loadSelectAllSetting(settingsResult['selectAllSetting'])); } From bbd11c66d55526ce7e3fd5fabee6c81ed031f68b Mon Sep 17 00:00:00 2001 From: Louise Davies Date: Wed, 25 Sep 2024 11:13:35 +0100 Subject: [PATCH 2/3] Fix tests broken by either renamed varible not being renamed properly or module mock implementation being outdated --- .../datagateway-common/src/api/retryICATErrors.ts | 13 ++++++++----- packages/datagateway-common/src/setupTests.tsx | 2 +- .../src/state/actions/actions.test.tsx | 2 +- .../src/state/actions/actions.test.tsx | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/datagateway-common/src/api/retryICATErrors.ts b/packages/datagateway-common/src/api/retryICATErrors.ts index 26056ddd3..bb879acbd 100644 --- a/packages/datagateway-common/src/api/retryICATErrors.ts +++ b/packages/datagateway-common/src/api/retryICATErrors.ts @@ -1,7 +1,7 @@ import { AxiosError } from 'axios'; import { useQueryClient } from 'react-query'; -export const createRetryICATErrors = ( +const createRetryICATErrors = ( retries: number ): ((failureCount: number, error: AxiosError) => boolean) => { return (failureCount: number, error: AxiosError) => @@ -26,7 +26,7 @@ const baseRetryICATErrors = ( return true; }; -const useRetryICATErrors = (): (( +export const useRetryICATErrors = (): (( failureCount: number, error: AxiosError ) => boolean) => { @@ -34,9 +34,12 @@ const useRetryICATErrors = (): (( const opts = queryClient.getDefaultOptions(); // TODO: do we want to be more elegant in handling other types of retry... const retries = - typeof opts.queries?.retry === 'number' ? opts.queries.retry : 3; + typeof opts.queries?.retry === 'number' + ? opts.queries.retry + : // explicitly handle boolean case as we set this in tests + opts.queries?.retry === false + ? 0 + : 3; return createRetryICATErrors(retries); }; - -export { useRetryICATErrors }; diff --git a/packages/datagateway-common/src/setupTests.tsx b/packages/datagateway-common/src/setupTests.tsx index 8ae41eb75..c4f3b73b8 100644 --- a/packages/datagateway-common/src/setupTests.tsx +++ b/packages/datagateway-common/src/setupTests.tsx @@ -65,7 +65,7 @@ setLogger({ // mock retry function to ensure it doesn't slow down query failure tests jest.mock('./api/retryICATErrors', () => ({ __esModule: true, - default: jest.fn().mockReturnValue(false), + useRetryICATErrors: jest.fn(() => () => false), })); // Mock Date.toLocaleDateString so that it always uses en-GB as locale and UTC timezone diff --git a/packages/datagateway-dataview/src/state/actions/actions.test.tsx b/packages/datagateway-dataview/src/state/actions/actions.test.tsx index de59fbba3..9a9e3d378 100644 --- a/packages/datagateway-dataview/src/state/actions/actions.test.tsx +++ b/packages/datagateway-dataview/src/state/actions/actions.test.tsx @@ -102,7 +102,7 @@ describe('Actions', () => { features: {}, idsUrl: 'ids', apiUrl: 'api', - retries: 1, + queryRetries: 1, breadcrumbs: [ { matchEntity: 'test', diff --git a/packages/datagateway-search/src/state/actions/actions.test.tsx b/packages/datagateway-search/src/state/actions/actions.test.tsx index d40924749..decc3942a 100644 --- a/packages/datagateway-search/src/state/actions/actions.test.tsx +++ b/packages/datagateway-search/src/state/actions/actions.test.tsx @@ -62,7 +62,7 @@ describe('Actions', () => { apiUrl: 'api', downloadApiUrl: 'download-api', icatUrl: 'icat', - retries: 0, + queryRetries: 0, selectAllSetting: false, searchableEntities: ['investigation', 'dataset', 'datafile'], maxNumResults: 150, From 03de9cb6dc7e4b1d3a72abdb5b3674592fd966b3 Mon Sep 17 00:00:00 2001 From: Louise Davies Date: Tue, 1 Oct 2024 10:18:20 +0100 Subject: [PATCH 3/3] Address PR comments - Remove TODO - Refactor duplicate code into dg-common --- .../src/api/retryICATErrors.ts | 2 +- packages/datagateway-common/src/index.tsx | 1 + ...ryClientSettingsUpdater.component.test.tsx | 122 ++++++++++++++++++ .../queryClientSettingsUpdater.component.tsx | 39 ++++++ .../datagateway-dataview/src/App.test.tsx | 68 +--------- packages/datagateway-dataview/src/App.tsx | 26 +--- .../datagateway-download/src/App.test.tsx | 15 ++- packages/datagateway-download/src/App.tsx | 22 ++-- packages/datagateway-search/src/App.test.tsx | 63 +-------- packages/datagateway-search/src/App.tsx | 26 +--- 10 files changed, 191 insertions(+), 193 deletions(-) create mode 100644 packages/datagateway-common/src/queryClientSettingsUpdater.component.test.tsx create mode 100644 packages/datagateway-common/src/queryClientSettingsUpdater.component.tsx diff --git a/packages/datagateway-common/src/api/retryICATErrors.ts b/packages/datagateway-common/src/api/retryICATErrors.ts index bb879acbd..da95484c2 100644 --- a/packages/datagateway-common/src/api/retryICATErrors.ts +++ b/packages/datagateway-common/src/api/retryICATErrors.ts @@ -32,7 +32,7 @@ export const useRetryICATErrors = (): (( ) => boolean) => { const queryClient = useQueryClient(); const opts = queryClient.getDefaultOptions(); - // TODO: do we want to be more elegant in handling other types of retry... + const retries = typeof opts.queries?.retry === 'number' ? opts.queries.retry diff --git a/packages/datagateway-common/src/index.tsx b/packages/datagateway-common/src/index.tsx index 19c3f238d..e1313bfbf 100644 --- a/packages/datagateway-common/src/index.tsx +++ b/packages/datagateway-common/src/index.tsx @@ -62,6 +62,7 @@ export { export { default as Sticky } from './sticky.component'; export { default as DGThemeProvider } from './dgThemeProvider.component'; export { default as Mark } from './mark.component'; +export * from './queryClientSettingsUpdater.component'; export { default as HomePage } from './homePage/homePage.component'; diff --git a/packages/datagateway-common/src/queryClientSettingsUpdater.component.test.tsx b/packages/datagateway-common/src/queryClientSettingsUpdater.component.test.tsx new file mode 100644 index 000000000..09c62cec2 --- /dev/null +++ b/packages/datagateway-common/src/queryClientSettingsUpdater.component.test.tsx @@ -0,0 +1,122 @@ +import React from 'react'; +import { render, RenderResult } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { Provider } from 'react-redux'; +import thunk from 'redux-thunk'; +import { initialState as dGCommonInitialState } from './state/reducers/dgcommon.reducer'; +import { StateType } from './state/app.types'; +import { createLocation } from 'history'; +import configureStore from 'redux-mock-store'; +import { + QueryClientSettingsUpdater, + QueryClientSettingsUpdaterRedux, +} from './queryClientSettingsUpdater.component'; + +describe('QueryClientSettingsUpdater', () => { + const renderComponent = ( + queryRetries: number | undefined = undefined, + queryClient = new QueryClient() + ): RenderResult => { + function Wrapper({ + children, + }: React.PropsWithChildren): JSX.Element { + return ( + + {children} + + ); + } + return render( + , + { + wrapper: Wrapper, + } + ); + }; + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it('syncs retry prop to query client when it updates', async () => { + const queryClient = new QueryClient({ + // set random other option to check it doesn't get overridden + defaultOptions: { queries: { staleTime: 300000 } }, + }); + let queryRetries = 1; + const { rerender } = renderComponent(queryRetries, queryClient); + + expect(queryClient.getDefaultOptions()).toEqual({ + queries: { staleTime: 300000, retry: 1 }, + }); + + queryRetries = 0; + + rerender( + + ); + + expect(queryClient.getDefaultOptions()).toEqual({ + queries: { staleTime: 300000, retry: 0 }, + }); + }); +}); + +describe('QueryClientSettingsUpdaterRedux', () => { + const initialState: StateType = { + dgcommon: dGCommonInitialState, + router: { + action: 'POP', + location: { ...createLocation('/'), query: {} }, + }, + }; + const renderComponent = ( + state: StateType = initialState, + queryClient = new QueryClient() + ): RenderResult => { + const mockStore = configureStore([thunk]); + function Wrapper({ + children, + }: React.PropsWithChildren): JSX.Element { + return ( + + + {children} + + + ); + } + return render( + , + { + wrapper: Wrapper, + } + ); + }; + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it('syncs retry setting to query client when it updates', async () => { + const queryClient = new QueryClient({ + // set random other option to check it doesn't get overridden + defaultOptions: { queries: { staleTime: 300000 } }, + }); + const { rerender } = renderComponent(initialState, queryClient); + + initialState.dgcommon.queryRetries = 0; + + rerender(); + + expect(queryClient.getDefaultOptions()).toEqual({ + queries: { staleTime: 300000, retry: 0 }, + }); + }); +}); diff --git a/packages/datagateway-common/src/queryClientSettingsUpdater.component.tsx b/packages/datagateway-common/src/queryClientSettingsUpdater.component.tsx new file mode 100644 index 000000000..81af687c4 --- /dev/null +++ b/packages/datagateway-common/src/queryClientSettingsUpdater.component.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { QueryClient } from 'react-query'; +import { StateType } from './state/app.types'; +import { useSelector } from 'react-redux'; + +export const QueryClientSettingsUpdater: React.FC<{ + queryRetries: number | undefined; + queryClient: QueryClient; +}> = (props) => { + const { queryClient, queryRetries } = props; + + React.useEffect(() => { + if (typeof queryRetries !== 'undefined') { + const opts = queryClient.getDefaultOptions(); + queryClient.setDefaultOptions({ + ...opts, + queries: { ...opts.queries, retry: queryRetries }, + }); + } + }, [queryClient, queryRetries]); + + return null; +}; + +export const QueryClientSettingsUpdaterRedux: React.FC<{ + queryClient: QueryClient; +}> = (props) => { + const { queryClient } = props; + const queryRetries = useSelector( + (state: StateType) => state.dgcommon.queryRetries + ); + + return ( + + ); +}; diff --git a/packages/datagateway-dataview/src/App.test.tsx b/packages/datagateway-dataview/src/App.test.tsx index f20f7d3d0..8cdc7ddd4 100644 --- a/packages/datagateway-dataview/src/App.test.tsx +++ b/packages/datagateway-dataview/src/App.test.tsx @@ -1,22 +1,9 @@ import * as React from 'react'; -import App, { QueryClientSettingUpdater } from './App'; +import App from './App'; import log from 'loglevel'; -import { - render, - screen, - waitFor, - type RenderResult, -} from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import PageContainer from './page/pageContainer.component'; import { configureApp, settingsLoaded } from './state/actions'; -import { StateType } from './state/app.types'; -import { Provider } from 'react-redux'; -import configureStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { QueryClient, QueryClientProvider } from 'react-query'; -import { dGCommonInitialState } from 'datagateway-common'; -import { initialState as dgDataViewInitialState } from './state/reducers/dgdataview.reducer'; -import { createLocation } from 'history'; jest .mock('loglevel') @@ -92,54 +79,3 @@ describe('App', () => { expect(await screen.findByText('app.error')).toBeInTheDocument(); }); }); - -describe('QueryClientSettingUpdater', () => { - const initialState: StateType = { - dgcommon: dGCommonInitialState, - dgdataview: dgDataViewInitialState, - router: { - action: 'POP', - location: { ...createLocation('/'), query: {} }, - }, - }; - const renderComponent = ( - state: StateType = initialState, - queryClient = new QueryClient() - ): RenderResult => { - const mockStore = configureStore([thunk]); - function Wrapper({ - children, - }: React.PropsWithChildren): JSX.Element { - return ( - - - {children} - - - ); - } - return render(, { - wrapper: Wrapper, - }); - }; - - beforeEach(() => { - jest.restoreAllMocks(); - }); - - it('syncs retry setting to query client when it updates', async () => { - const queryClient = new QueryClient({ - // set random other option to check it doesn't get overridden - defaultOptions: { queries: { staleTime: 300000 } }, - }); - const { rerender } = renderComponent(initialState, queryClient); - - initialState.dgcommon.queryRetries = 0; - - rerender(); - - expect(queryClient.getDefaultOptions()).toEqual({ - queries: { staleTime: 300000, retry: 0 }, - }); - }); -}); diff --git a/packages/datagateway-dataview/src/App.tsx b/packages/datagateway-dataview/src/App.tsx index 47925eace..2f76f18e0 100644 --- a/packages/datagateway-dataview/src/App.tsx +++ b/packages/datagateway-dataview/src/App.tsx @@ -7,6 +7,7 @@ import { Preloader, BroadcastSignOutType, RequestPluginRerenderType, + QueryClientSettingsUpdaterRedux, } from 'datagateway-common'; import { createBrowserHistory, @@ -17,7 +18,7 @@ import { import log from 'loglevel'; import React from 'react'; import { Translation } from 'react-i18next'; -import { batch, connect, Provider, useSelector } from 'react-redux'; +import { batch, connect, Provider } from 'react-redux'; import { AnyAction, applyMiddleware, compose, createStore, Store } from 'redux'; import { createLogger } from 'redux-logger'; import thunk, { ThunkDispatch } from 'redux-thunk'; @@ -106,27 +107,6 @@ const queryClient = new QueryClient({ }, }); -export const QueryClientSettingUpdater: React.FC<{ - queryClient: QueryClient; -}> = (props) => { - const { queryClient } = props; - const queryRetries = useSelector( - (state: StateType) => state.dgcommon.queryRetries - ); - - React.useEffect(() => { - if (typeof queryRetries !== 'undefined') { - const opts = queryClient.getDefaultOptions(); - queryClient.setDefaultOptions({ - ...opts, - queries: { ...opts.queries, retry: queryRetries }, - }); - } - }, [queryClient, queryRetries]); - - return null; -}; - document.addEventListener(MicroFrontendId, (e) => { const action = (e as CustomEvent).detail; if (action.type === BroadcastSignOutType) { @@ -205,7 +185,7 @@ class App extends React.Component { - + { }); }); -describe('QueryClientSettingUpdater', () => { +describe('QueryClientSettingUpdaterContext', () => { let settings = mockedSettings; const renderComponent = (queryClient = new QueryClient()): RenderResult => { function Wrapper({ @@ -46,9 +46,12 @@ describe('QueryClientSettingUpdater', () => { ); } - return render(, { - wrapper: Wrapper, - }); + return render( + , + { + wrapper: Wrapper, + } + ); }; beforeEach(() => { @@ -65,7 +68,7 @@ describe('QueryClientSettingUpdater', () => { settings.queryRetries = 0; - rerender(); + rerender(); expect(queryClient.getDefaultOptions()).toEqual({ queries: { staleTime: 300000, retry: 0 }, diff --git a/packages/datagateway-download/src/App.tsx b/packages/datagateway-download/src/App.tsx index 0dbfc7c28..6f2f4cb4d 100644 --- a/packages/datagateway-download/src/App.tsx +++ b/packages/datagateway-download/src/App.tsx @@ -14,6 +14,7 @@ import DOIGenerationForm from './DOIGenerationForm/DOIGenerationForm.component'; import AdminDownloadStatusTable from './downloadStatus/adminDownloadStatusTable.component'; import DownloadTabs from './downloadTab/downloadTab.component'; +import { QueryClientSettingsUpdater } from 'datagateway-common'; const queryClient = new QueryClient({ defaultOptions: { @@ -24,23 +25,18 @@ const queryClient = new QueryClient({ }, }); -export const QueryClientSettingUpdater: React.FC<{ +export const QueryClientSettingsUpdaterContext: React.FC<{ queryClient: QueryClient; }> = (props) => { const { queryClient } = props; const { queryRetries } = React.useContext(DownloadSettingsContext); - React.useEffect(() => { - if (typeof queryRetries !== 'undefined') { - const opts = queryClient.getDefaultOptions(); - queryClient.setDefaultOptions({ - ...opts, - queries: { ...opts.queries, retry: queryRetries }, - }); - } - }, [queryClient, queryRetries]); - - return null; + return ( + + ); }; /** @@ -99,7 +95,7 @@ class App extends Component { - + Finished loading diff --git a/packages/datagateway-search/src/App.test.tsx b/packages/datagateway-search/src/App.test.tsx index fca4d2003..76b626f48 100644 --- a/packages/datagateway-search/src/App.test.tsx +++ b/packages/datagateway-search/src/App.test.tsx @@ -1,17 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App, { QueryClientSettingUpdater } from './App'; +import App from './App'; import log from 'loglevel'; -import { RenderResult, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { configureApp, settingsLoaded } from './state/actions'; -import { dGCommonInitialState } from 'datagateway-common'; -import { createLocation } from 'history'; -import { QueryClient, QueryClientProvider } from 'react-query'; -import { Provider } from 'react-redux'; -import thunk from 'redux-thunk'; -import configureStore from 'redux-mock-store'; -import { initialState as dgSearchInitialState } from './state/reducers/dgsearch.reducer'; -import { StateType } from './state/app.types'; jest.mock('loglevel').mock('./state/actions', () => ({ ...jest.requireActual('./state/actions'), @@ -78,54 +70,3 @@ describe('App', () => { expect(await screen.findByText('app.error')).toBeInTheDocument(); }); }); - -describe('QueryClientSettingUpdater', () => { - const initialState: StateType = { - dgcommon: dGCommonInitialState, - dgsearch: dgSearchInitialState, - router: { - action: 'POP', - location: { ...createLocation('/'), query: {} }, - }, - }; - const renderComponent = ( - state: StateType = initialState, - queryClient = new QueryClient() - ): RenderResult => { - const mockStore = configureStore([thunk]); - function Wrapper({ - children, - }: React.PropsWithChildren): JSX.Element { - return ( - - - {children} - - - ); - } - return render(, { - wrapper: Wrapper, - }); - }; - - beforeEach(() => { - jest.restoreAllMocks(); - }); - - it('syncs retry setting to query client when it updates', async () => { - const queryClient = new QueryClient({ - // set random other option to check it doesn't get overridden - defaultOptions: { queries: { staleTime: 300000 } }, - }); - const { rerender } = renderComponent(initialState, queryClient); - - initialState.dgcommon.queryRetries = 0; - - rerender(); - - expect(queryClient.getDefaultOptions()).toEqual({ - queries: { staleTime: 300000, retry: 0 }, - }); - }); -}); diff --git a/packages/datagateway-search/src/App.tsx b/packages/datagateway-search/src/App.tsx index 7e3ea47a5..7a14d51b5 100644 --- a/packages/datagateway-search/src/App.tsx +++ b/packages/datagateway-search/src/App.tsx @@ -6,6 +6,7 @@ import { Preloader, BroadcastSignOutType, RequestPluginRerenderType, + QueryClientSettingsUpdaterRedux, } from 'datagateway-common'; import { createBrowserHistory, @@ -16,7 +17,7 @@ import { } from 'history'; import log from 'loglevel'; import React from 'react'; -import { batch, connect, Provider, useSelector } from 'react-redux'; +import { batch, connect, Provider } from 'react-redux'; import { AnyAction, applyMiddleware, compose, createStore, Store } from 'redux'; import { createLogger } from 'redux-logger'; import thunk, { ThunkDispatch } from 'redux-thunk'; @@ -99,27 +100,6 @@ const queryClient = new QueryClient({ }, }); -export const QueryClientSettingUpdater: React.FC<{ - queryClient: QueryClient; -}> = (props) => { - const { queryClient } = props; - const queryRetries = useSelector( - (state: StateType) => state.dgcommon.queryRetries - ); - - React.useEffect(() => { - if (typeof queryRetries !== 'undefined') { - const opts = queryClient.getDefaultOptions(); - queryClient.setDefaultOptions({ - ...opts, - queries: { ...opts.queries, retry: queryRetries }, - }); - } - }, [queryClient, queryRetries]); - - return null; -}; - document.addEventListener(MicroFrontendId, (e) => { const action = (e as CustomEvent).detail; if (action.type === BroadcastSignOutType) { @@ -196,7 +176,7 @@ class App extends React.Component { - +