Skip to content

Commit e56ce4d

Browse files
feat(settings): simplify hook, split meta and LS cases (#3111)
1 parent e41fcb4 commit e56ce4d

File tree

17 files changed

+136
-151
lines changed

17 files changed

+136
-151
lines changed

src/containers/Tenant/Diagnostics/TopQueries/QueriesTableWithDrawer.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const b = cn('kv-top-queries');
1818
interface SimpleTableWithDrawerProps {
1919
columns: Column<KeyValueRow>[];
2020
data: KeyValueRow[];
21-
loading?: boolean;
21+
isFetching?: boolean;
22+
isLoading?: boolean;
2223
onRowClick?: (
2324
row: KeyValueRow | null,
2425
index?: number,
@@ -39,7 +40,8 @@ interface SimpleTableWithDrawerProps {
3940
export function QueriesTableWithDrawer({
4041
columns,
4142
data,
42-
loading,
43+
isFetching,
44+
isLoading,
4345
onRowClick,
4446
columnsWidthLSKey,
4547
emptyDataMessage,
@@ -104,7 +106,8 @@ export function QueriesTableWithDrawer({
104106
columnsWidthLSKey={columnsWidthLSKey}
105107
columns={columns}
106108
data={data}
107-
isFetching={loading}
109+
isFetching={isFetching}
110+
isLoading={isLoading}
108111
settings={tableSettings}
109112
onRowClick={handleRowClick}
110113
rowClassName={(row) => b('row', {active: isEqual(row, selectedRow)})}

src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,12 @@ export const RunningQueriesData = ({
9696
</TableWithControlsLayout.Controls>
9797

9898
{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
99-
<TableWithControlsLayout.Table loading={isLoading}>
99+
<TableWithControlsLayout.Table>
100100
<QueriesTableWithDrawer
101101
columns={columnsToShow}
102102
data={rows || []}
103-
loading={isFetching && currentData === undefined}
103+
isFetching={isFetching && currentData === undefined}
104+
isLoading={isLoading}
104105
columnsWidthLSKey={RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY}
105106
emptyDataMessage={i18n('no-data')}
106107
sortOrder={tableSort}

src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,12 @@ export const TopQueriesData = ({
162162
</TableWithControlsLayout.Controls>
163163

164164
{error ? <ResponseError error={parseQueryErrorToString(error)} /> : null}
165-
<TableWithControlsLayout.Table loading={isLoading}>
165+
<TableWithControlsLayout.Table>
166166
<QueriesTableWithDrawer
167167
columns={columnsToShow}
168168
data={rows || []}
169-
loading={isFetching && currentData === undefined}
169+
isFetching={isFetching && currentData === undefined}
170+
isLoading={isLoading}
170171
columnsWidthLSKey={TOP_QUERIES_COLUMNS_WIDTH_LS_KEY}
171172
emptyDataMessage={i18n('no-data')}
172173
sortOrder={tableSort}

src/services/api/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type {AxiosWrapperOptions} from '@gravity-ui/axios-wrapper';
22
import type {AxiosRequestConfig} from 'axios';
33

44
import {codeAssistBackend} from '../../store';
5-
import {uiFactory} from '../../uiFactory/uiFactory';
65

76
import {AuthAPI} from './auth';
87
import {CodeAssistAPI} from './codeAssist';
@@ -27,6 +26,7 @@ interface YdbEmbeddedAPIProps {
2726
proxyMeta: undefined | boolean;
2827
// this setting allows to use schema object path relative to database in api requests
2928
useRelativePath: undefined | boolean;
29+
useMetaSettings: undefined | boolean;
3030
csrfTokenGetter: undefined | (() => string | undefined);
3131
defaults: undefined | AxiosRequestConfig;
3232
}
@@ -54,6 +54,7 @@ export class YdbEmbeddedAPI {
5454
csrfTokenGetter = () => undefined,
5555
defaults = {},
5656
useRelativePath = false,
57+
useMetaSettings = false,
5758
}: YdbEmbeddedAPIProps) {
5859
const axiosParams: AxiosWrapperOptions = {config: {withCredentials, ...defaults}};
5960
const baseApiParams = {singleClusterMode, proxyMeta, useRelativePath};
@@ -62,7 +63,7 @@ export class YdbEmbeddedAPI {
6263
if (webVersion) {
6364
this.meta = new MetaAPI(axiosParams, baseApiParams);
6465
}
65-
if (uiFactory.useMetaSettings) {
66+
if (useMetaSettings) {
6667
this.metaSettings = new MetaSettingsAPI(axiosParams, baseApiParams);
6768
}
6869

src/services/api/metaSettings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class MetaSettingsAPI extends BaseMetaAPI {
2525
preventBatching,
2626
}: GetSingleSettingParams & {preventBatching?: boolean}) {
2727
if (preventBatching) {
28-
return this.get<Setting>(this.getPath('/meta/user_settings'), {name, user});
28+
return this.get<Setting | undefined>(this.getPath('/meta/user_settings'), {name, user});
2929
}
3030

3131
return new Promise<Setting>((resolve, reject) => {

src/store/configureStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export function configureStore({
7474
proxyMeta: false,
7575
csrfTokenGetter: undefined,
7676
useRelativePath: false,
77+
useMetaSettings: false,
7778
defaults: undefined,
7879
}),
7980
} = {}) {

src/store/reducers/authentication/authentication.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const initialState: AuthenticationState = {
1111
isAuthenticated: true,
1212
user: undefined,
1313
id: undefined,
14+
metaUser: undefined,
1415
};
1516

1617
export const slice = createSlice({
@@ -45,13 +46,13 @@ export const slice = createSlice({
4546
selectIsUserAllowedToMakeChanges: (state) => state.isUserAllowedToMakeChanges,
4647
selectIsViewerUser: (state) => state.isViewerUser,
4748
selectUser: (state) => state.user,
48-
selectID: (state) => state.id,
49+
selectMetaUser: (state) => state.metaUser ?? state.id,
4950
},
5051
});
5152

5253
export default slice.reducer;
5354
export const {setIsAuthenticated, setUser} = slice.actions;
54-
export const {selectIsUserAllowedToMakeChanges, selectIsViewerUser, selectUser, selectID} =
55+
export const {selectIsUserAllowedToMakeChanges, selectIsViewerUser, selectUser, selectMetaUser} =
5556
slice.selectors;
5657

5758
export const authenticationApi = api.injectEndpoints({

src/store/reducers/authentication/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ export interface AuthenticationState {
55

66
user: string | undefined;
77
id: string | undefined;
8+
9+
metaUser: string | undefined;
810
}

src/store/reducers/capabilities/capabilities.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {createSelector} from '@reduxjs/toolkit';
22

33
import type {Capability, MetaCapability, SecuritySetting} from '../../../types/api/capabilities';
4+
import {serializeReduxError} from '../../../utils/errors/serializeReduxError';
45
import type {AppDispatch, RootState} from '../../defaultStore';
56

67
import {api} from './../api';
@@ -30,10 +31,7 @@ export const capabilitiesApi = api.injectEndpoints({
3031
} catch (error) {
3132
// If capabilities endpoint is not available, there will be an error
3233
// That means no new features are available
33-
// Serialize the error to make it Redux-compatible
34-
const serializedError =
35-
error instanceof Error ? {message: error.message, name: error.name} : error;
36-
return {error: serializedError};
34+
return {error: serializeReduxError(error)};
3735
}
3836
},
3937
}),

src/store/reducers/settings/api.ts

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,69 @@
1-
import {isNil} from 'lodash';
2-
31
import type {
42
GetSettingsParams,
53
GetSingleSettingParams,
64
SetSingleSettingParams,
7-
Setting,
85
} from '../../../types/api/settings';
6+
import {serializeReduxError} from '../../../utils/errors/serializeReduxError';
97
import type {AppDispatch} from '../../defaultStore';
108
import {api} from '../api';
119

1210
import {SETTINGS_OPTIONS} from './constants';
11+
import {parseSettingValue, stringifySettingValue} from './utils';
1312

1413
export const settingsApi = api.injectEndpoints({
1514
endpoints: (builder) => ({
16-
getSingleSetting: builder.query({
17-
queryFn: async ({name, user}: GetSingleSettingParams, baseApi) => {
15+
getSingleSetting: builder.query<unknown, GetSingleSettingParams>({
16+
queryFn: async ({name, user}) => {
1817
try {
19-
if (!window.api.metaSettings) {
18+
if (!window.api?.metaSettings) {
2019
throw new Error('MetaSettings API is not available');
2120
}
21+
2222
const data = await window.api.metaSettings.getSingleSetting({
2323
name,
2424
user,
2525
// Directly access options here to avoid them in cache key
2626
preventBatching: SETTINGS_OPTIONS[name]?.preventBatching,
2727
});
2828

29-
const dispatch = baseApi.dispatch as AppDispatch;
30-
31-
// Try to sync local value if there is no backend value
32-
syncLocalValueToMetaIfNoData(data, dispatch);
33-
34-
return {data};
29+
return {data: parseSettingValue(data?.value)};
3530
} catch (error) {
36-
return {error};
31+
return {error: serializeReduxError(error)};
3732
}
3833
},
3934
}),
4035
setSingleSetting: builder.mutation({
41-
queryFn: async (params: SetSingleSettingParams) => {
36+
queryFn: async ({
37+
name,
38+
user,
39+
value,
40+
}: Omit<SetSingleSettingParams, 'value'> & {value: unknown}) => {
4241
try {
43-
if (!window.api.metaSettings) {
42+
if (!window.api?.metaSettings) {
4443
throw new Error('MetaSettings API is not available');
4544
}
4645

47-
const data = await window.api.metaSettings.setSingleSetting(params);
46+
const data = await window.api.metaSettings.setSingleSetting({
47+
name,
48+
user,
49+
value: stringifySettingValue(value),
50+
});
4851

4952
if (data.status !== 'SUCCESS') {
50-
throw new Error('Setting status is not SUCCESS');
53+
throw new Error('Cannot set setting - status is not SUCCESS');
5154
}
5255

5356
return {data};
5457
} catch (error) {
55-
return {error};
58+
return {error: serializeReduxError(error)};
5659
}
5760
},
5861
async onQueryStarted(args, {dispatch, queryFulfilled}) {
5962
const {name, user, value} = args;
6063

6164
// Optimistically update existing cache entry
6265
const patchResult = dispatch(
63-
settingsApi.util.updateQueryData('getSingleSetting', {name, user}, (draft) => {
64-
return {...draft, name, user, value};
65-
}),
66+
settingsApi.util.updateQueryData('getSingleSetting', {name, user}, () => value),
6667
);
6768
try {
6869
await queryFulfilled;
@@ -74,39 +75,31 @@ export const settingsApi = api.injectEndpoints({
7475
getSettings: builder.query({
7576
queryFn: async ({name, user}: GetSettingsParams, baseApi) => {
7677
try {
77-
if (!window.api.metaSettings) {
78+
if (!window.api?.metaSettings) {
7879
throw new Error('MetaSettings API is not available');
7980
}
8081
const data = await window.api.metaSettings.getSettings({name, user});
8182

82-
const patches: Promise<void>[] = [];
83+
const patches: Promise<unknown>[] = [];
8384
const dispatch = baseApi.dispatch as AppDispatch;
8485

85-
// Upsert received data in getSingleSetting cache
86+
// Upsert received data in getSingleSetting cache to prevent further redundant requests
8687
name.forEach((settingName) => {
87-
const settingData = data[settingName] ?? {};
88+
const settingData = data[settingName];
8889

8990
const cacheEntryParams: GetSingleSettingParams = {
9091
name: settingName,
9192
user,
9293
};
93-
const newValue = {name: settingName, user, value: settingData?.value};
94+
const newSettingValue = parseSettingValue(settingData?.value);
9495

9596
const patch = dispatch(
9697
settingsApi.util.upsertQueryData(
9798
'getSingleSetting',
9899
cacheEntryParams,
99-
newValue,
100+
newSettingValue,
100101
),
101-
).then(() => {
102-
// Try to sync local value if there is no backend value
103-
// Do it after upsert if finished to ensure proper values update order
104-
// 1. New entry added to cache with nil value
105-
// 2. Positive entry update - local storage value replace nil in cache
106-
// 3.1. Set is successful, local value in cache
107-
// 3.2. Set is not successful, cache value reverted to previous nil
108-
syncLocalValueToMetaIfNoData(settingData, dispatch);
109-
});
102+
);
110103

111104
patches.push(patch);
112105
});
@@ -116,24 +109,10 @@ export const settingsApi = api.injectEndpoints({
116109

117110
return {data};
118111
} catch (error) {
119-
return {error};
112+
return {error: serializeReduxError(error)};
120113
}
121114
},
122115
}),
123116
}),
124117
overrideExisting: 'throw',
125118
});
126-
127-
function syncLocalValueToMetaIfNoData(params: Setting, dispatch: AppDispatch) {
128-
const localValue = localStorage.getItem(params.name);
129-
130-
if (isNil(params.value) && !isNil(localValue)) {
131-
dispatch(
132-
settingsApi.endpoints.setSingleSetting.initiate({
133-
name: params.name,
134-
user: params.user,
135-
value: localValue,
136-
}),
137-
);
138-
}
139-
}

0 commit comments

Comments
 (0)