From e1db91d478f1473591cd1d2ecee675616b4303ef Mon Sep 17 00:00:00 2001 From: Krum Tyukenov Date: Fri, 14 Nov 2025 13:47:53 +0200 Subject: [PATCH] RI-7705: refactor RdiInstancesList --- ....config.tsx => RdiInstancesList.config.ts} | 26 ++------ .../RdiInstancesList.types.ts | 5 +- .../RdiInstancesListCell.spec.tsx | 65 +++++++++---------- .../RdiInstancesListCell.tsx | 28 ++++---- .../RdiInstancesListCellSelect.tsx | 13 ++++ 5 files changed, 67 insertions(+), 70 deletions(-) rename redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/{RdiInstancesList.config.tsx => RdiInstancesList.config.ts} (78%) create mode 100644 redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCellSelect/RdiInstancesListCellSelect.tsx diff --git a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.config.tsx b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.config.ts similarity index 78% rename from redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.config.tsx rename to redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.config.ts index 33a7eb7337..242ba161cc 100644 --- a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.config.tsx +++ b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.config.ts @@ -1,9 +1,8 @@ -import React from 'react' - import { ColumnDef, Table } from 'uiSrc/components/base/layout/table' import { RDI_COLUMN_FIELD_NAME_MAP, RdiListColumn } from 'uiSrc/constants' import { RdiInstance } from 'uiSrc/slices/interfaces' +import RdiInstancesListCellSelect from './components/RdiInstancesListCellSelect/RdiInstancesListCellSelect' import RdiInstancesListCellControls from './components/RdiInstancesListCellControls/RdiInstancesListCellControls' import RdiInstancesListCell from './components/RdiInstancesListCell/RdiInstancesListCell' @@ -17,21 +16,14 @@ export const BASE_COLUMNS: ColumnDef[] = [ isHeaderCustom: true, enableSorting: false, header: Table.HeaderMultiRowSelectionButton, - cell: (props) => ( - e.stopPropagation()} - /> - ), + cell: RdiInstancesListCellSelect, }, { id: RdiListColumn.Name, accessorKey: RdiListColumn.Name, header: RDI_COLUMN_FIELD_NAME_MAP.get(RdiListColumn.Name), enableSorting: true, - cell: (props) => ( - - ), + cell: RdiInstancesListCell, sortingFn: (rowA, rowB) => `${rowA.original.name?.toLowerCase()}`.localeCompare( `${rowB.original.name?.toLowerCase()}`, @@ -42,9 +34,7 @@ export const BASE_COLUMNS: ColumnDef[] = [ accessorKey: RdiListColumn.Url, header: RDI_COLUMN_FIELD_NAME_MAP.get(RdiListColumn.Url), enableSorting: true, - cell: (props) => ( - - ), + cell: RdiInstancesListCell, sortingFn: (rowA, rowB) => `${rowA.original.url?.toLowerCase()}`.localeCompare( `${rowB.original.url?.toLowerCase()}`, @@ -55,18 +45,14 @@ export const BASE_COLUMNS: ColumnDef[] = [ accessorKey: RdiListColumn.Version, header: RDI_COLUMN_FIELD_NAME_MAP.get(RdiListColumn.Version), enableSorting: true, - cell: (props) => ( - - ), + cell: RdiInstancesListCell, }, { id: RdiListColumn.LastConnection, accessorKey: RdiListColumn.LastConnection, header: RDI_COLUMN_FIELD_NAME_MAP.get(RdiListColumn.LastConnection), enableSorting: true, - cell: (props) => ( - - ), + cell: RdiInstancesListCell, sortingFn: (rowA, rowB) => { const a = rowA.original.lastConnection const b = rowB.original.lastConnection diff --git a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.types.ts b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.types.ts index fd0f1b428c..60d07bd7e3 100644 --- a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.types.ts +++ b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.types.ts @@ -4,8 +4,5 @@ import type { CellContext } from 'uiSrc/components/base/layout/table' import type { RdiInstance } from 'uiSrc/slices/interfaces' export type IRdiListCell = ( - props: CellContext & { - field?: keyof RdiInstance - withCopyIcon?: boolean - }, + props: CellContext, ) => ReactElement | null diff --git a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.spec.tsx b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.spec.tsx index 3feed1cef6..29bec51a22 100644 --- a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.spec.tsx +++ b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.spec.tsx @@ -1,8 +1,6 @@ import React from 'react' import { render, screen, userEvent } from 'uiSrc/utils/test-utils' - import { rdiInstanceFactory } from 'uiSrc/mocks/rdi/RdiInstance.factory' - import RdiInstancesListCell from './RdiInstancesListCell' import { lastConnectionFormat } from 'uiSrc/utils' @@ -19,7 +17,8 @@ jest.mock('uiSrc/utils', () => ({ lastConnectionFormat: jest.fn(() => '3 min ago'), })) -const buildRow = ( +const makeProps = ( + columnId: string, overrides: Partial> = {}, ) => { const instance = rdiInstanceFactory.build({ @@ -28,7 +27,11 @@ const buildRow = ( url: 'https://example', ...overrides, }) - return { row: { original: instance } as any, instance } + return { + row: { original: instance } as any, + column: { id: columnId } as any, + instance, + } } describe('RdiInstancesListCell', () => { @@ -36,41 +39,43 @@ describe('RdiInstancesListCell', () => { jest.clearAllMocks() }) - it('should render null when field is not provided', () => { - const { row } = buildRow() - const { container } = render() + it('should render null when value is missing for the column', () => { + const { row, column } = makeProps('version', { version: undefined as any }) + const { container } = render( + , + ) expect(container.firstChild).toBeNull() }) it('should render text value and data-testid for a string field (name)', () => { - const { row, instance } = buildRow({ id: 'cell-1', name: 'My Endpoint' }) + const { row, column, instance } = makeProps('name', { + id: 'cell-1', + name: 'My Endpoint', + }) - render() + render() expect(screen.getByText('My Endpoint')).toBeInTheDocument() - // data-testid includes id and text expect( screen.getByTestId(`rdi-list-cell-${instance.id}-${instance.name}`), ).toBeInTheDocument() }) - it('should not show copy icon by default', () => { - const { row } = buildRow() + it('should not show copy icon for non-url field', () => { + const { row, column } = makeProps('name') - render() + render() expect(screen.queryByRole('button')).not.toBeInTheDocument() }) it('should show copy icon and call handleCopyUrl with url text and id', async () => { - const { row, instance } = buildRow({ + const { row, column, instance } = makeProps('url', { id: 'cpy-1', url: 'https://ri.example', }) - render( - , - ) + render() const btn = screen.getByRole('button') await userEvent.click(btn, { pointerEventsCheck: 0 }) @@ -82,27 +87,17 @@ describe('RdiInstancesListCell', () => { expect(id).toBe(instance.id) }) - it('should format lastConnection via lastConnectionFormat and pass formatted text to handler', async () => { + it('should format lastConnection via lastConnectionFormat and render formatted text (no copy icon)', async () => { const date = new Date('2023-01-01T00:00:00.000Z') - const { row, instance } = buildRow({ id: 'last-1', lastConnection: date }) - - render( - , - ) + const { row, column } = makeProps('lastConnection', { + id: 'last-1', + lastConnection: date, + }) + + render() - // Uses mocked formatter expect(lastConnectionFormat).toHaveBeenCalledWith(date as any) expect(screen.getByText('3 min ago')).toBeInTheDocument() - - const btn = screen.getByRole('button') - await userEvent.click(btn, { pointerEventsCheck: 0 }) - - const [, text, id] = mockHandleCopyUrl.mock.calls[0] - expect(text).toBe('3 min ago') - expect(id).toBe(instance.id) + expect(screen.queryByRole('button')).not.toBeInTheDocument() }) }) diff --git a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.tsx b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.tsx index fe54724f9b..d5eb185143 100644 --- a/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.tsx +++ b/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/components/RdiInstancesListCell/RdiInstancesListCell.tsx @@ -5,26 +5,32 @@ import { IconButton } from 'uiSrc/components/base/forms/buttons' import { CopyIcon } from 'uiSrc/components/base/icons' import { Text } from 'uiSrc/components/base/text' import { lastConnectionFormat } from 'uiSrc/utils' -import { RdiInstance } from 'uiSrc/slices/interfaces' +import { RdiListColumn } from 'uiSrc/constants' import { handleCopyUrl } from '../../methods/handlers' import { IRdiListCell } from '../../RdiInstancesList.types' import { CellContainer } from './RdiInstancesListCell.styles' -const RdiInstancesListCell: IRdiListCell = ({ row, field, withCopyIcon }) => { - const instance = row.original as RdiInstance +const fieldCopyIcon: Record = { + [RdiListColumn.Url]: true, +} + +const fieldFormatters: Record string> = { + [RdiListColumn.LastConnection]: lastConnectionFormat, +} + +const RdiInstancesListCell: IRdiListCell = ({ row, column }) => { + const item = row.original + const id = item.id + const field = column.id as keyof typeof item + const value = item[field] - if (!field) { + if (!field || !value) { return null } - const id = instance.id - const value = instance[field] - const text = - // lastConnection is returned as a string - field === 'lastConnection' - ? lastConnectionFormat(value as any) - : value?.toString() + const text = fieldFormatters[field]?.(value) ?? value?.toString() + const withCopyIcon = fieldCopyIcon[field] return ( ( + e.stopPropagation()} + /> +) + +export default RdiInstancesListCellSelect