Skip to content

Commit 759ba53

Browse files
feat(settings): simplify hook, split meta and LS cases
1 parent 7d4199c commit 759ba53

File tree

17 files changed

+149
-161
lines changed

17 files changed

+149
-161
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
@@ -2,6 +2,7 @@ import {createSelector} from '@reduxjs/toolkit';
22

33
import type {Capability, MetaCapability, SecuritySetting} from '../../../types/api/capabilities';
44
import type {AppDispatch, RootState} from '../../defaultStore';
5+
import {serializeError} from '../../utils';
56

67
import {api} from './../api';
78

@@ -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: serializeError(error)};
3735
}
3836
},
3937
}),

src/store/reducers/settings/api.ts

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,83 @@
1-
import {isNil} from 'lodash';
2-
31
import type {
42
GetSettingsParams,
53
GetSingleSettingParams,
64
SetSingleSettingParams,
7-
Setting,
85
} from '../../../types/api/settings';
96
import type {AppDispatch} from '../../defaultStore';
7+
import {serializeError} from '../../utils';
108
import {api} from '../api';
119

1210
import {SETTINGS_OPTIONS} from './constants';
11+
import {getSettingDefault, 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, Partial<GetSingleSettingParams>>({
16+
queryFn: async ({name, user}) => {
1817
try {
19-
if (!window.api.metaSettings) {
20-
throw new Error('MetaSettings API is not available');
18+
if (!name || !window.api.metaSettings) {
19+
throw new Error(
20+
'Cannot get setting, no MetaSettings API or neccessary params are missing',
21+
);
2122
}
23+
24+
const defaultValue = getSettingDefault(name) as unknown;
25+
26+
if (!user) {
27+
return {data: defaultValue};
28+
}
29+
2230
const data = await window.api.metaSettings.getSingleSetting({
2331
name,
2432
user,
2533
// Directly access options here to avoid them in cache key
2634
preventBatching: SETTINGS_OPTIONS[name]?.preventBatching,
2735
});
2836

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};
37+
return {data: parseSettingValue(data?.value) ?? defaultValue};
3538
} catch (error) {
36-
return {error};
39+
return {error: serializeError(error)};
3740
}
3841
},
3942
}),
4043
setSingleSetting: builder.mutation({
41-
queryFn: async (params: SetSingleSettingParams) => {
44+
queryFn: async ({
45+
name,
46+
user,
47+
value,
48+
}: Partial<Omit<SetSingleSettingParams, 'value'>> & {value: unknown}) => {
4249
try {
43-
if (!window.api.metaSettings) {
44-
throw new Error('MetaSettings API is not available');
50+
if (!name || !user || !window.api.metaSettings) {
51+
throw new Error(
52+
'Cannot set setting, no MetaSettings API or neccessary params are missing',
53+
);
4554
}
4655

47-
const data = await window.api.metaSettings.setSingleSetting(params);
56+
const data = await window.api.metaSettings.setSingleSetting({
57+
name,
58+
user,
59+
value: stringifySettingValue(value),
60+
});
4861

4962
if (data.status !== 'SUCCESS') {
50-
throw new Error('Setting status is not SUCCESS');
63+
throw new Error('Cannot set setting - status is not SUCCESS');
5164
}
5265

5366
return {data};
5467
} catch (error) {
55-
return {error};
68+
return {error: serializeError(error)};
5669
}
5770
},
5871
async onQueryStarted(args, {dispatch, queryFulfilled}) {
5972
const {name, user, value} = args;
6073

74+
if (!name) {
75+
return;
76+
}
77+
6178
// Optimistically update existing cache entry
6279
const patchResult = dispatch(
63-
settingsApi.util.updateQueryData('getSingleSetting', {name, user}, (draft) => {
64-
return {...draft, name, user, value};
65-
}),
80+
settingsApi.util.updateQueryData('getSingleSetting', {name, user}, () => value),
6681
);
6782
try {
6883
await queryFulfilled;
@@ -72,41 +87,41 @@ export const settingsApi = api.injectEndpoints({
7287
},
7388
}),
7489
getSettings: builder.query({
75-
queryFn: async ({name, user}: GetSettingsParams, baseApi) => {
90+
queryFn: async ({name, user}: Partial<GetSettingsParams>, baseApi) => {
7691
try {
77-
if (!window.api.metaSettings) {
78-
throw new Error('MetaSettings API is not available');
92+
if (!window.api.metaSettings || !name || !user) {
93+
throw new Error(
94+
'Cannot get settings, no MetaSettings API or neccessary params are missing',
95+
);
7996
}
8097
const data = await window.api.metaSettings.getSettings({name, user});
8198

82-
const patches: Promise<void>[] = [];
99+
const patches: Promise<unknown>[] = [];
83100
const dispatch = baseApi.dispatch as AppDispatch;
84101

85-
// Upsert received data in getSingleSetting cache
102+
// Upsert received data in getSingleSetting cache to prevent further redundant requests
86103
name.forEach((settingName) => {
87-
const settingData = data[settingName] ?? {};
104+
const settingData = data[settingName];
105+
106+
const defaultValue = getSettingDefault(settingName);
88107

89108
const cacheEntryParams: GetSingleSettingParams = {
90109
name: settingName,
91110
user,
92111
};
93-
const newValue = {name: settingName, user, value: settingData?.value};
112+
const newSetting = {
113+
name: settingName,
114+
user,
115+
value: parseSettingValue(settingData?.value) ?? defaultValue,
116+
};
94117

95118
const patch = dispatch(
96119
settingsApi.util.upsertQueryData(
97120
'getSingleSetting',
98121
cacheEntryParams,
99-
newValue,
122+
newSetting,
100123
),
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-
});
124+
);
110125

111126
patches.push(patch);
112127
});
@@ -116,24 +131,10 @@ export const settingsApi = api.injectEndpoints({
116131

117132
return {data};
118133
} catch (error) {
119-
return {error};
134+
return {error: serializeError(error)};
120135
}
121136
},
122137
}),
123138
}),
124139
overrideExisting: 'throw',
125140
});
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)