Skip to content

Commit 42c816e

Browse files
Save and load theme using settings api (#4433)
* set theme from settings api * store system theme in react. load theme name from api in settings page * theme settings component added --------- Co-authored-by: Pavel Angelov <[email protected]>
1 parent 5dd21f2 commit 42c816e

File tree

7 files changed

+211
-52
lines changed

7 files changed

+211
-52
lines changed

redisinsight/ui/src/components/config/Config.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ import { useEffect } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { useLocation } from 'react-router-dom'
44
import { isNumber } from 'lodash'
5-
import { BrowserStorageItem, FeatureFlags } from 'uiSrc/constants'
5+
import { BrowserStorageItem, FeatureFlags, Theme } from 'uiSrc/constants'
66
import { BuildType } from 'uiSrc/constants/env'
77
import { BUILD_FEATURES } from 'uiSrc/constants/featuresHighlighting'
8-
import { localStorageService, setObjectStorage } from 'uiSrc/services'
8+
import {
9+
localStorageService,
10+
setObjectStorage,
11+
themeService,
12+
} from 'uiSrc/services'
13+
914
import {
1015
appFeatureFlagsFeaturesSelector,
1116
setFeaturesToHighlight,
@@ -102,6 +107,12 @@ const Config = () => {
102107
}
103108
}, [id])
104109

110+
useEffect(() => {
111+
if (config) {
112+
checkAndSetTheme()
113+
}
114+
}, [config])
115+
105116
useEffect(() => {
106117
if (config && spec && envDependentFeature?.flag) {
107118
checkSettingsToShowPopup()
@@ -198,6 +209,12 @@ const Config = () => {
198209
)
199210
}
200211

212+
const checkAndSetTheme = () => {
213+
const theme = config?.theme
214+
if (theme && localStorageService.get(BrowserStorageItem.theme) !== theme)
215+
themeService.applyTheme(theme as Theme)
216+
}
217+
201218
return null
202219
}
203220

redisinsight/ui/src/pages/settings/SettingsPage.tsx

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,23 @@
1-
import React, { useContext, useEffect, useState } from 'react'
1+
import React, { useEffect, useState } from 'react'
22
import cx from 'classnames'
33
import {
44
EuiCallOut,
55
EuiCollapsibleNavGroup,
6-
EuiForm,
7-
EuiFormRow,
86
EuiLoadingSpinner,
9-
EuiSuperSelect,
107
EuiText,
118
EuiTitle,
129
} from '@elastic/eui'
1310
import { useDispatch, useSelector } from 'react-redux'
1411

1512
import { setTitle } from 'uiSrc/utils'
16-
import { FeatureFlags, Theme, THEMES } from 'uiSrc/constants'
13+
import { FeatureFlags } from 'uiSrc/constants'
1714
import { useDebouncedEffect } from 'uiSrc/services'
1815
import {
1916
ConsentsNotifications,
2017
ConsentsPrivacy,
2118
FeatureFlagComponent,
2219
} from 'uiSrc/components'
23-
import {
24-
sendEventTelemetry,
25-
sendPageViewTelemetry,
26-
TelemetryEvent,
27-
TelemetryPageView,
28-
} from 'uiSrc/telemetry'
29-
import { ThemeContext } from 'uiSrc/contexts/themeContext'
20+
import { sendPageViewTelemetry, TelemetryPageView } from 'uiSrc/telemetry'
3021
import {
3122
fetchUserConfigSettings,
3223
fetchUserSettingsSpec,
@@ -45,6 +36,7 @@ import {
4536
AdvancedSettings,
4637
CloudSettings,
4738
WorkbenchSettings,
39+
ThemeSettings,
4840
} from './components'
4941
import { DateTimeFormatter } from './components/general-settings'
5042
import styles from './styles.module.scss'
@@ -57,14 +49,6 @@ const SettingsPage = () => {
5749

5850
const dispatch = useDispatch()
5951

60-
const options = THEMES
61-
const themeContext = useContext(ThemeContext)
62-
let { theme, changeTheme, usingSystemTheme } = themeContext
63-
64-
if (usingSystemTheme) {
65-
theme = Theme.System
66-
}
67-
6852
useEffect(() => {
6953
// componentDidMount
7054
// fetch config settings, after that take spec
@@ -80,36 +64,9 @@ const SettingsPage = () => {
8064
useDebouncedEffect(() => setLoading(settingsLoading), 100, [settingsLoading])
8165
setTitle('Settings')
8266

83-
const onChange = (value: string) => {
84-
const previousValue = theme
85-
changeTheme(value)
86-
sendEventTelemetry({
87-
event: TelemetryEvent.SETTINGS_COLOR_THEME_CHANGED,
88-
eventData: {
89-
previousColorTheme: previousValue,
90-
currentColorTheme: value,
91-
},
92-
})
93-
}
94-
9567
const Appearance = () => (
9668
<>
97-
<EuiForm component="form">
98-
<EuiTitle size="xs">
99-
<h4>Color Theme</h4>
100-
</EuiTitle>
101-
<Spacer size="m" />
102-
<EuiFormRow label="Specifies the color theme to be used in Redis Insight:">
103-
<EuiSuperSelect
104-
options={options}
105-
valueOfSelected={theme}
106-
onChange={onChange}
107-
style={{ marginTop: '12px' }}
108-
data-test-subj="select-theme"
109-
/>
110-
</EuiFormRow>
111-
<Spacer size="xl" />
112-
</EuiForm>
69+
<ThemeSettings />
11370
<ConsentsNotifications />
11471
<Divider colorVariable="separatorColor" />
11572
<Spacer />

redisinsight/ui/src/pages/settings/components/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,12 @@ import AdvancedSettings from './advanced-settings'
22
import WorkbenchSettings from './workbench-settings'
33
import CloudSettings from './cloud-settings'
44
import GeneralSettings from './general-settings'
5+
import ThemeSettings from './theme-settings'
56

6-
export { AdvancedSettings, WorkbenchSettings, CloudSettings, GeneralSettings }
7+
export {
8+
AdvancedSettings,
9+
WorkbenchSettings,
10+
CloudSettings,
11+
GeneralSettings,
12+
ThemeSettings,
13+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React from 'react'
2+
import { cloneDeep } from 'lodash'
3+
import {
4+
cleanup,
5+
fireEvent,
6+
mockedStore,
7+
render,
8+
screen,
9+
waitFor,
10+
} from 'uiSrc/utils/test-utils'
11+
import { Theme, THEMES } from 'uiSrc/constants'
12+
import { TelemetryEvent } from 'uiSrc/telemetry'
13+
import { updateUserConfigSettingsAction } from 'uiSrc/slices/user/user-settings'
14+
15+
import ThemeSettings from './ThemeSettings'
16+
17+
jest.mock('uiSrc/telemetry', () => ({
18+
...jest.requireActual('uiSrc/telemetry'),
19+
sendEventTelemetry: jest.fn(),
20+
}))
21+
22+
jest.mock('uiSrc/slices/user/user-settings', () => {
23+
const original = jest.requireActual('uiSrc/slices/user/user-settings')
24+
return {
25+
...original,
26+
updateUserConfigSettingsAction: jest.fn(() => ({ type: 'TEST_ACTION' })),
27+
}
28+
})
29+
30+
const { sendEventTelemetry } = require('uiSrc/telemetry')
31+
32+
let store: typeof mockedStore
33+
34+
describe('ThemeSettings', () => {
35+
beforeEach(() => {
36+
cleanup()
37+
store = cloneDeep(mockedStore)
38+
store.clearActions()
39+
})
40+
41+
it('should render', () => {
42+
expect(render(<ThemeSettings />)).toBeTruthy()
43+
})
44+
45+
it('should update the selected theme and dispatch telemetry on change', async () => {
46+
const initialTheme = Theme.Dark
47+
const newTheme = Theme.Light
48+
49+
// @ts-ignore-next-line
50+
store.getState().user.settings.config = {
51+
...store.getState().user.settings.config,
52+
theme: initialTheme,
53+
}
54+
55+
render(<ThemeSettings />, { store })
56+
57+
const dropdownButton = screen.getByTestId('select-theme')
58+
fireEvent.click(dropdownButton)
59+
60+
await waitFor(() => {
61+
expect(screen.getByText('Light Theme')).toBeInTheDocument()
62+
})
63+
64+
fireEvent.click(screen.getByText('Light Theme'))
65+
66+
expect(updateUserConfigSettingsAction).toHaveBeenCalledWith({
67+
theme: newTheme,
68+
})
69+
70+
expect(sendEventTelemetry).toHaveBeenCalledWith({
71+
event: TelemetryEvent.SETTINGS_COLOR_THEME_CHANGED,
72+
eventData: {
73+
previousColorTheme: initialTheme,
74+
currentColorTheme: newTheme,
75+
},
76+
})
77+
})
78+
79+
it('should display all theme options from THEMES', async () => {
80+
// @ts-ignore-next-line
81+
store.getState().user.settings.config = {
82+
...store.getState().user.settings.config,
83+
theme: Theme.Dark,
84+
}
85+
86+
render(<ThemeSettings />, { store })
87+
88+
const dropdownButton = screen.getByTestId('select-theme')
89+
fireEvent.click(dropdownButton)
90+
91+
const darkTheme = THEMES.find((theme) => theme.value === Theme.Dark)
92+
93+
const rest = THEMES.filter((theme) => theme.value !== Theme.Dark)
94+
95+
await waitFor(() => {
96+
// Selected theme name appears 2 times in the dropdown
97+
// once in the list and once in the selected value
98+
expect(
99+
screen.queryAllByText(darkTheme?.inputDisplay as string).length,
100+
).toEqual(2)
101+
102+
rest.forEach((theme) => {
103+
expect(
104+
screen.getByText(theme.inputDisplay as string),
105+
).toBeInTheDocument()
106+
})
107+
})
108+
})
109+
})
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React, { useContext, useEffect, useRef, useState } from 'react'
2+
import { useDispatch, useSelector } from 'react-redux'
3+
import { EuiForm, EuiFormRow, EuiSuperSelect, EuiTitle } from '@elastic/eui'
4+
import { Spacer } from 'uiSrc/components/base/layout/spacer'
5+
import {
6+
updateUserConfigSettingsAction,
7+
userSettingsSelector,
8+
} from 'uiSrc/slices/user/user-settings'
9+
import { ThemeContext } from 'uiSrc/contexts/themeContext'
10+
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
11+
import { THEMES } from 'uiSrc/constants'
12+
13+
const ThemeSettings = () => {
14+
const dispatch = useDispatch()
15+
const [selectedTheme, setSelectedTheme] = useState('')
16+
const options = THEMES
17+
const themeContext = useContext(ThemeContext)
18+
const { theme, changeTheme } = themeContext
19+
const { config } = useSelector(userSettingsSelector)
20+
const previousThemeRef = useRef<string>(theme)
21+
22+
useEffect(() => {
23+
if (config) {
24+
setSelectedTheme(config.theme)
25+
previousThemeRef.current = config.theme
26+
}
27+
}, [config])
28+
29+
const onChange = (value: string) => {
30+
const previousValue = previousThemeRef.current
31+
if (previousValue === value) {
32+
return
33+
}
34+
changeTheme(value)
35+
dispatch(updateUserConfigSettingsAction({ theme: value }))
36+
sendEventTelemetry({
37+
event: TelemetryEvent.SETTINGS_COLOR_THEME_CHANGED,
38+
eventData: {
39+
previousColorTheme: previousValue,
40+
currentColorTheme: value,
41+
},
42+
})
43+
}
44+
45+
return (
46+
<EuiForm component="form">
47+
<EuiTitle size="xs">
48+
<h4>Color Theme</h4>
49+
</EuiTitle>
50+
<Spacer size="m" />
51+
<EuiFormRow label="Specifies the color theme to be used in Redis Insight:">
52+
<EuiSuperSelect
53+
options={options}
54+
valueOfSelected={selectedTheme}
55+
onChange={onChange}
56+
style={{ marginTop: '12px' }}
57+
data-test-subj="select-theme"
58+
data-testid="select-theme"
59+
/>
60+
</EuiFormRow>
61+
<Spacer size="xl" />
62+
</EuiForm>
63+
)
64+
}
65+
66+
export default ThemeSettings
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import ThemeSettings from './ThemeSettings'
2+
3+
export default ThemeSettings

redisinsight/ui/src/services/theme.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ThemeService {
3030

3131
document.adoptedStyleSheets = [sheet]
3232

33-
localStorageService.set(BrowserStorageItem.theme, actualTheme)
33+
localStorageService.set(BrowserStorageItem.theme, newTheme)
3434
document.body.classList.value = `theme_${actualTheme}`
3535
}
3636

0 commit comments

Comments
 (0)