Skip to content

Commit 4b30652

Browse files
committed
Refactor query-client-provider and required-providers render to use the actual client used in the app
1 parent 021a1e4 commit 4b30652

File tree

2 files changed

+115
-67
lines changed

2 files changed

+115
-67
lines changed

web_ui/src/providers/query-client-provider/query-client-provider.component.tsx

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ReactNode, useLayoutEffect, useMemo, useRef } from 'react';
66
import { getErrorMessage } from '@geti/core/src/services/utils';
77
import {
88
DefaultOptions,
9+
MutationCache,
910
QueryCache,
1011
QueryClient,
1112
QueryClientProvider as TanstackQueryClientProvider,
@@ -14,7 +15,63 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
1415
import { isAxiosError } from 'axios';
1516

1617
import { NOTIFICATION_TYPE } from '../../notification/notification-toast/notification-type.enum';
17-
import { useNotification } from '../../notification/notification.component';
18+
import { AddNotificationProps, useNotification } from '../../notification/notification.component';
19+
20+
export const createGetiQueryClient = ({
21+
defaultQueryOptions,
22+
addNotification,
23+
}: {
24+
defaultQueryOptions?: DefaultOptions;
25+
addNotification: (props: AddNotificationProps) => string;
26+
}): QueryClient => {
27+
const notify = { current: addNotification };
28+
29+
const queryCache = new QueryCache({
30+
onError: (error, query) => {
31+
if (isAxiosError(error) && query.meta && 'notifyOnError' in query.meta) {
32+
const message = query.meta.errorMessage;
33+
if (query.meta.notifyOnError === true) {
34+
notify.current({
35+
message: typeof message === 'string' ? message : getErrorMessage(error),
36+
type: NOTIFICATION_TYPE.ERROR,
37+
});
38+
}
39+
}
40+
41+
if (query.meta && 'disableGlobalErrorHandling' in query.meta) {
42+
if (query.meta.disableGlobalErrorHandling === true) {
43+
return;
44+
}
45+
}
46+
},
47+
});
48+
49+
const mutationCache = new MutationCache({
50+
onError: (error) => {
51+
// TODO: If we decide to add an "opt-out" mechanism we can use the same logic
52+
// as query cache and use the `meta` field
53+
if (isAxiosError(error)) {
54+
const message = error.message;
55+
notify.current({
56+
message: typeof message === 'string' ? message : getErrorMessage(error),
57+
type: NOTIFICATION_TYPE.ERROR,
58+
});
59+
}
60+
},
61+
});
62+
63+
return new QueryClient({
64+
defaultOptions: {
65+
queries: {
66+
refetchIntervalInBackground: false,
67+
refetchOnWindowFocus: false,
68+
},
69+
...defaultQueryOptions,
70+
},
71+
queryCache,
72+
mutationCache,
73+
});
74+
};
1875

1976
declare module '@tanstack/react-query' {
2077
interface Register {
@@ -32,9 +89,11 @@ declare module '@tanstack/react-query' {
3289
export const QueryClientProvider = ({
3390
children,
3491
defaultQueryOptions,
92+
customQueryClient,
3593
}: {
3694
children: ReactNode;
3795
defaultQueryOptions?: DefaultOptions;
96+
customQueryClient?: QueryClient;
3897
}) => {
3998
const { addNotification } = useNotification();
4099

@@ -46,37 +105,12 @@ export const QueryClientProvider = ({
46105
}, [addNotification]);
47106

48107
const queryClient = useMemo(() => {
49-
const queryCache = new QueryCache({
50-
onError: (error, query) => {
51-
if (isAxiosError(error) && query.meta && 'notifyOnError' in query.meta) {
52-
const message = query.meta.errorMessage;
53-
if (query.meta.notifyOnError === true) {
54-
notify.current({
55-
message: typeof message === 'string' ? message : getErrorMessage(error),
56-
type: NOTIFICATION_TYPE.ERROR,
57-
});
58-
}
59-
}
108+
if (customQueryClient) {
109+
return customQueryClient;
110+
}
60111

61-
if (query.meta && 'disableGlobalErrorHandling' in query.meta) {
62-
if (query.meta.disableGlobalErrorHandling === true) {
63-
return;
64-
}
65-
}
66-
},
67-
});
68-
69-
return new QueryClient({
70-
defaultOptions: {
71-
queries: {
72-
refetchIntervalInBackground: false,
73-
refetchOnWindowFocus: false,
74-
},
75-
...defaultQueryOptions,
76-
},
77-
queryCache,
78-
});
79-
}, [defaultQueryOptions]);
112+
return createGetiQueryClient({ addNotification, defaultQueryOptions });
113+
}, [addNotification, customQueryClient, defaultQueryOptions]);
80114

81115
return (
82116
<TanstackQueryClientProvider client={queryClient}>

web_ui/src/test-utils/required-providers-render.tsx

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (C) 2022-2025 Intel Corporation
22
// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE
33

4-
import { ReactElement, ReactNode, Suspense } from 'react';
4+
import { ReactElement, ReactNode, Suspense, useMemo } from 'react';
55

66
import { CustomFeatureFlags, DEV_FEATURE_FLAGS } from '@geti/core';
77
import QUERY_KEYS from '@geti/core/src/requests/query-keys';
@@ -11,13 +11,16 @@ import {
1111
} from '@geti/core/src/services/application-services-provider.component';
1212
import { OnboardingProfile } from '@geti/core/src/users/services/onboarding-service.interface';
1313
import { defaultTheme, IntelBrandedLoading, Provider as ThemeProvider } from '@geti/ui';
14-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
1514
import { render, RenderOptions, RenderResult } from '@testing-library/react';
1615
import { AuthProvider } from 'react-oidc-context';
1716
import { MemoryRouter as Router } from 'react-router-dom';
1817

1918
import { AccountStatusDTO } from '../core/organizations/dtos/organizations.interface';
20-
import { NotificationProvider, Notifications } from '../notification/notification.component';
19+
import { NotificationProvider, Notifications, useNotification } from '../notification/notification.component';
20+
import {
21+
createGetiQueryClient,
22+
QueryClientProvider,
23+
} from '../providers/query-client-provider/query-client-provider.component';
2124
import { TusUploadProvider } from '../providers/tus-upload-provider/tus-upload-provider.component';
2225
import { getMockedWorkspace } from './mocked-items-factory/mocked-workspace';
2326

@@ -26,68 +29,79 @@ interface RequiredProvidersProps extends Partial<ApplicationServicesContextProps
2629
initialEntries?: string[];
2730
featureFlags?: CustomFeatureFlags;
2831
profile?: OnboardingProfile | null;
29-
queryClient?: QueryClient;
3032
}
3133

3234
const prefilledOrgId = '000000000000000000000001';
3335

34-
const usePrefilledQueryClient = (featureFlags?: CustomFeatureFlags, profile?: OnboardingProfile | null) => {
35-
const queryClient = new QueryClient({
36-
defaultOptions: {
37-
queries: {
38-
gcTime: 1000,
36+
const PrefilledQueryClientProvider = ({
37+
children,
38+
featureFlags,
39+
profile,
40+
}: {
41+
children: ReactNode;
42+
featureFlags?: CustomFeatureFlags;
43+
profile?: OnboardingProfile | null;
44+
}) => {
45+
const { addNotification } = useNotification();
46+
47+
const prefilledQueryClient = useMemo(() => {
48+
const client = createGetiQueryClient({
49+
addNotification,
50+
defaultQueryOptions: {
51+
queries: {
52+
gcTime: 1000,
53+
},
3954
},
40-
},
41-
});
42-
43-
queryClient.setQueryData(QUERY_KEYS.FEATURE_FLAGS, {
44-
...DEV_FEATURE_FLAGS,
45-
...featureFlags,
46-
});
47-
48-
if (profile !== null) {
49-
queryClient.setQueryData(QUERY_KEYS.USER_ONBOARDING_PROFILE, {
50-
organizations: [{ id: prefilledOrgId, status: AccountStatusDTO.ACTIVE }],
51-
hasAcceptedUserTermsAndConditions: true,
52-
...profile,
5355
});
54-
}
5556

56-
['123', 'org-id', 'organization-id', prefilledOrgId].forEach((organizationId) => {
57-
const workspaceKey = QUERY_KEYS.WORKSPACES(organizationId);
57+
client.setQueryData(QUERY_KEYS.FEATURE_FLAGS, {
58+
...DEV_FEATURE_FLAGS,
59+
...featureFlags,
60+
});
5861

59-
queryClient.setQueryData(workspaceKey, [
60-
getMockedWorkspace({ id: 'workspace-1', name: 'Workspace 1' }),
61-
getMockedWorkspace({ id: 'workspace-2', name: 'Workspace 2' }),
62-
]);
63-
});
62+
if (profile !== null && profile !== undefined) {
63+
client.setQueryData(QUERY_KEYS.USER_ONBOARDING_PROFILE, {
64+
...profile,
65+
organizations: [{ id: prefilledOrgId, status: AccountStatusDTO.ACTIVE }],
66+
hasAcceptedUserTermsAndConditions: true,
67+
});
68+
}
69+
70+
['123', 'org-id', 'organization-id', prefilledOrgId].forEach((organizationId) => {
71+
const workspaceKey = QUERY_KEYS.WORKSPACES(organizationId);
72+
73+
client.setQueryData(workspaceKey, [
74+
getMockedWorkspace({ id: 'workspace-1', name: 'Workspace 1' }),
75+
getMockedWorkspace({ id: 'workspace-2', name: 'Workspace 2' }),
76+
]);
77+
});
6478

65-
return queryClient;
79+
return client;
80+
}, [addNotification, featureFlags, profile]);
81+
82+
return <QueryClientProvider customQueryClient={prefilledQueryClient}>{children}</QueryClientProvider>;
6683
};
6784

6885
export const RequiredProviders = ({
6986
children,
7087
featureFlags,
7188
initialEntries,
7289
profile,
73-
queryClient,
7490
...services
7591
}: RequiredProvidersProps): JSX.Element => {
76-
const prefilledQueryClient = usePrefilledQueryClient(featureFlags, profile);
77-
7892
return (
7993
<Suspense fallback={<IntelBrandedLoading />}>
8094
<Router initialEntries={initialEntries}>
8195
<AuthProvider>
8296
<NotificationProvider>
8397
<Notifications />
84-
<QueryClientProvider client={queryClient ?? prefilledQueryClient}>
98+
<PrefilledQueryClientProvider featureFlags={featureFlags} profile={profile}>
8599
<ThemeProvider theme={defaultTheme}>
86100
<ApplicationServicesProvider useInMemoryEnvironment {...services}>
87101
<TusUploadProvider>{children}</TusUploadProvider>
88102
</ApplicationServicesProvider>
89103
</ThemeProvider>
90-
</QueryClientProvider>
104+
</PrefilledQueryClientProvider>
91105
</NotificationProvider>
92106
</AuthProvider>
93107
</Router>

0 commit comments

Comments
 (0)