Skip to content

Commit fac12c0

Browse files
authored
Merge pull request #4090 from RedisInsight/fe/feature/RI-6300_Multiple_delimiters_in_Tree_view
#RI-6300 - Multiple delimiters in Tree view
2 parents 22dcf40 + dee27ff commit fac12c0

File tree

31 files changed

+467
-148
lines changed

31 files changed

+467
-148
lines changed

redisinsight/ui/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { createRoot } from 'react-dom/client'
33
import App from 'uiSrc/App'
44
import Router from 'uiSrc/Router'
55
import { listenPluginsEvents } from 'uiSrc/plugins/pluginEvents'
6+
import { migrateLocalStorageData } from 'uiSrc/services'
67
import 'uiSrc/styles/base/_fonts.scss'
78
import 'uiSrc/styles/main.scss'
89

10+
migrateLocalStorageData()
911
listenPluginsEvents()
1012

1113
const rootEl = document.getElementById('root')

redisinsight/ui/indexElectron.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import React from 'react'
22
import { createRoot } from 'react-dom/client'
33
import AppElectron from 'uiSrc/electron/AppElectron'
44
import { listenPluginsEvents } from 'uiSrc/plugins/pluginEvents'
5+
import { migrateLocalStorageData } from 'uiSrc/services'
56
import 'uiSrc/styles/base/_fonts.scss'
67
import 'uiSrc/styles/main.scss'
78

89
window.app.sendWindowId((_e: any, windowId: string = '') => {
910
window.windowId = windowId || window.windowId
1011

12+
migrateLocalStorageData()
1113
listenPluginsEvents()
1214

1315
const rootEl = document.getElementById('root')

redisinsight/ui/src/constants/browser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { EuiComboBoxOptionOption } from '@elastic/eui'
12
import { KeyValueFormat, SortOrder } from './keys'
23

3-
export const DEFAULT_DELIMITER = ':'
4+
export const DEFAULT_DELIMITER: EuiComboBoxOptionOption = { label: ':' }
45
export const DEFAULT_TREE_SORTING = SortOrder.ASC
56
export const DEFAULT_SHOW_HIDDEN_RECOMMENDATIONS = false
67

redisinsight/ui/src/helpers/constructKeysToTree.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@ import { IKeyPropTypes } from 'uiSrc/constants/prop-types/keys'
33

44
interface Props {
55
items: IKeyPropTypes[]
6-
delimiter?: string
6+
delimiterPattern?: string
7+
delimiters?: string[]
78
sorting?: SortOrder
89
}
910

1011
export const constructKeysToTree = (props: Props): any[] => {
11-
const { items: keys, delimiter = ':', sorting = 'ASC' } = props
12-
const keysSymbol = `keys${delimiter}keys`
12+
const { items: keys, delimiterPattern = ':', delimiters = [], sorting = 'ASC' } = props
13+
const keysSymbol = `keys${delimiterPattern}keys`
1314
const tree: any = {}
1415

1516
keys.forEach((key: any) => {
1617
// eslint-disable-next-line prefer-object-spread
1718
let currentNode: any = tree
1819
const { nameString: name = '' } = key
19-
const nameSplitted = name.split(delimiter)
20+
const nameSplitted = name.split(new RegExp(delimiterPattern, 'g'))
2021
const lastIndex = nameSplitted.length - 1
2122

2223
nameSplitted.forEach((value:any, index: number) => {
@@ -78,33 +79,34 @@ export const constructKeysToTree = (props: Props): any[] => {
7879
return treeNodes.map((key, index) => {
7980
const name = key?.toString()
8081
const node: any = { nameString: name }
81-
const tillNowKeyName = previousKey + name + delimiter
8282
const path = prevIndex ? `${prevIndex}.${index}` : `${index}`
8383

8484
// populate node with children nodes
8585
if (!tree[key].isLeaf && Object.keys(tree[key]).length > 0) {
86+
const delimiterView = delimiters.length === 1 ? delimiters[0] : '-'
8687
node.children = formatTreeData(
8788
tree[key],
88-
tillNowKeyName,
89+
`${previousKey + name + delimiterView}`,
8990
delimiter,
9091
path,
9192
)
9293
node.keyCount = node.children.reduce((a: any, b:any) => a + (b.keyCount || 1), 0)
9394
node.keyApproximate = (node.keyCount / keys.length) * 100
95+
node.fullName = previousKey + name
9496
} else {
9597
// populate leaf
9698
node.isLeaf = true
9799
node.children = []
98100
node.nameString = name.slice(0, -keysSymbol.length)
99101
node.nameBuffer = tree[key]?.name
102+
node.fullName = previousKey + name + delimiter
100103
}
101104

102105
node.path = path
103-
node.fullName = tillNowKeyName
104106
node.id = getUniqueId()
105107
return node
106108
})
107109
}
108110

109-
return formatTreeData(tree, '', delimiter)
111+
return formatTreeData(tree, '', delimiterPattern)
110112
}

redisinsight/ui/src/helpers/tests/constructKeysToTree.spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { DEFAULT_DELIMITER } from 'uiSrc/constants'
2-
import { constructKeysToTreeMockResult } from './constructKeysToTreeMockResult'
1+
import { constructKeysToTreeMockResult, delimiterMock } from './constructKeysToTreeMockResult'
32
import { constructKeysToTree } from '../constructKeysToTree'
43

54
const constructKeysToTreeTests: any[] = [
65
[{
76
items: [
87
{ nameString: 'keys:1:2', type: 'hash', ttl: -1, size: 71 },
9-
{ nameString: 'keys2', type: 'hash', ttl: -1, size: 71 },
108
{ nameString: 'keys:1:1', type: 'hash', ttl: -1, size: 71 },
119
{ nameString: 'empty::test', type: 'hash', ttl: -1, size: 71 },
1210
{ nameString: 'test1', type: 'hash', ttl: -1, size: 71 },
@@ -15,8 +13,9 @@ const constructKeysToTreeTests: any[] = [
1513
{ nameString: 'keys1', type: 'hash', ttl: -1, size: 71 },
1614
{ nameString: 'keys:3', type: 'hash', ttl: -1, size: 71 },
1715
{ nameString: 'keys:2', type: 'hash', ttl: -1, size: 71 },
16+
{ nameString: 'keys_2', type: 'hash', ttl: -1, size: 71 },
1817
],
19-
delimiter: DEFAULT_DELIMITER
18+
delimiterPattern: delimiterMock
2019
},
2120
constructKeysToTreeMockResult
2221
]
Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export const delimiterMock = ':|_'
12
export const constructKeysToTreeMockResult = [
23
{
34
nameString: 'empty',
@@ -10,19 +11,19 @@ export const constructKeysToTreeMockResult = [
1011
isLeaf: true,
1112
children: [],
1213
path: '0.0.0',
13-
fullName: 'empty::empty::testkeys:keys:',
14+
fullName: `empty--empty::testkeys${delimiterMock}keys${delimiterMock}`,
1415
}
1516
],
1617
keyCount: 1,
1718
keyApproximate: 10,
1819
path: '0.0',
19-
fullName: 'empty::',
20+
fullName: 'empty-',
2021
}
2122
],
2223
keyCount: 1,
2324
keyApproximate: 10,
2425
path: '0',
25-
fullName: 'empty:',
26+
fullName: 'empty',
2627
},
2728
{
2829
nameString: 'keys',
@@ -35,74 +36,74 @@ export const constructKeysToTreeMockResult = [
3536
isLeaf: true,
3637
children: [],
3738
path: '1.0.0',
38-
fullName: 'keys:1:keys:1:1keys:keys:',
39+
fullName: `keys-1-keys:1:1keys${delimiterMock}keys${delimiterMock}`,
3940
},
4041
{
4142
nameString: 'keys:1:2',
4243
isLeaf: true,
4344
children: [],
4445
path: '1.0.1',
45-
fullName: 'keys:1:keys:1:2keys:keys:',
46+
fullName: `keys-1-keys:1:2keys${delimiterMock}keys${delimiterMock}`,
4647
}
4748
],
4849
keyCount: 2,
4950
keyApproximate: 20,
5051
path: '1.0',
51-
fullName: 'keys:1:',
52+
fullName: 'keys-1',
5253
},
5354
{
54-
nameString: 'keys:1',
55+
nameString: 'keys_2',
5556
isLeaf: true,
5657
children: [],
5758
path: '1.1',
58-
fullName: 'keys:keys:1keys:keys:',
59+
fullName: `keys-keys_2keys${delimiterMock}keys${delimiterMock}`,
5960
},
6061
{
61-
nameString: 'keys:2',
62+
nameString: 'keys:1',
6263
isLeaf: true,
6364
children: [],
6465
path: '1.2',
65-
fullName: 'keys:keys:2keys:keys:',
66+
fullName: `keys-keys:1keys${delimiterMock}keys${delimiterMock}`,
6667
},
6768
{
68-
nameString: 'keys:3',
69+
nameString: 'keys:2',
6970
isLeaf: true,
7071
children: [],
7172
path: '1.3',
72-
fullName: 'keys:keys:3keys:keys:',
73+
fullName: `keys-keys:2keys${delimiterMock}keys${delimiterMock}`,
74+
},
75+
{
76+
nameString: 'keys:3',
77+
isLeaf: true,
78+
children: [],
79+
path: '1.4',
80+
fullName: `keys-keys:3keys${delimiterMock}keys${delimiterMock}`,
7381
}
7482
],
75-
keyCount: 5,
76-
keyApproximate: 50,
83+
keyCount: 6,
84+
keyApproximate: 60,
7785
path: '1',
78-
fullName: 'keys:',
86+
fullName: 'keys',
7987
},
8088
{
8189
nameString: 'keys1',
8290
isLeaf: true,
8391
children: [],
8492
path: '2',
85-
fullName: 'keys1keys:keys:',
86-
},
87-
{
88-
nameString: 'keys2',
89-
isLeaf: true,
90-
children: [],
91-
path: '3',
92-
fullName: 'keys2keys:keys:',
93+
fullName: `keys1keys${delimiterMock}keys${delimiterMock}`,
9394
},
9495
{
9596
nameString: 'test1',
9697
isLeaf: true,
9798
children: [],
98-
path: '4',
99-
fullName: 'test1keys:keys:',
99+
path: '3',
100+
fullName: `test1keys${delimiterMock}keys${delimiterMock}`,
100101
},
101102
{
102103
nameString: 'test2',
103104
isLeaf: true,
104105
children: [],
105-
path: '5',
106-
fullName: 'test2keys:keys:',
106+
path: '4',
107+
fullName: `test2keys${delimiterMock}keys${delimiterMock}`,
107108
}
108109
]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ const KeyList = forwardRef((props: Props, ref) => {
279279
minWidth: 94,
280280
truncateText: true,
281281
render: (cellData: string) => (
282-
<KeyRowName nameString={cellData} />
282+
<KeyRowName nameString={cellData} shortName={cellData} />
283283
)
284284
},
285285
{

redisinsight/ui/src/pages/browser/components/key-row-name/KeyRowName.spec.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ describe('KeyRowName', () => {
1313
expect(render(<KeyRowName {...instance(mockedProps)} />)).toBeTruthy()
1414
})
1515

16-
it('should render Loading if no nameString', () => {
17-
const { queryByTestId } = render(<KeyRowName nameString={undefined} />)
16+
it('should render Loading if no nameString and shortName', () => {
17+
const { queryByTestId } = render(<KeyRowName nameString={undefined} shortName={undefined} />)
1818

1919
expect(queryByTestId(loadingTestId)).toBeInTheDocument()
2020
})
2121

2222
it('content should be no more than 200 symbols', () => {
2323
const longName = Array.from({ length: 250 }, () => '1').join('')
24-
const { queryByTestId } = render(<KeyRowName nameString={longName} />)
24+
const { queryByTestId } = render(<KeyRowName nameString={longName} shortName={longName} />)
2525

2626
expect(queryByTestId(loadingTestId)).not.toBeInTheDocument()
2727
expect(queryByTestId(`key-${longName}`)).toHaveTextContent(longName.slice(0, 200))

redisinsight/ui/src/pages/browser/components/key-row-name/KeyRowName.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import styles from './styles.module.scss'
1111

1212
export interface Props {
1313
nameString: Maybe<string>
14+
shortName: Maybe<string>
1415
}
1516

1617
const KeyRowName = (props: Props) => {
17-
const { nameString } = props
18+
const { nameString, shortName } = props
1819

19-
if (isUndefined(nameString)) {
20+
if (isUndefined(shortName)) {
2021
return (
2122
<EuiLoadingContent
2223
lines={1}
@@ -27,13 +28,13 @@ const KeyRowName = (props: Props) => {
2728
}
2829

2930
// Better to cut the long string, because it could affect virtual scroll performance
30-
const nameContent = replaceSpaces(nameString?.substring?.(0, 200))
31+
const nameContent = replaceSpaces(shortName?.substring?.(0, 200))
3132
const nameTooltipContent = formatLongName(nameString)
3233

3334
return (
3435
<div className={styles.keyName}>
3536
<EuiText color="subdued" size="s" style={{ maxWidth: '100%', display: 'flex' }}>
36-
<div style={{ display: 'flex' }} className="truncateText" data-testid={`key-${nameString}`}>
37+
<div style={{ display: 'flex' }} className="truncateText" data-testid={`key-${shortName}`}>
3738
<EuiToolTip
3839
title="Key Name"
3940
className={styles.tooltip}

redisinsight/ui/src/pages/browser/components/key-tree/KeyTree.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import cx from 'classnames'
4-
54
import { useParams } from 'react-router-dom'
5+
import { escapeRegExp } from 'lodash'
6+
67
import {
78
appContextBrowserTree,
89
resetBrowserTree,
@@ -13,7 +14,7 @@ import { constructKeysToTree } from 'uiSrc/helpers'
1314
import VirtualTree from 'uiSrc/pages/browser/components/virtual-tree'
1415
import TreeViewSVG from 'uiSrc/assets/img/icons/treeview.svg'
1516
import { KeysStoreData } from 'uiSrc/slices/interfaces/keys'
16-
import { Nullable, bufferToString } from 'uiSrc/utils'
17+
import { Nullable, bufferToString, comboBoxToArray } from 'uiSrc/utils'
1718
import { IKeyPropTypes } from 'uiSrc/constants/prop-types/keys'
1819
import { KeyTypes, ModulesKeyTypes } from 'uiSrc/constants'
1920
import { RedisResponseBuffer, RedisString } from 'uiSrc/slices/interfaces'
@@ -59,14 +60,20 @@ const KeyTree = forwardRef((props: Props, ref) => {
5960

6061
const { instanceId } = useParams<{ instanceId: string }>()
6162
const { openNodes } = useSelector(appContextBrowserTree)
62-
const { treeViewDelimiter: delimiter = '', treeViewSort: sorting } = useSelector(appContextDbConfig)
63+
const { treeViewDelimiter, treeViewSort: sorting } = useSelector(appContextDbConfig)
6364
const { nameString: selectedKeyName = null } = useSelector(selectedKeyDataSelector) ?? {}
6465

6566
const [statusOpen, setStatusOpen] = useState(openNodes)
6667
const [constructingTree, setConstructingTree] = useState(false)
6768
const [firstDataLoaded, setFirstDataLoaded] = useState<boolean>(!!keysState.keys.length)
6869
const [items, setItems] = useState<IKeyPropTypes[]>(parseKeyNames(keysState.keys ?? []))
6970

71+
// escape regexp symbols and join and transform to regexp
72+
const delimiters = comboBoxToArray(treeViewDelimiter)
73+
const delimiterPattern = delimiters
74+
.map(escapeRegExp)
75+
.join('|')
76+
7077
const dispatch = useDispatch()
7178

7279
useImperativeHandle(ref, () => ({
@@ -86,8 +93,8 @@ const KeyTree = forwardRef((props: Props, ref) => {
8693
// open all parents for selected key
8794
const openSelectedKey = (selectedKeyName: Nullable<string> = '') => {
8895
if (selectedKeyName) {
89-
const parts = selectedKeyName.split(delimiter)
90-
const parents = parts.map((_, index) => parts.slice(0, index + 1).join(delimiter) + delimiter)
96+
const parts = selectedKeyName.split(delimiterPattern)
97+
const parents = parts.map((_, index) => parts.slice(0, index + 1).join(delimiterPattern) + delimiterPattern)
9198

9299
// remove key name from parents
93100
parents.pop()
@@ -110,7 +117,7 @@ const KeyTree = forwardRef((props: Props, ref) => {
110117
}
111118

112119
setItems(parseKeyNames(keysState.keys))
113-
}, [keysState.lastRefreshTime, delimiter, sorting])
120+
}, [keysState.lastRefreshTime, delimiterPattern, sorting])
114121

115122
useEffect(() => {
116123
openSelectedKey(selectedKeyName)
@@ -188,7 +195,8 @@ const KeyTree = forwardRef((props: Props, ref) => {
188195
<VirtualTree
189196
items={items}
190197
loadingIcon={TreeViewSVG}
191-
delimiter={delimiter}
198+
delimiters={delimiters}
199+
delimiterPattern={delimiterPattern}
192200
sorting={sorting}
193201
deleting={deleting}
194202
statusSelected={selectedKeyName}

0 commit comments

Comments
 (0)