Skip to content

Commit e8a17a1

Browse files
fix(UserSettings): separate Setting, enable additional settings (#396)
1 parent a7df6d1 commit e8a17a1

File tree

7 files changed

+157
-114
lines changed

7 files changed

+157
-114
lines changed

src/containers/App/Content.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import ReduxTooltip from '../ReduxTooltip/ReduxTooltip';
1616
import Header from '../Header/Header';
1717
import AppIcons from '../AppIcons/AppIcons';
1818

19-
import {getSettingValue} from '../../store/reducers/settings';
19+
import {getParsedSettingValue} from '../../store/reducers/settings';
2020
import {THEME_KEY} from '../../utils/constants';
2121

2222
import './App.scss';
@@ -100,7 +100,7 @@ ContentWrapper.propTypes = {
100100

101101
function mapStateToProps(state) {
102102
return {
103-
theme: getSettingValue(state, THEME_KEY),
103+
theme: getParsedSettingValue(state, THEME_KEY),
104104
isAuthenticated: state.authentication.isAuthenticated,
105105
singleClusterMode: state.singleClusterMode,
106106
};

src/containers/AsideNavigation/AsideNavigation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import userChecked from '../../assets/icons/user-check.svg';
1919
import settingsIcon from '../../assets/icons/settings.svg';
2020
import supportIcon from '../../assets/icons/support.svg';
2121

22-
import UserSettings from '../UserSettings/UserSettings';
22+
import {UserSettings} from '../UserSettings/UserSettings';
2323

2424
import routes, {createHref, CLUSTER_PAGES} from '../../routes';
2525

src/containers/Nodes/Nodes.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
DEFAULT_TABLE_SETTINGS,
2222
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
2323
} from '../../utils/constants';
24-
import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
24+
import {useAutofetcher, useSetting, useTypedSelector} from '../../utils/hooks';
2525
import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
2626

2727
import {setHeader} from '../../store/reducers/header';
@@ -33,7 +33,7 @@ import {
3333
resetNodesState,
3434
getComputeNodes,
3535
} from '../../store/reducers/nodes';
36-
import {changeFilter, getSettingValue} from '../../store/reducers/settings';
36+
import {changeFilter} from '../../store/reducers/settings';
3737
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
3838

3939
import {isDatabaseEntityType} from '../Tenant/utils/schema';
@@ -72,14 +72,12 @@ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesPr
7272

7373
const nodes = useTypedSelector(getFilteredPreparedNodesList);
7474

75-
const useNodesEndpoint = useTypedSelector((state) =>
76-
getSettingValue(state, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY),
77-
);
75+
const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
7876

7977
const fetchNodes = useCallback(() => {
8078
// For not DB entities we always use /compute endpoint instead of /nodes
8179
// since /nodes can return data only for tenants
82-
if (path && (!JSON.parse(useNodesEndpoint) || !isDatabaseEntityType(type))) {
80+
if (path && (useNodesEndpoint || !isDatabaseEntityType(type))) {
8381
dispatch(getComputeNodes(path));
8482
} else {
8583
dispatch(getNodes({tenant: path}));

src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React from 'react';
2-
import {useSelector} from 'react-redux';
32
import cn from 'bem-cn-lite';
43

54
import {INVERTED_DISKS_KEY} from '../../../utils/constants';
6-
import {getSettingValue} from '../../../store/reducers/settings';
5+
import {useSetting} from '../../../utils/hooks';
76

87
import './DiskStateProgressBar.scss';
98

@@ -35,7 +34,7 @@ export function DiskStateProgressBar({
3534
severity,
3635
compact,
3736
}: DiskStateProgressBarProps) {
38-
const inverted = useSelector((state) => JSON.parse(getSettingValue(state, INVERTED_DISKS_KEY)));
37+
const [inverted] = useSetting<boolean | undefined>(INVERTED_DISKS_KEY);
3938

4039
const renderAllocatedPercent = () => {
4140
if (compact) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import type {ReactNode} from 'react';
2+
3+
import {RadioButton, Switch} from '@gravity-ui/uikit';
4+
import {Settings} from '@gravity-ui/navigation';
5+
6+
import {LabelWithPopover} from '../../components/LabelWithPopover/LabelWithPopover';
7+
8+
import {useSetting} from '../../utils/hooks';
9+
10+
import {b} from './UserSettings';
11+
12+
export type SettingsElementType = 'switch' | 'radio';
13+
14+
export interface SettingProps {
15+
type?: SettingsElementType;
16+
title: string;
17+
settingKey: string;
18+
helpPopoverContent?: ReactNode;
19+
values?: {value: string; content: string}[];
20+
defaultValue?: unknown;
21+
}
22+
23+
export const Setting = ({
24+
type = 'switch',
25+
settingKey,
26+
title,
27+
helpPopoverContent,
28+
values,
29+
defaultValue,
30+
}: SettingProps) => {
31+
const [settingValue, setValue] = useSetting(settingKey, defaultValue);
32+
33+
const renderTitleComponent = (value: ReactNode) => {
34+
if (helpPopoverContent) {
35+
return (
36+
<LabelWithPopover
37+
className={b('item-with-popup')}
38+
contentClassName={b('popup')}
39+
text={value}
40+
popoverContent={helpPopoverContent}
41+
/>
42+
);
43+
}
44+
45+
return value;
46+
};
47+
48+
const getSettingsElement = (elementType: SettingsElementType) => {
49+
switch (elementType) {
50+
case 'switch': {
51+
return <Switch checked={Boolean(settingValue)} onUpdate={setValue} />;
52+
}
53+
54+
case 'radio': {
55+
if (!values) {
56+
return null;
57+
}
58+
59+
return (
60+
<RadioButton value={String(settingValue)} onUpdate={setValue}>
61+
{values.map(({value, content}) => {
62+
return (
63+
<RadioButton.Option value={value} key={value}>
64+
{content}
65+
</RadioButton.Option>
66+
);
67+
})}
68+
</RadioButton>
69+
);
70+
}
71+
72+
default:
73+
return null;
74+
}
75+
};
76+
77+
return (
78+
<Settings.Item title={title} renderTitleComponent={renderTitleComponent}>
79+
{getSettingsElement(type)}
80+
</Settings.Item>
81+
);
82+
};
Lines changed: 61 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,102 @@
1-
import {ReactNode} from 'react';
2-
import {connect} from 'react-redux';
31
import cn from 'bem-cn-lite';
42

5-
import {RadioButton, Switch} from '@gravity-ui/uikit';
63
import {Settings} from '@gravity-ui/navigation';
74

85
import favoriteFilledIcon from '../../assets/icons/star.svg';
96
import flaskIcon from '../../assets/icons/flask.svg';
107

11-
import {LabelWithPopover} from '../../components/LabelWithPopover/LabelWithPopover';
12-
138
import {
149
ENABLE_QUERY_MODES_FOR_EXPLAIN,
1510
INVERTED_DISKS_KEY,
1611
THEME_KEY,
1712
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
1813
} from '../../utils/constants';
1914

20-
import {setSettingValue} from '../../store/reducers/settings';
15+
import {Setting, SettingProps} from './Setting';
2116

2217
import './UserSettings.scss';
2318

24-
const b = cn('ydb-user-settings');
19+
export const b = cn('ydb-user-settings');
2520

2621
enum Theme {
2722
light = 'light',
2823
dark = 'dark',
2924
system = 'system',
3025
}
3126

32-
function UserSettings(props: any) {
33-
const _onThemeChangeHandler = (value: string) => {
34-
props.setSettingValue(THEME_KEY, value);
35-
};
36-
37-
const _onInvertedDisksChangeHandler = (value: boolean) => {
38-
props.setSettingValue(INVERTED_DISKS_KEY, String(value));
39-
};
40-
41-
const _onNodesEndpointChangeHandler = (value: boolean) => {
42-
props.setSettingValue(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY, String(value));
43-
};
44-
45-
const _onExplainQueryModesChangeHandler = (value: boolean) => {
46-
props.setSettingValue(ENABLE_QUERY_MODES_FOR_EXPLAIN, String(value));
47-
};
48-
49-
const renderBreakNodesSettingsItem = (title: ReactNode) => {
50-
return (
51-
<LabelWithPopover
52-
className={b('item-with-popup')}
53-
contentClassName={b('popup')}
54-
text={title}
55-
popoverContent={
56-
'Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on older versions'
57-
}
58-
/>
59-
);
60-
};
27+
const themeValues = [
28+
{
29+
value: Theme.system,
30+
content: 'System',
31+
},
32+
{
33+
value: Theme.light,
34+
content: 'Light',
35+
},
36+
{
37+
value: Theme.dark,
38+
content: 'Dark',
39+
},
40+
];
41+
42+
export enum SettingsSection {
43+
general = 'general',
44+
experiments = 'experiments',
45+
}
6146

62-
const renderEnableExplainQueryModesItem = (title: ReactNode) => {
63-
return (
64-
<LabelWithPopover
65-
className={b('item-with-popup')}
66-
contentClassName={b('popup')}
67-
text={title}
68-
popoverContent={
69-
'Enable script | scan query mode selector for both run and explain. May not work on some versions'
70-
}
71-
/>
72-
);
73-
};
47+
interface UserSettingsProps {
48+
settings?: Partial<Record<SettingsSection, SettingProps[]>>;
49+
}
7450

51+
export const UserSettings = ({settings}: UserSettingsProps) => {
7552
return (
7653
<Settings>
7754
<Settings.Page
78-
id="general"
55+
id={SettingsSection.general}
7956
title="General"
8057
icon={{data: favoriteFilledIcon, height: 14, width: 14}}
8158
>
8259
<Settings.Section title="General">
83-
<Settings.Item title="Interface theme">
84-
<RadioButton value={props.theme} onUpdate={_onThemeChangeHandler}>
85-
<RadioButton.Option value={Theme.system}>System</RadioButton.Option>
86-
<RadioButton.Option value={Theme.light}>Light</RadioButton.Option>
87-
<RadioButton.Option value={Theme.dark}>Dark</RadioButton.Option>
88-
</RadioButton>
89-
</Settings.Item>
60+
<Setting
61+
settingKey={THEME_KEY}
62+
title="Interface theme"
63+
type="radio"
64+
values={themeValues}
65+
/>
66+
{settings?.[SettingsSection.general]?.map((setting) => (
67+
<Setting key={setting.settingKey} {...setting} />
68+
))}
9069
</Settings.Section>
9170
</Settings.Page>
92-
<Settings.Page id="experiments" title="Experiments" icon={{data: flaskIcon}}>
71+
<Settings.Page
72+
id={SettingsSection.experiments}
73+
title="Experiments"
74+
icon={{data: flaskIcon}}
75+
>
9376
<Settings.Section title="Experiments">
94-
<Settings.Item title="Inverted disks space indicators">
95-
<Switch
96-
checked={props.invertedDisks}
97-
onUpdate={_onInvertedDisksChangeHandler}
98-
/>
99-
</Settings.Item>
100-
<Settings.Item
101-
title="Break the Nodes tab in Diagnostics"
102-
renderTitleComponent={renderBreakNodesSettingsItem}
103-
>
104-
<Switch
105-
checked={props.useNodesEndpointInDiagnostics}
106-
onUpdate={_onNodesEndpointChangeHandler}
107-
/>
108-
</Settings.Item>
109-
<Settings.Item
110-
title="Enable query modes for explain"
111-
renderTitleComponent={renderEnableExplainQueryModesItem}
112-
>
113-
<Switch
114-
checked={props.enableQueryModesForExplain}
115-
onUpdate={_onExplainQueryModesChangeHandler}
116-
/>
117-
</Settings.Item>
77+
<Setting
78+
settingKey={INVERTED_DISKS_KEY}
79+
title={'Inverted disks space indicators'}
80+
/>
81+
<Setting
82+
settingKey={USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY}
83+
title={'Break the Nodes tab in Diagnostics'}
84+
helpPopoverContent={
85+
'Use /viewer/json/nodes endpoint for Nodes Tab in diagnostics. It returns incorrect data on older versions'
86+
}
87+
/>
88+
<Setting
89+
settingKey={ENABLE_QUERY_MODES_FOR_EXPLAIN}
90+
title={'Enable query modes for explain'}
91+
helpPopoverContent={
92+
'Enable script | scan query mode selector for both run and explain. May not work on some versions'
93+
}
94+
/>
95+
{settings?.[SettingsSection.experiments]?.map((setting) => (
96+
<Setting key={setting.settingKey} {...setting} />
97+
))}
11898
</Settings.Section>
11999
</Settings.Page>
120100
</Settings>
121101
);
122-
}
123-
124-
const mapStateToProps = (state: any) => {
125-
const {theme, invertedDisks, useNodesEndpointInDiagnostics, enableQueryModesForExplain} =
126-
state.settings.userSettings;
127-
128-
return {
129-
theme,
130-
invertedDisks: JSON.parse(invertedDisks),
131-
useNodesEndpointInDiagnostics: JSON.parse(useNodesEndpointInDiagnostics),
132-
enableQueryModesForExplain: JSON.parse(enableQueryModesForExplain),
133-
};
134102
};
135-
136-
const mapDispatchToProps = {
137-
setSettingValue,
138-
};
139-
140-
export default connect(mapStateToProps, mapDispatchToProps)(UserSettings);

src/utils/hooks/useSetting.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ import {getParsedSettingValue, setSettingValue} from '../../store/reducers/setti
55

66
import {useTypedSelector} from './useTypedSelector';
77

8-
export const useSetting = <T>(key: string, defaultValue: T): [T, (value: T) => void] => {
8+
export const useSetting = <T>(key: string, defaultValue?: T): [T, (value: T) => void] => {
99
const dispatch = useDispatch();
1010

1111
const settingValue: T = useTypedSelector(
12-
(state) => getParsedSettingValue(state, key) || defaultValue,
12+
(state) => getParsedSettingValue(state, key) ?? defaultValue,
1313
);
1414

1515
const setValue = useCallback(
1616
(value: T) => {
17-
dispatch(setSettingValue(key, JSON.stringify(value)));
17+
const preparedValue = typeof value === 'string' ? value : JSON.stringify(value);
18+
19+
dispatch(setSettingValue(key, preparedValue));
1820
},
1921
[dispatch, key],
2022
);

0 commit comments

Comments
 (0)