Skip to content

Commit 1c4510b

Browse files
committed
#RI-3714 - Save context for Redisearch
1 parent 90badf2 commit 1c4510b

File tree

17 files changed

+405
-76
lines changed

17 files changed

+405
-76
lines changed

redisinsight/ui/src/constants/storage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ enum BrowserStorageItem {
33
instancesSorting = 'instancesSorting',
44
theme = 'theme',
55
browserViewType = 'browserViewType',
6+
browserSearchMode = 'browserSearchMode',
67
cliClientUuid = 'cliClientUuid',
78
cliResizableContainer = 'cliResizableContainer',
89
cliInputHistory = 'cliInputHistory',

redisinsight/ui/src/pages/browser/components/browser-left-panel/BrowserLeftPanel.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ const BrowserLeftPanel = (props: Props) => {
5252
const redisearchKeysState = useSelector(redisearchDataSelector)
5353
const { loading: redisearchLoading, isSearched: redisearchIsSearched } = useSelector(redisearchSelector)
5454
const { loading: patternLoading, viewType, searchMode, isSearched: patternIsSearched } = useSelector(keysSelector)
55-
const { keyList: { isDataLoaded } } = useSelector(appContextBrowser)
5655
const { contextInstanceId } = useSelector(appContextSelector)
56+
const {
57+
keyList: { isDataLoaded, scrollPatternTopPosition, scrollRedisearchTopPosition }
58+
} = useSelector(appContextBrowser)
5759

5860
const keyListRef = useRef<any>()
5961

@@ -62,6 +64,7 @@ const BrowserLeftPanel = (props: Props) => {
6264
const keysState = searchMode === SearchMode.Pattern ? patternKeysState : redisearchKeysState
6365
const loading = searchMode === SearchMode.Pattern ? patternLoading : redisearchLoading
6466
const isSearched = searchMode === SearchMode.Pattern ? patternIsSearched : redisearchIsSearched
67+
const scrollTopPosition = searchMode === SearchMode.Pattern ? scrollPatternTopPosition : scrollRedisearchTopPosition
6568

6669
useEffect(() => {
6770
if (!isDataLoaded || contextInstanceId !== instanceId) {
@@ -118,6 +121,7 @@ const BrowserLeftPanel = (props: Props) => {
118121
ref={keyListRef}
119122
keysState={keysState}
120123
loading={loading}
124+
scrollTopPosition={scrollTopPosition}
121125
loadMoreItems={loadMoreItems}
122126
selectKey={selectKey}
123127
/>

redisinsight/ui/src/pages/browser/components/key-list/KeyList.tsx

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ import {
3939
} from 'uiSrc/slices/browser/keys'
4040
import {
4141
appContextBrowser,
42-
setBrowserKeyListScrollPosition,
42+
setBrowserPatternScrollPosition,
4343
setBrowserIsNotRendered,
44+
setBrowserRedisearchScrollPosition,
4445
} from 'uiSrc/slices/app/context'
4546
import { GroupBadge } from 'uiSrc/components'
4647
import { SCAN_COUNT_DEFAULT } from 'uiSrc/constants/api'
@@ -59,6 +60,7 @@ export interface Props {
5960
hideHeader?: boolean
6061
keysState: KeysStoreData
6162
loading: boolean
63+
scrollTopPosition: number
6264
hideFooter?: boolean
6365
selectKey: ({ rowData }: { rowData: any }) => void
6466
loadMoreItems?: (
@@ -69,15 +71,15 @@ export interface Props {
6971

7072
const KeyList = forwardRef((props: Props, ref) => {
7173
let wheelTimer = 0
72-
const { selectKey, loadMoreItems, loading, keysState, hideFooter } = props
74+
const { selectKey, loadMoreItems, loading, keysState, scrollTopPosition, hideFooter } = props
7375

7476
const { instanceId = '' } = useParams<{ instanceId: string }>()
7577

7678
const selectedKey = useSelector(selectedKeySelector)
7779
const { total, nextCursor, previousResultCount } = useSelector(keysDataSelector)
7880
const { isSearched, isFiltered, viewType, searchMode } = useSelector(keysSelector)
7981
const { selectedIndex } = useSelector(redisearchSelector)
80-
const { keyList: { scrollTopPosition, isNotRendered: isNotRenderedContext } } = useSelector(appContextBrowser)
82+
const { keyList: { isNotRendered: isNotRenderedContext } } = useSelector(appContextBrowser)
8183

8284
const [, rerender] = useState({})
8385
const [firstDataLoaded, setFirstDataLoaded] = useState<boolean>(!!keysState.keys.length)
@@ -197,9 +199,13 @@ const KeyList = forwardRef((props: Props, ref) => {
197199
}
198200
}
199201

200-
const setScrollTopPosition = (position: number) => {
201-
dispatch(setBrowserKeyListScrollPosition(position))
202-
}
202+
const setScrollTopPosition = useCallback((position: number) => {
203+
if (searchMode === SearchMode.Pattern) {
204+
dispatch(setBrowserPatternScrollPosition(position))
205+
} else {
206+
dispatch(setBrowserRedisearchScrollPosition(position))
207+
}
208+
}, [searchMode])
203209

204210
const formatItem = useCallback((item: GetKeyInfoResponse): GetKeyInfoResponse => ({
205211
...item,
@@ -388,32 +394,37 @@ const KeyList = forwardRef((props: Props, ref) => {
388394
},
389395
]
390396

397+
const VirtualizeTable = () => (
398+
<VirtualTable
399+
selectable
400+
onRowClick={selectKey}
401+
headerHeight={0}
402+
rowHeight={43}
403+
threshold={50}
404+
columns={columns}
405+
loadMoreItems={onLoadMoreItems}
406+
onWheel={onWheelSearched}
407+
loading={loading || !firstDataLoaded}
408+
items={itemsRef.current}
409+
totalItemsCount={keysState.total ? keysState.total : Infinity}
410+
scanned={isSearched || isFiltered ? keysState.scanned : 0}
411+
noItemsMessage={getNoItemsMessage()}
412+
selectedKey={selectedKey.data}
413+
scrollTopProp={scrollTopPosition}
414+
setScrollTopPosition={setScrollTopPosition}
415+
hideFooter={hideFooter}
416+
onRowsRendered={({ overscanStartIndex, overscanStopIndex }) =>
417+
onRowsRenderedDebounced(overscanStartIndex, overscanStopIndex)}
418+
/>
419+
)
420+
391421
return (
392422
<div className={styles.page}>
393423
<div className={styles.content}>
394424
<div className={cx(styles.table, { [styles.table__withoutFooter]: hideFooter })}>
395425
<div className="key-list-table" data-testid="keyList-table">
396-
<VirtualTable
397-
selectable
398-
onRowClick={selectKey}
399-
headerHeight={0}
400-
rowHeight={43}
401-
threshold={50}
402-
columns={columns}
403-
loadMoreItems={onLoadMoreItems}
404-
onWheel={onWheelSearched}
405-
loading={loading || !firstDataLoaded}
406-
items={itemsRef.current}
407-
totalItemsCount={keysState.total ? keysState.total : Infinity}
408-
scanned={isSearched || isFiltered ? keysState.scanned : 0}
409-
noItemsMessage={getNoItemsMessage()}
410-
selectedKey={selectedKey.data}
411-
scrollTopProp={scrollTopPosition}
412-
setScrollTopPosition={setScrollTopPosition}
413-
hideFooter={hideFooter}
414-
onRowsRendered={({ overscanStartIndex, overscanStopIndex }) =>
415-
onRowsRenderedDebounced(overscanStartIndex, overscanStopIndex)}
416-
/>
426+
{searchMode === SearchMode.Pattern && VirtualizeTable()}
427+
{searchMode !== SearchMode.Pattern && VirtualizeTable()}
417428
</div>
418429
</div>
419430
</div>

redisinsight/ui/src/pages/browser/components/keys-header/KeysHeader.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ import { resetBrowserTree, setBrowserKeyListDataLoaded, } from 'uiSrc/slices/app
1717

1818
import { changeKeyViewType, changeSearchMode, fetchKeys, keysSelector, resetKeysData, } from 'uiSrc/slices/browser/keys'
1919
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
20-
import { REDISEARCH_MODULES } from 'uiSrc/slices/interfaces'
2120
import { KeysStoreData, KeyViewType, SearchMode } from 'uiSrc/slices/interfaces/keys'
2221
import { getBasedOnViewTypeEvent, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
23-
import AutoRefresh from '../auto-refresh'
22+
import { isRedisearchAvailable } from 'uiSrc/utils'
2423

24+
import AutoRefresh from '../auto-refresh'
2525
import FilterKeyType from '../filter-key-type'
2626
import RediSearchIndexesList from '../redisearch-key-list'
2727
import SearchKeyList from '../search-key-list'
@@ -129,8 +129,7 @@ const KeysHeader = (props: Props) => {
129129
tooltipText: 'Search by Values of Keys',
130130
ariaLabel: 'Search by Values of Keys button',
131131
dataTestId: 'search-mode-redisearch-btn',
132-
disabled: !modules?.some(({ name }) =>
133-
REDISEARCH_MODULES.some((search) => name === search)),
132+
disabled: !isRedisearchAvailable(modules),
134133
isActiveView() { return searchMode === this.type },
135134
getClassName() {
136135
return cx(styles.viewTypeBtn, { [styles.active]: this.isActiveView() })
@@ -255,6 +254,8 @@ const KeysHeader = (props: Props) => {
255254
}
256255

257256
dispatch(changeSearchMode(mode))
257+
258+
localStorageService.set(BrowserStorageItem.browserSearchMode, mode)
258259
}
259260

260261
const AddKeyBtn = (

redisinsight/ui/src/pages/browser/components/redisearch-key-list/RediSearchIndexesList.spec.tsx

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import {
1313
} from 'uiSrc/utils/test-utils'
1414
import { loadKeys, loadList, redisearchListSelector, setSelectedIndex } from 'uiSrc/slices/browser/redisearch'
1515
import { bufferToString, stringToBuffer } from 'uiSrc/utils'
16+
import { localStorageService } from 'uiSrc/services'
17+
import { SearchMode } from 'uiSrc/slices/interfaces/keys'
18+
import { RedisDefaultModules } from 'uiSrc/slices/interfaces'
19+
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
20+
import { changeSearchMode, fetchKeys } from 'uiSrc/slices/browser/keys'
21+
import { BrowserStorageItem } from 'uiSrc/constants'
1622
import RediSearchIndexesList, { Props } from './RediSearchIndexesList'
1723

1824
let store: typeof mockedStore
@@ -31,6 +37,7 @@ jest.mock('react-redux', () => ({
3137

3238
jest.mock('uiSrc/slices/browser/keys', () => ({
3339
...jest.requireActual('uiSrc/slices/browser/keys'),
40+
fetchKeys: jest.fn(),
3441
keysSelector: jest.fn().mockReturnValue({
3542
searchMode: 'Redisearch',
3643
}),
@@ -45,6 +52,23 @@ jest.mock('uiSrc/slices/browser/redisearch', () => ({
4552
}),
4653
}))
4754

55+
jest.mock('uiSrc/slices/instances/instances', () => ({
56+
...jest.requireActual('uiSrc/slices/instances/instances'),
57+
connectedInstanceSelector: jest.fn().mockReturnValue({
58+
id: '123',
59+
connectionType: 'STANDALONE',
60+
db: 0,
61+
}),
62+
}))
63+
64+
jest.mock('uiSrc/services', () => ({
65+
...jest.requireActual('uiSrc/services'),
66+
localStorageService: {
67+
set: jest.fn(),
68+
get: jest.fn(),
69+
},
70+
}))
71+
4872
describe('RediSearchIndexesList', () => {
4973
beforeEach(() => {
5074
const state: any = store.getState();
@@ -55,10 +79,25 @@ describe('RediSearchIndexesList', () => {
5579
...state.browser,
5680
keys: {
5781
...state.browser.keys,
58-
searchMode: 'Redisearch',
82+
searchMode: SearchMode.Redisearch,
5983
},
6084
redisearch: { ...state.browser.redisearch, loading: false }
85+
},
86+
connections: {
87+
...state.connections,
88+
instances: {
89+
...state.connections.instances,
90+
connectedInstance: {
91+
...state.connections.instances.connectedInstance,
92+
modules: [{ name: RedisDefaultModules.Search, }],
93+
}
94+
}
6195
}
96+
}));
97+
98+
(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
99+
...state.connections.instances.connectedInstance,
100+
loading: true,
62101
}))
63102
})
64103

@@ -68,13 +107,45 @@ describe('RediSearchIndexesList', () => {
68107
expect(searchInput).toBeInTheDocument()
69108
})
70109

110+
it('should render and call changeSearchMode if no RediSearch module', () => {
111+
localStorageService.set = jest.fn();
112+
113+
(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
114+
loading: false,
115+
modules: [],
116+
}))
117+
118+
expect(render(<RediSearchIndexesList {...instance(mockedProps)} />)).toBeTruthy()
119+
120+
const expectedActions = [
121+
changeSearchMode(SearchMode.Pattern),
122+
changeSearchMode(SearchMode.Pattern),
123+
]
124+
125+
expect(clearStoreActions(store.getActions())).toEqual(
126+
clearStoreActions(expectedActions)
127+
)
128+
129+
expect(localStorageService.set).toBeCalledWith(
130+
BrowserStorageItem.browserSearchMode,
131+
SearchMode.Pattern,
132+
)
133+
})
134+
71135
it('"loadList" should be called after render', () => {
72-
render(
136+
const { rerender } = render(
73137
<RediSearchIndexesList {...instance(mockedProps)} />
74-
)
138+
);
139+
140+
(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
141+
loading: false,
142+
modules: [{ name: RedisDefaultModules.Search, }]
143+
}))
144+
145+
rerender(<RediSearchIndexesList {...instance(mockedProps)} />)
75146

76147
const expectedActions = [
77-
loadList()
148+
loadList(),
78149
]
79150
expect(clearStoreActions(store.getActions())).toEqual(
80151
clearStoreActions(expectedActions)
@@ -93,29 +164,40 @@ describe('RediSearchIndexesList', () => {
93164
expect(onCreateIndexMock).toBeCalled()
94165
})
95166

96-
it('"setSelectedIndex" and "loadKeys" should be called after select Index', () => {
97-
const index = stringToBuffer('idx');
167+
it('"setSelectedIndex" and "loadKeys" should be called after select Index', async () => {
168+
const index = stringToBuffer('idx')
169+
const fetchKeysMock = jest.fn();
170+
171+
(fetchKeys as jest.Mock).mockReturnValue(fetchKeysMock);
98172

99173
(redisearchListSelector as jest.Mock).mockReturnValue({
100174
data: [index],
101175
loading: false,
102176
error: '',
177+
selectedIndex: null,
103178
})
104179

105180
const { queryByText } = render(
106181
<RediSearchIndexesList {...instance(mockedProps)} />
107-
)
182+
);
183+
184+
(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
185+
loading: false,
186+
modules: [{ name: RedisDefaultModules.Search, }]
187+
}))
108188

109189
fireEvent.click(screen.getByTestId('select-search-mode'))
110190
fireEvent.click(queryByText(bufferToString(index)) || document)
111191

112192
const expectedActions = [
113-
loadList(),
114193
setSelectedIndex(index),
115-
loadKeys(),
194+
loadList(),
116195
]
196+
117197
expect(clearStoreActions(store.getActions())).toEqual(
118198
clearStoreActions(expectedActions)
119199
)
200+
201+
expect(fetchKeysMock).toBeCalled()
120202
})
121203
})

0 commit comments

Comments
 (0)