From 58577b2f3f2cb61674f6996cf6ff6aaf2ff5dc21 Mon Sep 17 00:00:00 2001 From: "Roman.Sergeenko" Date: Wed, 16 Nov 2022 17:34:35 +0400 Subject: [PATCH 1/3] #RI-3483 - add highlighting big keys --- .../components/top-keys/Table.spec.tsx | 14 +++- .../components/top-keys/Table.tsx | 75 +++++++++++++------ .../components/top-keys/styles.module.scss | 13 ++++ .../ui/src/utils/comparisons/bigKeys.ts | 37 +++++++++ .../ui/src/utils/comparisons/index.ts | 1 + .../utils/tests/comparisons/bigKeys.spec.ts | 23 ++++++ 6 files changed, 137 insertions(+), 26 deletions(-) create mode 100644 redisinsight/ui/src/utils/comparisons/bigKeys.ts create mode 100644 redisinsight/ui/src/utils/tests/comparisons/bigKeys.spec.ts diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx index 7c92da4915..99f1390405 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx @@ -9,14 +9,14 @@ const mockedProps = mock() const mockData = [ { name: 'name', - type: 'HASH', - memory: 1000, - length: 10, + type: 'hash', + memory: 10_000_000, + length: 100_000_000, ttl: 10 }, { name: 'name_1', - type: 'HASH', + type: 'hash', memory: 1000, length: null, ttl: -1 @@ -48,4 +48,10 @@ describe('Table', () => { expect(screen.getByTestId('length-empty-name_1')).toHaveTextContent('-') expect(screen.getByTestId('length-value-name')).toHaveTextContent('10') }) + + it('should highlight big keys', () => { + render() + expect(screen.getByTestId('nsp-usedMemory-value=10000000-highlighted')).toBeInTheDocument() + expect(screen.getByTestId('length-value-name-highlighted')).toBeInTheDocument() + }) }) diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.tsx index eda469b676..864a868102 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.tsx @@ -1,32 +1,46 @@ -import React, { useState } from 'react' import { EuiBasicTableColumn, + EuiButtonEmpty, EuiInMemoryTable, EuiTextColor, EuiToolTip, - EuiButtonEmpty, PropertySort } from '@elastic/eui' +import cx from 'classnames' import { isNull } from 'lodash' -import { useParams, useHistory } from 'react-router-dom' +import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import cx from 'classnames' +import { useHistory, useParams } from 'react-router-dom' +import { GroupBadge } from 'uiSrc/components' +import { Pages } from 'uiSrc/constants' +import { SCAN_COUNT_DEFAULT, SCAN_TREE_COUNT_DEFAULT } from 'uiSrc/constants/api' +import { + resetBrowserTree, + setBrowserKeyListDataLoaded, + setBrowserSelectedKey, + setBrowserTreeDelimiter +} from 'uiSrc/slices/app/context' +import { + changeSearchMode, + fetchKeys, + keysSelector, + resetKeysData, + setFilter, + setSearchMatch +} from 'uiSrc/slices/browser/keys' +import { KeyViewType, SearchMode } from 'uiSrc/slices/interfaces/keys' import { formatBytes, formatLongName, + HighlightType, + isBigKey, + stringToBuffer, truncateNumberToDuration, truncateNumberToFirstUnit, - truncateTTLToSeconds, - stringToBuffer + truncateTTLToSeconds } from 'uiSrc/utils' import { numberWithSpaces } from 'uiSrc/utils/numbers' -import { GroupBadge } from 'uiSrc/components' -import { setFilter, setSearchMatch, resetKeysData, fetchKeys, keysSelector, changeSearchMode } from 'uiSrc/slices/browser/keys' -import { SCAN_COUNT_DEFAULT, SCAN_TREE_COUNT_DEFAULT } from 'uiSrc/constants/api' -import { KeyViewType, SearchMode } from 'uiSrc/slices/interfaces/keys' -import { setBrowserKeyListDataLoaded, setBrowserSelectedKey, resetBrowserTree, setBrowserTreeDelimiter } from 'uiSrc/slices/app/context' -import { Pages } from 'uiSrc/constants' import { Key } from 'apiSrc/modules/database-analysis/models/key' import styles from './styles.module.scss' @@ -54,7 +68,7 @@ const Table = (props: Props) => { dispatch(setBrowserTreeDelimiter(delimiter)) dispatch(setFilter(null)) dispatch(setSearchMatch(name, SearchMode.Pattern)) - dispatch(resetKeysData()) + dispatch(resetKeysData(SearchMode.Pattern)) dispatch(fetchKeys( SearchMode.Pattern, '0', @@ -96,7 +110,7 @@ const Table = (props: Props) => { truncateText: true, render: (name: string) => { const tooltipContent = formatLongName(name) - const cellContent = name.substring(0, 200) + const cellContent = (name as string).substring(0, 200) return (
{ width: '9%', sortable: true, align: 'right', - render: (value: number) => { + render: (value: number, { type }) => { const [number, size] = formatBytes(value, 3, true) - + const isHighlight = isBigKey(type, HighlightType.Memory, value) return ( + {isHighlight ? (<>Consider splitting it into multiple keys
) : null} + {numberWithSpaces(value)} B + + )} + anchorClassName={cx({ [styles.highlight]: isHighlight })} data-testid="usedMemory-tooltip" > <> - + {number} {size} @@ -183,7 +206,7 @@ const Table = (props: Props) => { width: '15%', sortable: ({ length }) => length ?? -1, align: 'right', - render: (value: number, { name }) => { + render: (value: number, { name, type }) => { if (isNull(value)) { return ( @@ -191,10 +214,18 @@ const Table = (props: Props) => { ) } + + const isHighlight = isBigKey(type, HighlightType.Length, value) return ( - - {numberWithSpaces(value)} - + + + {numberWithSpaces(value)} + + ) } }, diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/styles.module.scss b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/styles.module.scss index ae0d2924db..9416453d93 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/styles.module.scss +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/styles.module.scss @@ -155,6 +155,19 @@ padding: 12px; } +.highlight { + background: var(--euiColorWarningLight); + color: var(--euiColorEmptyShade) !important; + border-radius: 4px; + padding: 1px 8px !important; + display: flex !important; + align-items: baseline; + + .count, .valueUnit { + color: var(--euiColorEmptyShade) !important; + } +} + :global(.euiTableCellContent) .delimiter span { cursor: pointer; color: var(--buttonSecondaryTextColor); diff --git a/redisinsight/ui/src/utils/comparisons/bigKeys.ts b/redisinsight/ui/src/utils/comparisons/bigKeys.ts new file mode 100644 index 0000000000..b777adf996 --- /dev/null +++ b/redisinsight/ui/src/utils/comparisons/bigKeys.ts @@ -0,0 +1,37 @@ +import { KeyTypes } from 'uiSrc/constants' + +enum HighlightType { + Length = 'length', + Memory = 'memory' +} + +interface DefaultConfig { [key: string]: number } + +const defaultMemoryConfig: { [key: string]: number } = { + memory: 5_000_000 +} + +const defaultConfig: { [key: string]: number } = { + length: 5_000, + memory: 5_000_000 +} + +const bigKeysConfig: { [key: string]: DefaultConfig } = { + [KeyTypes.List]: defaultConfig, + [KeyTypes.ZSet]: defaultConfig, + [KeyTypes.Set]: defaultConfig, + [KeyTypes.Hash]: defaultConfig, +} + +const isBigKey = (keyType: string, type: HighlightType, count: number): boolean => { + if (!count) return false + if (bigKeysConfig[keyType]?.[type]) return count >= bigKeysConfig[keyType][type] + if (defaultMemoryConfig[type]) return count >= defaultMemoryConfig[type] + + return false +} + +export { + HighlightType, + isBigKey +} diff --git a/redisinsight/ui/src/utils/comparisons/index.ts b/redisinsight/ui/src/utils/comparisons/index.ts index 1064323743..cb2d7d3145 100644 --- a/redisinsight/ui/src/utils/comparisons/index.ts +++ b/redisinsight/ui/src/utils/comparisons/index.ts @@ -1,3 +1,4 @@ export * from './getDiffKeysOfObjectValues' export * from './compareVersions' export * from './compareConsents' +export * from './bigKeys' diff --git a/redisinsight/ui/src/utils/tests/comparisons/bigKeys.spec.ts b/redisinsight/ui/src/utils/tests/comparisons/bigKeys.spec.ts new file mode 100644 index 0000000000..6a61ea5f24 --- /dev/null +++ b/redisinsight/ui/src/utils/tests/comparisons/bigKeys.spec.ts @@ -0,0 +1,23 @@ +import { KeyTypes } from 'uiSrc/constants' +import { HighlightType, isBigKey } from 'uiSrc/utils' + +const isBigKeyTests: any[] = [ + [KeyTypes.Hash, HighlightType.Memory, 100, false], + [KeyTypes.Hash, HighlightType.Memory, 5_000_000, true], + [KeyTypes.Hash, HighlightType.Length, 50_000_000, true], + [KeyTypes.String, HighlightType.Memory, 50_000_000, true], + [KeyTypes.String, HighlightType.Length, 50_000_000, false], + [KeyTypes.Stream, HighlightType.Memory, 50_000_000, true], + [KeyTypes.Stream, HighlightType.Length, 50_000_000, false], + [KeyTypes.Stream, HighlightType.Memory, 199, false], + ['newType', HighlightType.Memory, 98391283123123, true], + ['newType', HighlightType.Length, 98391283123123, false], +] + +describe('isBigKey', () => { + it.each(isBigKeyTests)('for input: %s (keyType), %s (type), %s (count) should be output: %s', + (keyType, type, count, expected) => { + const result = isBigKey(keyType, type, count) + expect(result).toBe(expected) + }) +}) From b240c717807c7089db31930141f5775d4c965eb8 Mon Sep 17 00:00:00 2001 From: "Roman.Sergeenko" Date: Wed, 16 Nov 2022 18:33:42 +0400 Subject: [PATCH 2/3] #RI-3483 - fix tests --- .../pages/databaseAnalysis/components/top-keys/Table.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx index 99f1390405..ac83acdc01 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/top-keys/Table.spec.tsx @@ -46,7 +46,7 @@ describe('Table', () => { it('should render correct length', () => { render(
) expect(screen.getByTestId('length-empty-name_1')).toHaveTextContent('-') - expect(screen.getByTestId('length-value-name')).toHaveTextContent('10') + expect(screen.getByTestId(/length-value-name/).textContent).toEqual('100 000 000') }) it('should highlight big keys', () => { From 01470f882105aeb7a17e08711496585acc6d77f9 Mon Sep 17 00:00:00 2001 From: vlad-dargel Date: Mon, 21 Nov 2022 21:18:02 +0100 Subject: [PATCH 3/3] add tests for highlight big keys --- .../e2e/pageObjects/memory-efficiency-page.ts | 2 ++ .../memory-efficiency/top-keys-table.e2e.ts | 28 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/e2e/pageObjects/memory-efficiency-page.ts b/tests/e2e/pageObjects/memory-efficiency-page.ts index 542235a88c..845f0388b5 100644 --- a/tests/e2e/pageObjects/memory-efficiency-page.ts +++ b/tests/e2e/pageObjects/memory-efficiency-page.ts @@ -26,6 +26,8 @@ export class MemoryEfficiencyPage { topKeysKeyName = Selector('[data-testid=top-keys-table-name]'); topNamespacesEmptyContainer = Selector('[data-testid=top-namespaces-empty]'); topNamespacesEmptyMessage = Selector('[data-testid=top-namespaces-message]'); + topKeysKeySizeCell = Selector('[data-testid^=nsp-usedMemory-value]'); + topKeysLengthCell = Selector('[data-testid^=length-value]'); // TABLE namespaceTable = Selector('[data-testid=nsp-table-memory]'); nameSpaceTableRows = this.namespaceTable.find('[data-testid^=row-]'); diff --git a/tests/e2e/tests/critical-path/memory-efficiency/top-keys-table.e2e.ts b/tests/e2e/tests/critical-path/memory-efficiency/top-keys-table.e2e.ts index 9f39523dcd..187660e840 100644 --- a/tests/e2e/tests/critical-path/memory-efficiency/top-keys-table.e2e.ts +++ b/tests/e2e/tests/critical-path/memory-efficiency/top-keys-table.e2e.ts @@ -5,15 +5,19 @@ import { rte } from '../../../helpers/constants'; import { acceptLicenseTermsAndAddDatabaseApi } from '../../../helpers/database'; import { commonUrl, ossStandaloneRedisearch } from '../../../helpers/conf'; import { deleteStandaloneDatabaseApi } from '../../../helpers/api/api-database'; -import { deleteAllKeysFromDB, populateDBWithHashes } from '../../../helpers/keys'; +import { deleteAllKeysFromDB, populateDBWithHashes, populateHashWithFields } from '../../../helpers/keys'; +import { Common } from '../../../helpers/common'; const memoryEfficiencyPage = new MemoryEfficiencyPage(); const myRedisDatabasePage = new MyRedisDatabasePage(); const browserPage = new BrowserPage(); const cliPage = new CliPage(); +const common = new Common(); const chance = new Chance(); const keyToAddParameters = { keysCount: 13, keyNameStartWith: 'hashKey'}; +const keyName = `TestHashKey-${common.generateWord(10)}`; +const keyToAddParameters2 = { fieldsCount: 80000, keyName, fieldStartWith: 'hashField', fieldValueStartWith: 'hashValue' }; const members = [...Array(100).keys()].toString().replace(/,/g, ' '); // The smallest key const keyNamesMemory = ['string', 'list', 'bloom', 'set']; const keyNamesLength = ['string', 'set', 'list']; @@ -66,3 +70,25 @@ test await t.click(memoryEfficiencyPage.topKeysKeyName.nth(1).find('button')); await t.expect(browserPage.keyNameFormDetails.find('b').textContent).eql(keyNamesLength[1]); }); +test + .before(async t => { + await acceptLicenseTermsAndAddDatabaseApi(ossStandaloneRedisearch, ossStandaloneRedisearch.databaseName); + // Create keys + await populateHashWithFields('localhost', '8102', keyToAddParameters2); + // Go to Analysis Tools page + await t.click(myRedisDatabasePage.analysisPageButton); + }) + .after(async t => { + await t.click(myRedisDatabasePage.browserButton); + await browserPage.deleteKeyByName(keyName); + await deleteStandaloneDatabaseApi(ossStandaloneRedisearch); + })('Big highlighted key tooltip', async t => { + const tooltipText = 'Consider splitting it into multiple keys'; + + await t.click(memoryEfficiencyPage.newReportBtn); + // Tooltip with text "Consider splitting it into multiple keys" is displayed for highlighted keys + await t.hover(memoryEfficiencyPage.topKeysKeySizeCell); + await t.expect(browserPage.tooltip.textContent).contains(tooltipText, `"${tooltipText}" is not displayed in Key size tooltip`); + await t.hover(memoryEfficiencyPage.topKeysLengthCell); + await t.expect(browserPage.tooltip.textContent).contains(tooltipText, `"${tooltipText}" is not displayed in Length tooltip`); + });