diff --git a/jest.config.cjs b/jest.config.cjs index 9f367cd721..c1cb891709 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -12,8 +12,10 @@ module.exports = { '\\.(css|less|sass|scss)$': 'identity-obj-proxy', '\\.scss\\?inline$': '/redisinsight/__mocks__/scssRaw.js', 'uiSrc/(.*)': '/redisinsight/ui/src/$1', + '@redislabsdev/redis-ui-components': '@redis-ui/components', '@redislabsdev/redis-ui-styles': '@redis-ui/styles', '@redislabsdev/redis-ui-icons': '@redis-ui/icons', + '@redislabsdev/redis-ui-table': '@redis-ui/table', 'monaco-editor': '/redisinsight/__mocks__/monacoMock.js', 'monaco-yaml': '/redisinsight/__mocks__/monacoYamlMock.js', unified: '/redisinsight/__mocks__/unified.js', diff --git a/package.json b/package.json index bf1645ab77..ec72b23580 100644 --- a/package.json +++ b/package.json @@ -237,6 +237,7 @@ "@redis-ui/components": "^38.1.3", "@redis-ui/icons": "^4.16.1", "@redis-ui/styles": "^11.0.2", + "@redis-ui/table": "^2.4.0", "@reduxjs/toolkit": "^1.6.2", "@stablelib/snappy": "^1.0.2", "ajv": "^8.17.1", diff --git a/redisinsight/ui/src/components/base/layout/table/index.ts b/redisinsight/ui/src/components/base/layout/table/index.ts new file mode 100644 index 0000000000..8bb5e30087 --- /dev/null +++ b/redisinsight/ui/src/components/base/layout/table/index.ts @@ -0,0 +1 @@ +export * from '@redis-ui/table' diff --git a/redisinsight/ui/src/components/shortcuts-flyout/ShortcutsFlyout.tsx b/redisinsight/ui/src/components/shortcuts-flyout/ShortcutsFlyout.tsx index 29d463d30e..0918576b97 100644 --- a/redisinsight/ui/src/components/shortcuts-flyout/ShortcutsFlyout.tsx +++ b/redisinsight/ui/src/components/shortcuts-flyout/ShortcutsFlyout.tsx @@ -1,7 +1,5 @@ import React from 'react' -import cx from 'classnames' import { useDispatch, useSelector } from 'react-redux' -import { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui' import { appInfoSelector, setShortcutsFlyoutState } from 'uiSrc/slices/app/info' import { KeyboardShortcut } from 'uiSrc/components' import { BuildType } from 'uiSrc/constants/env' @@ -12,44 +10,42 @@ import { DrawerBody, } from 'uiSrc/components/base/layout/drawer' import { Title } from 'uiSrc/components/base/text/Title' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { SHORTCUTS, ShortcutGroup, separator } from './schema' -import styles from './styles.module.scss' - const ShortcutsFlyout = () => { const { isShortcutsFlyoutOpen, server } = useSelector(appInfoSelector) const dispatch = useDispatch() - const tableColumns: EuiBasicTableColumn[] = [ + const tableColumns: ColumnDefinition[] = [ { - name: '', - field: 'description', - width: '60%', + header: 'Description', + id: 'description', + accessorKey: 'description', + enableSorting: false, }, { - name: '', - field: 'keys', - width: '40%', - render: (items: string[]) => ( - - ), + header: 'Shortcut', + id: 'keys', + accessorKey: 'keys', + enableSorting: false, + cell: ({ + row: { + original: { keys }, + }, + }) => , }, ] const ShortcutsTable = ({ name, items }: ShortcutGroup) => ( -
+
{name} - + ) diff --git a/redisinsight/ui/src/components/shortcuts-flyout/styles.module.scss b/redisinsight/ui/src/components/shortcuts-flyout/styles.module.scss deleted file mode 100644 index 19e96695b3..0000000000 --- a/redisinsight/ui/src/components/shortcuts-flyout/styles.module.scss +++ /dev/null @@ -1,38 +0,0 @@ -.table { - :global(thead) { - display: none; - } - - :global { - td, tr { - border-color: var(--tableLightBorderColor) !important; - } - } - - &:global(.inMemoryTableDefault) { - :global { - .euiTableCellContent .euiTableCellContent__text { - padding-top: 0 !important; - white-space: normal !important; - } - } - } - - :global([class*='Badge-RedisUI']) { - height: 22px; - min-width: 34px !important; - display: flex; - justify-content: center; - align-items: center; - - font-weight: 500; - - :global(.badgeArrowUp), :global(.badgeArrowDown), :global(.shiftSymbol) { - position: relative; - } - - :global(.cmdSymbol) { - font-size: 12px; - } - } -} diff --git a/redisinsight/ui/src/packages/clients-list/src/components/table-view/TableView.tsx b/redisinsight/ui/src/packages/clients-list/src/components/table-view/TableView.tsx index ef781091d2..7055826bc9 100644 --- a/redisinsight/ui/src/packages/clients-list/src/components/table-view/TableView.tsx +++ b/redisinsight/ui/src/packages/clients-list/src/components/table-view/TableView.tsx @@ -1,17 +1,16 @@ import React, { useEffect, useState } from 'react' import cx from 'classnames' -import { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' export interface Props { query: string result: any - matched?: number } const noResultMessage = 'No results' const TableView = React.memo(({ result, query }: Props) => { - const [columns, setColumns] = useState[]>([]) + const [columns, setColumns] = useState[]>([]) useEffect(() => { if (!result?.length) { @@ -19,9 +18,10 @@ const TableView = React.memo(({ result, query }: Props) => { } const newColumns = Object.keys(result[0]).map((item) => ({ - field: item, - name: item, - truncateText: true, + header: item, + id: item, + accessorKey: item, + enableSorting: true, })) setColumns(newColumns) @@ -29,20 +29,8 @@ const TableView = React.memo(({ result, query }: Props) => { return (
- 10, - })} - responsive={false} - data-testid={`query-table-result-${query}`} - /> +
+ {!result?.length && {noResultMessage}} ) }) diff --git a/redisinsight/ui/src/packages/clients-list/tsconfig.json b/redisinsight/ui/src/packages/clients-list/tsconfig.json deleted file mode 100644 index 929997cae1..0000000000 --- a/redisinsight/ui/src/packages/clients-list/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - /* Specify ECMAScript target version */ - "target": "es5", - /* Specify module code generation */ - "module": "esnext", - /* Specify library files to be included in the compilation. */ - "lib": ["ESNext", "DOM"], - /* Specify JSX code generation */ - "jsx": "react", - /* Generate corresponding .map files */ - "sourceMap": true, - /* Enable all strict type-checking options */ - "strict": true, - /* Specify module resolution strategy */ - "moduleResolution": "node", - /* Base directory to resolve non-absolute module names */ - "baseUrl": "./src", - /* Maps imports to locations - e.g. ~models will go to ./src/models */ - "paths": { - "~/*": ["./*"] - }, - /* List of folders to include type definitions from */ - "typeRoots": ["node_modules/@types"], - /* allow import React instead of import * as React */ - "allowSyntheticDefaultImports": true, - /* Emit interop between CommonJS and ES modules */ - "esModuleInterop": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/redisinsight/ui/src/packages/redisearch/src/components/TableInfoResult/TableInfoResult.tsx b/redisinsight/ui/src/packages/redisearch/src/components/TableInfoResult/TableInfoResult.tsx index 5193d82a06..683a5cbf22 100644 --- a/redisinsight/ui/src/packages/redisearch/src/components/TableInfoResult/TableInfoResult.tsx +++ b/redisinsight/ui/src/packages/redisearch/src/components/TableInfoResult/TableInfoResult.tsx @@ -1,8 +1,9 @@ /* eslint-disable react/prop-types */ -import React, { ReactElement, useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import cx from 'classnames' import { toUpper, flatten, isArray, isEmpty, map, uniq } from 'lodash' -import { EuiBasicTableColumn, EuiIcon, EuiInMemoryTable } from '@elastic/eui' +import { EuiIcon } from '@elastic/eui' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { ColorText, Text } from '../../../../../components/base/text' import { LoadingContent } from '../../../../../components/base/layout' @@ -14,7 +15,6 @@ export interface Props { result: any } -const loadingMessage = 'loading...' const noResultsMessage = 'No results found.' const noOptionsMessage = 'No options found' @@ -22,7 +22,6 @@ const TableInfoResult = React.memo((props: Props) => { const { result: resultProp, query } = props const [result, setResult] = useState(resultProp) - const [items, setItems] = useState([]) useEffect(() => { @@ -42,18 +41,17 @@ const TableInfoResult = React.memo((props: Props) => { const uniqColumns = uniq(flatten(map(items, (item) => Object.keys(item)))) ?? [] - const columns: EuiBasicTableColumn[] = uniqColumns.map( + const columns: ColumnDefinition[] = uniqColumns.map( (title: string = ' ') => ({ - field: title, - name: toUpper(title), - truncateText: true, - align: isBooleanColumn(title) ? 'center' : 'left', - 'data-testid': `query-column-${title}`, - // sortable: (value) => (value[title] ? value[title].toLowerCase() : Infinity), - render: function Cell(initValue?: string): ReactElement | null { + header: toUpper(title), + id: title, + accessorKey: title, + enableSorting: false, + cell: ({ row: { original } }) => { + const initValue = original[title] if (isBooleanColumn(title)) { return ( -
+
{
) } - return {initValue} }, }), @@ -119,19 +116,9 @@ const TableInfoResult = React.memo((props: Props) => { return (
{isDataArr && ( -
+
{Header()} - 10, - })} - responsive={false} - data-testid={`query-table-result-${query}`} - /> +
{Footer()} )} diff --git a/redisinsight/ui/src/packages/redisearch/src/components/TableResult/TableResult.tsx b/redisinsight/ui/src/packages/redisearch/src/components/TableResult/TableResult.tsx index 980aa07834..5ff7f4dc73 100644 --- a/redisinsight/ui/src/packages/redisearch/src/components/TableResult/TableResult.tsx +++ b/redisinsight/ui/src/packages/redisearch/src/components/TableResult/TableResult.tsx @@ -1,8 +1,9 @@ -import React, { ReactElement, useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' import parse from 'html-react-parser' import cx from 'classnames' import { flatten, isArray, isEmpty, map, uniq } from 'lodash' -import { EuiBasicTableColumn, EuiInMemoryTable, EuiToolTip } from '@elastic/eui' +import { EuiToolTip } from '@elastic/eui' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { ColorText } from '../../../../../components/base/text/ColorText' import { IconButton } from '../../../../../components/base/forms/buttons' @@ -17,13 +18,12 @@ export interface Props { cursorId?: null | number } -const loadingMessage = 'loading...' const noResultsMessage = 'No results found.' const TableResult = React.memo((props: Props) => { const { result, query, matched, cursorId } = props - const [columns, setColumns] = useState[]>([]) + const [columns, setColumns] = useState[]>([]) const checkShouldParsedHTML = (query: string) => { const command = query.toUpperCase() @@ -49,15 +49,13 @@ const TableResult = React.memo((props: Props) => { const uniqColumns = uniq(flatten(map(result, (doc) => Object.keys(doc)))) ?? [] - const newColumns: EuiBasicTableColumn[] = uniqColumns.map( + const newColumns: ColumnDefinition[] = uniqColumns.map( (title: string = ' ') => ({ - field: title, - name: title, - truncateText: true, - dataType: 'string', - 'data-testid': `query-column-${title}`, - // sortable: (value) => (value[title] ? value[title].toLowerCase() : Infinity), - render: function Cell(initValue: string = ''): ReactElement | string { + header: title, + id: title, + accessorKey: title, + cell: ({ row: { original } }) => { + const initValue = original[title] || '' if (!initValue || (isArray(initValue) && isEmpty(initValue))) { return '' } @@ -72,7 +70,11 @@ const TableResult = React.memo((props: Props) => { } return ( -
+
{ )}
{isDataArr && ( - 10, - })} - responsive={false} - data-testid={`query-table-result-${query}`} - /> +
+
+ )} {isDataEl &&
{result}
} {!isDataArr && !isDataEl && ( diff --git a/redisinsight/ui/src/packages/redisgraph/src/App.tsx b/redisinsight/ui/src/packages/redisgraph/src/App.tsx index 87d03a93a3..e42fa330ef 100644 --- a/redisinsight/ui/src/packages/redisgraph/src/App.tsx +++ b/redisinsight/ui/src/packages/redisgraph/src/App.tsx @@ -1,8 +1,9 @@ import React from 'react' import { JSONTree } from 'react-json-tree' +import { Table } from 'uiSrc/components/base/layout/table' + import { ResultsParser } from './parser' import Graph from './Graph' -import { Table } from './Table' import { COMPACT_FLAG } from './constants' const isDarkTheme = document.body.classList.contains('theme_DARK') @@ -40,9 +41,10 @@ export function TableApp(props: { command?: string; data: any }) {
({ - field: h, - name: h, - render: (d) => ( + id: h, + header: h, + accessorKey: h, + cell: ({ row: { original: d } }) => ( - ) -} diff --git a/redisinsight/ui/src/packages/vite.config.mjs b/redisinsight/ui/src/packages/vite.config.mjs index 2e18c3983f..6374f7455b 100644 --- a/redisinsight/ui/src/packages/vite.config.mjs +++ b/redisinsight/ui/src/packages/vite.config.mjs @@ -37,8 +37,10 @@ export default defineConfig({ alias: { lodash: 'lodash-es', '@elastic/eui$': '@elastic/eui/optimize/lib', + '@redislabsdev/redis-ui-components': '@redis-ui/components', '@redislabsdev/redis-ui-styles': '@redis-ui/styles', '@redislabsdev/redis-ui-icons': '@redis-ui/icons', + '@redislabsdev/redis-ui-table': '@redis-ui/table', uiSrc: fileURLToPath(new URL('../../src', import.meta.url)), apiSrc: fileURLToPath(new URL('../../../api/src', import.meta.url)), }, diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.spec.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.spec.tsx index 1085a28ea3..ecd45c9773 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.spec.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.spec.tsx @@ -9,13 +9,10 @@ describe('RedisCloudDatabasesResult', () => { it('should render', () => { const columnsMock = [ { - field: 'subscriptionId', - className: 'column_subscriptionId', - name: 'Subscription ID', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, + id: 'subscriptionId', + accessorKey: 'subscriptionId', + header: 'Subscription ID', + enableSorting: true, }, ] expect( diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx index 780b2a9397..bbe16dcb4b 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx @@ -1,10 +1,5 @@ import React, { useState, useEffect } from 'react' import { useSelector } from 'react-redux' -import { - EuiInMemoryTable, - EuiBasicTableColumn, - PropertySort, -} from '@elastic/eui' import { InstanceRedisCloud, AddRedisDatabaseStatus, @@ -22,10 +17,11 @@ import { SearchInput } from 'uiSrc/components/base/inputs' import { Title } from 'uiSrc/components/base/text/Title' import { Text } from 'uiSrc/components/base/text' import { FormField } from 'uiSrc/components/base/forms/FormField' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import styles from './styles.module.scss' export interface Props { - columns: EuiBasicTableColumn[] + columns: ColumnDefinition[] onView: () => void onBack: () => void } @@ -37,15 +33,10 @@ const RedisCloudDatabaseListResult = ({ columns, onBack, onView }: Props) => { const [items, setItems] = useState([]) const [message, setMessage] = useState(loadingMsg) - const { loading, dataAdded: instances } = useSelector(cloudSelector) + const { dataAdded: instances } = useSelector(cloudSelector) useEffect(() => setItems(instances), [instances]) - const sort: PropertySort = { - field: 'name', - direction: 'asc', - } - const countSuccessAdded = instances.filter( ({ statusAdded }) => statusAdded === AddRedisDatabaseStatus.Success, )?.length @@ -112,15 +103,17 @@ const RedisCloudDatabaseListResult = ({ columns, onBack, onView }: Props) => {
- + {!items.length && {message}}
diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.spec.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.spec.tsx index 10e21c1c74..fce380d23b 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.spec.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.spec.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { EuiInMemoryTable } from '@elastic/eui' import { render, fireEvent, screen } from 'uiSrc/utils/test-utils' +import { Table } from 'uiSrc/components/base/layout/table' import RedisCloudDatabasesResultPage from './RedisCloudDatabasesResultPage' import RedisCloudDatabasesResult, { @@ -25,13 +25,10 @@ const mockRedisCloudDatabasesResult = ( onBack
-
diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.tsx index 857ef2ee88..aeed8daf78 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResultPage.tsx @@ -1,4 +1,4 @@ -import { EuiBasicTableColumn, EuiIcon, EuiToolTip } from '@elastic/eui' +import { EuiIcon, EuiToolTip } from '@elastic/eui' import React, { useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' @@ -13,7 +13,6 @@ import { InstanceRedisCloud, AddRedisDatabaseStatus, LoadedCloud, - RedisCloudSubscriptionType, RedisCloudSubscriptionTypeText, } from 'uiSrc/slices/interfaces' import { @@ -27,6 +26,7 @@ import { FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { IconButton } from 'uiSrc/components/base/forms/buttons' import { CopyIcon } from 'uiSrc/components/base/icons' import { ColorText, Text } from 'uiSrc/components/base/text' +import { ColumnDefinition } from 'uiSrc/components/base/layout/table' import RedisCloudDatabasesResult from './RedisCloudDatabasesResult' import styles from './styles.module.scss' @@ -60,16 +60,17 @@ const RedisCloudDatabasesResultPage = () => { navigator.clipboard.writeText(text) } - const columns: EuiBasicTableColumn[] = [ + const columns: ColumnDefinition[] = [ { - field: 'name', - className: 'column_name', - name: 'Database', - dataType: 'auto', - truncateText: true, - sortable: true, - width: '195px', - render: function InstanceCell(name: string = '') { + header: 'Database', + id: 'name', + accessorKey: 'name', + enableSorting: true, + cell: function InstanceCell({ + row: { + original: { name }, + }, + }) { const cellContent = replaceSpaces(name.substring(0, 200)) return (
@@ -86,23 +87,21 @@ const RedisCloudDatabasesResultPage = () => { }, }, { - field: 'subscriptionId', - className: 'column_subscriptionId', - name: 'Subscription ID', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, + header: 'Subscription ID', + id: 'subscriptionId', + accessorKey: 'subscriptionId', + enableSorting: true, }, { - field: 'subscriptionName', - className: 'column_subscriptionName', - name: 'Subscription', - dataType: 'string', - sortable: true, - width: '300px', - truncateText: true, - render: function SubscriptionCell(name: string = '') { + header: 'Subscription', + id: 'subscriptionName', + accessorKey: 'subscriptionName', + enableSorting: true, + cell: function SubscriptionCell({ + row: { + original: { subscriptionName: name }, + }, + }) { const cellContent = replaceSpaces(name.substring(0, 200)) return (
@@ -119,35 +118,32 @@ const RedisCloudDatabasesResultPage = () => { }, }, { - field: 'subscriptionType', - className: 'column_subscriptionType', - name: 'Type', - width: '95px', - dataType: 'string', - sortable: true, - truncateText: true, - render: (type: RedisCloudSubscriptionType) => - RedisCloudSubscriptionTypeText[type] ?? '-', + header: 'Type', + id: 'subscriptionType', + accessorKey: 'subscriptionType', + enableSorting: true, + cell: ({ + row: { + original: { subscriptionType }, + }, + }) => RedisCloudSubscriptionTypeText[subscriptionType!] ?? '-', }, { - field: 'status', - className: 'column_status', - name: 'Status', - dataType: 'string', - sortable: true, - width: '95px', - truncateText: true, - hideForMobile: true, + header: 'Status', + id: 'status', + accessorKey: 'status', + enableSorting: true, }, { - field: 'publicEndpoint', - className: 'column_publicEndpoint', - name: 'Endpoint', - width: '310px', - dataType: 'auto', - truncateText: true, - sortable: true, - render: function PublicEndpoint(publicEndpoint: string) { + header: 'Endpoint', + id: 'publicEndpoint', + accessorKey: 'publicEndpoint', + enableSorting: true, + cell: function PublicEndpoint({ + row: { + original: { publicEndpoint }, + }, + }) { const text = publicEndpoint return (
@@ -169,14 +165,11 @@ const RedisCloudDatabasesResultPage = () => { }, }, { - field: 'modules', - className: 'column_modules', - name: 'Capabilities', - dataType: 'auto', - align: 'left', - width: '200px', - sortable: true, - render: function Modules(_modules: any[], instance: InstanceRedisCloud) { + header: 'Capabilities', + id: 'modules', + accessorKey: 'modules', + enableSorting: true, + cell: function Modules({ row: { original: instance } }) { return ( ({ name }))} @@ -185,14 +178,11 @@ const RedisCloudDatabasesResultPage = () => { }, }, { - field: 'options', - className: 'column_options', - name: 'Options', - dataType: 'auto', - align: 'left', - width: '180px', - sortable: true, - render: function Opitions(_opts: any[], instance: InstanceRedisCloud) { + header: 'Options', + id: 'options', + accessorKey: 'options', + enableSorting: true, + cell: function Opitions({ row: { original: instance } }) { const options = parseInstanceOptionsCloud( instance.databaseId, instancesForOptions, @@ -201,17 +191,15 @@ const RedisCloudDatabasesResultPage = () => { }, }, { - field: 'messageAdded', - className: 'column_message', - name: 'Result', - dataType: 'string', - align: 'left', - width: '110px', - sortable: true, - render: function Message( - messageAdded: string, - { statusAdded }: InstanceRedisCloud, - ) { + header: 'Result', + id: 'messageAdded', + accessorKey: 'messageAdded', + enableSorting: true, + cell: function Message({ + row: { + original: { statusAdded, messageAdded }, + }, + }) { return ( <> {statusAdded === AddRedisDatabaseStatus.Success ? ( diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/styles.module.scss b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/styles.module.scss index 4fe1a78e2c..5223d88189 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/styles.module.scss +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/styles.module.scss @@ -13,16 +13,6 @@ width: 266px !important; } -.table { - @include eui.scrollBar; - overflow: auto; - max-height: calc(100vh - 320px) !important; - - @media (min-width: 1130px) { - max-height: calc(100vh - 260px) !important; - } -} - .panelCancelBtn { max-width: 350px !important; margin-left: -10px; diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.spec.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.spec.tsx index c246b7ed92..16e4c1c040 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.spec.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.spec.tsx @@ -9,13 +9,10 @@ describe('RedisCloudDatabases', () => { it('should render', () => { const columnsMock = [ { - field: 'subscriptionId', - className: 'column_subscriptionId', - name: 'Subscription ID', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, + header: 'Subscription ID', + id: 'subscriptionId', + accessorKey: 'subscriptionId', + enableSorting: true, }, ] expect( diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.tsx index 53b53c765d..7890b1c081 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabases/RedisCloudDatabases.tsx @@ -1,12 +1,5 @@ import React, { useState, useEffect } from 'react' -import { - EuiInMemoryTable, - EuiBasicTableColumn, - EuiTableSelectionType, - PropertySort, - EuiPopover, - EuiToolTip, -} from '@elastic/eui' +import { EuiPopover, EuiToolTip } from '@elastic/eui' import { map, pick } from 'lodash' import { useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' @@ -28,10 +21,11 @@ import { Title } from 'uiSrc/components/base/text/Title' import { SearchInput } from 'uiSrc/components/base/inputs' import { Text } from 'uiSrc/components/base/text' import { FormField } from 'uiSrc/components/base/forms/FormField' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import styles from '../styles.module.scss' export interface Props { - columns: EuiBasicTableColumn[] + columns: ColumnDefinition[] onClose: () => void onBack: () => void onSubmit: ( @@ -85,11 +79,6 @@ const RedisCloudDatabasesPage = ({ } }, [instances]) - const sort: PropertySort = { - field: 'name', - direction: 'asc', - } - const handleSubmit = () => { onSubmit( map(selection, (i) => @@ -106,9 +95,19 @@ const RedisCloudDatabasesPage = ({ setIsPopoverOpen(false) } - const selectionValue: EuiTableSelectionType = { - onSelectionChange: (selected: InstanceRedisCloud[]) => - setSelection(selected), + const selectionValue = { + onSelectionChange: (selected: InstanceRedisCloud) => + setSelection((previous) => { + const isSelected = previous.some( + (item) => item.databaseId === selected.databaseId, + ) + if (isSelected) { + return previous.filter( + (item) => item.databaseId !== selected.databaseId, + ) + } + return [...previous, selected] + }), } const onQueryChange = (term: string) => { @@ -222,17 +221,18 @@ const RedisCloudDatabasesPage = ({
- + {!items.length && {message}}
diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.spec.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.spec.tsx index 060e4c23ff..b15d8862c8 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.spec.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.spec.tsx @@ -1,6 +1,6 @@ import React from 'react' import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' -import { EuiInMemoryTable } from '@elastic/eui' +import { Table } from 'uiSrc/components/base/layout/table' import RedisCloudDatabasesPage from './RedisCloudDatabasesPage' import RedisCloudDatabases from './RedisCloudDatabases' @@ -32,13 +32,10 @@ const mockRedisCloudDatabases = (props: RedisCloudDatabasesProps) => ( onSubmit
-
diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.tsx index 25a1c98ae6..558588af9f 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/RedisCloudDatabasesPage.tsx @@ -1,4 +1,4 @@ -import { EuiBasicTableColumn, EuiToolTip } from '@elastic/eui' +import { EuiToolTip } from '@elastic/eui' import React, { useEffect, useRef } from 'react' import { useHistory } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' @@ -21,7 +21,6 @@ import { InstanceRedisCloud, LoadedCloud, OAuthSocialAction, - RedisCloudSubscriptionType, RedisCloudSubscriptionTypeText, } from 'uiSrc/slices/interfaces' import { DatabaseListModules, DatabaseListOptions } from 'uiSrc/components' @@ -31,6 +30,7 @@ import { oauthCloudUserSelector } from 'uiSrc/slices/oauth/cloud' import { IconButton } from 'uiSrc/components/base/forms/buttons' import { CopyIcon } from 'uiSrc/components/base/icons' import { Text } from 'uiSrc/components/base/text' +import { ColumnDefinition } from 'uiSrc/components/base/layout/table' import RedisCloudDatabases from './RedisCloudDatabases' import styles from './styles.module.scss' @@ -119,16 +119,17 @@ const RedisCloudDatabasesPage = () => { navigator.clipboard.writeText(text) } - const columns: EuiBasicTableColumn[] = [ + const columns: ColumnDefinition[] = [ { - field: 'name', - className: 'column_name', - name: 'Database', - dataType: 'auto', - truncateText: true, - sortable: true, - width: '195px', - render: function InstanceCell(name: string = '') { + header: 'Database', + id: 'name', + accessorKey: 'name', + enableSorting: true, + cell: ({ + row: { + original: { name }, + }, + }) => { const cellContent = replaceSpaces(name.substring(0, 200)) return (
@@ -145,26 +146,28 @@ const RedisCloudDatabasesPage = () => { }, }, { - field: 'subscriptionId', - className: 'column_subscriptionId', - name: 'Subscription ID', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, - render: (subscriptionId: string) => ( + header: 'Subscription ID', + id: 'subscriptionId', + accessorKey: 'subscriptionId', + enableSorting: true, + cell: ({ + row: { + original: { subscriptionId }, + }, + }) => ( {subscriptionId} ), }, { - field: 'subscriptionName', - className: 'column_subscriptionName', - name: 'Subscription', - dataType: 'string', - sortable: true, - width: '300px', - truncateText: true, - render: function SubscriptionCell(name: string = '') { + header: 'Subscription', + id: 'subscriptionName', + accessorKey: 'subscriptionName', + enableSorting: true, + cell: ({ + row: { + original: { subscriptionName: name }, + }, + }) => { const cellContent = replaceSpaces(name.substring(0, 200)) return (
@@ -181,35 +184,32 @@ const RedisCloudDatabasesPage = () => { }, }, { - field: 'subscriptionType', - className: 'column_subscriptionType', - name: 'Type', - width: '95px', - dataType: 'string', - sortable: true, - truncateText: true, - render: (type: RedisCloudSubscriptionType) => - RedisCloudSubscriptionTypeText[type] ?? '-', + header: 'Type', + id: 'subscriptionType', + accessorKey: 'subscriptionType', + enableSorting: true, + cell: ({ + row: { + original: { subscriptionType }, + }, + }) => RedisCloudSubscriptionTypeText[subscriptionType!] ?? '-', }, { - field: 'status', - className: 'column_status', - name: 'Status', - dataType: 'string', - sortable: true, - width: '110px', - truncateText: true, - hideForMobile: true, + header: 'Status', + id: 'status', + accessorKey: 'status', + enableSorting: true, }, { - field: 'publicEndpoint', - className: 'column_publicEndpoint', - name: 'Endpoint', - width: '310px', - dataType: 'auto', - truncateText: true, - sortable: true, - render: function PublicEndpoint(publicEndpoint: string) { + header: 'Endpoint', + id: 'publicEndpoint', + accessorKey: 'publicEndpoint', + enableSorting: true, + cell: ({ + row: { + original: { publicEndpoint }, + }, + }) => { const text = publicEndpoint return (
@@ -231,14 +231,11 @@ const RedisCloudDatabasesPage = () => { }, }, { - field: 'modules', - className: 'column_modules', - name: 'Capabilities', - dataType: 'auto', - align: 'left', - width: '200px', - sortable: true, - render: function Modules(_, instance: InstanceRedisCloud) { + header: 'Capabilities', + id: 'modules', + accessorKey: 'modules', + enableSorting: true, + cell: function Modules({ row: { original: instance } }) { return ( ({ name }))} @@ -247,14 +244,11 @@ const RedisCloudDatabasesPage = () => { }, }, { - field: 'options', - className: 'column_options', - name: 'Options', - dataType: 'auto', - align: 'left', - width: '180px', - sortable: true, - render: function Opitions(_, instance: InstanceRedisCloud) { + header: 'Options', + id: 'options', + accessorKey: 'options', + enableSorting: true, + cell: ({ row: { original: instance } }) => { const options = parseInstanceOptionsCloud( instance.databaseId, instances || [], diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/styles.module.scss b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/styles.module.scss index 595bc94116..3403fac2fb 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/styles.module.scss +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases/styles.module.scss @@ -14,16 +14,6 @@ width: 266px !important; } -.table { - @include eui.scrollBar; - overflow: auto; - max-height: calc(100vh - 320px) !important; - - @media (min-width: 1130px) { - max-height: calc(100vh - 260px) !important; - } -} - .panelCancelBtn { max-width: 350px !important; margin-left: -10px; diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.spec.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.spec.tsx index 8c02df0c1c..8ff00a038d 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.spec.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.spec.tsx @@ -3,6 +3,7 @@ import { instance, mock } from 'ts-mockito' import { RedisCloudSubscription, RedisCloudSubscriptionStatus, + RedisCloudSubscriptionType, } from 'uiSrc/slices/interfaces' import { render } from 'uiSrc/utils/test-utils' import RedisCloudSubscriptions, { Props } from './RedisCloudSubscriptions' @@ -13,13 +14,10 @@ describe('RedisCloudSubscriptions', () => { it('should render', () => { const columnsMock = [ { - field: 'subscriptionId', - className: 'column_subscriptionId', - name: 'Subscription ID', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, + id: 'subscriptionId', + accessorKey: 'subscriptionId', + header: 'Subscription ID', + enableSorting: true, }, ] @@ -31,6 +29,8 @@ describe('RedisCloudSubscriptions', () => { provider: 'provider', region: 'region', status: RedisCloudSubscriptionStatus.Active, + type: RedisCloudSubscriptionType.Fixed, + free: false, }, ] expect( diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.tsx index 66727a21e3..f963985480 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptions/RedisCloudSubscriptions.tsx @@ -1,13 +1,6 @@ import React, { useState, useEffect } from 'react' import { map } from 'lodash' -import { - EuiInMemoryTable, - EuiBasicTableColumn, - EuiTableSelectionType, - PropertySort, - EuiPopover, - EuiToolTip, -} from '@elastic/eui' +import { EuiPopover, EuiToolTip } from '@elastic/eui' import cx from 'classnames' import { InstanceRedisCloud, @@ -20,6 +13,7 @@ import { LoadingContent } from 'uiSrc/components/base/layout' import MessageBar from 'uiSrc/components/message-bar/MessageBar' import validationErrors from 'uiSrc/constants/validationErrors' import { AutodiscoveryPageTemplate } from 'uiSrc/templates' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { @@ -35,7 +29,7 @@ import { FormField } from 'uiSrc/components/base/forms/FormField' import styles from '../styles.module.scss' export interface Props { - columns: EuiBasicTableColumn[] + columns: ColumnDefinition[] subscriptions: Nullable loading: boolean account: Nullable @@ -90,11 +84,6 @@ const RedisCloudSubscriptions = ({ const countStatusFailed = items.length - countStatusActive - const sort: PropertySort = { - field: 'status', - direction: 'asc', - } - const handleSubmit = () => { onSubmit( map(selection, ({ id, type, free }) => ({ @@ -113,11 +102,27 @@ const RedisCloudSubscriptions = ({ setIsPopoverOpen(false) } - const selectionValue: EuiTableSelectionType = { - selectable: ({ status, numberOfDatabases }) => - status === RedisCloudSubscriptionStatus.Active && numberOfDatabases !== 0, - onSelectionChange: (selected: RedisCloudSubscription[]) => - setSelection(selected), + const selectionValue = { + onSelectionChange: (selected: RedisCloudSubscription) => + setSelection((previous) => { + const canSelect = + selected.status === RedisCloudSubscriptionStatus.Active && + selected.numberOfDatabases !== 0 + + if (!canSelect) { + return previous + } + + const isSelected = previous.some( + (item) => item.id === selected.id && item.type === selected.type, + ) + if (isSelected) { + return previous.filter( + (item) => !(item.id === selected.id && item.type === selected.type), + ) + } + return [...previous, selected] + }), } const onQueryChange = (term: string) => { @@ -281,15 +286,16 @@ const RedisCloudSubscriptions = ({
- {!items.length && ( {message} diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptionsPage.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptionsPage.tsx index 381a102059..ebeee79270 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptionsPage.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/RedisCloudSubscriptionsPage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' import { isNumber } from 'lodash' -import { EuiBasicTableColumn, EuiToolTip } from '@elastic/eui' +import { EuiToolTip } from '@elastic/eui' import { Pages } from 'uiSrc/constants' import { @@ -12,7 +12,6 @@ import { RedisCloudSubscription, RedisCloudSubscriptionStatus, RedisCloudSubscriptionStatusText, - RedisCloudSubscriptionType, RedisCloudSubscriptionTypeText, } from 'uiSrc/slices/interfaces' import { @@ -28,6 +27,7 @@ import { oauthCloudUserSelector } from 'uiSrc/slices/oauth/cloud' import { IconButton } from 'uiSrc/components/base/forms/buttons' import { ToastDangerIcon } from 'uiSrc/components/base/icons' import { Text } from 'uiSrc/components/base/text' +import { ColumnDefinition } from 'uiSrc/components/base/layout/table' import RedisCloudSubscriptions from './RedisCloudSubscriptions/RedisCloudSubscriptions' import styles from './styles.module.scss' @@ -125,15 +125,16 @@ const RedisCloudSubscriptionsPage = () => { ) - const columns: EuiBasicTableColumn[] = [ + const columns: ColumnDefinition[] = [ { - field: 'alert', - className: 'column_status_alert', - name: '', - width: '20px', - align: 'center', - dataType: 'auto', - render: function AlertIcon(_, { status, numberOfDatabases }) { + id: 'alert', + accessorKey: 'alert', + header: '', + cell: function AlertIcon({ + row: { + original: { status, numberOfDatabases }, + }, + }) { return status !== RedisCloudSubscriptionStatus.Active || numberOfDatabases === 0 ? ( { }, }, { - field: 'id', - className: 'column_id', - name: 'Id', - dataType: 'string', - sortable: true, - width: '90px', - truncateText: true, - render: (id: string) => {id}, + id: 'id', + accessorKey: 'id', + header: 'Id', + enableSorting: true, + cell: ({ + row: { + original: { id }, + }, + }) => {id}, }, { - field: 'name', - className: 'column_name', - name: 'Subscription', - dataType: 'auto', - truncateText: true, - sortable: true, - width: '385px', - render: function InstanceCell(name = '') { + id: 'name', + accessorKey: 'name', + header: 'Subscription', + enableSorting: true, + cell: function InstanceCell({ + row: { + original: { name }, + }, + }) { const cellContent = replaceSpaces(name.substring(0, 200)) return (
@@ -190,52 +193,59 @@ const RedisCloudSubscriptionsPage = () => { }, }, { - field: 'type', - className: 'column_type', - name: 'Type', - width: '120px', - dataType: 'string', - sortable: true, - render: (type: RedisCloudSubscriptionType) => - RedisCloudSubscriptionTypeText[type] ?? '-', + id: 'type', + accessorKey: 'type', + header: 'Type', + enableSorting: true, + cell: ({ + row: { + original: { type }, + }, + }) => RedisCloudSubscriptionTypeText[type] ?? '-', }, { - field: 'provider', - className: 'column_provider', - name: 'Cloud provider', - width: '155px', - dataType: 'string', - sortable: true, - render: (provider: string) => provider ?? '-', + id: 'provider', + accessorKey: 'provider', + header: 'Cloud provider', + enableSorting: true, + cell: ({ + row: { + original: { provider }, + }, + }) => provider ?? '-', }, { - field: 'region', - className: 'column_region', - name: 'Region', - width: '115px', - dataType: 'string', - sortable: true, - render: (region: string) => region ?? '-', + id: 'region', + accessorKey: 'region', + header: 'Region', + enableSorting: true, + cell: ({ + row: { + original: { region }, + }, + }) => region ?? '-', }, { - field: 'numberOfDatabases', - className: 'column_num_of_dbs', - name: '# databases', - width: '120px', - dataType: 'string', - sortable: true, - render: (numberOfDatabases: number) => - isNumber(numberOfDatabases) ? numberOfDatabases : '-', + id: 'numberOfDatabases', + accessorKey: 'numberOfDatabases', + header: '# databases', + enableSorting: true, + cell: ({ + row: { + original: { numberOfDatabases }, + }, + }) => (isNumber(numberOfDatabases) ? numberOfDatabases : '-'), }, { - field: 'status', - className: 'column_id', - name: 'Status', - dataType: 'string', - width: '135px', - sortable: true, - render: (status: RedisCloudSubscriptionStatus) => - RedisCloudSubscriptionStatusText[status] ?? '-', + id: 'status', + accessorKey: 'status', + header: 'Status', + enableSorting: true, + cell: ({ + row: { + original: { status }, + }, + }) => RedisCloudSubscriptionStatusText[status] ?? '-', }, ] diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/styles.module.scss b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/styles.module.scss index 6d319e67ef..16942e4647 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/styles.module.scss +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-subscriptions/styles.module.scss @@ -23,16 +23,6 @@ padding-bottom: 5px !important; } -.table { - @include eui.scrollBar; - overflow: auto; - max-height: calc(100vh - 370px) !important; -} - -.tableEmpty tbody { - display: none; -} - .hideTableMessage { tbody tr { display: none; diff --git a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/SentinelDatabasesResultPage.tsx b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/SentinelDatabasesResultPage.tsx index cb429dd108..fbfbed801d 100644 --- a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/SentinelDatabasesResultPage.tsx +++ b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/SentinelDatabasesResultPage.tsx @@ -1,8 +1,4 @@ -import { - EuiBasicTableColumn, - EuiIcon, - EuiToolTip, -} from '@elastic/eui' +import { EuiIcon, EuiToolTip } from '@elastic/eui' import { pick } from 'lodash' import { useHistory } from 'react-router-dom' import React, { useEffect, useState } from 'react' @@ -30,6 +26,7 @@ import { SentinelInputFieldType } from 'uiSrc/components/input-field-sentinel/In import { IconButton, PrimaryButton } from 'uiSrc/components/base/forms/buttons' import { InfoIcon, CopyIcon } from 'uiSrc/components/base/icons' import { ColorText, Text } from 'uiSrc/components/base/text' +import { ColumnDefinition } from 'uiSrc/components/base/layout/table' import { Loader } from 'uiSrc/components/base/display' import SentinelDatabasesResult from './components' @@ -111,58 +108,54 @@ const SentinelDatabasesResultPage = () => { ) } - const columns: EuiBasicTableColumn[] = [ + const columns: ColumnDefinition[] = [ { - field: 'message', - className: 'column_status', - name: 'Result', - dataType: 'string', - align: 'left', - width: '110px', - sortable: true, - render: function Message( - _status: string, - { status, message, name, loading = false }, - ) { - return ( -
- {loading && } - {!loading && status === AddRedisDatabaseStatus.Success && ( - {message} - )} - {!loading && status !== AddRedisDatabaseStatus.Success && ( - - - Error  - - - - )} -
- ) - }, + header: 'Result', + id: 'message', + accessorKey: 'message', + enableSorting: true, + cell: ({ + row: { + original: { status, message, name, loading = false }, + }, + }) => ( +
+ {loading && } + {!loading && status === AddRedisDatabaseStatus.Success && ( + {message} + )} + {!loading && status !== AddRedisDatabaseStatus.Success && ( + + + Error  + + + + )} +
+ ), }, { - field: 'name', - className: 'column_masterName', - name: 'Primary Group', - truncateText: true, - sortable: true, - width: '175px', - render: (name: string) => ( - {name} - ), + header: 'Primary Group', + id: 'name', + accessorKey: 'name', + enableSorting: true, + cell: ({ + row: { + original: { name }, + }, + }) => {name}, }, { - field: 'alias', - className: 'column_db_alias', - name: 'Database Alias*', - width: '300px', - sortable: true, - render: function InstanceAliasCell( - _alias: string, - { id, alias, error, loading = false, status }, - ) { + header: 'Database Alias*', + id: 'alias', + accessorKey: 'alias', + enableSorting: true, + cell: ({ + row: { + original: { id, alias, error, loading = false, status }, + }, + }) => { if ( error?.statusCode !== ApiStatusCode.Unauthorized || status === AddRedisDatabaseStatus.Success @@ -170,33 +163,29 @@ const SentinelDatabasesResultPage = () => { return alias } return ( -
- -
+ ) }, }, { - field: 'host', - className: 'column_address', - name: 'Address', - width: '190px', - dataType: 'auto', - truncateText: true, - sortable: ({ host, port }) => `${host}:${port}`, - render: function Address( - _host: string, - { host, port }: ModifiedSentinelMaster, - ) { + header: 'Address', + id: 'host', + accessorKey: 'host', + enableSorting: true, + cell: ({ + row: { + original: { host, port }, + }, + }) => { const text = `${host}:${port}` return (
@@ -219,25 +208,20 @@ const SentinelDatabasesResultPage = () => { }, }, { - field: 'numberOfSlaves', - className: 'column_numberOfSlaves', - name: '# of replicas', - dataType: 'number', - align: 'center', - sortable: true, - width: '135px', - truncateText: true, - hideForMobile: true, + header: '# of replicas', + id: 'numberOfSlaves', + accessorKey: 'numberOfSlaves', + enableSorting: true, }, { - field: 'username', - className: 'column_username', - name: 'Username', - width: '285px', - render: function UsernameCell( - _username: string, - { username, id, loading = false, error, status }, - ) { + header: 'Username', + id: 'username', + accessorKey: 'username', + cell: ({ + row: { + original: { username, id, loading = false, error, status }, + }, + }) => { if ( error?.statusCode !== ApiStatusCode.Unauthorized || status === AddRedisDatabaseStatus.Success @@ -262,14 +246,14 @@ const SentinelDatabasesResultPage = () => { }, }, { - field: 'password', - className: 'column_password', - name: 'Password', - width: '285px', - render: function PasswordCell( - _password: string, - { password, id, error, loading = false, status }, - ) { + header: 'Password', + id: 'password', + accessorKey: 'password', + cell: ({ + row: { + original: { password, id, error, loading = false, status }, + }, + }) => { if ( error?.statusCode !== ApiStatusCode.Unauthorized || status === AddRedisDatabaseStatus.Success @@ -293,15 +277,14 @@ const SentinelDatabasesResultPage = () => { }, }, { - field: 'db', - className: 'column_db', - width: '170px', - align: 'center', - name: 'Database Index', - render: function DbCell( - _password: string, - { db, id, loading = false, status, error }, - ) { + header: 'Database Index', + id: 'db', + accessorKey: 'db', + cell: ({ + row: { + original: { db, id, loading = false, status, error }, + }, + }) => { if (status === AddRedisDatabaseStatus.Success) { return db || not assigned } @@ -325,18 +308,16 @@ const SentinelDatabasesResultPage = () => { }, ] - // add column with actions if someone error has come if (countSuccessAdded !== items.length) { - const columnActions: EuiBasicTableColumn = { - field: 'actions', - className: 'column_actions', - align: 'left', - name: '', - width: '200px', - render: function ButtonCell( - _password: string, - { name, error, alias, loading = false }, - ) { + const columnActions: ColumnDefinition = { + header: '', + id: 'actions', + accessorKey: 'actions', + cell: ({ + row: { + original: { name, error, alias, loading = false }, + }, + }) => { const isDisabled = !alias if ( error?.statusCode !== ApiStatusCode.Unauthorized && diff --git a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.spec.tsx b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.spec.tsx index be8b3fe8db..1f5d5c32ac 100644 --- a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.spec.tsx +++ b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.spec.tsx @@ -1,27 +1,24 @@ -import { EuiBasicTableColumn } from '@elastic/eui' import React from 'react' import { instance, mock } from 'ts-mockito' import { ModifiedSentinelMaster } from 'uiSrc/slices/interfaces' import { cleanup, render, screen, fireEvent } from 'uiSrc/utils/test-utils' +import { ColumnDefinition } from 'uiSrc/components/base/layout/table' import SentinelDatabasesResult, { Props } from './SentinelDatabasesResult' const mockedProps = mock() let mastersMock: ModifiedSentinelMaster[] -let columnsMock: EuiBasicTableColumn[] +let columnsMock: ColumnDefinition[] beforeEach(() => { cleanup() columnsMock = [ { - field: 'name', - className: 'column_name', - name: 'Master group', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, + header: 'Master group', + id: 'name', + accessorKey: 'name', + enableSorting: true, }, ] diff --git a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.tsx b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.tsx index 6347797749..4397123607 100644 --- a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.tsx +++ b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/SentinelDatabasesResult.tsx @@ -1,9 +1,4 @@ import React, { useState, useEffect } from 'react' -import { - EuiInMemoryTable, - EuiBasicTableColumn, - PropertySort, -} from '@elastic/eui' import { useSelector } from 'react-redux' import { SearchInput } from 'uiSrc/components/base/inputs' @@ -20,11 +15,12 @@ import { import { Title } from 'uiSrc/components/base/text/Title' import { Text } from 'uiSrc/components/base/text' import { FormField } from 'uiSrc/components/base/forms/FormField' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import styles from './styles.module.scss' export interface Props { countSuccessAdded: number - columns: EuiBasicTableColumn[] + columns: ColumnDefinition[] masters: ModifiedSentinelMaster[] onBack: () => void onViewDatabases: () => void @@ -47,11 +43,6 @@ const SentinelDatabasesResult = ({ const countFailAdded = masters?.length - countSuccessAdded - const sort: PropertySort = { - field: 'message', - direction: 'asc', - } - useEffect(() => { if (masters.length) { setItems(masters) @@ -126,15 +117,20 @@ const SentinelDatabasesResult = ({
- + {!items.length || loading ? ( + {message} + ) : ( +
+ )} diff --git a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/styles.module.scss b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/styles.module.scss index 4fe1a78e2c..5223d88189 100644 --- a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/styles.module.scss +++ b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases-result/components/SentinelDatabasesResult/styles.module.scss @@ -13,16 +13,6 @@ width: 266px !important; } -.table { - @include eui.scrollBar; - overflow: auto; - max-height: calc(100vh - 320px) !important; - - @media (min-width: 1130px) { - max-height: calc(100vh - 260px) !important; - } -} - .panelCancelBtn { max-width: 350px !important; margin-left: -10px; diff --git a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.spec.tsx b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.spec.tsx index 1afe21f791..1b612ad41c 100644 --- a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.spec.tsx +++ b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.spec.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { EuiInMemoryTable } from '@elastic/eui' import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' +import { Table } from 'uiSrc/components/base/layout/table' import SentinelDatabasesPage from './SentinelDatabasesPage' import SentinelDatabases from './components' import { Props as SentinelDatabasesProps } from './components/SentinelDatabases/SentinelDatabases' @@ -50,16 +50,7 @@ const mockSentinelDatabases = (props: SentinelDatabasesProps) => ( > onSubmit -
- -
+
) diff --git a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.tsx b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.tsx index 9272b38e97..9885d4bae8 100644 --- a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.tsx +++ b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/SentinelDatabasesPage.tsx @@ -1,4 +1,4 @@ -import { EuiBasicTableColumn, EuiIcon, EuiToolTip } from '@elastic/eui' +import { EuiIcon, EuiToolTip } from '@elastic/eui' import React, { useEffect, useState } from 'react' import { map, pick } from 'lodash' import { useHistory } from 'react-router-dom' @@ -20,6 +20,7 @@ import { SentinelInputFieldType } from 'uiSrc/components/input-field-sentinel/In import { IconButton } from 'uiSrc/components/base/forms/buttons' import { CopyIcon } from 'uiSrc/components/base/icons' import { Text } from 'uiSrc/components/base/text' +import { ColumnDefinition } from 'uiSrc/components/base/layout/table' import { CreateSentinelDatabaseDto } from 'apiSrc/modules/redis-sentinel/dto/create.sentinel.database.dto' import SentinelDatabases from './components' @@ -105,25 +106,28 @@ const SentinelDatabasesPage = () => { ) } - const columns: EuiBasicTableColumn[] = [ + const columns: ColumnDefinition[] = [ { - field: 'name', - className: 'column_masterName', - name: 'Primary Group', - truncateText: true, - sortable: true, - width: '211px', - render: (name: string) => ( - {name} - ), + header: 'Primary Group', + id: 'name', + accessorKey: 'name', + enableSorting: true, + cell: ({ + row: { + original: { name }, + }, + }) => {name}, }, { - field: 'alias', - className: 'column_db_alias', - name: 'Database Alias*', - width: '285px', - sortable: true, - render: function InstanceAliasCell(_alias: string, { id, alias, name }) { + header: 'Database Alias*', + id: 'alias', + accessorKey: 'alias', + enableSorting: true, + cell: function InstanceAliasCell({ + row: { + original: { id, alias, name }, + }, + }) { return (
{ }, }, { - field: 'host', - className: 'column_address', - name: 'Address', - width: '210px', - dataType: 'auto', - truncateText: true, - sortable: ({ host, port }) => `${host}:${port}`, - render: function Address( - _host: string, - { host, port }: ModifiedSentinelMaster, - ) { + header: 'Address', + id: 'host', + accessorKey: 'host', + enableSorting: true, + cell: ({ + row: { + original: { host, port }, + }, + }) => { const text = `${host}:${port}` return (
@@ -173,22 +175,20 @@ const SentinelDatabasesPage = () => { }, }, { - field: 'numberOfSlaves', - className: 'column_numberOfSlaves', - name: '# of replicas', - dataType: 'number', - align: 'center', - sortable: true, - width: '130px', - truncateText: true, - hideForMobile: true, + header: '# of replicas', + id: 'numberOfSlaves', + accessorKey: 'numberOfSlaves', + enableSorting: true, }, { - field: 'username', - className: 'column_username', - name: 'Username', - width: '285px', - render: function UsernameCell(_username: string, { username, id }) { + header: 'Username', + id: 'username', + accessorKey: 'username', + cell: function UsernameCell({ + row: { + original: { username, id }, + }, + }) { return (
{ }, }, { - field: 'password', - className: 'column_password', - name: 'Password', - width: '285px', - render: function PasswordCell(_password: string, { password, id }) { + header: 'Password', + id: 'password', + accessorKey: 'password', + cell: function PasswordCell({ + row: { + original: { password, id }, + }, + }) { return (
{ }, }, { - field: 'db', - className: 'column_db', - width: '200px', - dataType: 'auto', - name: 'Database Index', - render: function IndexCell(_index: string, { db = 0, id }) { + header: 'Database Index', + id: 'db', + accessorKey: 'db', + cell: function IndexCell({ + row: { + original: { db = 0, id }, + }, + }) { return (
() let mastersMock: ModifiedSentinelMaster[] -let columnsMock: EuiBasicTableColumn[] +let columnsMock: ColumnDefinition[] beforeEach(() => { cleanup() columnsMock = [ { - field: 'name', - className: 'column_name', - name: 'Master group', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, + header: 'Master group', + id: 'name', + accessorKey: 'name', + enableSorting: true, }, ] diff --git a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/components/SentinelDatabases/SentinelDatabases.tsx b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/components/SentinelDatabases/SentinelDatabases.tsx index eb5c912db2..b70cbf338f 100644 --- a/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/components/SentinelDatabases/SentinelDatabases.tsx +++ b/redisinsight/ui/src/pages/autodiscover-sentinel/sentinel-databases/components/SentinelDatabases/SentinelDatabases.tsx @@ -1,13 +1,6 @@ import React, { useState, useEffect } from 'react' import cx from 'classnames' -import { - EuiInMemoryTable, - EuiBasicTableColumn, - EuiTableSelectionType, - PropertySort, - EuiPopover, - EuiToolTip, -} from '@elastic/eui' +import { EuiPopover, EuiToolTip } from '@elastic/eui' import { useSelector } from 'react-redux' import { sentinelSelector } from 'uiSrc/slices/instances/sentinel' @@ -26,10 +19,11 @@ import { SearchInput } from 'uiSrc/components/base/inputs' import { Title } from 'uiSrc/components/base/text/Title' import { Text } from 'uiSrc/components/base/text' import { FormField } from 'uiSrc/components/base/forms/FormField' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import styles from '../../../styles.module.scss' export interface Props { - columns: EuiBasicTableColumn[] + columns: ColumnDefinition[] masters: ModifiedSentinelMaster[] onClose: () => void onBack: () => void @@ -58,11 +52,6 @@ const SentinelDatabases = ({ const { loading } = useSelector(sentinelSelector) - const sort: PropertySort = { - field: 'name', - direction: 'asc', - } - const updateSelection = ( selected: ModifiedSentinelMaster[], masters: ModifiedSentinelMaster[], @@ -100,9 +89,15 @@ const SentinelDatabases = ({ return selected || emptyAliases.length !== 0 } - const selectionValue: EuiTableSelectionType = { - onSelectionChange: (selected: ModifiedSentinelMaster[]) => - setSelection(selected), + const selectionValue = { + onSelectionChange: (selected: ModifiedSentinelMaster) => + setSelection((previous) => { + const isSelected = previous.some((item) => item.id === selected.id) + if (isSelected) { + return previous.filter((item) => item.id !== selected.id) + } + return [...previous, selected] + }), } const onQueryChange = (term: string) => { @@ -118,6 +113,8 @@ const SentinelDatabases = ({ item.numberOfSlaves?.toString().includes(value), ) + console.log('+++onQueryChange', itemsTemp) + if (!itemsTemp.length) { setMessage(notFoundMsg) } @@ -228,18 +225,18 @@ const SentinelDatabases = ({
- + {!items.length && {message}} {!masters.length && ( {notMastersMsg} diff --git a/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.spec.tsx b/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.spec.tsx index 93af6344d0..de65eae4eb 100644 --- a/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.spec.tsx +++ b/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.spec.tsx @@ -1,10 +1,10 @@ import React from 'react' -import { ModifiedClusterNodes } from 'uiSrc/pages/clusterDetails/ClusterDetailsPage' import { getLetterByIndex } from 'uiSrc/utils' import { rgb } from 'uiSrc/utils/colors' import { render, screen } from 'uiSrc/utils/test-utils' import ClusterNodesTable from './ClusterNodesTable' +import { ModifiedClusterNodes } from '../../ClusterDetailsPage' const mockNodes = [ { diff --git a/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.tsx b/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.tsx index 7343e490a2..10f392ea8e 100644 --- a/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.tsx +++ b/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/ClusterNodesTable.tsx @@ -1,16 +1,11 @@ -import { - EuiBasicTableColumn, - EuiIcon, - EuiInMemoryTable, - EuiToolTip, - PropertySort, -} from '@elastic/eui' +import { EuiIcon, EuiToolTip, PropertySort } from '@elastic/eui' import { IconType } from '@elastic/eui/src/components/icon/icon' import cx from 'classnames' import { map } from 'lodash' import React, { useState } from 'react' import { LoadingContent } from 'uiSrc/components/base/layout' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { InputIconSvg, KeyIconSvg, @@ -19,11 +14,11 @@ import { UserIconSvg, MeasureIconSvg, } from 'uiSrc/components/database-overview/components/icons' -import { ModifiedClusterNodes } from 'uiSrc/pages/clusterDetails/ClusterDetailsPage' import { formatBytes, Nullable } from 'uiSrc/utils' import { rgb } from 'uiSrc/utils/colors' import { numberWithSpaces } from 'uiSrc/utils/numbers' +import { ModifiedClusterNodes } from '../../ClusterDetailsPage' import styles from './styles.module.scss' const ClusterNodesTable = ({ @@ -53,17 +48,17 @@ const ClusterNodesTable = ({
) - const columns: EuiBasicTableColumn[] = [ + const columns: ColumnDefinition[] = [ { - name: ( -
- {`${nodes?.length} Primary nodes`} -
- ), - field: 'host', - dataType: 'string', - sortable: ({ index }) => index, - render: (value: number, { letter, port, color }) => ( + header: `${nodes?.length} Primary nodes`, + id: 'host', + accessorKey: 'host', + enableSorting: true, + cell: ({ + row: { + original: { letter, port, color }, + }, + }) => ( <>
- {value}:{port} + {letter}:{port}
), }, { - name: headerIconTemplate('Commands/s', MeasureIconSvg), - field: 'opsPerSecond', - width: '12%', - sortable: true, - align: 'right', - render: (value: number) => { + header: () => headerIconTemplate('Commands/s', MeasureIconSvg), + id: 'opsPerSecond', + accessorKey: 'opsPerSecond', + enableSorting: true, + cell: ({ + row: { + original: { opsPerSecond: value }, + }, + }) => { const isMax = isMaxValue('opsPerSecond', value) return ( { + header: () => headerIconTemplate('Network Input', InputIconSvg), + id: 'networkInKbps', + accessorKey: 'networkInKbps', + enableSorting: true, + cell: ({ + row: { + original: { networkInKbps: value }, + }, + }) => { const isMax = isMaxValue('networkInKbps', value) return ( <> @@ -121,12 +122,15 @@ const ClusterNodesTable = ({ }, }, { - name: headerIconTemplate('Network Output', OutputIconSvg), - field: 'networkOutKbps', - width: '12%', - sortable: true, - align: 'right', - render: (value: number) => { + header: () => headerIconTemplate('Network Output', OutputIconSvg), + id: 'networkOutKbps', + accessorKey: 'networkOutKbps', + enableSorting: true, + cell: ({ + row: { + original: { networkOutKbps: value }, + }, + }) => { const isMax = isMaxValue('networkOutKbps', value) return ( <> @@ -142,12 +146,15 @@ const ClusterNodesTable = ({ }, }, { - name: headerIconTemplate('Total Memory', MemoryIconSvg), - field: 'usedMemory', - width: '12%', - sortable: true, - align: 'right', - render: (value: number) => { + header: () => headerIconTemplate('Total Memory', MemoryIconSvg), + id: 'usedMemory', + accessorKey: 'usedMemory', + enableSorting: true, + cell: ({ + row: { + original: { usedMemory: value }, + }, + }) => { const [number, size] = formatBytes(value, 3, true) const isMax = isMaxValue('usedMemory', value) return ( @@ -169,12 +176,15 @@ const ClusterNodesTable = ({ }, }, { - name: headerIconTemplate('Total Keys', KeyIconSvg), - field: 'totalKeys', - width: '12%', - sortable: true, - align: 'right', - render: (value: number) => { + header: () => headerIconTemplate('Total Keys', KeyIconSvg), + id: 'totalKeys', + accessorKey: 'totalKeys', + enableSorting: true, + cell: ({ + row: { + original: { totalKeys: value }, + }, + }) => { const isMax = isMaxValue('totalKeys', value) return ( (
Clients
), - field: 'connectedClients', - width: '12%', - sortable: true, - align: 'right', - render: (value: number) => { + id: 'connectedClients', + accessorKey: 'connectedClients', + enableSorting: true, + cell: ({ + row: { + original: { connectedClients: value }, + }, + }) => { const isMax = isMaxValue('connectedClients', value) return ( )} {nodes && ( -
- +
setSort(sort)} - data-testid="primary-nodes-table" + data={nodes} + defaultSorting={[ + { + id: sort.field, + desc: sort.direction === 'desc', + }, + ]} + onSortingChange={(newSort) => + setSort({ + field: newSort[0].id, + direction: newSort[0].desc ? 'desc' : 'asc', + }) + } /> )} diff --git a/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/styles.module.scss b/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/styles.module.scss index 50d13b5d9f..5a94725b9f 100644 --- a/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/styles.module.scss +++ b/redisinsight/ui/src/pages/cluster-details/components/cluser-nodes-table/styles.module.scss @@ -1,29 +1,5 @@ $breakpoint-table: 1232px; -.wrapper { - max-width: 1920px; - - .loading { - margin-top: 40px; - width: 100%; - - :global { - .euiLoadingContent__singleLine { - height: 36px; - - &:first-child { - height: 42px; - margin-bottom: 18px; - } - } - - .euiLoadingContent__singleLine:last-child:not(:only-child) { - width: 100%; - } - } - } -} - .tableWrapper { @include eui.scrollBar; @@ -32,112 +8,12 @@ $breakpoint-table: 1232px; max-height: 100%; } -.table.tableNodes { - :global { - .euiTableHeaderCell { - min-width: 144px; - background-color: var(--euiColorEmptyShade); - - @media screen and (max-width: $breakpoint-table) { - min-width: 112px; - } - - .euiTableCellContent { - min-height: 78px; - padding: 12px 12px 18px 24px; - justify-content: flex-start; - align-items: flex-end; - - @media screen and (max-width: $breakpoint-table) { - padding: 12px 6px 18px 12px; - } - - &.euiTableCellContent--alignRight { - padding-left: 12px; - padding-right: 24px; - justify-content: flex-start; - align-items: flex-end; - flex-direction: row-reverse; - - @media screen and (max-width: $breakpoint-table) { - padding-left: 6px; - padding-right: 12px; - } - - .euiTableSortIcon { - margin-right: 4px; - margin-left: 0; - } - } - - &.euiTableCellContent--alignCenter { - justify-content: center; - - .euiTableSortIcon { - margin-right: 0; - margin-left: 4px; - } - } - - .euiTableCellContent__text { - font: - normal normal normal 12px/18px Graphik, - sans-serif; - } - } - - .euiTableHeaderButton { - border-bottom: 1px solid var(--euiColorLightShade); - outline: 1px solid var(--euiColorEmptyShade); - } - } - - .euiTableCellContent { - position: relative; - padding: 12px 12px 12px 24px; - font: normal normal 500 16px/18px Inconsolata; - - @media screen and (max-width: $breakpoint-table) { - padding: 12px 6px 12px 12px; - } - - &.euiTableCellContent--alignRight { - padding-left: 12px; - padding-right: 24px; - - @media screen and (max-width: $breakpoint-table) { - padding-left: 6px; - padding-right: 12px; - } - } - } - - .euiTableSortIcon { - width: 14px; - height: 14px; - margin-bottom: 2px; - fill: var(--htmlColor) !important; - } - - .euiTableHeaderButton.euiTableHeaderButton-isSorted { - span, - div { - color: var(--htmlColor) !important; - } - } - - .euiTableHeaderButton:focus .euiTableCellContent__text { - text-decoration: none; - } - } - - :global(.euiTableCellContent) .maxValue { - color: var(--euiTooltipTitleTextColor); - font-weight: bold; - } +.wrapper { + max-width: 1920px; - :global(.euiTableHeaderButton.euiTableHeaderButton-isSorted) .headerIcon { - fill: var(--htmlColor) !important; + .loading { + margin-top: 40px; + width: 100%; } .valueUnit { diff --git a/redisinsight/ui/src/pages/database-analysis/components/top-keys/Table.tsx b/redisinsight/ui/src/pages/database-analysis/components/top-keys/Table.tsx index fc6d86bd15..aa9505cd2d 100644 --- a/redisinsight/ui/src/pages/database-analysis/components/top-keys/Table.tsx +++ b/redisinsight/ui/src/pages/database-analysis/components/top-keys/Table.tsx @@ -1,12 +1,6 @@ -import { - EuiBasicTableColumn, - EuiInMemoryTable, - EuiToolTip, - PropertySort, -} from '@elastic/eui' -import cx from 'classnames' +import { EuiToolTip } from '@elastic/eui' import { isNil } from 'lodash' -import React, { useState } from 'react' +import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory, useParams } from 'react-router-dom' @@ -45,10 +39,9 @@ import { } from 'uiSrc/utils' import { numberWithSpaces } from 'uiSrc/utils/numbers' import { TableTextBtn } from 'uiSrc/pages/database-analysis/components/base/TableTextBtn' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { Key } from 'apiSrc/modules/database-analysis/models/key' -import styles from './styles.module.scss' - export interface Props { data: Key[] defaultSortField: string @@ -56,13 +49,12 @@ export interface Props { dataTestid?: string } -const Table = (props: Props) => { - const { data, defaultSortField, delimiter = ':', dataTestid = '' } = props - const [sort, setSort] = useState({ - field: defaultSortField, - direction: 'desc', - }) - +const TopKeysTable = ({ + data = [], + defaultSortField, + delimiter = ':', + dataTestid = '', +}: Props) => { const history = useHistory() const dispatch = useDispatch() @@ -96,49 +88,41 @@ const Table = (props: Props) => { history.push(Pages.browser(instanceId)) } - const setDataTestId = ({ name }: { name: string }) => ({ - 'data-testid': `row-${name}`, - }) - - const columns: EuiBasicTableColumn[] = [ + const columns: ColumnDefinition[] = [ { - name: 'Key Type', - field: 'type', - width: '10%', - align: 'left', - sortable: true, - render: (type: string) => ( -
- -
- ), + header: 'Key Type', + id: 'type', + accessorKey: 'type', + enableSorting: true, + cell: ({ + row: { + original: { type }, + }, + }) => , }, { - name: 'Key Name', - field: 'name', - dataType: 'string', - align: 'left', - width: 'auto', - height: '42px', - sortable: true, - truncateText: true, - render: (name: string) => { - const tooltipContent = formatLongName(name) + header: 'Key Name', + id: 'name', + accessorKey: 'name', + enableSorting: true, + minSize: 200, + cell: ({ + row: { + original: { name }, + }, + }) => { + const tooltipContent = formatLongName(name as string) const cellContent = (name as string).substring(0, 200) return ( -
+
handleRedirect(name)} + onClick={() => handleRedirect(name as string)} > {cellContent} @@ -148,12 +132,15 @@ const Table = (props: Props) => { }, }, { - name: 'TTL', - field: 'ttl', - width: '14%', - sortable: true, - align: 'left', - render: (value: number, { name }) => { + header: 'TTL', + id: 'ttl', + accessorKey: 'ttl', + enableSorting: true, + cell: ({ + row: { + original: { name, ttl: value }, + }, + }) => { if (isNil(value)) { return ( { } return ( - + { } > - <>{truncateNumberToFirstUnit(value)} + + {truncateNumberToFirstUnit(value)} + ) }, }, { - name: 'Key Size', - field: 'memory', - width: '9%', - sortable: true, - align: 'right', - render: (value: number, { type }) => { + header: 'Key Size', + id: 'memory', + accessorKey: 'memory', + enableSorting: true, + cell: ({ + row: { + original: { type, memory: value }, + }, + }) => { if (isNil(value)) { return ( { {numberWithSpaces(value)} B } - anchorClassName={cx({ [styles.highlight]: isHighlight })} data-testid="usedMemory-tooltip" > - <> - - {number} - - {size} - + + {number} {size} + ) }, }, { - name: 'Length', - field: 'length', - width: '15%', - sortable: ({ length }) => length ?? -1, - align: 'right', - render: (value: number, { name, type }) => { + header: 'Length', + id: 'length', + accessorKey: 'length', + enableSorting: true, + cell: ({ + row: { + original: { name, type, length: value }, + }, + }) => { if (isNil(value)) { return ( { content={ isHighlight ? 'Consider splitting it into multiple keys' : '' } - anchorClassName={cx({ [styles.highlight]: isHighlight })} data-testid="usedMemory-tooltip" > - {numberWithSpaces(value)} - + ) }, @@ -284,27 +273,19 @@ const Table = (props: Props) => { ] return ( -
-
- setSort(sort)} - data-testid="nsp-table" - /> -
+
+
) } -export default Table +export default TopKeysTable diff --git a/redisinsight/ui/src/pages/database-analysis/components/top-keys/TopKeys.tsx b/redisinsight/ui/src/pages/database-analysis/components/top-keys/TopKeys.tsx index e43c433e2d..7e1a5e078b 100644 --- a/redisinsight/ui/src/pages/database-analysis/components/top-keys/TopKeys.tsx +++ b/redisinsight/ui/src/pages/database-analysis/components/top-keys/TopKeys.tsx @@ -8,12 +8,10 @@ import { Title } from 'uiSrc/components/base/text/Title' import { DatabaseAnalysis } from 'apiSrc/modules/database-analysis/models' import Table from './Table' -import styles from './styles.module.scss' export interface Props { data: Nullable loading: boolean - delimiter?: string } const MAX_TOP_KEYS = 15 @@ -30,7 +28,7 @@ const TopKeys = ({ data, loading }: Props) => { } return ( -
+
{topKeysLength.length < MAX_TOP_KEYS && diff --git a/redisinsight/ui/src/pages/database-analysis/components/top-keys/styles.module.scss b/redisinsight/ui/src/pages/database-analysis/components/top-keys/styles.module.scss deleted file mode 100644 index 8e5df1cb88..0000000000 --- a/redisinsight/ui/src/pages/database-analysis/components/top-keys/styles.module.scss +++ /dev/null @@ -1,235 +0,0 @@ -.wrapper .textBtn { - border: none; - min-width: auto !important; - min-height: auto; - border-radius: 4px; - background: transparent !important; - margin-left: 18px; - box-shadow: none !important; - - &:global(.euiButton.euiButton--secondary) { - color: var(--wbHoverIconColor) !important; - - &:hover, - &:focus { - color: var(--wbHoverIconColor) !important; - } - } - - :global(.euiButton__content) { - padding: 6px; - } - - :global(.euiButton__text) { - font: - normal normal normal 13px/17px Graphik, - sans-serif !important; - } - - &.activeBtn:global(.euiButton.euiButton-isDisabled.euiButton--fill.euiButton--secondary), - :global(.euiButton.euiButton-isDisabled.euiButton--fill.euiButton--secondary) { - background: var(--browserComponentActive) !important; - color: var(--wbActiveIconColor) !important; - opacity: 1 !important; - } -} - -.tableWrapper { - @include eui.scrollBar; - - overflow: auto; - position: relative; - max-height: 100%; - - :global(.euiTableRow-isExpandedRow .euiTableCellContent) { - padding: 0 12px !important; - } -} - -.table { - &:hover, - &:focus { - :global(.euiButton__text) { - color: var(--wbHoverIconColor) !important; - } - } - &:global(.fixedLayout) table { - table-layout: fixed; - } - - :global { - .euiTableRowCell { - border-color: transparent !important; - } - - .euiTableRowCell:first-child { - padding-left: 12px; - } - - .euiTableRowCell:last-child { - padding-right: 12px; - } - - .euiTableHeaderCell:first-child .euiTableCellContent { - padding-left: 24px; - } - - .euiTableHeaderCell:last-child .euiTableCellContent { - padding-right: 24px; - } - - .euiTableHeaderCell { - min-width: 42px; - background-color: var(--euiColorEmptyShade); - - .euiTableCellContent { - padding: 12px; - justify-content: flex-start; - align-items: flex-end; - - &.euiTableCellContent--alignRight { - justify-content: flex-start; - align-items: flex-end; - flex-direction: row-reverse; - - .euiTableSortIcon { - margin-right: 4px; - margin-left: 0; - } - } - - &.euiTableCellContent--alignCenter { - justify-content: center; - - .euiTableSortIcon { - margin-right: 0; - margin-left: 4px; - } - } - - .euiTableCellContent__text { - font: - normal normal normal 12px/17px Graphik, - sans-serif; - } - } - - .euiTableCellContent { - border-bottom: 1px solid var(--euiColorLightShade); - outline: 1px solid var(--euiColorEmptyShade); - } - } - - .euiTableRowCell .euiTableCellContent { - padding: 10px; - font: - normal normal normal 14px/17px Graphik, - sans-serif; - } - - .euiTableHeaderButton.euiTableHeaderButton-isSorted { - span, - div { - color: var(--htmlColor) !important; - } - } - - .euiTableHeaderButton:focus .euiTableCellContent__text { - text-decoration: none; - } - } - - .valueUnit { - font: - normal normal normal 12px/17px Graphik, - sans-serif !important; - margin-left: 4px; - color: var(--euiColorMediumShade) !important; - } - - .headerCell { - display: flex; - flex-direction: column; - align-items: flex-end; - justify-content: flex-end; - } -} - -.rightAlign { - justify-content: flex-end; - 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); - font: - normal normal normal 13px/17px Graphik, - sans-serif; - - &:hover { - text-decoration: underline; - } -} - -:global(.euiTableCellContent) .count { - color: var(--euiTextSubduedColor); - font: normal normal 500 16px/17px Inconsolata; -} - -.badgesContainer { - display: flex; - flex-wrap: wrap; - - :global(.euiBadge) span { - font: - normal normal normal 12px/16px Graphik, - sans-serif; - padding-top: 0 !important; - } -} - -.wrapper .link { - width: max-content; - - &:hover, - &:focus { - background-color: transparent !important; - } - - :global(.euiButtonEmpty__content) { - padding: 0; - width: max-content; - - &:hover, - &:focus { - text-decoration: underline; - } - } - - :global(.euiButtonEmpty__content.euiButtonContent .euiButtonEmpty__text) { - font: - normal normal normal 13px/17px Graphik, - sans-serif; - color: var(--buttonSecondaryTextColor) !important; - } - - :global(.euiButtonEmpty__content.euiButtonContent .euiButtonEmpty__text):hover, - :global(.euiButtonEmpty__content.euiButtonContent .euiButtonEmpty__text):focus { - color: var(--euiTextSubduedColorHover); - } -} diff --git a/redisinsight/ui/src/pages/database-analysis/components/top-namespace/Table.tsx b/redisinsight/ui/src/pages/database-analysis/components/top-namespace/Table.tsx index d326c2aea8..761610eccd 100644 --- a/redisinsight/ui/src/pages/database-analysis/components/top-namespace/Table.tsx +++ b/redisinsight/ui/src/pages/database-analysis/components/top-namespace/Table.tsx @@ -1,14 +1,7 @@ -import { forIn } from 'lodash' -import React, { useEffect, useState } from 'react' -import { - EuiBasicTableColumn, - EuiInMemoryTable, - EuiToolTip, - PropertySort, -} from '@elastic/eui' +import React from 'react' +import { EuiToolTip } from '@elastic/eui' import { useHistory, useParams } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' -import cx from 'classnames' import { extrapolate, @@ -39,10 +32,9 @@ import { setBrowserTreeDelimiter, } from 'uiSrc/slices/app/context' import { TableTextBtn } from 'uiSrc/pages/database-analysis/components/base/TableTextBtn' -import { IconButton } from 'uiSrc/components/base/forms/buttons' -import { ChevronDownIcon, ChevronUpIcon } from 'uiSrc/components/base/icons' -import { NspSummary } from 'apiSrc/modules/database-analysis/models/nsp-summary' -import { NspTypeSummary } from 'apiSrc/modules/database-analysis/models/nsp-type-summary' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' +import { ColorText } from 'uiSrc/components/base/text' +import { NspSummary } from 'apiSrc/modules/database-analysis/models' import styles from './styles.module.scss' @@ -55,21 +47,14 @@ export interface Props { dataTestid?: string } -const NameSpacesTable = (props: Props) => { - const { - data, - defaultSortField, - delimiter, - isExtrapolated, - extrapolation, - dataTestid = '', - } = props - const [sort, setSort] = useState<PropertySort>({ - field: defaultSortField, - direction: 'desc', - }) - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<any>({}) - +const NameSpacesTable = ({ + data = [], + defaultSortField, + delimiter, + isExtrapolated, + extrapolation, + dataTestid = '', +}: Props) => { const history = useHistory() const dispatch = useDispatch() @@ -77,22 +62,7 @@ const NameSpacesTable = (props: Props) => { const { viewType } = useSelector(keysSelector) - useEffect(() => { - setItemIdToExpandedRowMap((prev: any) => { - const items: any = {} - forIn(prev, (_val, nsp: string) => { - const item = data?.find((d) => d.nsp === nsp) - - if (item) { - items[nsp] = expandedRow(item) - } - }) - - return items - }) - }, [isExtrapolated]) - - const handleRedirect = (nsp: string, filter: string) => { + const handleRedirect = (nsp: string, filter: string | null) => { dispatch(changeSearchMode(SearchMode.Pattern)) dispatch(setBrowserTreeDelimiter([{ label: delimiter }])) dispatch(setFilter(filter)) @@ -116,24 +86,8 @@ const NameSpacesTable = (props: Props) => { history.push(Pages.browser(instanceId)) } - const toggleDetails = (item: NspSummary) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap } - const nsp = item.nsp as string - - if (itemIdToExpandedRowMapValues[nsp]) { - delete itemIdToExpandedRowMapValues[nsp] - } else { - itemIdToExpandedRowMapValues[nsp] = expandedRow(item) - } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues) - } - - const setDataTestId = ({ nsp }: { nsp: string }) => ({ - 'data-testid': `row-${nsp}`, - }) - const expandedRow = (item: NspSummary) => ( - <div style={{ width: '100%' }}> + <div> {item.types.map((type, index) => { const extrapolated = extrapolate(type.memory, { apply: isExtrapolated, @@ -152,7 +106,6 @@ const NameSpacesTable = (props: Props) => { <div className="truncateText"> <EuiToolTip title="Key Pattern" - anchorClassName={styles.tooltip} position="bottom" content={`${item.nsp}:*`} > @@ -164,21 +117,22 @@ const NameSpacesTable = (props: Props) => { </TableTextBtn> </EuiToolTip> </div> - <div className={styles.badgesContainer}> + <div> <GroupBadge type={type.type} /> </div> - <div className={styles.rightAlign}> - <span className={styles.count} data-testid="usedMemory-value"> - {formatNumber} - </span> - <span className={styles.valueUnit}>{size}</span> + <div> + <ColorText color="subdued" data-testid="usedMemory-value"> + {formatNumber} {size} + </ColorText> </div> - <div className={styles.rightAlign}> - {extrapolate( - type.keys, - { extrapolation, apply: isExtrapolated }, - (val: number) => numberWithSpaces(Math.round(val)), - )} + <div> + <ColorText color="subdued"> + {extrapolate( + type.keys, + { extrapolation, apply: isExtrapolated }, + (val: number) => numberWithSpaces(Math.round(val)), + )} + </ColorText> </div> </div> ) @@ -186,31 +140,31 @@ const NameSpacesTable = (props: Props) => { </div> ) - const columns: EuiBasicTableColumn<NspSummary>[] = [ + const columns: ColumnDefinition<NspSummary>[] = [ { - name: 'Key Pattern', - field: 'nsp', - dataType: 'string', - height: '42px', - align: 'left', - width: 'auto', - sortable: true, - truncateText: true, - className: 'nsp-cell', - render: (nsp: string, { types }: { types: any[] }) => { + header: 'Key Pattern', + id: 'nsp', + accessorKey: 'nsp', + enableSorting: true, + cell: ({ + row: { + original: { nsp, types }, + }, + }) => { const filterType = types.length > 1 ? null : types[0].type const textWithDelimiter = `${nsp}${delimiter}*` const cellContent = textWithDelimiter?.substring(0, 200) const tooltipContent = formatLongName(textWithDelimiter) return ( - <div className={cx(styles.delimiter, 'truncateText')}> + <div className="truncateText"> <EuiToolTip title="Key Pattern" - anchorClassName={styles.tooltip} position="bottom" content={tooltipContent} > - <TableTextBtn onClick={() => handleRedirect(nsp, filterType)}> + <TableTextBtn + onClick={() => handleRedirect(nsp as string, filterType)} + > {cellContent} </TableTextBtn> </EuiToolTip> @@ -219,26 +173,31 @@ const NameSpacesTable = (props: Props) => { }, }, { - name: 'Data Type', - field: 'types', - width: '32%', - align: 'left', - className: 'dataType', - render: (value: NspTypeSummary[]) => ( - <div className={styles.badgesContainer}> + header: 'Data Type', + id: 'types', + accessorKey: 'types', + cell: ({ + row: { + original: { types: value }, + }, + }) => ( + <div> {value.map(({ type }) => ( - <GroupBadge key={type} type={type} className={styles.badge} /> + <GroupBadge key={type} type={type} /> ))} </div> ), }, { - name: 'Total Memory', - field: 'memory', - width: '13%', - sortable: true, - align: 'right', - render: (value: number) => { + header: 'Total Memory', + id: 'memory', + accessorKey: 'memory', + enableSorting: true, + cell: ({ + row: { + original: { memory: value }, + }, + }) => { const extrapolated = extrapolate(value, { apply: isExtrapolated, extrapolation, @@ -257,89 +216,61 @@ const NameSpacesTable = (props: Props) => { content={`${formatValueBytes} B`} data-testid="usedMemory-tooltip" > - <> - <span - className={styles.count} - data-testid={`nsp-usedMemory-value=${value}`} - > - {formatValue} - </span> - <span className={styles.valueUnit}>{size}</span> - </> + <ColorText + color="subdued" + data-testid={`nsp-usedMemory-value=${value}`} + > + {formatValue} {size} + </ColorText> </EuiToolTip> ) }, }, { - name: 'Total Keys', - field: 'keys', - width: '11%', - sortable: true, - align: 'right', - render: (value: number) => ( - <span className={styles.count} data-testid={`keys-value-${value}`}> - {extrapolate( - value, - { extrapolation, apply: isExtrapolated }, - (val: number) => numberWithSpaces(Math.round(val)), - )} + header: 'Total Keys', + id: 'keys', + accessorKey: 'keys', + enableSorting: true, + cell: ({ + row: { + original: { keys: value }, + }, + }) => ( + <span data-testid={`keys-value-${value}`}> + <ColorText color="subdued"> + {extrapolate( + value, + { extrapolation, apply: isExtrapolated }, + (val: number) => numberWithSpaces(Math.round(val)), + )} + </ColorText> </span> ), }, { - name: '\u00A0', - width: '42px', - className: 'expandBtn', - isExpander: true, - render: (item: NspSummary) => { - const { types, nsp } = item - return ( - <> - {types.length > 1 && ( - <IconButton - size="S" - style={{ marginRight: '6px' }} - onClick={() => toggleDetails(item)} - aria-label={ - itemIdToExpandedRowMap[nsp as string] ? 'Collapse' : 'Expand' - } - icon={ - itemIdToExpandedRowMap[nsp as string] - ? ChevronUpIcon - : ChevronDownIcon - } - data-testid={`expand-arrow-${nsp}`} - /> - )} - </> - ) - }, + id: 'expand', + header: () => null, + size: 40, + cell: ({ row }) => <Table.ExpandRowButton row={row} />, }, ] return ( - <div className={styles.wrapper} data-testid={dataTestid}> - <div className={styles.tableWrapper}> - <EuiInMemoryTable - items={data ?? []} - columns={columns} - className={cx( - 'inMemoryTableDefault', - 'noHeaderBorders', - 'stickyHeader', - 'fixedLayout', - styles.table, - )} - responsive={false} - itemId="nsp" - itemIdToExpandedRowMap={itemIdToExpandedRowMap} - isExpandable - rowProps={setDataTestId} - sorting={{ sort }} - onTableChange={({ sort }: any) => setSort(sort)} - data-testid="nsp-table" - /> - </div> + <div data-testid={dataTestid}> + <Table + columns={columns} + data={data ?? []} + defaultSorting={[ + { + id: defaultSortField, + desc: true, + }, + ]} + stripedRows + expandRowOnClick + getIsRowExpandable={(row) => row.types.length > 1} + renderExpandedRow={({ original }) => expandedRow(original)} + /> </div> ) } diff --git a/redisinsight/ui/src/pages/database-analysis/components/top-namespace/TopNamespace.tsx b/redisinsight/ui/src/pages/database-analysis/components/top-namespace/TopNamespace.tsx index 0fc143e25b..1714438ac6 100644 --- a/redisinsight/ui/src/pages/database-analysis/components/top-namespace/TopNamespace.tsx +++ b/redisinsight/ui/src/pages/database-analysis/components/top-namespace/TopNamespace.tsx @@ -1,5 +1,4 @@ import { isNull } from 'lodash' -import cx from 'classnames' import React, { useEffect, useState } from 'react' import { useDispatch } from 'react-redux' import { useHistory, useParams } from 'react-router-dom' @@ -64,10 +63,7 @@ const TopNamespace = (props: Props) => { if (!data?.topMemoryNsp?.length && !data?.topKeysNsp?.length) { return ( - <div - className={cx('section', styles.wrapper)} - data-testid="top-namespaces-empty" - > + <div className="section" data-testid="top-namespaces-empty"> <div className="section-title-wrapper"> <Title size="M" className="section-title"> TOP NAMESPACES @@ -94,7 +90,7 @@ const TopNamespace = (props: Props) => { } return ( - <div className={cx('section', styles.wrapper)} data-testid="top-namespaces"> + <div className="section" data-testid="top-namespaces"> <div className="section-title-wrapper"> <Title size="M" className="section-title"> TOP NAMESPACES diff --git a/redisinsight/ui/src/pages/database-analysis/components/top-namespace/styles.module.scss b/redisinsight/ui/src/pages/database-analysis/components/top-namespace/styles.module.scss index 0730959af1..d89e5e9adf 100644 --- a/redisinsight/ui/src/pages/database-analysis/components/top-namespace/styles.module.scss +++ b/redisinsight/ui/src/pages/database-analysis/components/top-namespace/styles.module.scss @@ -1,40 +1,4 @@ -.wrapper { - .textBtn { - border: none; - min-width: auto !important; - min-height: auto; - border-radius: 4px; - background: transparent !important; - margin-left: 18px; - box-shadow: none !important; - - &:global(.euiButton.euiButton--secondary) { - color: var(--wbHoverIconColor) !important; - - &:hover, - &:focus { - color: var(--wbHoverIconColor) !important; - } - } - - :global(.euiButton__content) { - padding: 6px; - } - - :global(.euiButton__text) { - font: - normal normal normal 13px/17px Graphik, - sans-serif !important; - } - - &.activeBtn:global(.euiButton.euiButton-isDisabled.euiButton--fill.euiButton--secondary), - :global(.euiButton.euiButton-isDisabled.euiButton--fill.euiButton--secondary) { - background: var(--browserComponentActive) !important; - color: var(--wbActiveIconColor) !important; - opacity: 1 !important; - } - } - +:global(.section-content) { .noNamespaceMsg { display: flex; align-items: center; @@ -60,247 +24,13 @@ } } -.wrapper { - .wrapper .link { - width: max-content; - - &:hover, - &:focus { - background-color: transparent !important; - } - - :global(.euiButtonEmpty__content) { - padding: 0; - width: max-content; - - &:hover, - &:focus { - text-decoration: underline; - } - } - - :global(.euiButtonEmpty__content.euiButtonContent .euiButtonEmpty__text) { - font: - normal normal normal 13px/17px Graphik, - sans-serif; - color: var(--buttonSecondaryTextColor) !important; - } - - :global(.euiButtonEmpty__content.euiButtonContent .euiButtonEmpty__text):hover, - :global(.euiButtonEmpty__content.euiButtonContent .euiButtonEmpty__text):focus { - color: var(--euiTextSubduedColorHover); - } - - &.expanded { - padding: 0 20px 0 12px; - } - - &.expanded :global(.euiButtonEmpty__content.euiButtonContent .euiButtonEmpty__text) { - color: var(--euiTextSubduedColor) !important; - - &:hover, - &:focus { - color: var(--euiTextSubduedColorHover) !important; - } - } - } -} - -.tableWrapper { - @include eui.scrollBar; - - overflow: auto; - position: relative; - max-height: 100%; - overflow-y: hidden; - - :global(.euiTableRow-isExpandedRow .euiTableCellContent) { - padding: 0 12px !important; - } -} - -.wrapper .table { - &:global(.fixedLayout) table { - table-layout: fixed; - } - - :global { - .euiTableRowCell { - border-color: transparent !important; - } - - .nsp-cell { - height: 42px; - } - - .euiTableRowCell:first-child { - padding-left: 12px; - } - - .euiTableRowCell:last-child { - padding-right: 12px; - } - - .euiTableHeaderCell:first-child .euiTableCellContent { - padding-left: 24px; - } - - .euiTableHeaderCell:last-child .euiTableCellContent { - padding-right: 24px; - } - - .euiTableHeaderCell { - min-width: 42px; - background-color: var(--euiColorEmptyShade); - - .euiTableCellContent { - padding: 12px; - justify-content: flex-start; - align-items: flex-end; - - &.euiTableCellContent--alignRight { - justify-content: flex-start; - align-items: flex-end; - flex-direction: row-reverse; - - .euiTableSortIcon { - margin-right: 4px; - margin-left: 0; - } - } - - &.euiTableCellContent--alignCenter { - justify-content: center; - - .euiTableSortIcon { - margin-right: 0; - margin-left: 4px; - } - } - - .euiTableCellContent__text { - font: - normal normal normal 12px/17px Graphik, - sans-serif; - } - } - - .euiTableCellContent { - border-bottom: 1px solid var(--euiColorLightShade); - outline: 1px solid var(--euiColorEmptyShade); - } - } - - .expandBtn.euiTableCellContent { - padding: 0; - } - - .euiTableCellContent { - padding: 10px; - font: - normal normal normal 14px/24px Graphik, - sans-serif; - - &:global(.dataType) { - padding: 10px 6px 2px; - } - } - - .euiTableHeaderButton.euiTableHeaderButton-isSorted { - span, - div { - color: var(--htmlColor) !important; - } - } - - .euiTableHeaderButton:focus .euiTableCellContent__text { - text-decoration: none; - } - } - - .valueUnit { - font: - normal normal normal 12px/17px Graphik, - sans-serif !important; - margin-left: 4px; - color: var(--euiColorMediumShade) !important; - } - - .headerCell { - display: flex; - flex-direction: column; - align-items: flex-end; - justify-content: flex-end; - } -} - .expanded { - display: grid; - grid-template-columns: auto calc(32%) calc(13% + 10px) calc(11% + 22px); - grid-column-gap: 0; + display: flex; height: 42px; - width: 100%; - & div { + > div { display: flex; align-items: center; + flex: 1; } - - .badge { - margin: 0 !important; - } - - .rightAlign { - padding: 0 12px; - - &:last-child { - padding: 0 26px 0 12px; - } - } -} - -.rightAlign { - justify-content: flex-end; - padding: 12px; -} - -:global(.euiTableCellContent) .delimiter span { - cursor: pointer; - color: var(--buttonSecondaryTextColor); - font: - normal normal normal 13px/17px Graphik, - sans-serif; - - &:hover { - text-decoration: underline; - } -} - -:global(.euiTableCellContent) .count { - color: var(--euiTextSubduedColor); - font: normal normal 500 16px/17px Inconsolata; -} - -.badgesContainer { - display: flex; - flex-wrap: wrap; - - & .badge { - margin: 0 7px 8px; - } - - & .badge + .badge { - margin-left: 6px !important; - } - - :global(.euiBadge) span { - font: - normal normal normal 12px/16px Graphik, - sans-serif; - padding-top: 0 !important; - } -} - -.tooltip { - width: auto; } diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.spec.tsx b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.spec.tsx index 9dbea04f18..8e44ecb079 100644 --- a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.spec.tsx @@ -10,9 +10,9 @@ describe('TableResult', () => { }) it('should not render table for empty data', () => { - render(<TableResult data={[]} />) + const { container } = render(<TableResult data={[]} />) - expect(screen.queryByTestId('result-log-table')).not.toBeInTheDocument() + expect(container.childNodes.length).toBe(0) }) it('should render table data with success messages', () => { diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx index 7aa3a63797..75cdb013d0 100644 --- a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx +++ b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/TableResult.tsx @@ -1,9 +1,7 @@ -import { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui' -import cx from 'classnames' import React from 'react' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { ErrorImportResult } from 'uiSrc/slices/interfaces' -import { Maybe } from 'uiSrc/utils' import styles from './styles.module.scss' @@ -29,31 +27,40 @@ const TableResult = (props: Props) => { </ul> ) - const columns: EuiBasicTableColumn<any>[] = [ + const columns: ColumnDefinition<DataImportResult>[] = [ { - name: '#', - field: 'index', - width: '4%', - render: (index: number) => ( - <span data-testid={`table-index-${index}`}>({index})</span> - ), + header: '#', + id: 'index', + accessorKey: 'index', + cell: ({ + row: { + original: { index }, + }, + }) => <span data-testid={`table-index-${index}`}>({index})</span>, }, { - name: 'Host:Port', - field: 'host', - width: '25%', - truncateText: true, - render: (_host, { host, port, index }) => ( + header: 'Host:Port', + id: 'host', + accessorKey: 'host', + cell: ({ + row: { + original: { host, port, index }, + }, + }) => ( <div data-testid={`table-host-port-${index}`}> {host}:{port} </div> ), }, { - name: 'Result', - field: 'errors', - width: '25%', - render: (errors: Maybe<ErrorImportResult[]>, { index }) => ( + header: 'Result', + id: 'errors', + accessorKey: 'errors', + cell: ({ + row: { + original: { errors, index }, + }, + }) => ( <div data-testid={`table-result-${index}`}> {errors ? ( <ErrorResult errors={errors.map((e) => e.message)} /> @@ -69,19 +76,7 @@ const TableResult = (props: Props) => { return ( <div className={styles.tableWrapper}> - <EuiInMemoryTable - items={data ?? []} - columns={columns} - className={cx( - 'inMemoryTableDefault', - 'noBorders', - 'stickyHeader', - styles.table, - )} - responsive={false} - itemId="index" - data-testid="result-log-table" - /> + <Table columns={columns} data={data} defaultSorting={[]} /> </div> ) } diff --git a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/styles.module.scss b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/styles.module.scss index e7d431e138..cf242b91db 100644 --- a/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/styles.module.scss +++ b/redisinsight/ui/src/pages/home/components/import-database/components/TableResult/styles.module.scss @@ -1,32 +1,5 @@ .tableWrapper { max-height: 200px; @include eui.scrollBar; - overflow: auto; - - .table { - :global { - .euiTableHeaderCell { - background-color: var(--browserTableRowEven); - } - - .euiTableRowCell { - vertical-align: top !important; - } - - .euiTableCellContent { - white-space: normal !important; - font-size: 12px !important; - padding: 8px 14px; - } - } - } - - :global(.inMemoryTableDefault.noBorders) { - &.table { - :global(.euiTableHeaderCell:last-child) { - border-right: 1px solid var(--euiColorLightShade) !important; - } - } - } } diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/TestConnectionsTable.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/TestConnectionsTable.tsx index 8e26bfec07..a045a4282a 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/TestConnectionsTable.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/TestConnectionsTable.tsx @@ -1,8 +1,6 @@ -import { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui' -import cx from 'classnames' import React from 'react' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' -import { Maybe } from 'uiSrc/utils' import { IRdiConnectionResult } from 'uiSrc/slices/interfaces' import styles from './styles.module.scss' @@ -14,22 +12,26 @@ export interface Props { const TestConnectionsTable = (props: Props) => { const { data } = props - const columns: EuiBasicTableColumn<IRdiConnectionResult>[] = [ + const columns: ColumnDefinition<IRdiConnectionResult>[] = [ { - name: 'Name', - field: 'target', - width: '50%', - truncateText: true, - render: (target: string) => ( - <div data-testid={`table-target-${target}`}>{target}</div> - ), + header: 'Name', + id: 'target', + accessorKey: 'target', + cell: ({ + row: { + original: { target }, + }, + }) => <div data-testid={`table-target-${target}`}>{target}</div>, }, { - name: 'Result', - field: 'error', - width: '50%', - truncateText: true, - render: (error: Maybe<string>, { target }) => ( + header: 'Result', + id: 'error', + accessorKey: 'error', + cell: ({ + row: { + original: { target, error }, + }, + }) => ( <div data-testid={`table-result-${target}`}> {error || 'Successful'} </div> @@ -41,17 +43,10 @@ const TestConnectionsTable = (props: Props) => { return ( <div className={styles.tableWrapper}> - <EuiInMemoryTable - items={data ?? []} + <Table columns={columns} - className={cx( - 'inMemoryTableDefault', - 'noBorders', - 'stickyHeader', - styles.table, - )} - responsive={false} - itemId="index" + data={data ?? []} + defaultSorting={[]} data-testid="connections-log-table" /> </div> diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/styles.module.scss b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/styles.module.scss index aa82401e98..4cf0a0df4e 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/styles.module.scss +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-table/styles.module.scss @@ -2,22 +2,4 @@ max-height: calc(100vh - 282px); @include eui.scrollBar; overflow: auto; - - .table { - :global { - .euiTableHeaderCell { - background-color: var(--browserTableRowEven); - } - - .euiTableRowCell { - vertical-align: top !important; - } - - .euiTableCellContent { - white-space: normal !important; - font-size: 12px !important; - padding: 8px 14px; - } - } - } } diff --git a/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.spec.tsx b/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.spec.tsx index 1d41d48ec3..b44d016c4d 100644 --- a/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.spec.tsx +++ b/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.spec.tsx @@ -32,9 +32,6 @@ describe('Clients', () => { it('renders the clients table', () => { render(<Clients {...mockedProps} />) - // Assert that the table is rendered - expect(screen.getByTestId('clients-table')).toBeInTheDocument() - // Assert that the table columns are rendered const columnHeaders = screen.getAllByRole('columnheader') expect(columnHeaders).toHaveLength(6) // 6 columns diff --git a/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.tsx b/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.tsx index dbf2affeff..b048e85db8 100644 --- a/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.tsx +++ b/redisinsight/ui/src/pages/rdi/statistics/clients/Clients.tsx @@ -1,10 +1,9 @@ -import { EuiBasicTableColumn } from '@elastic/eui' import React from 'react' import { IClients } from 'uiSrc/slices/interfaces' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import Accordion from '../components/accordion' import Panel from '../components/panel' -import Table from '../components/table' type ClientsData = { id: string @@ -15,36 +14,42 @@ type ClientsData = { user: string } -const columns: EuiBasicTableColumn<ClientsData>[] = [ +const columns: ColumnDefinition<ClientsData>[] = [ { - name: 'ID', - field: 'id', - sortable: true, + header: 'ID', + id: 'id', + accessorKey: 'id', + enableSorting: true, }, { - name: 'ADDR', - field: 'addr', - sortable: true, + header: 'ADDR', + id: 'addr', + accessorKey: 'addr', + enableSorting: true, }, { - name: 'Age', - field: 'ageSec', - sortable: true, + header: 'Age', + id: 'ageSec', + accessorKey: 'ageSec', + enableSorting: true, }, { - name: 'Name', - field: 'name', - sortable: true, + header: 'Name', + id: 'name', + accessorKey: 'name', + enableSorting: true, }, { - name: 'Idle', - field: 'idleSec', - sortable: true, + header: 'Idle', + id: 'idleSec', + accessorKey: 'idleSec', + enableSorting: true, }, { - name: 'User', - field: 'user', - sortable: true, + header: 'User', + id: 'user', + accessorKey: 'user', + enableSorting: true, }, ] @@ -63,7 +68,7 @@ const Clients = ({ onRefreshClicked, onChangeAutoRefresh, }: Props) => { - const clients = Object.keys(data).map((key) => { + const clients: ClientsData[] = Object.keys(data).map((key) => { const client = data[key] return { id: key, @@ -82,11 +87,10 @@ const Clients = ({ onRefreshClicked={onRefreshClicked} onChangeAutoRefresh={onChangeAutoRefresh} > - <Table<ClientsData> - id="clients" + <Table columns={columns} - items={clients} - initialSortField="id" + data={clients} + defaultSorting={[{ id: 'id', desc: false }]} /> </Accordion> </Panel> diff --git a/redisinsight/ui/src/pages/rdi/statistics/components/table/Table.spec.tsx b/redisinsight/ui/src/pages/rdi/statistics/components/table/Table.spec.tsx deleted file mode 100644 index 268478ab84..0000000000 --- a/redisinsight/ui/src/pages/rdi/statistics/components/table/Table.spec.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' - -import { render } from 'uiSrc/utils/test-utils' -import Table from './Table' - -describe('Table', () => { - const columns = [ - { field: 'name', name: 'Name' }, - { field: 'age', name: 'Age' }, - ] - - const items = [ - { name: 'John Doe', age: 25 }, - { name: 'Jane Smith', age: 30 }, - ] - - it('should render', () => { - expect( - render( - <Table - id="test-table" - columns={columns} - items={items} - initialSortField="name" - />, - ), - ).toBeTruthy() - }) -}) diff --git a/redisinsight/ui/src/pages/rdi/statistics/components/table/Table.tsx b/redisinsight/ui/src/pages/rdi/statistics/components/table/Table.tsx deleted file mode 100644 index 0993fc0684..0000000000 --- a/redisinsight/ui/src/pages/rdi/statistics/components/table/Table.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - Criteria, - EuiBasicTableColumn, - EuiInMemoryTable, - PropertySort, -} from '@elastic/eui' -import React, { useState } from 'react' - -import styles from './styles.module.scss' - -export interface Props<T> { - id: string - columns: EuiBasicTableColumn<T>[] - items: T[] - initialSortField: string -} - -const Table = <T,>({ id, columns, items, initialSortField }: Props<T>) => { - const [sort, setSort] = useState<PropertySort>({ - field: initialSortField, - direction: 'asc', - }) - - return ( - <EuiInMemoryTable - data-testid={`${id}-table`} - className={styles.table} - items={items} - columns={columns} - sorting={{ sort }} - onTableChange={({ sort }: Criteria<T>) => { - setSort(sort as PropertySort) - }} - /> - ) -} - -export default Table diff --git a/redisinsight/ui/src/pages/rdi/statistics/components/table/index.ts b/redisinsight/ui/src/pages/rdi/statistics/components/table/index.ts deleted file mode 100644 index f0f7164d0c..0000000000 --- a/redisinsight/ui/src/pages/rdi/statistics/components/table/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Table from './Table' - -export default Table diff --git a/redisinsight/ui/src/pages/rdi/statistics/components/table/styles.module.scss b/redisinsight/ui/src/pages/rdi/statistics/components/table/styles.module.scss deleted file mode 100644 index 46534b16be..0000000000 --- a/redisinsight/ui/src/pages/rdi/statistics/components/table/styles.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -.table { - thead, tfoot { - background-color: var(--rdiSecondaryBgColor); - th { - padding: 10px 0; - } - } - - :global { - .euiTableFooterCell { - background-color: inherit !important; - color: inherit !important; - } - .euiTableRow { - &:nth-child(even) { - background-color: var(--browserTableRowEven); - } - - &:nth-child(odd) { - &:hover { - background-color: transparent; - } - } - } - } -} diff --git a/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.spec.tsx b/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.spec.tsx index 78e679e0f4..cebd5f8537 100644 --- a/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.spec.tsx +++ b/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.spec.tsx @@ -50,9 +50,6 @@ describe('DataStreams', () => { it('renders the data streams table', () => { render(<DataStreams {...mockedProps} />) - // Assert that the table is rendered - expect(screen.getByTestId('data-streams-table')).toBeInTheDocument() - // Assert that the table columns are rendered const columnHeaders = screen.getAllByRole('columnheader') expect(columnHeaders).toHaveLength(10) // 10 columns diff --git a/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.tsx b/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.tsx index ded96d10b9..d64c3ec0c8 100644 --- a/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.tsx +++ b/redisinsight/ui/src/pages/rdi/statistics/data-streams/DataStreams.tsx @@ -1,12 +1,12 @@ -import { EuiTableFieldDataColumnType, EuiToolTip } from '@elastic/eui' import React from 'react' +import { EuiToolTip } from '@elastic/eui' import { IDataStreams } from 'uiSrc/slices/interfaces' import { formatLongName } from 'uiSrc/utils' import { FormatedDate } from 'uiSrc/components' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import Accordion from '../components/accordion' import Panel from '../components/panel' -import Table from '../components/table' type DataStreamsData = { name: string @@ -29,6 +29,75 @@ interface Props { onChangeAutoRefresh: (enableAutoRefresh: boolean, refreshRate: string) => void } +const columns: ColumnDefinition<DataStreamsData>[] = [ + { + header: 'Stream name', + id: 'name', + accessorKey: 'name', + enableSorting: true, + cell: ({ getValue }) => ( + <EuiToolTip content={getValue<string>()}> + <span>{formatLongName(getValue<string>(), 30, 0, '...')}</span> + </EuiToolTip> + ), + }, + { + header: 'Total', + id: 'total', + accessorKey: 'total', + enableSorting: true, + }, + { + header: 'Pending', + id: 'pending', + accessorKey: 'pending', + enableSorting: true, + }, + { + header: 'Inserted', + id: 'inserted', + accessorKey: 'inserted', + enableSorting: true, + }, + { + header: 'Updated', + id: 'updated', + accessorKey: 'updated', + enableSorting: true, + }, + { + header: 'Deleted', + id: 'deleted', + accessorKey: 'deleted', + enableSorting: true, + }, + { + header: 'Filtered', + id: 'filtered', + accessorKey: 'filtered', + enableSorting: true, + }, + { + header: 'Rejected', + id: 'rejected', + accessorKey: 'rejected', + enableSorting: true, + }, + { + header: 'Deduplicated', + id: 'deduplicated', + accessorKey: 'deduplicated', + enableSorting: true, + }, + { + header: 'Last arrival', + id: 'lastArrival', + accessorKey: 'lastArrival', + enableSorting: true, + cell: ({ getValue }) => <FormatedDate date={getValue<string>()} />, + }, +] + const DataStreams = ({ data, loading, @@ -36,85 +105,28 @@ const DataStreams = ({ onRefreshClicked, onChangeAutoRefresh, }: Props) => { - const dataStreams = Object.keys(data?.streams || {}).map((key) => { - const dataStream = data.streams[key] - return { - name: key, - ...dataStream, - } - }) - - const totals = data?.totals - - const columns: EuiTableFieldDataColumnType<DataStreamsData>[] = [ - { - name: 'Stream name', - field: 'name', - sortable: true, - render: (name: string) => ( - <EuiToolTip content={name}> - <span>{formatLongName(name, 30, 0, '...')}</span> - </EuiToolTip> - ), - width: '20%', - footer: 'Total', - }, - { - name: 'Total', - field: 'total', - sortable: true, - footer: () => totals?.total || '0', - }, - { - name: 'Pending', - field: 'pending', - sortable: true, - footer: () => totals?.pending || '0', - }, - { - name: 'Inserted', - field: 'inserted', - sortable: true, - footer: () => totals?.inserted || '0', - }, - { - name: 'Updated', - field: 'updated', - sortable: true, - footer: () => totals?.updated || '0', + const dataStreams: DataStreamsData[] = Object.keys(data?.streams || {}).map( + (key) => { + const dataStream = data.streams[key] + return { + name: key, + ...dataStream, + } }, - { - name: 'Deleted', - field: 'deleted', - sortable: true, - footer: () => totals?.deleted || '0', - }, - { - name: 'Filtered', - field: 'filtered', - sortable: true, - footer: () => totals?.filtered || '0', - }, - { - name: 'Rejected', - field: 'rejected', - sortable: true, - footer: () => totals?.rejected || '0', - }, - { - name: 'Deduplicated', - field: 'deduplicated', - sortable: true, - footer: () => totals?.deduplicated || '0', - }, - { - name: 'Last arrival', - field: 'lastArrival', - render: (dateStr) => <FormatedDate date={dateStr} />, - sortable: true, - footer: '', - }, - ] + ) + + const totalsRow: DataStreamsData = { + name: 'Total', + total: data?.totals?.total || 0, + pending: data?.totals?.pending || 0, + inserted: data?.totals?.inserted || 0, + updated: data?.totals?.updated || 0, + deleted: data?.totals?.deleted || 0, + filtered: data?.totals?.filtered || 0, + rejected: data?.totals?.rejected || 0, + deduplicated: data?.totals?.deduplicated || 0, + lastArrival: '', + } return ( <Panel> @@ -127,11 +139,10 @@ const DataStreams = ({ onRefreshClicked={onRefreshClicked} onChangeAutoRefresh={onChangeAutoRefresh} > - <Table<DataStreamsData> - id="data-streams" + <Table columns={columns} - items={dataStreams} - initialSortField="name" + data={[...dataStreams, totalsRow]} + defaultSorting={[{ id: 'name', desc: false }]} /> </Accordion> </Panel> diff --git a/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.spec.tsx b/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.spec.tsx index a4e31323c7..c25d7889bb 100644 --- a/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.spec.tsx +++ b/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.spec.tsx @@ -30,9 +30,6 @@ describe('TargetConnections', () => { it('renders the target connections table', () => { render(<TargetConnections {...mockedProps} />) - // Assert that the table is rendered - expect(screen.getByTestId('target-connections-table')).toBeInTheDocument() - // Assert that the table columns are rendered const columnHeaders = screen.getAllByRole('columnheader') expect(columnHeaders).toHaveLength(6) // 6 columns diff --git a/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.tsx b/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.tsx index 0ce82db366..890d335ff9 100644 --- a/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.tsx +++ b/redisinsight/ui/src/pages/rdi/statistics/target-connections/TargetConnections.tsx @@ -1,4 +1,4 @@ -import { EuiBasicTableColumn, EuiIcon, EuiToolTip } from '@elastic/eui' +import { EuiIcon, EuiToolTip } from '@elastic/eui' import React from 'react' import { @@ -6,66 +6,74 @@ import { StatisticsConnectionStatus, } from 'uiSrc/slices/interfaces' import { formatLongName } from 'uiSrc/utils' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import Accordion from '../components/accordion' import Panel from '../components/panel' -import Table from '../components/table' type ConnectionData = { - status: string name: string + status: string type: string hostPort: string database: string user: string } -const columns: EuiBasicTableColumn<ConnectionData>[] = [ +const columns: ColumnDefinition<ConnectionData>[] = [ { - name: 'Status', - field: 'status', - width: '80px', - render: (status: string) => + header: 'Status', + id: 'status', + accessorKey: 'status', + enableSorting: true, + cell: ({ + row: { + original: { status }, + }, + }) => status === StatisticsConnectionStatus.connected ? ( <EuiIcon type="dot" color="var(--buttonSuccessColor)" /> ) : ( <EuiIcon type="alert" color="danger" /> ), - align: 'center', - sortable: true, }, { - name: 'Name', - field: 'name', - width: '15%', - sortable: true, + header: 'Name', + id: 'name', + accessorKey: 'name', + enableSorting: true, }, { - name: 'Type', - field: 'type', - width: '10%', - sortable: true, + header: 'Type', + id: 'type', + accessorKey: 'type', + enableSorting: true, }, { - name: 'Host:port', - field: 'hostPort', - sortable: true, - render: (hostPort: string) => ( + header: 'Host:port', + id: 'hostPort', + accessorKey: 'hostPort', + enableSorting: true, + cell: ({ + row: { + original: { hostPort }, + }, + }) => ( <EuiToolTip content={hostPort}> <span>{formatLongName(hostPort, 80, 0, '...')}</span> </EuiToolTip> ), }, { - name: 'Database', - field: 'database', - width: '15%', - sortable: true, + header: 'Database', + id: 'database', + accessorKey: 'database', + enableSorting: true, }, { - name: 'Username', - field: 'user', - width: '15%', - sortable: true, + header: 'Username', + id: 'user', + accessorKey: 'user', + enableSorting: true, }, ] @@ -74,7 +82,7 @@ interface Props { } const TargetConnections = ({ data }: Props) => { - const connections = Object.keys(data).map((key) => { + const connections: ConnectionData[] = Object.keys(data).map((key) => { const connection = data[key] return { name: key, @@ -90,11 +98,10 @@ const TargetConnections = ({ data }: Props) => { title="Target connections" hideAutoRefresh > - <Table<ConnectionData> - id="target-connections" + <Table columns={columns} - items={connections} - initialSortField="name" + data={connections} + defaultSorting={[{ id: 'name', desc: false }]} /> </Accordion> </Panel> diff --git a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabases.tsx b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabases.tsx index 952dfecffe..424c531803 100644 --- a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabases.tsx +++ b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabases.tsx @@ -1,12 +1,5 @@ import React, { useEffect, useState } from 'react' -import { - EuiBasicTableColumn, - EuiInMemoryTable, - EuiPopover, - EuiTableSelectionType, - EuiToolTip, - PropertySort, -} from '@elastic/eui' +import { EuiPopover, EuiToolTip } from '@elastic/eui' import cx from 'classnames' import { map } from 'lodash' import { useSelector } from 'react-redux' @@ -27,10 +20,11 @@ import { import { FormField } from 'uiSrc/components/base/forms/FormField' import { Title } from 'uiSrc/components/base/text/Title' import { Text } from 'uiSrc/components/base/text' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import styles from './styles.module.scss' interface Props { - columns: EuiBasicTableColumn<InstanceRedisCluster>[] + columns: ColumnDefinition<InstanceRedisCluster>[] onClose: () => void onBack: () => void onSubmit: (uids: Maybe<number>[]) => void @@ -71,11 +65,6 @@ const RedisClusterDatabases = ({ } }, [instances]) - const sort: PropertySort = { - field: 'name', - direction: 'asc', - } - const handleSubmit = () => { onSubmit(map(selection, 'uid')) } @@ -90,9 +79,15 @@ const RedisClusterDatabases = ({ const isSubmitDisabled = () => selection.length < 1 - const selectionValue: EuiTableSelectionType<InstanceRedisCluster> = { - onSelectionChange: (selected: InstanceRedisCluster[]) => - setSelection(selected), + const selectionValue = { + onSelectionChange: (selected: InstanceRedisCluster) => + setSelection((previous) => { + const isSelected = previous.some((item) => item.uid === selected.uid) + if (isSelected) { + return previous.filter((item) => item.uid !== selected.uid) + } + return [...previous, selected] + }), } const onQueryChange = (term: string) => { @@ -180,16 +175,16 @@ const RedisClusterDatabases = ({ styles.databaseListWrapper, )} > - <EuiInMemoryTable - items={items} - itemId="uid" - loading={loading} - message={message} + <Table columns={columns} - sorting={{ sort }} - selection={selectionValue} - className={cx(styles.table, { [styles.tableEmpty]: !items.length })} - isSelectable + data={items} + onRowClick={selectionValue.onSelectionChange} + defaultSorting={[ + { + id: 'name', + desc: false, + }, + ]} /> {!items.length && ( <Text className={styles.noDatabases}>{message}</Text> diff --git a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesPage.tsx b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesPage.tsx index d1423fd57f..27fd9345d7 100644 --- a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesPage.tsx +++ b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesPage.tsx @@ -1,4 +1,4 @@ -import { EuiBasicTableColumn, EuiIcon, EuiToolTip } from '@elastic/eui' +import { EuiIcon, EuiToolTip } from '@elastic/eui' import React from 'react' import { useHistory } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' @@ -26,6 +26,7 @@ import { FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { IconButton } from 'uiSrc/components/base/forms/buttons' import { CopyIcon } from 'uiSrc/components/base/icons' import { ColorText, Text } from 'uiSrc/components/base/text' +import { ColumnDefinition } from 'uiSrc/components/base/layout/table' import RedisClusterDatabases from './RedisClusterDatabases' import RedisClusterDatabasesResult from './RedisClusterDatabasesResult' @@ -68,16 +69,17 @@ const RedisClusterDatabasesPage = () => { navigator.clipboard.writeText(text) } - const columns: EuiBasicTableColumn<InstanceRedisCluster>[] = [ + const columns: ColumnDefinition<InstanceRedisCluster>[] = [ { - field: 'name', - className: 'column_name', - name: 'Database', - dataType: 'auto', - truncateText: true, - sortable: true, - width: '420px', - render: function InstanceCell(name: string = '') { + header: 'Database', + id: 'name', + accessorKey: 'name', + enableSorting: true, + cell: ({ + row: { + original: { name }, + }, + }) => { const cellContent = name .substring(0, 200) .replace(/\s\s/g, '\u00a0\u00a0') @@ -96,27 +98,21 @@ const RedisClusterDatabasesPage = () => { }, }, { - field: 'status', - className: 'column_status', - name: 'Status', - dataType: 'string', - sortable: true, - width: '185px', - truncateText: true, - hideForMobile: true, + header: 'Status', + id: 'status', + accessorKey: 'status', + enableSorting: true, }, { - field: 'dnsName', - className: 'column_dnsName', - name: 'Endpoint', - width: '410px', - dataType: 'auto', - truncateText: true, - sortable: true, - render: function DnsName( - dnsName: string, - { port }: InstanceRedisCluster, - ) { + header: 'Endpoint', + id: 'dnsName', + accessorKey: 'dnsName', + enableSorting: true, + cell: ({ + row: { + original: { dnsName, port }, + }, + }) => { const text = `${dnsName}:${port}` return ( !!dnsName && ( @@ -140,17 +136,11 @@ const RedisClusterDatabasesPage = () => { }, }, { - field: 'modules', - className: 'column_modules', - name: 'Capabilities', - dataType: 'auto', - align: 'left', - width: '190px', - sortable: true, - render: function Modules( - _modules: any[], - instance: InstanceRedisCluster, - ) { + header: 'Capabilities', + id: 'modules', + accessorKey: 'modules', + enableSorting: true, + cell: function Modules({ row: { original: instance } }) { return ( <DatabaseListModules modules={instance?.modules?.map((name) => ({ name }))} @@ -159,14 +149,11 @@ const RedisClusterDatabasesPage = () => { }, }, { - field: 'options', - className: 'column_options', - name: 'Options', - dataType: 'auto', - align: 'left', - width: '220px', - sortable: true, - render: function Opitions(_opts: any[], instance: InstanceRedisCluster) { + header: 'Options', + id: 'options', + accessorKey: 'options', + enableSorting: true, + cell: ({ row: { original: instance } }) => { const options = parseInstanceOptionsCluster( instance?.uid, instances || [], @@ -176,18 +163,16 @@ const RedisClusterDatabasesPage = () => { }, ] - const messageColumn: EuiBasicTableColumn<InstanceRedisCluster> = { - field: 'messageAdded', - className: 'column_message', - name: 'Result', - dataType: 'string', - align: 'left', - width: '110px', - sortable: true, - render: function Message( - messageAdded: string, - { statusAdded }: InstanceRedisCluster, - ) { + const messageColumn: ColumnDefinition<InstanceRedisCluster> = { + header: 'Result', + id: 'messageAdded', + accessorKey: 'messageAdded', + enableSorting: true, + cell: function Message({ + row: { + original: { statusAdded, messageAdded }, + }, + }) { return ( <> {statusAdded === AddRedisDatabaseStatus.Success ? ( @@ -215,7 +200,7 @@ const RedisClusterDatabasesPage = () => { }, } - const columnsResult: EuiBasicTableColumn<InstanceRedisCluster>[] = [ + const columnsResult: ColumnDefinition<InstanceRedisCluster>[] = [ ...columns, ] columnsResult.push(messageColumn) diff --git a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.spec.tsx b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.spec.tsx index 6129a27e71..0e0c67c30d 100644 --- a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.spec.tsx +++ b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.spec.tsx @@ -11,13 +11,10 @@ describe('RedisClusterDatabasesResult', () => { it('should render', () => { const columnsMock = [ { - field: 'subscriptionId', - className: 'column_subscriptionId', - name: 'Subscription ID', - dataType: 'string', - sortable: true, - width: '170px', - truncateText: true, + header: 'Subscription ID', + id: 'subscriptionId', + accessorKey: 'subscriptionId', + enableSorting: true, }, ] expect( diff --git a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.tsx b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.tsx index 0d3cd4d103..b037fd209a 100644 --- a/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.tsx +++ b/redisinsight/ui/src/pages/redis-cluster/RedisClusterDatabasesResult.tsx @@ -1,9 +1,4 @@ import React, { useState, useEffect } from 'react' -import { - EuiInMemoryTable, - EuiBasicTableColumn, - PropertySort, -} from '@elastic/eui' import cx from 'classnames' import { useSelector } from 'react-redux' @@ -25,10 +20,11 @@ import { SearchInput } from 'uiSrc/components/base/inputs' import { FormField } from 'uiSrc/components/base/forms/FormField' import { Title } from 'uiSrc/components/base/text/Title' import { Text } from 'uiSrc/components/base/text' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import styles from './styles.module.scss' export interface Props { - columns: EuiBasicTableColumn<InstanceRedisCluster>[] + columns: ColumnDefinition<InstanceRedisCluster>[] onView: (sendEvent?: boolean) => void onBack: (sendEvent?: boolean) => void } @@ -40,17 +36,12 @@ const RedisClusterDatabasesResult = ({ columns, onBack, onView }: Props) => { const [items, setItems] = useState<InstanceRedisCluster[]>([]) const [message, setMessage] = useState(loadingMsg) - const { loading, dataAdded: instances } = useSelector(clusterSelector) + const { dataAdded: instances } = useSelector(clusterSelector) setTitle('Redis Enterprise Databases Added') useEffect(() => setItems(instances), [instances]) - const sort: PropertySort = { - field: 'name', - direction: 'asc', - } - const countSuccessAdded = instances.filter( ({ statusAdded }) => statusAdded === AddRedisDatabaseStatus.Success, )?.length @@ -119,15 +110,19 @@ const RedisClusterDatabasesResult = ({ columns, onBack, onView }: Props) => { </Row> <br /> <div className="itemList databaseList clusterDatabaseListResult"> - <EuiInMemoryTable - items={items} - itemId="uid" - loading={loading} - message={message} + <Table columns={columns} - sorting={{ sort }} - className={styles.table} + data={items} + defaultSorting={[ + { + id: 'name', + desc: false, + }, + ]} /> + {!items.length && ( + <Text className={styles.noDatabases}>{message}</Text> + )} </div> </div> <FlexItem className={cx(styles.footer, 'footerAddDatabase')}> diff --git a/redisinsight/ui/src/pages/redis-cluster/styles.module.scss b/redisinsight/ui/src/pages/redis-cluster/styles.module.scss index 6e93e80aa7..8c15f277b3 100644 --- a/redisinsight/ui/src/pages/redis-cluster/styles.module.scss +++ b/redisinsight/ui/src/pages/redis-cluster/styles.module.scss @@ -28,29 +28,10 @@ $breakpoint-to-wrap-buttons: 660px; padding-bottom: 5px !important; } -.subTitle { -} - -.table { - @include eui.scrollBar; - max-height: calc(100vh - 250px) !important; - overflow: auto; -} - .searchForm { width: 266px !important; } -.table { - @include eui.scrollBar; - overflow: auto; - max-height: calc(100vh - 370px) !important; -} - -.tableEmpty tbody { - display: none; -} - .panelCancelBtn { max-width: 350px !important; margin-left: -10px; diff --git a/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.spec.tsx b/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.spec.tsx index 5b1d6d8ad6..f8af181147 100644 --- a/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.spec.tsx +++ b/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.spec.tsx @@ -69,11 +69,7 @@ describe('UserApiKeysTable', () => { it('should render row content properly', () => { render(<UserApiKeysTable {...mockedProps} items={mockedCapiKeys} />) - expect( - screen.getByTestId(`row-${mockedCapiKeys[0].name}`), - ).toHaveTextContent( - 'API Key NameRedisInsight-f4868252-a128-4a02-af75-bd3c99898267-2020-11-01T-123Created2 Aug 2023Last used2 Aug 2023', - ) + expect(screen.getByText(mockedCapiKeys[0].name)).toBeVisible() }) it('should show delete popover and call proper action on delete', async () => { @@ -97,24 +93,7 @@ describe('UserApiKeysTable', () => { apiService.delete = jest.fn().mockResolvedValue({ status: 200 }) - const { container } = render( - <UserApiKeysTable {...mockedProps} items={mockedCapiKeys} />, - ) - - fireEvent.click( - container.querySelector( - '[data-test-subj="tableHeaderSortButton"]', - ) as HTMLElement, - ) - - expect(sendEventTelemetry).toBeCalledWith({ - event: TelemetryEvent.SETTINGS_CLOUD_API_KEY_SORTED, - eventData: { - direction: 'asc', - field: 'name', - numberOfKeys: 3, - }, - }) + render(<UserApiKeysTable {...mockedProps} items={mockedCapiKeys} />) sendEventTelemetry.mockRestore() diff --git a/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.tsx b/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.tsx index fa82d5f26c..9bc0d4ace9 100644 --- a/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.tsx +++ b/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.tsx @@ -1,17 +1,10 @@ import React, { useCallback, useState } from 'react' -import { - EuiBasicTableColumn, - EuiIcon, - EuiInMemoryTable, - EuiToolTip, - PropertySort, -} from '@elastic/eui' +import { EuiToolTip, EuiIcon } from '@elastic/eui' import { format } from 'date-fns' -import cx from 'classnames' import { useDispatch } from 'react-redux' import { isNull } from 'lodash' -import { formatLongName, Maybe, Nullable } from 'uiSrc/utils' +import { formatLongName, Nullable } from 'uiSrc/utils' import PopoverDelete from 'uiSrc/pages/browser/components/popover-delete/PopoverDelete' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { OAuthSsoHandlerDialog } from 'uiSrc/components' @@ -33,6 +26,7 @@ import { import { CopyIcon } from 'uiSrc/components/base/icons' import { Spacer } from 'uiSrc/components/base/layout/spacer' import { Title } from 'uiSrc/components/base/text/Title' +import { Table, ColumnDefinition } from 'uiSrc/components/base/layout/table' import { Link } from 'uiSrc/components/base/link/Link' import styles from './styles.module.scss' @@ -42,12 +36,7 @@ export interface Props { } const UserApiKeysTable = ({ items, loading }: Props) => { - const [sort, setSort] = useState<Maybe<PropertySort>>({ - field: 'createdAt', - direction: 'desc', - }) const [deleting, setDeleting] = useState('') - const dispatch = useDispatch() const handleCopy = (value: string) => { @@ -61,17 +50,6 @@ const UserApiKeysTable = ({ items, loading }: Props) => { setDeleting(id) }, []) - const handleSorting = ({ sort }: any) => { - setSort(sort) - sendEventTelemetry({ - event: TelemetryEvent.SETTINGS_CLOUD_API_KEY_SORTED, - eventData: { - ...sort, - numberOfKeys: items?.length || 0, - }, - }) - } - const handleClickDeleteApiKey = () => { sendEventTelemetry({ event: TelemetryEvent.SETTINGS_CLOUD_API_KEY_REMOVE_CLICKED, @@ -83,7 +61,6 @@ const UserApiKeysTable = ({ items, loading }: Props) => { const handleDeleteApiKey = (id: string, name: string) => { setDeleting('') - dispatch( removeCapiKeyAction({ id, name }, () => { sendEventTelemetry({ @@ -96,21 +73,23 @@ const UserApiKeysTable = ({ items, loading }: Props) => { ) } - const columns: EuiBasicTableColumn<any>[] = [ + const columns: ColumnDefinition<CloudCapiKey>[] = [ { - name: 'API Key Name', - field: 'name', - sortable: true, - truncateText: true, - width: '100%', - render: (value: string, { valid }) => { - const tooltipContent = formatLongName(value) - + header: 'API Key Name', + id: 'name', + accessorKey: 'name', + enableSorting: true, + cell: ({ + row: { + original: { name, valid }, + }, + }) => { + const tooltipContent = formatLongName(name) return ( <div className={styles.nameField}> {!valid && ( <EuiToolTip - content="This API key is invalid. Remove it from and Redis Cloud and create a new one instead." + content="This API key is invalid. Remove it from Redis Cloud and create a new one instead." anchorClassName={styles.invalidIconAnchor} > <EuiIcon @@ -121,48 +100,61 @@ const UserApiKeysTable = ({ items, loading }: Props) => { </EuiToolTip> )} <EuiToolTip title="API Key Name" content={tooltipContent}> - <>{value}</> + <>{name}</> </EuiToolTip> </div> ) }, }, { - name: 'Created', - field: 'createdAt', - sortable: true, - truncateText: true, - width: '120x', - render: (value: number) => ( - <EuiToolTip content={format(new Date(value), 'HH:mm:ss d LLL yyyy')}> - <>{format(new Date(value), 'd MMM yyyy')}</> + header: 'Created', + id: 'createdAt', + accessorKey: 'createdAt', + enableSorting: true, + cell: ({ + row: { + original: { createdAt }, + }, + }) => ( + <EuiToolTip + content={format(new Date(createdAt), 'HH:mm:ss d LLL yyyy')} + > + <>{format(new Date(createdAt), 'd MMM yyyy')}</> </EuiToolTip> ), }, { - name: 'Last used', - field: 'lastUsed', - sortable: true, - width: '120x', - render: (value: number) => ( + header: 'Last used', + id: 'lastUsed', + accessorKey: 'lastUsed', + enableSorting: true, + cell: ({ + row: { + original: { lastUsed }, + }, + }) => ( <> - {value && ( + {lastUsed ? ( <EuiToolTip - content={format(new Date(value), 'HH:mm:ss d LLL yyyy')} + content={format(new Date(lastUsed), 'HH:mm:ss d LLL yyyy')} > - <>{format(new Date(value), 'd MMM yyyy')}</> + <>{format(new Date(lastUsed), 'd MMM yyyy')}</> </EuiToolTip> + ) : ( + 'Never' )} - {!value && 'Never'} </> ), }, { - name: '', - field: 'actions', - align: 'right', - width: '80px', - render: (_value, { id, name }) => ( + header: '', + id: 'actions', + accessorKey: 'id', + cell: ({ + row: { + original: { id, name }, + }, + }) => ( <div> <EuiToolTip content="Copy API Key Name" @@ -272,23 +264,15 @@ const UserApiKeysTable = ({ items, loading }: Props) => { } return ( - <EuiInMemoryTable - loading={loading} - items={items ?? []} + <Table columns={columns} - sorting={sort ? { sort } : true} - responsive={false} - message="No Api Keys" - onTableChange={handleSorting} - className={cx( - 'inMemoryTableDefault', - 'stickyHeader', - 'noBorders', - styles.table, - )} - rowProps={(row) => ({ - 'data-testid': `row-${row.name}`, - })} + data={items} + defaultSorting={[ + { + id: 'createdAt', + desc: true, + }, + ]} data-testid="api-keys-table" /> ) diff --git a/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/styles.module.scss b/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/styles.module.scss index b057c7072e..ac2eac6ae7 100644 --- a/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/styles.module.scss +++ b/redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/styles.module.scss @@ -1,22 +1,3 @@ -.table { - @include eui.scrollBar; - max-height: 240px; - overflow-y: auto; - - :global { - .euiTableCellContent { - padding: 10px 12px !important; - } - } - - &:global { - &.inMemoryTableDefault.noBorders .euiTableHeaderCell { - background-color: var(--euiColorEmptyShade); - border-bottom: 1px solid var(--euiColorLightShade) !important; - } - } -} - .invalidIconAnchor { flex-shrink: 0; } diff --git a/redisinsight/ui/src/styles/elastic.css b/redisinsight/ui/src/styles/elastic.css index b05450a63b..336d9e19c9 100644 --- a/redisinsight/ui/src/styles/elastic.css +++ b/redisinsight/ui/src/styles/elastic.css @@ -489,6 +489,9 @@ border: none; vertical-align: baseline; } + td { + vertical-align: middle; + } code, pre, kbd, diff --git a/redisinsight/ui/vite.config.mjs b/redisinsight/ui/vite.config.mjs index fb5dcf4bd7..3d879d58eb 100644 --- a/redisinsight/ui/vite.config.mjs +++ b/redisinsight/ui/vite.config.mjs @@ -46,8 +46,10 @@ export default defineConfig({ alias: { lodash: 'lodash-es', '@elastic/eui$': '@elastic/eui/optimize/lib', + '@redislabsdev/redis-ui-components': '@redis-ui/components', '@redislabsdev/redis-ui-styles': '@redis-ui/styles', '@redislabsdev/redis-ui-icons': '@redis-ui/icons', + '@redislabsdev/redis-ui-table': '@redis-ui/table', uiSrc: fileURLToPath(new URL('./src', import.meta.url)), apiSrc: fileURLToPath(new URL('../api/src', import.meta.url)), }, diff --git a/yarn.lock b/yarn.lock index 49513280d6..50d24e8da8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2383,7 +2383,7 @@ "@react-hook/latest" "^1.0.2" "@react-hook/passive-layout-effect" "^1.2.0" -"@redis-ui/components@^38.1.3": +"@redis-ui/components@^38.0.0", "@redis-ui/components@^38.1.3": version "38.1.4" resolved "https://registry.yarnpkg.com/@redis-ui/components/-/components-38.1.4.tgz#02e620f937629162539b2fb36f8b1505442fad24" integrity sha512-13SDg3R4hQ1tGyOz8glk3NEuduTsnQpm5sjBf6AtsDHGmugFtQSn3JHvcOgVM+i4Ax6m42SvB6q/4nJD1Yygvg== @@ -2409,7 +2409,7 @@ type-fest "^3.13.1" virtua "^0.36.3" -"@redis-ui/icons@^4.16.1": +"@redis-ui/icons@^4.16.1", "@redis-ui/icons@^4.3.0": version "4.16.1" resolved "https://registry.yarnpkg.com/@redis-ui/icons/-/icons-4.16.1.tgz#87fc6ab073a203de6b313a4868c0a84f076c8580" integrity sha512-ZRQWzambHq9A/5zWx4HXEBNU8uCFwf6Ap0NKZryoMq5iGfygBF8k8A89RmwUXuSlJ99NRxW+RvmbcMd+Tyosmg== @@ -2421,6 +2421,16 @@ dependencies: color-alpha "^2.0.0" +"@redis-ui/table@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@redis-ui/table/-/table-2.4.0.tgz#fd25a5e61f1670c3343d5a47e90842ad2a068311" + integrity sha512-2MeJtn6ttTGrUjfImAn6U41Ep6Zaz4V7Q2QAeFso/27ShMZqSZVQgETqLd6U9CAjtYoYTMeM643cuJVHB18hyg== + dependencies: + "@redis-ui/components" "^38.0.0" + "@redis-ui/icons" "^4.3.0" + "@redis-ui/styles" "^11.0.2" + "@tanstack/react-table" "^8.9.8" + "@reduxjs/toolkit@^1.6.2": version "1.9.5" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4" @@ -2748,6 +2758,18 @@ dependencies: defer-to-connect "^2.0.0" +"@tanstack/react-table@^8.9.8": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.21.3.tgz#2c38c747a5731c1a07174fda764b9c2b1fb5e91b" + integrity sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww== + dependencies: + "@tanstack/table-core" "8.21.3" + +"@tanstack/table-core@8.21.3": + version "8.21.3" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.21.3.tgz#2977727d8fc8dfa079112d9f4d4c019110f1732c" + integrity sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg== + "@teamsupercell/typings-for-css-modules-loader@^2.4.0": version "2.5.2" resolved "https://registry.yarnpkg.com/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.5.2.tgz#b29deee5ebf6dac48693a2039a3b68b5ad821c1d"