Skip to content

Commit ae2fbbe

Browse files
feat: migrate from external settings api (#621)
1 parent 992cfa8 commit ae2fbbe

File tree

13 files changed

+166
-150
lines changed

13 files changed

+166
-150
lines changed

src/containers/App/Content.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import ReduxTooltip from '../ReduxTooltip/ReduxTooltip';
1616
import Header from '../Header/Header';
1717
import AppIcons from '../AppIcons/AppIcons';
1818

19-
import {getParsedSettingValue} from '../../store/reducers/settings/settings';
2019
import {THEME_KEY} from '../../utils/constants';
20+
import {useSetting} from '../../utils/hooks';
2121

2222
import './App.scss';
2323
import PropTypes from 'prop-types';
@@ -72,7 +72,10 @@ Content.propTypes = {
7272
};
7373

7474
function ContentWrapper(props) {
75-
const {theme, singleClusterMode, isAuthenticated} = props;
75+
const {singleClusterMode, isAuthenticated} = props;
76+
77+
const [theme] = useSetting(THEME_KEY);
78+
7679
return (
7780
<HistoryContext.Consumer>
7881
{(history) => (
@@ -96,15 +99,13 @@ function ContentWrapper(props) {
9699
}
97100

98101
ContentWrapper.propTypes = {
99-
theme: PropTypes.string,
100102
singleClusterMode: PropTypes.bool,
101103
isAuthenticated: PropTypes.bool,
102104
children: PropTypes.node,
103105
};
104106

105107
function mapStateToProps(state) {
106108
return {
107-
theme: getParsedSettingValue(state, THEME_KEY),
108109
isAuthenticated: state.authentication.isAuthenticated,
109110
singleClusterMode: state.singleClusterMode,
110111
};

src/containers/AsideNavigation/AsideNavigation.tsx

+12-27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {useState} from 'react';
2-
import {connect} from 'react-redux';
2+
import {useDispatch} from 'react-redux';
33
import {useLocation} from 'react-router';
44
import {useHistory} from 'react-router-dom';
55
import cn from 'bem-cn-lite';
@@ -19,7 +19,6 @@ import settingsIcon from '../../assets/icons/settings.svg';
1919
import supportIcon from '../../assets/icons/support.svg';
2020

2121
import {logout} from '../../store/reducers/authentication/authentication';
22-
import {getParsedSettingValue, setSettingValue} from '../../store/reducers/settings/settings';
2322
import {TENANT_PAGE, TENANT_PAGES_IDS} from '../../store/reducers/tenant/constants';
2423
import routes, {TENANT, createHref, parseQuery} from '../../routes';
2524
import {useSetting, useTypedSelector} from '../../utils/hooks';
@@ -117,10 +116,6 @@ function YdbUserDropdown({isCompact, popupAnchor, ydbUser}: YdbUserDropdownProps
117116

118117
interface AsideNavigationProps {
119118
children: React.ReactNode;
120-
ydbUser: string;
121-
compact: boolean;
122-
logout: VoidFunction;
123-
setSettingValue: (name: string, value: string) => void;
124119
}
125120

126121
enum Panel {
@@ -189,15 +184,19 @@ export const useGetLeftNavigationItems = () => {
189184

190185
function AsideNavigation(props: AsideNavigationProps) {
191186
const history = useHistory();
187+
const dispatch = useDispatch();
192188

193189
const [visiblePanel, setVisiblePanel] = useState<Panel>();
194190

195-
const setIsCompact = (compact: boolean) => {
196-
props.setSettingValue(ASIDE_HEADER_COMPACT_KEY, JSON.stringify(compact));
197-
};
191+
const {user: ydbUser} = useTypedSelector((state) => state.authentication);
192+
const [compact, setIsCompact] = useSetting<boolean>(ASIDE_HEADER_COMPACT_KEY);
198193

199194
const menuItems = useGetLeftNavigationItems();
200195

196+
const onLogout = () => {
197+
dispatch(logout());
198+
};
199+
201200
return (
202201
<React.Fragment>
203202
<AsideHeader
@@ -207,7 +206,7 @@ function AsideNavigation(props: AsideNavigationProps) {
207206
onClick: () => history.push('/'),
208207
}}
209208
menuItems={menuItems}
210-
compact={props.compact}
209+
compact={compact}
211210
onChangeCompact={setIsCompact}
212211
className={b()}
213212
renderContent={() => props.children}
@@ -248,8 +247,8 @@ function AsideNavigation(props: AsideNavigationProps) {
248247
isCompact={compact}
249248
popupAnchor={asideRef}
250249
ydbUser={{
251-
login: props.ydbUser,
252-
logout: props.logout,
250+
login: ydbUser,
251+
logout: onLogout,
253252
}}
254253
/>
255254
</React.Fragment>
@@ -269,18 +268,4 @@ function AsideNavigation(props: AsideNavigationProps) {
269268
);
270269
}
271270

272-
const mapStateToProps = (state: any) => {
273-
const {user: ydbUser} = state.authentication;
274-
275-
return {
276-
ydbUser,
277-
compact: getParsedSettingValue(state, ASIDE_HEADER_COMPACT_KEY),
278-
};
279-
};
280-
281-
const mapDispatchToProps = {
282-
logout,
283-
setSettingValue,
284-
};
285-
286-
export default connect(mapStateToProps, mapDispatchToProps)(AsideNavigation);
271+
export default AsideNavigation;

src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,7 @@ export const Partitions = ({path}: PartitionsProps) => {
7070
error: nodesError,
7171
} = useTypedSelector((state) => state.nodesList);
7272

73-
const [hiddenColumns, setHiddenColumns] = useSetting<string[]>(
74-
PARTITIONS_HIDDEN_COLUMNS_KEY,
75-
[],
76-
);
73+
const [hiddenColumns, setHiddenColumns] = useSetting<string[]>(PARTITIONS_HIDDEN_COLUMNS_KEY);
7774

7875
const [columns, columnsIdsForSelector] = useGetPartitionsColumns(selectedConsumer);
7976

src/containers/Tenant/Query/QueryEditor/QueryEditor.js

+4-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
setTenantPath,
1818
} from '../../../../store/reducers/executeQuery';
1919
import {getExplainQuery, getExplainQueryAst} from '../../../../store/reducers/explainQuery';
20-
import {getParsedSettingValue, setSettingValue} from '../../../../store/reducers/settings/settings';
2120
import {setShowPreview} from '../../../../store/reducers/schema/schema';
2221
import {
2322
DEFAULT_IS_QUERY_RESULT_COLLAPSED,
@@ -86,6 +85,7 @@ function QueryEditor(props) {
8685
const [queryMode, setQueryMode] = useQueryModes();
8786
const [useMultiSchema] = useSetting(QUERY_USE_MULTI_SCHEMA_KEY);
8887
const [lastUsedQueryAction, setLastUsedQueryAction] = useSetting(LAST_USED_QUERY_ACTION_KEY);
88+
const [savedQueries, setSavedQueries] = useSetting(SAVED_QUERIES_KEY);
8989

9090
useEffect(() => {
9191
if (savedPath !== path) {
@@ -417,7 +417,7 @@ function QueryEditor(props) {
417417

418418
const storageEventHandler = (event) => {
419419
if (event.key === SAVED_QUERIES_KEY) {
420-
props.setSettingValue(SAVED_QUERIES_KEY, event.newValue);
420+
setSavedQueries(event.newValue);
421421
}
422422
};
423423

@@ -430,8 +430,6 @@ function QueryEditor(props) {
430430
const onSaveQueryHandler = (queryName) => {
431431
const {
432432
executeQuery: {input},
433-
savedQueries = [],
434-
setSettingValue,
435433
} = props;
436434

437435
const queryIndex = savedQueries.findIndex(
@@ -445,11 +443,11 @@ function QueryEditor(props) {
445443
newSavedQueries.push(newQuery);
446444
}
447445

448-
setSettingValue(SAVED_QUERIES_KEY, JSON.stringify(newSavedQueries));
446+
setSavedQueries(newSavedQueries);
449447
};
450448

451449
const renderControls = () => {
452-
const {executeQuery, explainQuery, savedQueries} = props;
450+
const {executeQuery, explainQuery} = props;
453451

454452
return (
455453
<QueryEditorControls
@@ -511,7 +509,6 @@ const mapStateToProps = (state) => {
511509
return {
512510
executeQuery: state.executeQuery,
513511
explainQuery: state.explainQuery,
514-
savedQueries: getParsedSettingValue(state, SAVED_QUERIES_KEY),
515512
showPreview: state.schema.showPreview,
516513
currentSchema: state.schema.currentSchema,
517514
monacoHotKey: state.executeQuery?.monacoHotKey,
@@ -525,7 +522,6 @@ const mapDispatchToProps = {
525522
goToNextQuery,
526523
getExplainQuery,
527524
getExplainQueryAst,
528-
setSettingValue,
529525
setShowPreview,
530526
setMonacoHotKey,
531527
setTenantPath,

src/services/api.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import type {StorageApiRequestParams} from '../store/reducers/storage/types';
3434

3535
import {backend as BACKEND} from '../store';
3636
import {prepareSortValue} from '../utils/filters';
37-
import {settingsApi} from '../utils/settings';
3837

3938
const config = {withCredentials: !window.custom_backend};
4039

@@ -378,10 +377,12 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
378377
path_id: tenantId?.PathId,
379378
});
380379
}
381-
postSetting(name: string, value: string) {
380+
381+
/** @deprecated use localStorage instead */
382+
postSetting(settingsApi: string, name: string, value: string) {
382383
return this.request({
383384
method: 'PATCH',
384-
url: settingsApi || '',
385+
url: settingsApi,
385386
data: {[name]: value},
386387
});
387388
}

src/services/settings.ts

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import {TENANT_PAGES_IDS} from '../store/reducers/tenant/constants';
2+
3+
import {
4+
ASIDE_HEADER_COMPACT_KEY,
5+
CLUSTER_INFO_HIDDEN_KEY,
6+
INVERTED_DISKS_KEY,
7+
LANGUAGE_KEY,
8+
LAST_USED_QUERY_ACTION_KEY,
9+
PARTITIONS_HIDDEN_COLUMNS_KEY,
10+
QUERY_INITIAL_MODE_KEY,
11+
QUERY_USE_MULTI_SCHEMA_KEY,
12+
SAVED_QUERIES_KEY,
13+
TENANT_INITIAL_PAGE_KEY,
14+
THEME_KEY,
15+
USE_BACKEND_PARAMS_FOR_TABLES_KEY,
16+
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
17+
} from '../utils/constants';
18+
import {QUERY_ACTIONS, QUERY_MODES} from '../utils/query';
19+
import {parseJson} from '../utils/utils';
20+
21+
export type SettingsObject = Record<string, unknown>;
22+
23+
const USE_LOCAL_STORAGE_FOR_SETTINGS_KEY = 'useLocalStorageForSettings';
24+
25+
/** User settings keys and their default values */
26+
const DEFAULT_USER_SETTINGS: SettingsObject = {
27+
[THEME_KEY]: 'system',
28+
[LANGUAGE_KEY]: undefined,
29+
[INVERTED_DISKS_KEY]: false,
30+
[USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY]: false,
31+
[QUERY_USE_MULTI_SCHEMA_KEY]: false,
32+
[SAVED_QUERIES_KEY]: [],
33+
[TENANT_INITIAL_PAGE_KEY]: TENANT_PAGES_IDS.query,
34+
[QUERY_INITIAL_MODE_KEY]: QUERY_MODES.script,
35+
[LAST_USED_QUERY_ACTION_KEY]: QUERY_ACTIONS.execute,
36+
[ASIDE_HEADER_COMPACT_KEY]: true,
37+
[PARTITIONS_HIDDEN_COLUMNS_KEY]: [],
38+
[CLUSTER_INFO_HIDDEN_KEY]: true,
39+
[USE_BACKEND_PARAMS_FOR_TABLES_KEY]: false,
40+
};
41+
42+
class SettingsManager {
43+
constructor() {
44+
// Migrate settings to LS if external API was used before
45+
const settingsApi = window.web_version ? window.systemSettings?.settingsApi : undefined;
46+
47+
const useLocalStorage = this.readUserSettingsValue(USE_LOCAL_STORAGE_FOR_SETTINGS_KEY);
48+
49+
if (settingsApi && !useLocalStorage) {
50+
const externalUserSettings = window.userSettings;
51+
52+
if (externalUserSettings) {
53+
Object.entries(externalUserSettings).forEach(([key, value]) =>
54+
this.setUserSettingsValue(key, value),
55+
);
56+
}
57+
58+
this.setUserSettingsValue(USE_LOCAL_STORAGE_FOR_SETTINGS_KEY, true);
59+
}
60+
}
61+
62+
/**
63+
* User settings - settings stored in LS or external store
64+
*/
65+
getUserSettings() {
66+
return this.extractSettingsFromLS();
67+
}
68+
69+
/**
70+
* Returns parsed settings value.
71+
* If value cannot be parsed, returns initially stored string.
72+
* If there is no value, return default value
73+
*/
74+
readUserSettingsValue(key: string, defaultValue?: unknown) {
75+
return this.readValueFromLS(key) ?? defaultValue ?? DEFAULT_USER_SETTINGS[key];
76+
}
77+
78+
/**
79+
* Stringify value and set it to LS
80+
*/
81+
setUserSettingsValue(key: string, value: unknown) {
82+
return this.setValueToLS(key, value);
83+
}
84+
85+
private extractSettingsFromLS = () => {
86+
return Object.entries(DEFAULT_USER_SETTINGS).reduce<SettingsObject>((acc, [key, value]) => {
87+
acc[key] = this.readUserSettingsValue(key, value);
88+
return acc;
89+
}, {});
90+
};
91+
92+
private readValueFromLS = (key: string): unknown => {
93+
try {
94+
const value = localStorage.getItem(key);
95+
96+
return parseJson(value);
97+
} catch {
98+
return undefined;
99+
}
100+
};
101+
102+
private setValueToLS = (key: string, value: unknown): void => {
103+
try {
104+
if (typeof value === 'string') {
105+
localStorage.setItem(key, value);
106+
} else {
107+
localStorage.setItem(key, JSON.stringify(value));
108+
}
109+
} catch {}
110+
};
111+
}
112+
113+
export const settingsManager = new SettingsManager();

src/store/reducers/executeQuery.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import type {
99
QueryInHistory,
1010
} from '../../types/store/executeQuery';
1111
import type {QueryRequestParams, QueryMode, QuerySyntax} from '../../types/store/query';
12-
import {getValueFromLS, parseJson} from '../../utils/utils';
1312
import {QUERIES_HISTORY_KEY} from '../../utils/constants';
1413
import {QUERY_MODES, QUERY_SYNTAX, parseQueryAPIExecuteResponse} from '../../utils/query';
1514
import {parseQueryError} from '../../utils/error';
15+
import {settingsManager} from '../../services/settings';
1616
import '../../services/api';
1717

1818
import {createRequestActionTypes, createApiRequest} from '../utils';
@@ -28,7 +28,10 @@ const GO_TO_NEXT_QUERY = 'query/GO_TO_NEXT_QUERY';
2828
const SET_MONACO_HOT_KEY = 'query/SET_MONACO_HOT_KEY';
2929
const SET_TENANT_PATH = 'query/SET_TENANT_PATH';
3030

31-
const queriesHistoryInitial: string[] = parseJson(getValueFromLS(QUERIES_HISTORY_KEY, '[]'));
31+
const queriesHistoryInitial = settingsManager.readUserSettingsValue(
32+
QUERIES_HISTORY_KEY,
33+
[],
34+
) as string[];
3235

3336
const sliceLimit = queriesHistoryInitial.length - MAXIMUM_QUERIES_IN_HISTORY;
3437

@@ -97,7 +100,7 @@ const executeQuery: Reducer<ExecuteQueryState, ExecuteQueryAction> = (
97100
const newQueries = [...state.history.queries, {queryText, syntax}].slice(
98101
state.history.queries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0,
99102
);
100-
window.localStorage.setItem(QUERIES_HISTORY_KEY, JSON.stringify(newQueries));
103+
settingsManager.setUserSettingsValue(QUERIES_HISTORY_KEY, newQueries);
101104
const currentIndex = newQueries.length - 1;
102105

103106
return {

0 commit comments

Comments
 (0)