Skip to content

Commit 9f82408

Browse files
authored
feat: use rtk query (#795)
1 parent 05fbeff commit 9f82408

File tree

15 files changed

+218
-252
lines changed

15 files changed

+218
-252
lines changed
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import type {IResponseError} from '../../../types/api/error';
21
import i18n from '../i18n';
32

43
interface ResponseErrorProps {
5-
error?: IResponseError;
4+
error?: unknown;
65
className?: string;
76
defaultMessage?: string;
87
}
@@ -12,5 +11,13 @@ export const ResponseError = ({
1211
className,
1312
defaultMessage = i18n('responseError.defaultMessage'),
1413
}: ResponseErrorProps) => {
15-
return <div className={`error ${className}`}>{error?.statusText || defaultMessage}</div>;
14+
let statusText = '';
15+
if (error && typeof error === 'object') {
16+
if ('statusText' in error && typeof error.statusText === 'string') {
17+
statusText = error.statusText;
18+
} else if ('message' in error && typeof error.message === 'string') {
19+
statusText = error.message;
20+
}
21+
}
22+
return <div className={`error ${className}`}>{statusText || defaultMessage}</div>;
1623
};

src/containers/AppWithClusters/useClusterData.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
1+
import React from 'react';
2+
13
import {useLocation} from 'react-router';
24

35
import {parseQuery} from '../../routes';
4-
import {selectClusterInfo} from '../../store/reducers/clusters/selectors';
6+
import {clustersApi} from '../../store/reducers/clusters/clusters';
57
import {getAdditionalNodesProps} from '../../utils/additionalProps';
68
import {USE_CLUSTER_BALANCER_AS_BACKEND_KEY} from '../../utils/constants';
7-
import {useSetting, useTypedSelector} from '../../utils/hooks';
8-
import {useClustersList} from '../Clusters/useClustersList';
9+
import {useSetting} from '../../utils/hooks';
910

1011
export function useClusterData() {
11-
useClustersList();
1212
const location = useLocation();
13+
const {clusterName} = parseQuery(location);
1314

14-
const queryParams = parseQuery(location);
15+
const {data} = clustersApi.useGetClustersListQuery(undefined);
1516

16-
const {clusterName} = queryParams;
17+
const info = React.useMemo(() => {
18+
const clusters = data || [];
19+
return clusters.find((cluster) => cluster.name === clusterName);
20+
}, [data, clusterName]);
1721

18-
const {
19-
solomon: monitoring,
20-
balancer,
21-
versions,
22-
cluster,
23-
} = useTypedSelector((state) =>
24-
selectClusterInfo(state, typeof clusterName === 'string' ? clusterName : ''),
25-
);
22+
const {solomon: monitoring, balancer, versions, cluster} = info || {};
2623

2724
const [useClusterBalancerAsBackend] = useSetting<boolean>(USE_CLUSTER_BALANCER_AS_BACKEND_KEY);
2825

src/containers/Cluster/ClusterInfo/ClusterInfo.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,12 @@ const getInfo = (
123123
}
124124

125125
if (cluster.SystemTablets) {
126+
const tablets = cluster.SystemTablets.slice(0).sort(compareTablets);
126127
info.push({
127128
label: i18n('tablets'),
128129
value: (
129130
<div className={b('system-tablets')}>
130-
{cluster.SystemTablets.sort(compareTablets).map((tablet, tabletIndex) => (
131+
{tablets.map((tablet, tabletIndex) => (
131132
<Tablet key={tabletIndex} tablet={tablet} />
132133
))}
133134
</div>
@@ -226,7 +227,7 @@ export const ClusterInfo = ({
226227
}
227228

228229
if (error) {
229-
<ResponseError error={error} className={b('error')} />;
230+
return <ResponseError error={error} className={b('error')} />;
230231
}
231232

232233
return <InfoViewer dots={true} info={clusterInfo} />;

src/containers/Clusters/Clusters.scss

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
@import '../../styles/mixins.scss';
1+
@use '../../styles/mixins.scss';
22

33
.clusters {
44
overflow: auto;
55

66
padding-top: 15px;
77

8-
@include body-2-typography();
9-
@include flex-container();
8+
@include mixins.body-2-typography();
9+
@include mixins.flex-container();
1010

1111
&__cluster {
1212
display: flex;
@@ -128,7 +128,7 @@
128128

129129
&__text {
130130
color: var(--g-color-text-primary);
131-
@include body-2-typography();
131+
@include mixins.body-2-typography();
132132

133133
&::first-letter {
134134
color: var(--g-color-text-danger);
@@ -145,13 +145,13 @@
145145
overflow: auto;
146146

147147
padding-left: 5px;
148-
@include flex-container();
148+
@include mixins.flex-container();
149149
}
150150

151151
&__table-content {
152152
overflow: auto;
153153

154-
@include freeze-nth-column(1);
154+
@include mixins.freeze-nth-column(1);
155155
}
156156
&__balancer-cell {
157157
display: flex;
@@ -173,4 +173,9 @@
173173
display: flex;
174174
align-items: center;
175175
}
176+
177+
&__error {
178+
margin-left: 15px;
179+
@include mixins.body-2-typography();
180+
}
176181
}

src/containers/Clusters/Clusters.tsx

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,21 @@ import DataTable from '@gravity-ui/react-data-table';
44
import {Select, TableColumnSetup} from '@gravity-ui/uikit';
55
import {Helmet} from 'react-helmet-async';
66

7+
import {ResponseError} from '../../components/Errors/ResponseError';
78
import {Loader} from '../../components/Loader';
89
import {Search} from '../../components/Search';
9-
import {changeClustersFilters, fetchClustersList} from '../../store/reducers/clusters/clusters';
10+
import {changeClustersFilters, clustersApi} from '../../store/reducers/clusters/clusters';
1011
import {
12+
aggregateClustersInfo,
13+
filterClusters,
1114
selectClusterNameFilter,
12-
selectClustersAggregation,
13-
selectClustersList,
14-
selectFilteredClusters,
15-
selectLoadingFlag,
1615
selectServiceFilter,
1716
selectStatusFilter,
1817
selectVersionFilter,
19-
selectVersions,
2018
} from '../../store/reducers/clusters/selectors';
21-
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
22-
import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
19+
import {DEFAULT_POLLING_INTERVAL, DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
20+
import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
21+
import {getMinorVersion} from '../../utils/versions';
2322

2423
import {ClustersStatistics} from './ClustersStatistics';
2524
import {CLUSTERS_COLUMNS} from './columns';
@@ -37,23 +36,16 @@ import {useSelectedColumns} from './useSelectedColumns';
3736
import './Clusters.scss';
3837

3938
export function Clusters() {
39+
const query = clustersApi.useGetClustersListQuery(undefined, {
40+
pollingInterval: DEFAULT_POLLING_INTERVAL,
41+
});
42+
4043
const dispatch = useTypedDispatch();
4144

42-
const loading = useTypedSelector(selectLoadingFlag);
43-
const clusters = useTypedSelector(selectClustersList);
44-
const filteredClusters = useTypedSelector(selectFilteredClusters);
45-
const aggregation = useTypedSelector(selectClustersAggregation);
4645
const clusterName = useTypedSelector(selectClusterNameFilter);
4746
const status = useTypedSelector(selectStatusFilter);
4847
const service = useTypedSelector(selectServiceFilter);
4948
const version = useTypedSelector(selectVersionFilter);
50-
const versions = useTypedSelector(selectVersions);
51-
52-
const fetchData = React.useCallback(() => {
53-
dispatch(fetchClustersList());
54-
}, [dispatch]);
55-
56-
useAutofetcher(fetchData, [fetchData], true);
5749

5850
const changeStatus = (value: string[]) => {
5951
dispatch(changeClustersFilters({status: value}));
@@ -76,24 +68,41 @@ export function Clusters() {
7668
[COLUMNS_NAMES.TITLE],
7769
);
7870

79-
const servicesToSelect = React.useMemo(() => {
71+
const clusters = query.data;
72+
73+
const {servicesToSelect, versions} = React.useMemo(() => {
8074
const clustersServices = new Set<string>();
75+
const uniqVersions = new Set<string>();
8176

82-
clusters.forEach((cluster) => {
77+
const clusterList = clusters ?? [];
78+
clusterList.forEach((cluster) => {
8379
if (cluster.service) {
8480
clustersServices.add(cluster.service);
8581
}
82+
cluster.cluster?.Versions?.forEach((v) => {
83+
uniqVersions.add(getMinorVersion(v));
84+
});
8685
});
8786

88-
return Array.from(clustersServices).map((clusterService) => {
89-
return {
90-
value: clusterService,
91-
content: clusterService,
92-
};
93-
});
87+
return {
88+
servicesToSelect: Array.from(clustersServices).map((value) => ({
89+
value,
90+
content: value,
91+
})),
92+
versions: Array.from(uniqVersions).map((value) => ({value, content: value})),
93+
};
9494
}, [clusters]);
9595

96-
if (loading && !clusters.length) {
96+
const filteredClusters = React.useMemo(() => {
97+
return filterClusters(clusters ?? [], {clusterName, status, service, version});
98+
}, [clusterName, clusters, service, status, version]);
99+
100+
const aggregation = React.useMemo(
101+
() => aggregateClustersInfo(filteredClusters),
102+
[filteredClusters],
103+
);
104+
105+
if (query.isLoading) {
97106
return <Loader size="l" />;
98107
}
99108

@@ -162,6 +171,7 @@ export function Clusters() {
162171
/>
163172
</div>
164173
</div>
174+
{query.isError ? <ResponseError error={query.error} className={b('error')} /> : null}
165175
<div className={b('table-wrapper')}>
166176
<div className={b('table-content')}>
167177
<DataTable

src/containers/Clusters/useClustersList.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/services/api.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,8 +513,10 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
513513
}
514514

515515
// used if not single cluster mode
516-
getClustersList() {
517-
return this.get<MetaClusters>(`${META_BACKEND || ''}/meta/clusters`, null);
516+
getClustersList(_?: never, {signal}: {signal?: AbortSignal} = {}) {
517+
return this.get<MetaClusters>(`${META_BACKEND || ''}/meta/clusters`, null, {
518+
requestConfig: {signal},
519+
});
518520
}
519521
}
520522

src/store/configureStore.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {configureStore as configureReduxStore} from '@reduxjs/toolkit';
2-
import type {Action, Reducer, UnknownAction} from '@reduxjs/toolkit';
2+
import type {Action, Dispatch, Middleware, Reducer, UnknownAction} from '@reduxjs/toolkit';
33
import type {History} from 'history';
44
import {createBrowserHistory} from 'history';
55
import {listenForHistoryChange} from 'redux-location-state';
@@ -8,16 +8,18 @@ import {createApi} from '../services/api';
88

99
import {getUrlData} from './getUrlData';
1010
import rootReducer from './reducers';
11+
import {api as storeApi} from './reducers/api';
1112
import {UPDATE_REF} from './reducers/tooltip';
1213
import getLocationMiddleware from './state-url-mapping';
1314

1415
export let backend: string | undefined, basename: string, clusterName: string | undefined;
1516

16-
function _configureStore<S = any, A extends Action = UnknownAction, P = S>(
17-
aRootReducer: Reducer<S, A, P>,
18-
history: History,
19-
preloadedState: P,
20-
) {
17+
function _configureStore<
18+
S = any,
19+
A extends Action = UnknownAction,
20+
P = S,
21+
M extends Middleware<{}, S, Dispatch> = any,
22+
>(aRootReducer: Reducer<S, A, P>, history: History, preloadedState: P, middleware: M[]) {
2123
const {locationMiddleware, reducersWithLocation} = getLocationMiddleware(history, aRootReducer);
2224

2325
const store = configureReduxStore({
@@ -30,7 +32,7 @@ function _configureStore<S = any, A extends Action = UnknownAction, P = S>(
3032
ignoredPaths: ['tooltip.currentHoveredRef'],
3133
ignoredActions: [UPDATE_REF],
3234
},
33-
}).concat(locationMiddleware),
35+
}).concat(locationMiddleware, ...middleware),
3436
});
3537

3638
return store;
@@ -54,7 +56,9 @@ export function configureStore({
5456
}));
5557
const history = createBrowserHistory({basename});
5658

57-
const store = _configureStore(aRootReducer, history, {singleClusterMode});
59+
const store = _configureStore(aRootReducer, history, {singleClusterMode}, [
60+
storeApi.middleware,
61+
]);
5862
listenForHistoryChange(store, history);
5963

6064
// Interceptor to process OIDC auth

src/store/reducers/api.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type {BaseQueryFn} from '@reduxjs/toolkit/query';
2+
import {createApi} from '@reduxjs/toolkit/query/react';
3+
4+
export const api = createApi({
5+
baseQuery: fakeBaseQuery(),
6+
/**
7+
* This api has endpoints injected in adjacent files,
8+
* which is why no endpoints are shown below.
9+
*/
10+
endpoints: () => ({}),
11+
});
12+
13+
export const _NEVER = Symbol();
14+
type NEVER = typeof _NEVER;
15+
16+
/**
17+
* Creates a "fake" baseQuery to be used if your api *only* uses the `queryFn` definition syntax.
18+
* This also allows you to specify a specific error type to be shared by all your `queryFn` definitions.
19+
*
20+
* Can't use fakeBaseQuery from @reduxjs/toolkit/query, because of error
21+
*/
22+
function fakeBaseQuery<ErrorType>(): BaseQueryFn<void, NEVER, ErrorType, {}> {
23+
return function () {
24+
throw new Error(
25+
'When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.',
26+
);
27+
};
28+
}

0 commit comments

Comments
 (0)