Skip to content

Commit f2f2e8d

Browse files
authored
Merge pull request #1407 from RedisInsight/feature/RI-3483_highlight-big-keys
Feature/ri 3483 highlight big keys
2 parents 899528b + 15c3d18 commit f2f2e8d

File tree

8 files changed

+167
-28
lines changed

8 files changed

+167
-28
lines changed

redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ const mockedProps = mock<Props>()
99
const mockData = [
1010
{
1111
name: 'name',
12-
type: 'HASH',
13-
memory: 1000,
14-
length: 10,
12+
type: 'hash',
13+
memory: 10_000_000,
14+
length: 100_000_000,
1515
ttl: 10
1616
},
1717
{
1818
name: 'name_1',
19-
type: 'HASH',
19+
type: 'hash',
2020
memory: 1000,
2121
length: null,
2222
ttl: -1
@@ -46,6 +46,12 @@ describe('Table', () => {
4646
it('should render correct length', () => {
4747
render(<Table {...instance(mockedProps)} data={mockData} />)
4848
expect(screen.getByTestId('length-empty-name_1')).toHaveTextContent('-')
49-
expect(screen.getByTestId('length-value-name')).toHaveTextContent('10')
49+
expect(screen.getByTestId(/length-value-name/).textContent).toEqual('100 000 000')
50+
})
51+
52+
it('should highlight big keys', () => {
53+
render(<Table {...instance(mockedProps)} data={mockData} />)
54+
expect(screen.getByTestId('nsp-usedMemory-value=10000000-highlighted')).toBeInTheDocument()
55+
expect(screen.getByTestId('length-value-name-highlighted')).toBeInTheDocument()
5056
})
5157
})

redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.tsx

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,46 @@
1-
import React, { useState } from 'react'
21
import {
32
EuiBasicTableColumn,
3+
EuiButtonEmpty,
44
EuiInMemoryTable,
55
EuiTextColor,
66
EuiToolTip,
7-
EuiButtonEmpty,
87
PropertySort
98
} from '@elastic/eui'
9+
import cx from 'classnames'
1010
import { isNull } from 'lodash'
11-
import { useParams, useHistory } from 'react-router-dom'
11+
import React, { useState } from 'react'
1212
import { useDispatch, useSelector } from 'react-redux'
13-
import cx from 'classnames'
13+
import { useHistory, useParams } from 'react-router-dom'
14+
import { GroupBadge } from 'uiSrc/components'
15+
import { Pages } from 'uiSrc/constants'
16+
import { SCAN_COUNT_DEFAULT, SCAN_TREE_COUNT_DEFAULT } from 'uiSrc/constants/api'
17+
import {
18+
resetBrowserTree,
19+
setBrowserKeyListDataLoaded,
20+
setBrowserSelectedKey,
21+
setBrowserTreeDelimiter
22+
} from 'uiSrc/slices/app/context'
23+
import {
24+
changeSearchMode,
25+
fetchKeys,
26+
keysSelector,
27+
resetKeysData,
28+
setFilter,
29+
setSearchMatch
30+
} from 'uiSrc/slices/browser/keys'
31+
import { KeyViewType, SearchMode } from 'uiSrc/slices/interfaces/keys'
1432

1533
import {
1634
formatBytes,
1735
formatLongName,
36+
HighlightType,
37+
isBigKey,
38+
stringToBuffer,
1839
truncateNumberToDuration,
1940
truncateNumberToFirstUnit,
20-
truncateTTLToSeconds,
21-
stringToBuffer
41+
truncateTTLToSeconds
2242
} from 'uiSrc/utils'
2343
import { numberWithSpaces } from 'uiSrc/utils/numbers'
24-
import { GroupBadge } from 'uiSrc/components'
25-
import { setFilter, setSearchMatch, resetKeysData, fetchKeys, keysSelector, changeSearchMode } from 'uiSrc/slices/browser/keys'
26-
import { SCAN_COUNT_DEFAULT, SCAN_TREE_COUNT_DEFAULT } from 'uiSrc/constants/api'
27-
import { KeyViewType, SearchMode } from 'uiSrc/slices/interfaces/keys'
28-
import { setBrowserKeyListDataLoaded, setBrowserSelectedKey, resetBrowserTree, setBrowserTreeDelimiter } from 'uiSrc/slices/app/context'
29-
import { Pages } from 'uiSrc/constants'
3044
import { Key } from 'apiSrc/modules/database-analysis/models/key'
3145

3246
import styles from './styles.module.scss'
@@ -54,7 +68,7 @@ const Table = (props: Props) => {
5468
dispatch(setBrowserTreeDelimiter(delimiter))
5569
dispatch(setFilter(null))
5670
dispatch(setSearchMatch(name, SearchMode.Pattern))
57-
dispatch(resetKeysData())
71+
dispatch(resetKeysData(SearchMode.Pattern))
5872
dispatch(fetchKeys(
5973
SearchMode.Pattern,
6074
'0',
@@ -96,7 +110,7 @@ const Table = (props: Props) => {
96110
truncateText: true,
97111
render: (name: string) => {
98112
const tooltipContent = formatLongName(name)
99-
const cellContent = name.substring(0, 200)
113+
const cellContent = (name as string).substring(0, 200)
100114
return (
101115
<div data-testid="top-keys-table-name" className={cx(styles.delimiter, 'truncateText')}>
102116
<EuiToolTip
@@ -159,16 +173,25 @@ const Table = (props: Props) => {
159173
width: '9%',
160174
sortable: true,
161175
align: 'right',
162-
render: (value: number) => {
176+
render: (value: number, { type }) => {
163177
const [number, size] = formatBytes(value, 3, true)
164-
178+
const isHighlight = isBigKey(type, HighlightType.Memory, value)
165179
return (
166180
<EuiToolTip
167-
content={`${numberWithSpaces(value)} B`}
181+
content={(
182+
<>
183+
{isHighlight ? (<>Consider splitting it into multiple keys<br /></>) : null}
184+
{numberWithSpaces(value)} B
185+
</>
186+
)}
187+
anchorClassName={cx({ [styles.highlight]: isHighlight })}
168188
data-testid="usedMemory-tooltip"
169189
>
170190
<>
171-
<span className={styles.count} data-testid={`nsp-usedMemory-value=${value}`}>
191+
<span
192+
className={styles.count}
193+
data-testid={`nsp-usedMemory-value=${value}${isHighlight ? '-highlighted' : ''}`}
194+
>
172195
{number}
173196
</span>
174197
<span className={styles.valueUnit}>{size}</span>
@@ -183,18 +206,26 @@ const Table = (props: Props) => {
183206
width: '15%',
184207
sortable: ({ length }) => length ?? -1,
185208
align: 'right',
186-
render: (value: number, { name }) => {
209+
render: (value: number, { name, type }) => {
187210
if (isNull(value)) {
188211
return (
189212
<EuiTextColor color="subdued" style={{ maxWidth: '100%' }} data-testid={`length-empty-${name}`}>
190213
-
191214
</EuiTextColor>
192215
)
193216
}
217+
218+
const isHighlight = isBigKey(type, HighlightType.Length, value)
194219
return (
195-
<span className={styles.count} data-testid={`length-value-${name}`}>
196-
{numberWithSpaces(value)}
197-
</span>
220+
<EuiToolTip
221+
content={isHighlight ? 'Consider splitting it into multiple keys' : ''}
222+
anchorClassName={cx({ [styles.highlight]: isHighlight })}
223+
data-testid="usedMemory-tooltip"
224+
>
225+
<span className={styles.count} data-testid={`length-value-${name}${isHighlight ? '-highlighted' : ''}`}>
226+
{numberWithSpaces(value)}
227+
</span>
228+
</EuiToolTip>
198229
)
199230
}
200231
},

redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/styles.module.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,19 @@
155155
padding: 12px;
156156
}
157157

158+
.highlight {
159+
background: var(--euiColorWarningLight);
160+
color: var(--euiColorEmptyShade) !important;
161+
border-radius: 4px;
162+
padding: 1px 8px !important;
163+
display: flex !important;
164+
align-items: baseline;
165+
166+
.count, .valueUnit {
167+
color: var(--euiColorEmptyShade) !important;
168+
}
169+
}
170+
158171
:global(.euiTableCellContent) .delimiter span {
159172
cursor: pointer;
160173
color: var(--buttonSecondaryTextColor);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { KeyTypes } from 'uiSrc/constants'
2+
3+
enum HighlightType {
4+
Length = 'length',
5+
Memory = 'memory'
6+
}
7+
8+
interface DefaultConfig { [key: string]: number }
9+
10+
const defaultMemoryConfig: { [key: string]: number } = {
11+
memory: 5_000_000
12+
}
13+
14+
const defaultConfig: { [key: string]: number } = {
15+
length: 5_000,
16+
memory: 5_000_000
17+
}
18+
19+
const bigKeysConfig: { [key: string]: DefaultConfig } = {
20+
[KeyTypes.List]: defaultConfig,
21+
[KeyTypes.ZSet]: defaultConfig,
22+
[KeyTypes.Set]: defaultConfig,
23+
[KeyTypes.Hash]: defaultConfig,
24+
}
25+
26+
const isBigKey = (keyType: string, type: HighlightType, count: number): boolean => {
27+
if (!count) return false
28+
if (bigKeysConfig[keyType]?.[type]) return count >= bigKeysConfig[keyType][type]
29+
if (defaultMemoryConfig[type]) return count >= defaultMemoryConfig[type]
30+
31+
return false
32+
}
33+
34+
export {
35+
HighlightType,
36+
isBigKey
37+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './getDiffKeysOfObjectValues'
22
export * from './compareVersions'
33
export * from './compareConsents'
4+
export * from './bigKeys'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { KeyTypes } from 'uiSrc/constants'
2+
import { HighlightType, isBigKey } from 'uiSrc/utils'
3+
4+
const isBigKeyTests: any[] = [
5+
[KeyTypes.Hash, HighlightType.Memory, 100, false],
6+
[KeyTypes.Hash, HighlightType.Memory, 5_000_000, true],
7+
[KeyTypes.Hash, HighlightType.Length, 50_000_000, true],
8+
[KeyTypes.String, HighlightType.Memory, 50_000_000, true],
9+
[KeyTypes.String, HighlightType.Length, 50_000_000, false],
10+
[KeyTypes.Stream, HighlightType.Memory, 50_000_000, true],
11+
[KeyTypes.Stream, HighlightType.Length, 50_000_000, false],
12+
[KeyTypes.Stream, HighlightType.Memory, 199, false],
13+
['newType', HighlightType.Memory, 98391283123123, true],
14+
['newType', HighlightType.Length, 98391283123123, false],
15+
]
16+
17+
describe('isBigKey', () => {
18+
it.each(isBigKeyTests)('for input: %s (keyType), %s (type), %s (count) should be output: %s',
19+
(keyType, type, count, expected) => {
20+
const result = isBigKey(keyType, type, count)
21+
expect(result).toBe(expected)
22+
})
23+
})

tests/e2e/pageObjects/memory-efficiency-page.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export class MemoryEfficiencyPage {
2626
topKeysKeyName = Selector('[data-testid=top-keys-table-name]');
2727
topNamespacesEmptyContainer = Selector('[data-testid=top-namespaces-empty]');
2828
topNamespacesEmptyMessage = Selector('[data-testid=top-namespaces-message]');
29+
topKeysKeySizeCell = Selector('[data-testid^=nsp-usedMemory-value]');
30+
topKeysLengthCell = Selector('[data-testid^=length-value]');
2931
// TABLE
3032
namespaceTable = Selector('[data-testid=nsp-table-memory]');
3133
nameSpaceTableRows = this.namespaceTable.find('[data-testid^=row-]');

tests/e2e/tests/critical-path/memory-efficiency/top-keys-table.e2e.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import { rte } from '../../../helpers/constants';
55
import { acceptLicenseTermsAndAddDatabaseApi } from '../../../helpers/database';
66
import { commonUrl, ossStandaloneRedisearch } from '../../../helpers/conf';
77
import { deleteStandaloneDatabaseApi } from '../../../helpers/api/api-database';
8-
import { deleteAllKeysFromDB, populateDBWithHashes } from '../../../helpers/keys';
8+
import { deleteAllKeysFromDB, populateDBWithHashes, populateHashWithFields } from '../../../helpers/keys';
9+
import { Common } from '../../../helpers/common';
910

1011
const memoryEfficiencyPage = new MemoryEfficiencyPage();
1112
const myRedisDatabasePage = new MyRedisDatabasePage();
1213
const browserPage = new BrowserPage();
1314
const cliPage = new CliPage();
15+
const common = new Common();
1416
const chance = new Chance();
1517

1618
const keyToAddParameters = { keysCount: 13, keyNameStartWith: 'hashKey'};
19+
const keyName = `TestHashKey-${common.generateWord(10)}`;
20+
const keyToAddParameters2 = { fieldsCount: 80000, keyName, fieldStartWith: 'hashField', fieldValueStartWith: 'hashValue' };
1721
const members = [...Array(100).keys()].toString().replace(/,/g, ' '); // The smallest key
1822
const keyNamesMemory = ['string', 'list', 'bloom', 'set'];
1923
const keyNamesLength = ['string', 'set', 'list'];
@@ -66,3 +70,25 @@ test
6670
await t.click(memoryEfficiencyPage.topKeysKeyName.nth(1).find('button'));
6771
await t.expect(browserPage.keyNameFormDetails.find('b').textContent).eql(keyNamesLength[1]);
6872
});
73+
test
74+
.before(async t => {
75+
await acceptLicenseTermsAndAddDatabaseApi(ossStandaloneRedisearch, ossStandaloneRedisearch.databaseName);
76+
// Create keys
77+
await populateHashWithFields('localhost', '8102', keyToAddParameters2);
78+
// Go to Analysis Tools page
79+
await t.click(myRedisDatabasePage.analysisPageButton);
80+
})
81+
.after(async t => {
82+
await t.click(myRedisDatabasePage.browserButton);
83+
await browserPage.deleteKeyByName(keyName);
84+
await deleteStandaloneDatabaseApi(ossStandaloneRedisearch);
85+
})('Big highlighted key tooltip', async t => {
86+
const tooltipText = 'Consider splitting it into multiple keys';
87+
88+
await t.click(memoryEfficiencyPage.newReportBtn);
89+
// Tooltip with text "Consider splitting it into multiple keys" is displayed for highlighted keys
90+
await t.hover(memoryEfficiencyPage.topKeysKeySizeCell);
91+
await t.expect(browserPage.tooltip.textContent).contains(tooltipText, `"${tooltipText}" is not displayed in Key size tooltip`);
92+
await t.hover(memoryEfficiencyPage.topKeysLengthCell);
93+
await t.expect(browserPage.tooltip.textContent).contains(tooltipText, `"${tooltipText}" is not displayed in Length tooltip`);
94+
});

0 commit comments

Comments
 (0)