Skip to content

Commit 6728207

Browse files
authored
Merge pull request #2868 from RedisInsight/fe/feature/RI-5185-disable_refresh_for_keys
#RI-5185 - disable refresh when editing value of key
2 parents 6a510a3 + e460156 commit 6728207

File tree

21 files changed

+294
-56
lines changed

21 files changed

+294
-56
lines changed

redisinsight/ui/src/components/auto-refresh/AutoRefresh.spec.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,41 @@ describe('AutoRefresh', () => {
8787
expect(onRefresh).toBeCalledTimes(3)
8888
})
8989
})
90+
91+
it('should NOT call onRefresh with disabled state', async () => {
92+
const onRefresh = jest.fn()
93+
const { rerender } = render(<AutoRefresh {...instance(mockedProps)} onRefresh={onRefresh} />)
94+
95+
fireEvent.click(screen.getByTestId('auto-refresh-config-btn'))
96+
fireEvent.click(screen.getByTestId('auto-refresh-switch'))
97+
fireEvent.click(screen.getByTestId('refresh-rate'))
98+
fireEvent.change(screen.getByTestId(INLINE_ITEM_EDITOR), { target: { value: '1' } })
99+
100+
expect(screen.getByTestId(INLINE_ITEM_EDITOR)).toHaveValue('1')
101+
102+
screen.getByTestId(/apply-btn/).click()
103+
104+
await act(() => {
105+
rerender(<AutoRefresh {...instance(mockedProps)} onRefresh={onRefresh} disabled />)
106+
})
107+
108+
await act(async () => {
109+
await new Promise((r) => setTimeout(r, 1300))
110+
})
111+
expect(onRefresh).toBeCalledTimes(0)
112+
113+
await act(async () => {
114+
await new Promise((r) => setTimeout(r, 1300))
115+
})
116+
expect(onRefresh).toBeCalledTimes(0)
117+
118+
await act(() => {
119+
rerender(<AutoRefresh {...instance(mockedProps)} onRefresh={onRefresh} disabled={false} />)
120+
})
121+
122+
await act(async () => {
123+
await new Promise((r) => setTimeout(r, 1300))
124+
})
125+
expect(onRefresh).toBeCalledTimes(1)
126+
})
90127
})

redisinsight/ui/src/components/auto-refresh/AutoRefresh.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const AutoRefresh = ({
102102
useEffect(() => {
103103
updateLastRefresh()
104104

105-
if (enableAutoRefresh && !loading) {
105+
if (enableAutoRefresh && !loading && !disabled) {
106106
intervalRefresh = setInterval(() => {
107107
if (document.hidden) return
108108

@@ -117,7 +117,7 @@ const AutoRefresh = ({
117117
}
118118

119119
return () => clearInterval(intervalRefresh)
120-
}, [enableAutoRefresh, refreshRate, loading, lastRefreshTime])
120+
}, [enableAutoRefresh, refreshRate, loading, disabled, lastRefreshTime])
121121

122122
const getLastRefreshDelta = (time:Nullable<number>) => (Date.now() - (time || 0)) / 1_000
123123

@@ -163,12 +163,12 @@ const AutoRefresh = ({
163163
}
164164

165165
return (
166-
<div className={cx(styles.container, containerClassName, { [styles.enable]: enableAutoRefresh })}>
166+
<div className={cx(styles.container, containerClassName, { [styles.enable]: !disabled && enableAutoRefresh })}>
167167
<EuiTextColor className={styles.summary}>
168168
{displayText && (
169169
<span data-testid="refresh-message-label">{`${enableAutoRefresh ? 'Auto refresh:' : 'Last refresh:'}`}</span>
170170
)}
171-
<span className={styles.time} data-testid="refresh-message">
171+
<span className={cx(styles.time, { [styles.disabled]: disabled })} data-testid="refresh-message">
172172
{` ${enableAutoRefresh ? refreshRateMessage : refreshMessage}`}
173173
</span>
174174
</EuiTextColor>
@@ -185,7 +185,7 @@ const AutoRefresh = ({
185185
disabled={loading || disabled}
186186
onClick={handleRefreshClick}
187187
onMouseEnter={updateLastRefresh}
188-
className={cx(styles.btn, { [styles.rolling]: enableAutoRefresh })}
188+
className={cx(styles.btn, { [styles.rolling]: !disabled && enableAutoRefresh })}
189189
aria-labelledby={testid?.replaceAll?.('-', ' ') || 'Refresh button'}
190190
data-testid={testid || 'refresh-btn'}
191191
/>

redisinsight/ui/src/components/auto-refresh/styles.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
.time {
2929
padding-right: 6px;
3030
color: var(--euiTextSubduedColor) !important;
31+
32+
&.disabled {
33+
opacity: 0.5;
34+
}
3135
}
3236

3337
.summary {

redisinsight/ui/src/pages/browser/BrowserPage.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'
55
import { render, screen, fireEvent, mockedStore, cleanup, act, waitForEuiToolTipVisible } from 'uiSrc/utils/test-utils'
66
import { KeyTypes } from 'uiSrc/constants'
77
import { RootState } from 'uiSrc/slices/store'
8-
import { toggleBrowserFullScreen } from 'uiSrc/slices/browser/keys'
8+
import { setSelectedKeyRefreshDisabled, toggleBrowserFullScreen } from 'uiSrc/slices/browser/keys'
99
import BrowserPage from './BrowserPage'
1010
import KeyList, { Props as KeyListProps } from './components/key-list/KeyList'
1111

@@ -198,7 +198,7 @@ describe('KeyDetailsWrapper', () => {
198198
expect(queryByTestId('apply-btn')).toBeInTheDocument()
199199
expect(queryByTestId('apply-btn')).toBeDisabled()
200200

201-
expect(store.getActions()).toEqual([...afterRenderActions])
201+
expect(store.getActions()).toEqual([...afterRenderActions, setSelectedKeyRefreshDisabled(true)])
202202

203203
await act(async () => {
204204
fireEvent.mouseOver(screen.getByTestId('apply-btn'))
@@ -259,7 +259,7 @@ describe('KeyDetailsWrapper', () => {
259259
expect(queryByTestId('apply-btn')).toBeInTheDocument()
260260
expect(queryByTestId('apply-btn')).toBeDisabled()
261261

262-
expect(store.getActions()).toEqual([...afterRenderActions])
262+
expect(store.getActions()).toEqual([...afterRenderActions, setSelectedKeyRefreshDisabled(true)])
263263
})
264264

265265
it('Verify that user cannot save key value (List) with unprintable characters', () => {
@@ -313,7 +313,7 @@ describe('KeyDetailsWrapper', () => {
313313
expect(queryByTestId('apply-btn')).toBeInTheDocument()
314314
expect(queryByTestId('apply-btn')).toBeDisabled()
315315

316-
expect(store.getActions()).toEqual([...afterRenderActions])
316+
expect(store.getActions()).toEqual([...afterRenderActions, setSelectedKeyRefreshDisabled(true)])
317317
})
318318
})
319319

redisinsight/ui/src/pages/browser/modules/key-details-header/KeyDetailsHeader.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
3030
import { RedisResponseBuffer } from 'uiSrc/slices/interfaces'
3131
import { getBasedOnViewTypeEvent, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
3232

33-
import { resetStringValue } from 'uiSrc/slices/browser/string'
34-
import { Nullable } from 'uiSrc/utils'
3533
import { KeyDetailsHeaderFormatter } from './components/key-details-header-formatter'
3634
import { KeyDetailsHeaderName } from './components/key-details-header-name'
3735
import { KeyDetailsHeaderTTL } from './components/key-details-header-ttl'
@@ -61,7 +59,7 @@ const KeyDetailsHeader = ({
6159
keyType,
6260
Actions,
6361
}: KeyDetailsHeaderProps) => {
64-
const { loading, lastRefreshTime } = useSelector(selectedKeySelector)
62+
const { refreshing, loading, lastRefreshTime, isRefreshDisabled } = useSelector(selectedKeySelector)
6563
const {
6664
type,
6765
length,
@@ -169,7 +167,8 @@ const KeyDetailsHeader = ({
169167
<div className={styles.subtitleActionBtns}>
170168
<AutoRefresh
171169
postfix={type}
172-
loading={loading}
170+
disabled={isRefreshDisabled}
171+
loading={loading || refreshing}
173172
lastRefreshTime={lastRefreshTime}
174173
displayText={width > HIDE_LAST_REFRESH}
175174
containerClassName={styles.actionBtn}

redisinsight/ui/src/pages/browser/modules/key-details/KeyDetails.spec.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
import React from 'react'
22
import { instance, mock } from 'ts-mockito'
3-
import { render, screen } from 'uiSrc/utils/test-utils'
3+
import { cloneDeep } from 'lodash'
4+
import { cleanup, mockedStore, render, screen } from 'uiSrc/utils/test-utils'
45

6+
import { defaultSelectedKeyAction, setSelectedKeyRefreshDisabled } from 'uiSrc/slices/browser/keys'
57
import KeyDetails, { Props as KeyDetailsProps } from './KeyDetails'
68

79
const mockedProps = mock<KeyDetailsProps>()
810

11+
let store: typeof mockedStore
12+
beforeEach(() => {
13+
cleanup()
14+
store = cloneDeep(mockedStore)
15+
store.clearActions()
16+
})
17+
918
describe('KeyDetails', () => {
1019
it('should render', () => {
1120
expect(render(<KeyDetails {...instance(mockedProps)} />)).toBeTruthy()
1221
})
1322

23+
it('should call proper actions after render', () => {
24+
render(<KeyDetails {...instance(mockedProps)} />)
25+
26+
expect(store.getActions()).toEqual([
27+
defaultSelectedKeyAction(),
28+
setSelectedKeyRefreshDisabled(false)
29+
])
30+
})
31+
1432
it('should render nothing when there are no keys', () => {
1533
render(<KeyDetails {...instance(mockedProps)} totalKeys={0} keysLastRefreshTime={null} />)
1634

redisinsight/ui/src/pages/browser/modules/key-details/KeyDetails.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
keysSelector,
1010
selectedKeyDataSelector,
1111
selectedKeySelector,
12+
setSelectedKeyRefreshDisabled,
1213
} from 'uiSrc/slices/browser/keys'
1314
import { KeyTypes } from 'uiSrc/constants'
1415

@@ -57,6 +58,7 @@ const KeyDetails = (props: Props) => {
5758
// Restore key details from context in future
5859
// (selectedKey.data?.name !== keyProp)
5960
dispatch(fetchKeyInfo(keyProp))
61+
dispatch(setSelectedKeyRefreshDisabled(false))
6062
}, [keyProp])
6163

6264
useEffect(() => {

redisinsight/ui/src/pages/browser/modules/key-details/components/hash-details/hash-details-table/HashDetailsTable.spec.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import React from 'react'
22
import { instance, mock } from 'ts-mockito'
3+
import { cloneDeep } from 'lodash'
34
import { KeyValueCompressor, TEXT_DISABLED_COMPRESSED_VALUE } from 'uiSrc/constants'
45
import { hashDataSelector } from 'uiSrc/slices/browser/hash'
56
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
6-
import { RedisResponseBufferType } from 'uiSrc/slices/interfaces'
77
import { anyToBuffer, bufferToString } from 'uiSrc/utils'
8-
import { act, fireEvent, render, screen, waitForEuiToolTipVisible } from 'uiSrc/utils/test-utils'
8+
import { act, cleanup, fireEvent, mockedStore, render, screen, waitForEuiToolTipVisible } from 'uiSrc/utils/test-utils'
99
import { GZIP_COMPRESSED_VALUE_1, GZIP_COMPRESSED_VALUE_2, DECOMPRESSED_VALUE_STR_1, DECOMPRESSED_VALUE_STR_2 } from 'uiSrc/utils/tests/decompressors'
10+
import { setSelectedKeyRefreshDisabled } from 'uiSrc/slices/browser/keys'
1011
import { HashDetailsTable, Props } from './HashDetailsTable'
1112

1213
const mockedProps = mock<Props>()
13-
const fields: Array<{ field: RedisResponseBufferType, value: RedisResponseBufferType }> = [
14+
const fields: Array<{ field: any, value: any }> = [
1415
{ field: { type: 'Buffer', data: [49] }, value: { type: 'Buffer', data: [49, 65] } },
1516
{ field: { type: 'Buffer', data: [49, 50, 51] }, value: { type: 'Buffer', data: [49, 11] } },
1617
{ field: { type: 'Buffer', data: [50] }, value: { type: 'Buffer', data: [49, 234, 453] } },
@@ -39,6 +40,13 @@ jest.mock('uiSrc/slices/instances/instances', () => ({
3940
}),
4041
}))
4142

43+
let store: typeof mockedStore
44+
beforeEach(() => {
45+
cleanup()
46+
store = cloneDeep(mockedStore)
47+
store.clearActions()
48+
})
49+
4250
describe('HashDetailsTable', () => {
4351
it('should render', () => {
4452
expect(render(<HashDetailsTable {...instance(mockedProps)} />)).toBeTruthy()
@@ -92,8 +100,8 @@ describe('HashDetailsTable', () => {
92100
fields: [
93101
{ field: anyToBuffer(GZIP_COMPRESSED_VALUE_1), value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) },
94102
]
95-
})
96-
hashDataSelector.mockImplementation(hashDataSelectorMock)
103+
});
104+
(hashDataSelector as jest.Mock).mockImplementation(hashDataSelectorMock)
97105

98106
const { queryByTestId, queryAllByTestId } = render(<HashDetailsTable {...instance(mockedProps)} />)
99107
const fieldEl = queryAllByTestId(/hash-field-/)?.[0]
@@ -105,22 +113,22 @@ describe('HashDetailsTable', () => {
105113

106114
it('edit button should be disabled if data was compressed', async () => {
107115
const defaultState = jest.requireActual('uiSrc/slices/browser/hash').initialState
108-
const hashDataSelectorMock = jest.fn().mockReturnValue({
116+
const hashDataSelectorMock = jest.fn().mockReturnValueOnce({
109117
...defaultState,
110118
total: 1,
111119
key: '123zxczxczxc',
112120
fields: [
113121
{ field: anyToBuffer(GZIP_COMPRESSED_VALUE_1), value: anyToBuffer(GZIP_COMPRESSED_VALUE_2) },
114122
]
115-
})
116-
hashDataSelector.mockImplementation(hashDataSelectorMock)
123+
});
124+
(hashDataSelector as jest.Mock).mockImplementationOnce(hashDataSelectorMock);
117125

118-
connectedInstanceSelector.mockImplementation(() => ({
126+
(connectedInstanceSelector as jest.Mock).mockImplementationOnce(() => ({
119127
compressor: KeyValueCompressor.GZIP,
120128
}))
121129

122130
const { queryByTestId } = render(<HashDetailsTable {...instance(mockedProps)} />)
123-
const editBtn = queryByTestId(/edit-hash-button/)
131+
const editBtn = queryByTestId(/edit-hash-button/)!
124132

125133
fireEvent.click(editBtn)
126134

@@ -134,4 +142,19 @@ describe('HashDetailsTable', () => {
134142
expect(queryByTestId('hash-value-editor')).not.toBeInTheDocument()
135143
})
136144
})
145+
146+
it('should disable refresh after click on edit', async () => {
147+
render(<HashDetailsTable {...instance(mockedProps)} />)
148+
149+
const afterRenderActions = [...store.getActions()]
150+
151+
await act(() => {
152+
fireEvent.click(screen.queryAllByTestId(/edit-hash-button/)[0])
153+
})
154+
155+
expect(store.getActions()).toEqual([
156+
...afterRenderActions,
157+
setSelectedKeyRefreshDisabled(true)
158+
])
159+
})
137160
})

redisinsight/ui/src/pages/browser/modules/key-details/components/hash-details/hash-details-table/HashDetailsTable.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ import {
3737
updateHashFieldsAction,
3838
updateHashValueStateSelector,
3939
} from 'uiSrc/slices/browser/hash'
40-
import { keysSelector, selectedKeyDataSelector, selectedKeySelector } from 'uiSrc/slices/browser/keys'
40+
import {
41+
keysSelector,
42+
selectedKeyDataSelector,
43+
selectedKeySelector,
44+
setSelectedKeyRefreshDisabled
45+
} from 'uiSrc/slices/browser/keys'
4146
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
4247
import { RedisResponseBuffer, RedisString } from 'uiSrc/slices/interfaces'
4348
import { getBasedOnViewTypeEvent, getMatchType, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
@@ -120,6 +125,7 @@ const HashDetailsTable = (props: Props) => {
120125
setExpandedRows([])
121126
setViewFormat(viewFormatProp)
122127
setEditingIndex(null)
128+
dispatch(setSelectedKeyRefreshDisabled(false))
123129

124130
clearCache()
125131
}
@@ -165,6 +171,7 @@ const HashDetailsTable = (props: Props) => {
165171
valueItem?: RedisResponseBuffer
166172
) => {
167173
setEditingIndex(editing ? rowIndex : null)
174+
dispatch(setSelectedKeyRefreshDisabled(editing))
168175

169176
if (editing) {
170177
const value = bufferToSerializedFormat(viewFormat, valueItem, 4)

0 commit comments

Comments
 (0)