Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions redisinsight/ui/src/constants/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ enum BrowserStorageItem {
instancesSorting = 'instancesSorting',
theme = 'theme',
browserViewType = 'browserViewType',
browserSearchMode = 'browserSearchMode',
cliClientUuid = 'cliClientUuid',
cliResizableContainer = 'cliResizableContainer',
cliInputHistory = 'cliInputHistory',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ const BrowserLeftPanel = (props: Props) => {
const redisearchKeysState = useSelector(redisearchDataSelector)
const { loading: redisearchLoading, isSearched: redisearchIsSearched } = useSelector(redisearchSelector)
const { loading: patternLoading, viewType, searchMode, isSearched: patternIsSearched } = useSelector(keysSelector)
const { keyList: { isDataLoaded } } = useSelector(appContextBrowser)
const { contextInstanceId } = useSelector(appContextSelector)
const {
keyList: { isDataLoaded, scrollPatternTopPosition, scrollRedisearchTopPosition }
} = useSelector(appContextBrowser)

const keyListRef = useRef<any>()

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

useEffect(() => {
if (!isDataLoaded || contextInstanceId !== instanceId) {
Expand Down Expand Up @@ -118,6 +121,7 @@ const BrowserLeftPanel = (props: Props) => {
ref={keyListRef}
keysState={keysState}
loading={loading}
scrollTopPosition={scrollTopPosition}
loadMoreItems={loadMoreItems}
selectKey={selectKey}
/>
Expand Down
65 changes: 38 additions & 27 deletions redisinsight/ui/src/pages/browser/components/key-list/KeyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ import {
} from 'uiSrc/slices/browser/keys'
import {
appContextBrowser,
setBrowserKeyListScrollPosition,
setBrowserPatternScrollPosition,
setBrowserIsNotRendered,
setBrowserRedisearchScrollPosition,
} from 'uiSrc/slices/app/context'
import { GroupBadge } from 'uiSrc/components'
import { SCAN_COUNT_DEFAULT } from 'uiSrc/constants/api'
Expand All @@ -59,6 +60,7 @@ export interface Props {
hideHeader?: boolean
keysState: KeysStoreData
loading: boolean
scrollTopPosition: number
hideFooter?: boolean
selectKey: ({ rowData }: { rowData: any }) => void
loadMoreItems?: (
Expand All @@ -69,15 +71,15 @@ export interface Props {

const KeyList = forwardRef((props: Props, ref) => {
let wheelTimer = 0
const { selectKey, loadMoreItems, loading, keysState, hideFooter } = props
const { selectKey, loadMoreItems, loading, keysState, scrollTopPosition, hideFooter } = props

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

const selectedKey = useSelector(selectedKeySelector)
const { total, nextCursor, previousResultCount } = useSelector(keysDataSelector)
const { isSearched, isFiltered, viewType, searchMode } = useSelector(keysSelector)
const { selectedIndex } = useSelector(redisearchSelector)
const { keyList: { scrollTopPosition, isNotRendered: isNotRenderedContext } } = useSelector(appContextBrowser)
const { keyList: { isNotRendered: isNotRenderedContext } } = useSelector(appContextBrowser)

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

const setScrollTopPosition = (position: number) => {
dispatch(setBrowserKeyListScrollPosition(position))
}
const setScrollTopPosition = useCallback((position: number) => {
if (searchMode === SearchMode.Pattern) {
dispatch(setBrowserPatternScrollPosition(position))
} else {
dispatch(setBrowserRedisearchScrollPosition(position))
}
}, [searchMode])

const formatItem = useCallback((item: GetKeyInfoResponse): GetKeyInfoResponse => ({
...item,
Expand Down Expand Up @@ -388,32 +394,37 @@ const KeyList = forwardRef((props: Props, ref) => {
},
]

const VirtualizeTable = () => (
<VirtualTable
selectable
onRowClick={selectKey}
headerHeight={0}
rowHeight={43}
threshold={50}
columns={columns}
loadMoreItems={onLoadMoreItems}
onWheel={onWheelSearched}
loading={loading || !firstDataLoaded}
items={itemsRef.current}
totalItemsCount={keysState.total ? keysState.total : Infinity}
scanned={isSearched || isFiltered ? keysState.scanned : 0}
noItemsMessage={getNoItemsMessage()}
selectedKey={selectedKey.data}
scrollTopProp={scrollTopPosition}
setScrollTopPosition={setScrollTopPosition}
hideFooter={hideFooter}
onRowsRendered={({ overscanStartIndex, overscanStopIndex }) =>
onRowsRenderedDebounced(overscanStartIndex, overscanStopIndex)}
/>
)

return (
<div className={styles.page}>
<div className={styles.content}>
<div className={cx(styles.table, { [styles.table__withoutFooter]: hideFooter })}>
<div className="key-list-table" data-testid="keyList-table">
<VirtualTable
selectable
onRowClick={selectKey}
headerHeight={0}
rowHeight={43}
threshold={50}
columns={columns}
loadMoreItems={onLoadMoreItems}
onWheel={onWheelSearched}
loading={loading || !firstDataLoaded}
items={itemsRef.current}
totalItemsCount={keysState.total ? keysState.total : Infinity}
scanned={isSearched || isFiltered ? keysState.scanned : 0}
noItemsMessage={getNoItemsMessage()}
selectedKey={selectedKey.data}
scrollTopProp={scrollTopPosition}
setScrollTopPosition={setScrollTopPosition}
hideFooter={hideFooter}
onRowsRendered={({ overscanStartIndex, overscanStopIndex }) =>
onRowsRenderedDebounced(overscanStartIndex, overscanStopIndex)}
/>
{searchMode === SearchMode.Pattern && VirtualizeTable()}
{searchMode !== SearchMode.Pattern && VirtualizeTable()}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import { resetBrowserTree, setBrowserKeyListDataLoaded, } from 'uiSrc/slices/app

import { changeKeyViewType, changeSearchMode, fetchKeys, keysSelector, resetKeysData, } from 'uiSrc/slices/browser/keys'
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import { REDISEARCH_MODULES } from 'uiSrc/slices/interfaces'
import { KeysStoreData, KeyViewType, SearchMode } from 'uiSrc/slices/interfaces/keys'
import { getBasedOnViewTypeEvent, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import AutoRefresh from '../auto-refresh'
import { isRedisearchAvailable } from 'uiSrc/utils'

import AutoRefresh from '../auto-refresh'
import FilterKeyType from '../filter-key-type'
import RediSearchIndexesList from '../redisearch-key-list'
import SearchKeyList from '../search-key-list'
Expand Down Expand Up @@ -129,8 +129,7 @@ const KeysHeader = (props: Props) => {
tooltipText: 'Search by Values of Keys',
ariaLabel: 'Search by Values of Keys button',
dataTestId: 'search-mode-redisearch-btn',
disabled: !modules?.some(({ name }) =>
REDISEARCH_MODULES.some((search) => name === search)),
disabled: !isRedisearchAvailable(modules),
isActiveView() { return searchMode === this.type },
getClassName() {
return cx(styles.viewTypeBtn, { [styles.active]: this.isActiveView() })
Expand Down Expand Up @@ -256,6 +255,8 @@ const KeysHeader = (props: Props) => {
}

dispatch(changeSearchMode(mode))

localStorageService.set(BrowserStorageItem.browserSearchMode, mode)
}

const AddKeyBtn = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import {
} from 'uiSrc/utils/test-utils'
import { loadKeys, loadList, redisearchListSelector, setSelectedIndex } from 'uiSrc/slices/browser/redisearch'
import { bufferToString, stringToBuffer } from 'uiSrc/utils'
import { localStorageService } from 'uiSrc/services'
import { SearchMode } from 'uiSrc/slices/interfaces/keys'
import { RedisDefaultModules } from 'uiSrc/slices/interfaces'
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import { changeSearchMode, fetchKeys } from 'uiSrc/slices/browser/keys'
import { BrowserStorageItem } from 'uiSrc/constants'
import RediSearchIndexesList, { Props } from './RediSearchIndexesList'

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

jest.mock('uiSrc/slices/browser/keys', () => ({
...jest.requireActual('uiSrc/slices/browser/keys'),
fetchKeys: jest.fn(),
keysSelector: jest.fn().mockReturnValue({
searchMode: 'Redisearch',
}),
Expand All @@ -45,6 +52,23 @@ jest.mock('uiSrc/slices/browser/redisearch', () => ({
}),
}))

jest.mock('uiSrc/slices/instances/instances', () => ({
...jest.requireActual('uiSrc/slices/instances/instances'),
connectedInstanceSelector: jest.fn().mockReturnValue({
id: '123',
connectionType: 'STANDALONE',
db: 0,
}),
}))

jest.mock('uiSrc/services', () => ({
...jest.requireActual('uiSrc/services'),
localStorageService: {
set: jest.fn(),
get: jest.fn(),
},
}))

describe('RediSearchIndexesList', () => {
beforeEach(() => {
const state: any = store.getState();
Expand All @@ -55,10 +79,25 @@ describe('RediSearchIndexesList', () => {
...state.browser,
keys: {
...state.browser.keys,
searchMode: 'Redisearch',
searchMode: SearchMode.Redisearch,
},
redisearch: { ...state.browser.redisearch, loading: false }
},
connections: {
...state.connections,
instances: {
...state.connections.instances,
connectedInstance: {
...state.connections.instances.connectedInstance,
modules: [{ name: RedisDefaultModules.Search, }],
}
}
}
}));

(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
...state.connections.instances.connectedInstance,
loading: true,
}))
})

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

it('should render and call changeSearchMode if no RediSearch module', () => {
localStorageService.set = jest.fn();

(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
loading: false,
modules: [],
}))

expect(render(<RediSearchIndexesList {...instance(mockedProps)} />)).toBeTruthy()

const expectedActions = [
changeSearchMode(SearchMode.Pattern),
changeSearchMode(SearchMode.Pattern),
]

expect(clearStoreActions(store.getActions())).toEqual(
clearStoreActions(expectedActions)
)

expect(localStorageService.set).toBeCalledWith(
BrowserStorageItem.browserSearchMode,
SearchMode.Pattern,
)
})

it('"loadList" should be called after render', () => {
render(
const { rerender } = render(
<RediSearchIndexesList {...instance(mockedProps)} />
)
);

(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
loading: false,
modules: [{ name: RedisDefaultModules.Search, }]
}))

rerender(<RediSearchIndexesList {...instance(mockedProps)} />)

const expectedActions = [
loadList()
loadList(),
]
expect(clearStoreActions(store.getActions())).toEqual(
clearStoreActions(expectedActions)
Expand All @@ -93,29 +164,40 @@ describe('RediSearchIndexesList', () => {
expect(onCreateIndexMock).toBeCalled()
})

it('"setSelectedIndex" and "loadKeys" should be called after select Index', () => {
const index = stringToBuffer('idx');
it('"setSelectedIndex" and "loadKeys" should be called after select Index', async () => {
const index = stringToBuffer('idx')
const fetchKeysMock = jest.fn();

(fetchKeys as jest.Mock).mockReturnValue(fetchKeysMock);

(redisearchListSelector as jest.Mock).mockReturnValue({
data: [index],
loading: false,
error: '',
selectedIndex: null,
})

const { queryByText } = render(
<RediSearchIndexesList {...instance(mockedProps)} />
)
);

(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
loading: false,
modules: [{ name: RedisDefaultModules.Search, }]
}))

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

const expectedActions = [
loadList(),
setSelectedIndex(index),
loadKeys(),
loadList(),
]

expect(clearStoreActions(store.getActions())).toEqual(
clearStoreActions(expectedActions)
)

expect(fetchKeysMock).toBeCalled()
})
})
Loading