From e1fb6bacc331feec10018fc770ea3343e30c1161 Mon Sep 17 00:00:00 2001 From: Oleg Shur Date: Mon, 15 Aug 2022 13:46:13 +0300 Subject: [PATCH] Get rid of SmartTable component (#2444) * Get rid of SmartTable component * Clickable rows * Improve test coverage --- .../Brokers/BrokersList/BrokersList.style.ts | 5 - .../Brokers/BrokersList/BrokersList.tsx | 92 ++++----- .../BrokersList/__test__/BrokersList.spec.tsx | 176 ++++++++++-------- .../Connect/Details/Overview/Overview.tsx | 2 +- .../Connect/Details/Tasks/Tasks.tsx | 4 +- .../src/components/Connect/List/ListItem.tsx | 4 +- .../ConsumerGroups/Details/Details.tsx | 4 +- .../List/ConsumerGroupsTableCells.tsx | 23 --- .../components/ConsumerGroups/List/List.tsx | 138 +++++++------- .../ConsumerGroups/List/ListContainer.tsx | 11 +- .../ConsumerGroups/List/ListItem.tsx | 29 --- .../ConsumerGroupsTableCells.spec.tsx | 61 ------ .../List/__test__/List.spec.tsx | 78 +++----- .../List/__test__/ListItem.spec.tsx | 86 --------- .../__test__/ConsumerGroups.spec.tsx | 137 +++----------- .../KsqlDb/List/KsqlDbItem/KsqlDbItem.tsx | 38 ++-- .../KsqlDbItem/__test__/KsqlDbItem.spec.tsx | 73 ++++---- .../KsqlDb/List/__test__/List.spec.tsx | 9 +- .../GlobalSchemaSelector.tsx | 14 +- .../__test__/GlobalSchemaSelector.spec.tsx | 2 +- .../src/components/Schemas/List/List.tsx | 76 ++++---- .../src/components/Schemas/List/ListItem.tsx | 26 --- .../Schemas/List/__test__/List.spec.tsx | 26 ++- .../Schemas/List/__test__/ListItem.spec.tsx | 23 --- .../Schemas/List/__test__/fixtures.ts | 2 +- .../ConsumerGroups/TopicConsumerGroups.tsx | 2 +- .../MessageContent/MessageContent.styled.ts | 16 -- .../__tests__/MessageContent.styled.spec.tsx | 20 -- .../common/NewTable/ExpanderCell.tsx | 3 +- .../components/common/NewTable/LinkCell.tsx | 16 ++ .../common/NewTable/SelectRowCell.tsx | 3 +- .../common/NewTable/SelectRowHeader.tsx | 5 +- .../components/common/NewTable/SizeCell.tsx | 4 +- .../common/NewTable/Table.styled.ts | 6 +- .../src/components/common/NewTable/Table.tsx | 50 ++++- .../components/common/NewTable/TagCell.tsx | 12 ++ .../common/NewTable/TimestampCell.tsx | 4 +- .../common/NewTable/__test__/Table.spec.tsx | 103 +++++++++- .../src/components/common/NewTable/index.ts | 4 +- .../common/Pagination/PageControl.tsx | 26 --- .../common/Pagination/Pagination.styled.ts | 87 --------- .../common/Pagination/Pagination.tsx | 128 ------------- .../Pagination/__tests__/PageControl.spec.tsx | 35 ---- .../Pagination/__tests__/Pagination.spec.tsx | 91 --------- .../common/SmartTable/SmartTable.tsx | 134 ------------- .../common/SmartTable/TableColumn.tsx | 102 ---------- .../components/common/SmartTable/TableRow.tsx | 86 --------- .../src/components/common/Tag/Tag.styled.tsx | 3 +- .../src/components/common/Tag/getTagColor.ts | 12 +- .../common/table/Table/TableKeyLink.styled.ts | 4 - .../TableHeaderCell/TableHeaderCell.styled.ts | 6 - .../src/lib/__test__/propertyLookup.spec.ts | 18 -- .../src/lib/hooks/usePagination.ts | 17 -- .../src/lib/hooks/useTableState.ts | 78 -------- kafka-ui-react-app/src/lib/propertyLookup.ts | 9 - .../consumerGroups/__test__/fixtures.ts | 5 - kafka-ui-react-app/src/theme/theme.ts | 16 -- 57 files changed, 597 insertions(+), 1647 deletions(-) delete mode 100644 kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.style.ts delete mode 100644 kafka-ui-react-app/src/components/ConsumerGroups/List/ConsumerGroupsTableCells.tsx delete mode 100644 kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx delete mode 100644 kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ConsumerGroupsTableCells.spec.tsx delete mode 100644 kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ListItem.spec.tsx delete mode 100644 kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx delete mode 100644 kafka-ui-react-app/src/components/Schemas/List/__test__/ListItem.spec.tsx delete mode 100644 kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/__tests__/MessageContent.styled.spec.tsx create mode 100644 kafka-ui-react-app/src/components/common/NewTable/LinkCell.tsx create mode 100644 kafka-ui-react-app/src/components/common/NewTable/TagCell.tsx delete mode 100644 kafka-ui-react-app/src/components/common/Pagination/PageControl.tsx delete mode 100644 kafka-ui-react-app/src/components/common/Pagination/Pagination.styled.ts delete mode 100644 kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx delete mode 100644 kafka-ui-react-app/src/components/common/Pagination/__tests__/PageControl.spec.tsx delete mode 100644 kafka-ui-react-app/src/components/common/Pagination/__tests__/Pagination.spec.tsx delete mode 100644 kafka-ui-react-app/src/components/common/SmartTable/SmartTable.tsx delete mode 100644 kafka-ui-react-app/src/components/common/SmartTable/TableColumn.tsx delete mode 100644 kafka-ui-react-app/src/components/common/SmartTable/TableRow.tsx delete mode 100644 kafka-ui-react-app/src/lib/__test__/propertyLookup.spec.ts delete mode 100644 kafka-ui-react-app/src/lib/hooks/usePagination.ts delete mode 100644 kafka-ui-react-app/src/lib/hooks/useTableState.ts delete mode 100644 kafka-ui-react-app/src/lib/propertyLookup.ts diff --git a/kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.style.ts b/kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.style.ts deleted file mode 100644 index f5f8373333..0000000000 --- a/kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.style.ts +++ /dev/null @@ -1,5 +0,0 @@ -import styled from 'styled-components'; - -export const ClickableRow = styled.tr` - cursor: pointer; -`; diff --git a/kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.tsx b/kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.tsx index ae1a26899b..bbe00458e6 100644 --- a/kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.tsx +++ b/kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.tsx @@ -1,25 +1,21 @@ import React from 'react'; import { ClusterName } from 'redux/interfaces'; -import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted'; -import { NavLink, useNavigate } from 'react-router-dom'; -import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell'; -import { Table } from 'components/common/table/Table/Table.styled'; +import { useNavigate } from 'react-router-dom'; import PageHeading from 'components/common/PageHeading/PageHeading'; import * as Metrics from 'components/common/Metrics'; import useAppParams from 'lib/hooks/useAppParams'; import { useBrokers } from 'lib/hooks/api/brokers'; import { useClusterStats } from 'lib/hooks/api/clusters'; - -import { ClickableRow } from './BrokersList.style'; +import Table, { LinkCell, SizeCell } from 'components/common/NewTable'; +import { ColumnDef } from '@tanstack/react-table'; +import { clusterBrokerPath } from 'lib/paths'; const BrokersList: React.FC = () => { const navigate = useNavigate(); const { clusterName } = useAppParams<{ clusterName: ClusterName }>(); - const { data: clusterStats } = useClusterStats(clusterName); + const { data: clusterStats = {} } = useClusterStats(clusterName); const { data: brokers } = useBrokers(clusterName); - if (!clusterStats) return null; - const { brokerCount, activeControllers, @@ -32,6 +28,32 @@ const BrokersList: React.FC = () => { version, } = clusterStats; + const rows = React.useMemo(() => { + if (!diskUsage) return []; + + return diskUsage.map(({ brokerId, segmentSize, segmentCount }) => { + const broker = brokers?.find(({ id }) => id === brokerId); + return { + brokerId, + size: segmentSize, + count: segmentCount, + port: broker?.port, + host: broker?.host, + }; + }); + }, [diskUsage, brokers]); + + const columns = React.useMemo[]>( + () => [ + { header: 'Broker ID', accessorKey: 'brokerId', cell: LinkCell }, + { header: 'Segment Size', accessorKey: 'size', cell: SizeCell }, + { header: 'Segment Count', accessorKey: 'count' }, + { header: 'Port', accessorKey: 'port' }, + { header: 'Host', accessorKey: 'host' }, + ], + [] + ); + const replicas = (inSyncReplicasCount ?? 0) + (outOfSyncReplicasCount ?? 0); const areAllInSync = inSyncReplicasCount && replicas === inSyncReplicasCount; const partitionIsOffline = offlinePartitionCount && offlinePartitionCount > 0; @@ -95,49 +117,15 @@ const BrokersList: React.FC = () => { - - - - - - - - - - - - {(!diskUsage || diskUsage.length === 0) && ( - - - - )} - - {diskUsage && - diskUsage.length !== 0 && - diskUsage.map(({ brokerId, segmentSize, segmentCount }) => { - const brokerItem = brokers?.find(({ id }) => id === brokerId); - - return ( - navigate(`${brokerId}`)} - > - - - - - - - ); - })} - -
Disk usage data not available
- - {brokerId} - - - - {segmentCount}{brokerItem?.port}{brokerItem?.host}
+ + navigate(clusterBrokerPath(clusterName, brokerId)) + } + emptyMessage="Disk usage data not available" + /> ); }; diff --git a/kafka-ui-react-app/src/components/Brokers/BrokersList/__test__/BrokersList.spec.tsx b/kafka-ui-react-app/src/components/Brokers/BrokersList/__test__/BrokersList.spec.tsx index 550e8824d2..b90cef0a43 100644 --- a/kafka-ui-react-app/src/components/Brokers/BrokersList/__test__/BrokersList.spec.tsx +++ b/kafka-ui-react-app/src/components/Brokers/BrokersList/__test__/BrokersList.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render, WithRoute } from 'lib/testHelpers'; import { screen, waitFor } from '@testing-library/dom'; -import { clusterBrokersPath } from 'lib/paths'; +import { clusterBrokerPath, clusterBrokersPath } from 'lib/paths'; import { act } from '@testing-library/react'; import BrokersList from 'components/Brokers/BrokersList/BrokersList'; import userEvent from '@testing-library/user-event'; @@ -41,84 +41,108 @@ describe('BrokersList Component', () => { ); describe('BrokersList', () => { - beforeEach(() => { - (useBrokers as jest.Mock).mockImplementation(() => ({ - data: brokersPayload, - })); - (useClusterStats as jest.Mock).mockImplementation(() => ({ - data: clusterStatsPayload, - })); - }); - - it('renders', async () => { - renderComponent(); - expect(screen.getByRole('table')).toBeInTheDocument(); - const rows = screen.getAllByRole('row'); - expect(rows.length).toEqual(3); - }); - it('opens broker when row clicked', async () => { - renderComponent(); - await act(() => { - userEvent.click(screen.getByRole('cell', { name: '0' })); + describe('when the brokers are loaded', () => { + beforeEach(() => { + (useBrokers as jest.Mock).mockImplementation(() => ({ + data: brokersPayload, + })); + (useClusterStats as jest.Mock).mockImplementation(() => ({ + data: clusterStatsPayload, + })); + }); + it('renders', async () => { + renderComponent(); + expect(screen.getByRole('table')).toBeInTheDocument(); + expect(screen.getAllByRole('row').length).toEqual(3); + }); + it('opens broker when row clicked', async () => { + renderComponent(); + await act(() => { + userEvent.click(screen.getByRole('cell', { name: '0' })); + }); + await waitFor(() => + expect(mockedUsedNavigate).toBeCalledWith( + clusterBrokerPath(clusterName, '0') + ) + ); + }); + it('shows warning when offlinePartitionCount > 0', async () => { + (useClusterStats as jest.Mock).mockImplementation(() => ({ + data: { + ...clusterStatsPayload, + offlinePartitionCount: 1345, + }, + })); + renderComponent(); + const onlineWidget = screen.getByText( + clusterStatsPayload.onlinePartitionCount + ); + expect(onlineWidget).toBeInTheDocument(); + expect(onlineWidget).toHaveStyle({ color: '#E51A1A' }); + }); + it('shows right count when offlinePartitionCount > 0', async () => { + (useClusterStats as jest.Mock).mockImplementation(() => ({ + data: { + ...clusterStatsPayload, + inSyncReplicasCount: testInSyncReplicasCount, + outOfSyncReplicasCount: testOutOfSyncReplicasCount, + }, + })); + renderComponent(); + const onlineWidgetDef = screen.getByText(testInSyncReplicasCount); + const onlineWidget = screen.getByText( + `of ${testInSyncReplicasCount + testOutOfSyncReplicasCount}` + ); + expect(onlineWidgetDef).toBeInTheDocument(); + expect(onlineWidget).toBeInTheDocument(); + }); + it('shows right count when inSyncReplicasCount: undefined && outOfSyncReplicasCount: 1', async () => { + (useClusterStats as jest.Mock).mockImplementation(() => ({ + data: { + ...clusterStatsPayload, + inSyncReplicasCount: undefined, + outOfSyncReplicasCount: testOutOfSyncReplicasCount, + }, + })); + renderComponent(); + const onlineWidget = screen.getByText( + `of ${testOutOfSyncReplicasCount}` + ); + expect(onlineWidget).toBeInTheDocument(); + }); + it(`shows right count when inSyncReplicasCount: ${testInSyncReplicasCount} outOfSyncReplicasCount: undefined`, async () => { + (useClusterStats as jest.Mock).mockImplementation(() => ({ + data: { + ...clusterStatsPayload, + inSyncReplicasCount: testInSyncReplicasCount, + outOfSyncReplicasCount: undefined, + }, + })); + renderComponent(); + const onlineWidgetDef = screen.getByText(testInSyncReplicasCount); + const onlineWidget = screen.getByText(`of ${testInSyncReplicasCount}`); + expect(onlineWidgetDef).toBeInTheDocument(); + expect(onlineWidget).toBeInTheDocument(); }); - await waitFor(() => expect(mockedUsedNavigate).toBeCalledWith('0')); - }); - it('shows warning when offlinePartitionCount > 0', async () => { - (useClusterStats as jest.Mock).mockImplementation(() => ({ - data: { - ...clusterStatsPayload, - offlinePartitionCount: 1345, - }, - })); - renderComponent(); - const onlineWidget = screen.getByText( - clusterStatsPayload.onlinePartitionCount - ); - expect(onlineWidget).toBeInTheDocument(); - expect(onlineWidget).toHaveStyle({ color: '#E51A1A' }); - }); - it('shows right count when offlinePartitionCount > 0', async () => { - (useClusterStats as jest.Mock).mockImplementation(() => ({ - data: { - ...clusterStatsPayload, - inSyncReplicasCount: testInSyncReplicasCount, - outOfSyncReplicasCount: testOutOfSyncReplicasCount, - }, - })); - renderComponent(); - const onlineWidgetDef = screen.getByText(testInSyncReplicasCount); - const onlineWidget = screen.getByText( - `of ${testInSyncReplicasCount + testOutOfSyncReplicasCount}` - ); - expect(onlineWidgetDef).toBeInTheDocument(); - expect(onlineWidget).toBeInTheDocument(); }); - it('shows right count when inSyncReplicasCount: undefined outOfSyncReplicasCount: 1', async () => { - (useClusterStats as jest.Mock).mockImplementation(() => ({ - data: { - ...clusterStatsPayload, - inSyncReplicasCount: undefined, - outOfSyncReplicasCount: testOutOfSyncReplicasCount, - }, - })); - renderComponent(); - const onlineWidget = screen.getByText(`of ${testOutOfSyncReplicasCount}`); - expect(onlineWidget).toBeInTheDocument(); - }); - it(`shows right count when inSyncReplicasCount: ${testInSyncReplicasCount} outOfSyncReplicasCount: undefined`, async () => { - (useClusterStats as jest.Mock).mockImplementation(() => ({ - data: { - ...clusterStatsPayload, - inSyncReplicasCount: testInSyncReplicasCount, - outOfSyncReplicasCount: undefined, - }, - })); - renderComponent(); - const onlineWidgetDef = screen.getByText(testInSyncReplicasCount); - const onlineWidget = screen.getByText(`of ${testInSyncReplicasCount}`); - expect(onlineWidgetDef).toBeInTheDocument(); - expect(onlineWidget).toBeInTheDocument(); + describe('when diskUsage is empty', () => { + beforeEach(() => { + (useBrokers as jest.Mock).mockImplementation(() => ({ + data: brokersPayload, + })); + (useClusterStats as jest.Mock).mockImplementation(() => ({ + data: { ...clusterStatsPayload, diskUsage: undefined }, + })); + }); + + it('renders empty table', async () => { + renderComponent(); + expect(screen.getByRole('table')).toBeInTheDocument(); + expect( + screen.getByRole('row', { name: 'Disk usage data not available' }) + ).toBeInTheDocument(); + }); }); }); }); diff --git a/kafka-ui-react-app/src/components/Connect/Details/Overview/Overview.tsx b/kafka-ui-react-app/src/components/Connect/Details/Overview/Overview.tsx index c5c004f306..d1fdc56fb9 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Overview/Overview.tsx +++ b/kafka-ui-react-app/src/components/Connect/Details/Overview/Overview.tsx @@ -35,7 +35,7 @@ const Overview: React.FC = () => { )} - + {connector.status.state} diff --git a/kafka-ui-react-app/src/components/Connect/Details/Tasks/Tasks.tsx b/kafka-ui-react-app/src/components/Connect/Details/Tasks/Tasks.tsx index 8fbe687e11..74dd89ab87 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Tasks/Tasks.tsx +++ b/kafka-ui-react-app/src/components/Connect/Details/Tasks/Tasks.tsx @@ -43,7 +43,9 @@ const Tasks: React.FC = () => { - + - {selectable && ( - - )} - {React.Children.map(children, (child) => { - if (!isColumnElement(child)) { - return child; - } - const { cell, field, maxWidth, customTd } = child.props; - - const Cell = cell as React.FC> | undefined; - const TdComponent = customTd || Td; - - const content = Cell ? ( - - ) : ( - field && propertyLookup(field, dataItem) - ); - - return ( - - {content as React.ReactNode} - - ); - })} - - ); -}; diff --git a/kafka-ui-react-app/src/components/common/Tag/Tag.styled.tsx b/kafka-ui-react-app/src/components/common/Tag/Tag.styled.tsx index 7884dc2408..32a5784be3 100644 --- a/kafka-ui-react-app/src/components/common/Tag/Tag.styled.tsx +++ b/kafka-ui-react-app/src/components/common/Tag/Tag.styled.tsx @@ -4,7 +4,7 @@ interface Props { color: 'green' | 'gray' | 'yellow' | 'red' | 'white' | 'blue'; } -export const Tag = styled.p` +export const Tag = styled.span.attrs({ role: 'widget' })` border: none; border-radius: 16px; height: 20px; @@ -17,4 +17,5 @@ export const Tag = styled.p` padding-right: 0.75em; text-align: center; width: max-content; + margin: 2px 0; `; diff --git a/kafka-ui-react-app/src/components/common/Tag/getTagColor.ts b/kafka-ui-react-app/src/components/common/Tag/getTagColor.ts index a91b7b0d45..d52c667626 100644 --- a/kafka-ui-react-app/src/components/common/Tag/getTagColor.ts +++ b/kafka-ui-react-app/src/components/common/Tag/getTagColor.ts @@ -1,14 +1,6 @@ -import { - ConnectorState, - ConnectorStatus, - ConsumerGroup, - ConsumerGroupState, - TaskStatus, -} from 'generated-sources'; +import { ConnectorState, ConsumerGroupState } from 'generated-sources'; -const getTagColor = ({ - state, -}: ConnectorStatus | TaskStatus | ConsumerGroup) => { +const getTagColor = (state?: string) => { switch (state) { case ConnectorState.RUNNING: case ConsumerGroupState.STABLE: diff --git a/kafka-ui-react-app/src/components/common/table/Table/TableKeyLink.styled.ts b/kafka-ui-react-app/src/components/common/table/Table/TableKeyLink.styled.ts index 493c525b2d..7730102155 100644 --- a/kafka-ui-react-app/src/components/common/table/Table/TableKeyLink.styled.ts +++ b/kafka-ui-react-app/src/components/common/table/Table/TableKeyLink.styled.ts @@ -21,7 +21,3 @@ const tableLinkMixin = css( export const TableKeyLink = styled.td` ${tableLinkMixin} `; - -export const SmartTableKeyLink = styled.div` - ${tableLinkMixin} -`; diff --git a/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts b/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts index 549da1d067..bb486ea248 100644 --- a/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts +++ b/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts @@ -67,12 +67,6 @@ const DESCMixin = css( ` ); -export const Td = styled.td<{ maxWidth?: string }>` - overflow: hidden; - text-overflow: ellipsis; - max-width: ${(props) => props.maxWidth}; -`; - export const Title = styled.span( ({ isOrderable, isOrdered, sortOrder, theme: { table } }) => css` font-family: Inter, sans-serif; diff --git a/kafka-ui-react-app/src/lib/__test__/propertyLookup.spec.ts b/kafka-ui-react-app/src/lib/__test__/propertyLookup.spec.ts deleted file mode 100644 index 45f89b0a08..0000000000 --- a/kafka-ui-react-app/src/lib/__test__/propertyLookup.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { propertyLookup } from 'lib/propertyLookup'; - -describe('Property Lookup', () => { - const entityObject = { - prop: { - nestedProp: 1, - }, - }; - it('returns undefined if property not found', () => { - expect( - propertyLookup('prop.nonExistingProp', entityObject) - ).toBeUndefined(); - }); - - it('returns value of nested property if it exists', () => { - expect(propertyLookup('prop.nestedProp', entityObject)).toBe(1); - }); -}); diff --git a/kafka-ui-react-app/src/lib/hooks/usePagination.ts b/kafka-ui-react-app/src/lib/hooks/usePagination.ts deleted file mode 100644 index 6ce0134053..0000000000 --- a/kafka-ui-react-app/src/lib/hooks/usePagination.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useLocation } from 'react-router-dom'; - -const usePagination = () => { - const { search, pathname } = useLocation(); - const params = new URLSearchParams(search); - - const page = params.get('page'); - const perPage = params.get('perPage'); - - return { - page: page ? Number(page) : undefined, - perPage: perPage ? Number(perPage) : undefined, - pathname, - }; -}; - -export default usePagination; diff --git a/kafka-ui-react-app/src/lib/hooks/useTableState.ts b/kafka-ui-react-app/src/lib/hooks/useTableState.ts deleted file mode 100644 index a9a6eb850a..0000000000 --- a/kafka-ui-react-app/src/lib/hooks/useTableState.ts +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useCallback } from 'react'; -import { OrderableProps } from 'components/common/SmartTable/TableColumn'; - -export interface TableState { - data: T[]; - selectedIds: Set; - totalPages?: number; - idSelector: (row: T) => TId; - isRowSelectable: (row: T) => boolean; - selectedCount: number; - setRowsSelection: (rows: T[], selected: boolean) => void; - toggleSelection: (selected: boolean) => void; - orderable?: OrderableProps; -} - -export const useTableState = ( - data: T[], - options: { - totalPages: number; - isRowSelectable?: (row: T) => boolean; - idSelector: (row: T) => TId; - }, - orderable?: OrderableProps -): TableState => { - const [selectedIds, setSelectedIds] = React.useState(new Set()); - - const { idSelector, totalPages, isRowSelectable = () => true } = options; - - const selectedCount = selectedIds.size; - - const setRowsSelection = useCallback( - (rows: T[], selected: boolean) => { - rows.forEach((row) => { - const id = idSelector(row); - const newSet = new Set(selectedIds); - if (selected) { - newSet.add(id); - } else { - newSet.delete(id); - } - setSelectedIds(newSet); - }); - }, - [idSelector, selectedIds] - ); - - const toggleSelection = useCallback( - (selected: boolean) => { - const newSet = new Set(selected ? data.map((r) => idSelector(r)) : []); - setSelectedIds(newSet); - }, - [data, idSelector] - ); - - return React.useMemo>(() => { - return { - data, - totalPages, - selectedIds, - orderable, - selectedCount, - idSelector, - isRowSelectable, - setRowsSelection, - toggleSelection, - }; - }, [ - data, - orderable, - selectedIds, - totalPages, - selectedCount, - idSelector, - isRowSelectable, - setRowsSelection, - toggleSelection, - ]); -}; diff --git a/kafka-ui-react-app/src/lib/propertyLookup.ts b/kafka-ui-react-app/src/lib/propertyLookup.ts deleted file mode 100644 index 6a563ca623..0000000000 --- a/kafka-ui-react-app/src/lib/propertyLookup.ts +++ /dev/null @@ -1,9 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function propertyLookup( - path: string, - obj: T -) { - return path.split('.').reduce((prev, curr) => { - return prev ? prev[curr] : null; - }, obj); -} diff --git a/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts b/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts index 88caf4e671..7c130ef472 100644 --- a/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts +++ b/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts @@ -25,11 +25,6 @@ export const consumerGroups = [ }, ]; -export const noConsumerGroupsResponse = { - pageCount: 1, - consumerGroups: [], -}; - export const consumerGroupPayload = { groupId: 'amazon.msk.canary.group.broker-1', members: 0, diff --git a/kafka-ui-react-app/src/theme/theme.ts b/kafka-ui-react-app/src/theme/theme.ts index 44e3be8641..b890115d9b 100644 --- a/kafka-ui-react-app/src/theme/theme.ts +++ b/kafka-ui-react-app/src/theme/theme.ts @@ -447,22 +447,6 @@ const theme = { }, color: Colors.neutral[90], }, - pagination: { - backgroundColor: Colors.neutral[0], - currentPage: Colors.neutral[10], - borderColor: { - normal: Colors.neutral[30], - hover: Colors.neutral[50], - active: Colors.neutral[70], - disabled: Colors.neutral[20], - }, - color: { - normal: Colors.neutral[90], - hover: Colors.neutral[90], - active: Colors.neutral[90], - disabled: Colors.neutral[20], - }, - }, switch: { unchecked: Colors.brand[20], checked: Colors.brand[50],
{task.status?.id} {task.status?.workerId} - {task.status.state} + + {task.status.state} + {task.status.trace || 'null'} diff --git a/kafka-ui-react-app/src/components/Connect/List/ListItem.tsx b/kafka-ui-react-app/src/components/Connect/List/ListItem.tsx index 7260969ba1..4d10001947 100644 --- a/kafka-ui-react-app/src/components/Connect/List/ListItem.tsx +++ b/kafka-ui-react-app/src/components/Connect/List/ListItem.tsx @@ -72,7 +72,9 @@ const ListItem: React.FC = ({ ))} {status && {status.state}} + {status && {status.state}} + {runningTasks && ( diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx index b87035ae7d..74da5c0b09 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx @@ -82,7 +82,9 @@ const Details: React.FC = () => { - {consumerGroup.state} + + {consumerGroup.state} + {consumerGroup.members} diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/ConsumerGroupsTableCells.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/ConsumerGroupsTableCells.tsx deleted file mode 100644 index f4295600c1..0000000000 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/ConsumerGroupsTableCells.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { Tag } from 'components/common/Tag/Tag.styled'; -import { TableCellProps } from 'components/common/SmartTable/TableColumn'; -import { ConsumerGroup } from 'generated-sources'; -import { SmartTableKeyLink } from 'components/common/table/Table/TableKeyLink.styled'; -import getTagColor from 'components/common/Tag/getTagColor'; - -export const StatusCell: React.FC> = ({ - dataItem, -}) => { - return {dataItem.state}; -}; - -export const GroupIDCell: React.FC> = ({ - dataItem: { groupId }, -}) => { - return ( - - {groupId} - - ); -}; diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx index a73afc2041..3939f111ff 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx @@ -7,75 +7,84 @@ import { ConsumerGroupOrdering, SortOrder, } from 'generated-sources'; -import { useTableState } from 'lib/hooks/useTableState'; -import { SmartTable } from 'components/common/SmartTable/SmartTable'; -import { TableColumn } from 'components/common/SmartTable/TableColumn'; -import { - GroupIDCell, - StatusCell, -} from 'components/ConsumerGroups/List/ConsumerGroupsTableCells'; -import usePagination from 'lib/hooks/usePagination'; import useSearch from 'lib/hooks/useSearch'; import { useAppDispatch } from 'lib/hooks/redux'; import useAppParams from 'lib/hooks/useAppParams'; -import { ClusterNameRoute } from 'lib/paths'; +import { clusterConsumerGroupDetailsPath, ClusterNameRoute } from 'lib/paths'; import { fetchConsumerGroupsPaged } from 'redux/reducers/consumerGroups/consumerGroupsSlice'; -import PageLoader from 'components/common/PageLoader/PageLoader'; +import { ColumnDef } from '@tanstack/react-table'; +import Table, { TagCell, LinkCell } from 'components/common/NewTable'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { PER_PAGE } from 'lib/constants'; export interface Props { consumerGroups: ConsumerGroupDetails[]; - orderBy: string | null; - sortOrder: SortOrder; totalPages: number; - isFetched: boolean; - setConsumerGroupsSortOrderBy(orderBy: string | null): void; } -const List: React.FC = ({ - consumerGroups, - sortOrder, - orderBy, - totalPages, - isFetched, - setConsumerGroupsSortOrderBy, -}) => { - const { page, perPage } = usePagination(); +const List: React.FC = ({ consumerGroups, totalPages }) => { const [searchText, handleSearchText] = useSearch(); const dispatch = useAppDispatch(); const { clusterName } = useAppParams(); + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); React.useEffect(() => { dispatch( fetchConsumerGroupsPaged({ clusterName, - orderBy: (orderBy as ConsumerGroupOrdering) || undefined, - sortOrder, - page, - perPage, + orderBy: + (searchParams.get('sortBy') as ConsumerGroupOrdering) || undefined, + sortOrder: + (searchParams.get('sortDirection')?.toUpperCase() as SortOrder) || + undefined, + page: Number(searchParams.get('page') || 1), + perPage: Number(searchParams.get('perPage') || PER_PAGE), search: searchText, }) ); - }, [clusterName, orderBy, searchText, sortOrder, page, perPage, dispatch]); + }, [clusterName, searchText, dispatch, searchParams]); - const tableState = useTableState( - consumerGroups, - { - totalPages, - idSelector: (consumerGroup) => consumerGroup.groupId, - }, - { - handleOrderBy: setConsumerGroupsSortOrderBy, - orderBy, - sortOrder, - } + const columns = React.useMemo[]>( + () => [ + { + id: ConsumerGroupOrdering.NAME, + header: 'Group ID', + accessorKey: 'groupId', + cell: LinkCell, + }, + { + id: ConsumerGroupOrdering.MEMBERS, + header: 'Num Of Members', + accessorKey: 'members', + }, + { + header: 'Num Of Topics', + accessorKey: 'topics', + enableSorting: false, + }, + { + header: 'Messages Behind', + accessorKey: 'messagesBehind', + enableSorting: false, + }, + { + header: 'Coordinator', + accessorKey: 'coordinator.id', + enableSorting: false, + }, + { + id: ConsumerGroupOrdering.STATE, + header: 'State', + accessorKey: 'state', + cell: TagCell, + }, + ], + [] ); - if (!isFetched) { - return ; - } - return ( -
+ <> = ({ handleSearch={handleSearchText} /> - - - - - - - - -
+ + navigate( + clusterConsumerGroupDetailsPath(clusterName, original.groupId) + ) + } + /> + ); }; diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.tsx index f72ae2c16d..6c819eff4a 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.tsx @@ -2,24 +2,15 @@ import { connect } from 'react-redux'; import { RootState } from 'redux/interfaces'; import { getConsumerGroupsOrderBy, - getConsumerGroupsSortOrder, getConsumerGroupsTotalPages, - sortBy, selectAll, - getAreConsumerGroupsPagedFulfilled, } from 'redux/reducers/consumerGroups/consumerGroupsSlice'; import List from 'components/ConsumerGroups/List/List'; const mapStateToProps = (state: RootState) => ({ consumerGroups: selectAll(state), orderBy: getConsumerGroupsOrderBy(state), - sortOrder: getConsumerGroupsSortOrder(state), totalPages: getConsumerGroupsTotalPages(state), - isFetched: getAreConsumerGroupsPagedFulfilled(state), }); -const mapDispatchToProps = { - setConsumerGroupsSortOrderBy: sortBy, -}; - -export default connect(mapStateToProps, mapDispatchToProps)(List); +export default connect(mapStateToProps)(List); diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx deleted file mode 100644 index 9078487cef..0000000000 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { ConsumerGroup } from 'generated-sources'; -import { Tag } from 'components/common/Tag/Tag.styled'; -import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.styled'; -import getTagColor from 'components/common/Tag/getTagColor'; - -const ListItem: React.FC<{ consumerGroup: ConsumerGroup }> = ({ - consumerGroup, -}) => { - return ( - - - - {consumerGroup.groupId} - - - - - - - - - ); -}; - -export default ListItem; diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ConsumerGroupsTableCells.spec.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ConsumerGroupsTableCells.spec.tsx deleted file mode 100644 index 9effdd5b5e..0000000000 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ConsumerGroupsTableCells.spec.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { render } from 'lib/testHelpers'; -import { - GroupIDCell, - StatusCell, -} from 'components/ConsumerGroups/List/ConsumerGroupsTableCells'; -import { TableState } from 'lib/hooks/useTableState'; -import { ConsumerGroup, ConsumerGroupState } from 'generated-sources'; -import { screen } from '@testing-library/react'; - -describe('Consumer Groups Table Cells', () => { - const consumerGroup: ConsumerGroup = { - groupId: 'groupId', - members: 1, - topics: 1, - simple: true, - state: ConsumerGroupState.STABLE, - coordinator: { - id: 6598, - }, - }; - const mockTableState: TableState = { - data: [consumerGroup], - selectedIds: new Set([]), - idSelector: jest.fn(), - isRowSelectable: jest.fn(), - selectedCount: 0, - setRowsSelection: jest.fn(), - toggleSelection: jest.fn(), - }; - - describe('StatusCell', () => { - it('should Tag props render normally', () => { - render( - - ); - const linkElement = screen.getByRole('link'); - expect(linkElement).toBeInTheDocument(); - expect(linkElement).toHaveAttribute('href', `/${consumerGroup.groupId}`); - }); - }); - - describe('GroupIdCell', () => { - it('should GroupIdCell props render normally', () => { - render( - - ); - expect( - screen.getByText(consumerGroup.state as string) - ).toBeInTheDocument(); - }); - }); -}); diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx index 04b78a983b..500549c0aa 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx @@ -1,77 +1,59 @@ import React from 'react'; import List, { Props } from 'components/ConsumerGroups/List/List'; import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import { render } from 'lib/testHelpers'; import { consumerGroups as consumerGroupMock } from 'redux/reducers/consumerGroups/__test__/fixtures'; -import { ConsumerGroupOrdering, SortOrder } from 'generated-sources'; -import theme from 'theme/theme'; +import { clusterConsumerGroupDetailsPath } from 'lib/paths'; +import userEvent from '@testing-library/user-event'; +import ListContainer from 'components/ConsumerGroups/List/ListContainer'; + +const mockedUsedNavigate = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockedUsedNavigate, +})); + +describe('ListContainer', () => { + it('renders correctly', () => { + render(); + expect(screen.getByRole('table')).toBeInTheDocument(); + }); +}); describe('List', () => { - const setUpComponent = (props: Partial = {}) => { - const { - consumerGroups, - orderBy, - sortOrder, - totalPages, - setConsumerGroupsSortOrderBy, - } = props; + const renderComponent = (props: Partial = {}) => { + const { consumerGroups, totalPages } = props; return render( ); }; it('renders empty table', () => { - setUpComponent(); + renderComponent(); expect(screen.getByRole('table')).toBeInTheDocument(); - expect(screen.getByText('No active consumer groups')).toBeInTheDocument(); + expect( + screen.getByText('No active consumer groups found') + ).toBeInTheDocument(); }); describe('consumerGroups are fetched', () => { - beforeEach(() => setUpComponent({ consumerGroups: consumerGroupMock })); + beforeEach(() => renderComponent({ consumerGroups: consumerGroupMock })); it('renders all rows with consumers', () => { expect(screen.getByText('groupId1')).toBeInTheDocument(); expect(screen.getByText('groupId2')).toBeInTheDocument(); }); - describe('Testing the Ordering', () => { - it('should test the sort order functionality', async () => { - const thElement = screen.getByText(/consumer group id/i); - expect(thElement).toBeInTheDocument(); - expect(thElement).toHaveStyle(`color:${theme.table.th.color.active}`); - }); - }); - }); - - describe('consumerGroups are fetched with custom parameters', () => { - it('should test the order by functionality of another element', async () => { - const sortOrder = jest.fn(); - setUpComponent({ - consumerGroups: consumerGroupMock, - setConsumerGroupsSortOrderBy: sortOrder, - }); - const thElement = screen.getByText(/num of members/i); - expect(thElement).toBeInTheDocument(); - - userEvent.click(thElement); - expect(sortOrder).toBeCalled(); - }); - - it('should view the ordered list with the right prop', () => { - setUpComponent({ - consumerGroups: consumerGroupMock, - orderBy: ConsumerGroupOrdering.MEMBERS, - }); - expect(screen.getByText(/num of members/i)).toHaveStyle( - `color:${theme.table.th.color.active}` + it('handles onRowClick', () => { + const row = screen.getByRole('row', { name: 'groupId1 0 1 1' }); + expect(row).toBeInTheDocument(); + userEvent.click(row); + expect(mockedUsedNavigate).toHaveBeenCalledWith( + clusterConsumerGroupDetailsPath(':clusterName', 'groupId1') ); }); }); diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ListItem.spec.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ListItem.spec.tsx deleted file mode 100644 index 856271f574..0000000000 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ListItem.spec.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import ListItem from 'components/ConsumerGroups/List/ListItem'; -import { ConsumerGroupState, ConsumerGroup } from 'generated-sources'; -import { screen } from '@testing-library/react'; -import { render } from 'lib/testHelpers'; - -describe('List', () => { - const mockConsumerGroup = { - groupId: 'groupId', - members: 0, - topics: 1, - simple: false, - partitionAssignor: '', - coordinator: { - id: 1, - host: 'host', - }, - partitions: [ - { - consumerId: null, - currentOffset: 0, - endOffset: 0, - host: null, - messagesBehind: 0, - partition: 1, - topic: 'topic', - }, - ], - }; - const setupWrapper = (consumerGroup: ConsumerGroup) => ( -
{consumerGroup.members}{consumerGroup.topics}{consumerGroup.messagesBehind}{consumerGroup.coordinator?.id} - {consumerGroup.state} -
- - - -
- ); - - const getCell = () => screen.getAllByRole('cell')[5]; - - it('render empty ListItem', () => { - render(setupWrapper(mockConsumerGroup)); - expect(screen.getByRole('row')).toBeInTheDocument(); - }); - - it('renders item with stable status', () => { - render( - setupWrapper({ - ...mockConsumerGroup, - state: ConsumerGroupState.STABLE, - }) - ); - expect(screen.getByRole('row')).toHaveTextContent( - ConsumerGroupState.STABLE - ); - }); - - it('renders item with dead status', () => { - render( - setupWrapper({ - ...mockConsumerGroup, - state: ConsumerGroupState.DEAD, - }) - ); - expect(getCell()).toHaveTextContent(ConsumerGroupState.DEAD); - }); - - it('renders item with empty status', () => { - render( - setupWrapper({ - ...mockConsumerGroup, - state: ConsumerGroupState.EMPTY, - }) - ); - expect(getCell()).toHaveTextContent(ConsumerGroupState.EMPTY); - }); - - it('renders item with empty-string status', () => { - render( - setupWrapper({ - ...mockConsumerGroup, - state: ConsumerGroupState.UNKNOWN, - }) - ); - expect(getCell()).toHaveTextContent(ConsumerGroupState.UNKNOWN); - }); -}); diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx index 7aedbd038c..0e319e5bd3 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx @@ -1,22 +1,27 @@ import React from 'react'; -import { clusterConsumerGroupsPath, getNonExactPath } from 'lib/paths'; import { - act, - screen, - waitFor, - waitForElementToBeRemoved, -} from '@testing-library/react'; + clusterConsumerGroupDetailsPath, + clusterConsumerGroupResetOffsetsPath, + clusterConsumerGroupsPath, + getNonExactPath, +} from 'lib/paths'; +import { screen } from '@testing-library/react'; import ConsumerGroups from 'components/ConsumerGroups/ConsumerGroups'; -import { - consumerGroups, - noConsumerGroupsResponse, -} from 'redux/reducers/consumerGroups/__test__/fixtures'; import { render, WithRoute } from 'lib/testHelpers'; -import fetchMock from 'fetch-mock'; -import { ConsumerGroupOrdering, SortOrder } from 'generated-sources'; const clusterName = 'cluster1'; +jest.mock('components/ConsumerGroups/List/ListContainer', () => () => ( +
ListContainerMock
+)); +jest.mock('components/ConsumerGroups/Details/Details', () => () => ( +
DetailsMock
+)); +jest.mock( + 'components/ConsumerGroups/Details/ResetOffsets/ResetOffsets', + () => () =>
ResetOffsetsMock
+); + const renderComponent = (path?: string) => render( @@ -28,104 +33,18 @@ const renderComponent = (path?: string) => ); describe('ConsumerGroups', () => { - it('renders with initial state', async () => { + it('renders ListContainer', async () => { renderComponent(); - expect(screen.getByRole('progressbar')).toBeInTheDocument(); + expect(screen.getByText('ListContainerMock')).toBeInTheDocument(); }); - - describe('Default Route and Fetching Consumer Groups', () => { - const url = `/api/clusters/${clusterName}/consumer-groups/paged`; - afterEach(() => { - fetchMock.reset(); - }); - - it('renders empty table on no consumer group response', async () => { - fetchMock.getOnce(url, noConsumerGroupsResponse, { - query: { - orderBy: ConsumerGroupOrdering.NAME, - sortOrder: SortOrder.ASC, - }, - }); - await act(() => { - renderComponent(); - }); - expect(fetchMock.calls().length).toBe(1); - expect(screen.getByRole('table')).toBeInTheDocument(); - expect(screen.getByText('No active consumer groups')).toBeInTheDocument(); - }); - - it('renders with 404 from consumer groups', async () => { - const consumerGroupsMock = fetchMock.getOnce(url, 404, { - query: { - orderBy: ConsumerGroupOrdering.NAME, - sortOrder: SortOrder.ASC, - }, - }); - - renderComponent(); - - await waitFor(() => expect(consumerGroupsMock.called()).toBeTruthy()); - - expect(screen.queryByText('Consumers')).not.toBeInTheDocument(); - expect(screen.queryByRole('table')).not.toBeInTheDocument(); - }); - - it('renders with 200 from consumer groups', async () => { - const consumerGroupsMock = fetchMock.getOnce( - url, - { - pagedCount: 1, - consumerGroups, - }, - { - query: { - orderBy: ConsumerGroupOrdering.NAME, - sortOrder: SortOrder.ASC, - }, - } - ); - - renderComponent(); - - await waitForElementToBeRemoved(() => screen.getByRole('progressbar')); - await waitFor(() => expect(consumerGroupsMock.called()).toBeTruthy()); - - expect(screen.getByText('Consumers')).toBeInTheDocument(); - expect(screen.getByRole('table')).toBeInTheDocument(); - expect(screen.getByText(consumerGroups[0].groupId)).toBeInTheDocument(); - expect(screen.getByText(consumerGroups[1].groupId)).toBeInTheDocument(); - }); - - it('renders with 200 from consumer groups with Searched Query ', async () => { - const searchResult = consumerGroups[0]; - const searchText = searchResult.groupId; - - const consumerGroupsMock = fetchMock.getOnce( - url, - { - pagedCount: 1, - consumerGroups: [searchResult], - }, - { - query: { - orderBy: ConsumerGroupOrdering.NAME, - sortOrder: SortOrder.ASC, - search: searchText, - }, - } - ); - - renderComponent( - `${clusterConsumerGroupsPath(clusterName)}?q=${searchText}` - ); - - await waitForElementToBeRemoved(() => screen.getByRole('progressbar')); - await waitFor(() => expect(consumerGroupsMock.called()).toBeTruthy()); - - expect(screen.getByText(searchText)).toBeInTheDocument(); - expect( - screen.queryByText(consumerGroups[1].groupId) - ).not.toBeInTheDocument(); - }); + it('renders ResetOffsets', async () => { + renderComponent( + clusterConsumerGroupResetOffsetsPath(clusterName, 'groupId1') + ); + expect(screen.getByText('ResetOffsetsMock')).toBeInTheDocument(); + }); + it('renders Details', async () => { + renderComponent(clusterConsumerGroupDetailsPath(clusterName, 'groupId1')); + expect(screen.getByText('DetailsMock')).toBeInTheDocument(); }); }); diff --git a/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/KsqlDbItem.tsx b/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/KsqlDbItem.tsx index 8ea83b5490..bbb0844d68 100644 --- a/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/KsqlDbItem.tsx +++ b/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/KsqlDbItem.tsx @@ -1,10 +1,9 @@ import React from 'react'; import PageLoader from 'components/common/PageLoader/PageLoader'; import { KsqlStreamDescription, KsqlTableDescription } from 'generated-sources'; -import { useTableState } from 'lib/hooks/useTableState'; -import { SmartTable } from 'components/common/SmartTable/SmartTable'; -import { TableColumn } from 'components/common/SmartTable/TableColumn'; import { ksqlRowData } from 'components/KsqlDb/List/KsqlDbItem/utils/ksqlRowData'; +import Table from 'components/common/NewTable'; +import { ColumnDef } from '@tanstack/react-table'; export enum KsqlDbItemType { Tables = 'tables', @@ -31,27 +30,28 @@ export interface KsqlTableState { const KsqlDbItem: React.FC = ({ type, fetching, rows }) => { const preparedRows = rows[type]?.map(ksqlRowData) || []; - const tableState = useTableState(preparedRows, { - idSelector: ({ name }) => name, - totalPages: 0, - }); + + const columns = React.useMemo[]>( + () => [ + { header: 'Name', accessorKey: 'name' }, + { header: 'Topic', accessorKey: 'topic' }, + { header: 'Key Format', accessorKey: 'keyFormat' }, + { header: 'Value Format', accessorKey: 'valueFormat' }, + { header: 'Is Windowed', accessorKey: 'isWindowed' }, + ], + [] + ); if (fetching) { return ; } return ( - - - - - - - + ); }; diff --git a/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/__test__/KsqlDbItem.spec.tsx b/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/__test__/KsqlDbItem.spec.tsx index e1be151503..366f01c020 100644 --- a/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/__test__/KsqlDbItem.spec.tsx +++ b/kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/__test__/KsqlDbItem.spec.tsx @@ -7,59 +7,56 @@ import KsqlDbItem, { } from 'components/KsqlDb/List/KsqlDbItem/KsqlDbItem'; import { screen } from '@testing-library/dom'; import { fetchKsqlDbTablesPayload } from 'redux/reducers/ksqlDb/__test__/fixtures'; +import { act } from '@testing-library/react'; describe('KsqlDbItem', () => { const tablesPathname = clusterKsqlDbTablesPath(); - - const component = (props: Partial = {}) => ( - - - - ); - - it('renders progressbar when fetching tables and streams', () => { - render(component({ fetching: true }), { - initialEntries: [clusterKsqlDbTablesPath()], - }); - expect(screen.getByRole('progressbar')).toBeInTheDocument(); - }); - it('show no text if no data found', () => { - render(component({}), { - initialEntries: [clusterKsqlDbTablesPath()], - }); - expect(screen.getByText('No tables or streams found')).toBeInTheDocument(); - }); - it('renders with tables', () => { + const renderComponent = (props: Partial = {}) => { render( - component({ - rows: { - tables: fetchKsqlDbTablesPayload.tables, - streams: [], - }, - }), + + + , { initialEntries: [clusterKsqlDbTablesPath()], } ); + }; + + it('renders progressbar when fetching tables and streams', async () => { + await act(() => renderComponent({ fetching: true })); + expect(screen.getByRole('progressbar')).toBeInTheDocument(); + }); + + it('show no text if no data found', async () => { + await act(() => renderComponent({})); + expect(screen.getByText('No tables or streams found')).toBeInTheDocument(); + }); + + it('renders with tables', async () => { + await act(() => + renderComponent({ + rows: { + tables: fetchKsqlDbTablesPayload.tables, + streams: [], + }, + }) + ); expect(screen.getByRole('table').querySelectorAll('td')).toHaveLength(10); }); - it('renders with streams', () => { - render( - component({ + it('renders with streams', async () => { + await act(() => + renderComponent({ type: KsqlDbItemType.Streams, rows: { tables: [], streams: fetchKsqlDbTablesPayload.streams, }, - }), - { - initialEntries: [clusterKsqlDbTablesPath()], - } + }) ); expect(screen.getByRole('table').querySelectorAll('td')).toHaveLength(10); }); diff --git a/kafka-ui-react-app/src/components/KsqlDb/List/__test__/List.spec.tsx b/kafka-ui-react-app/src/components/KsqlDb/List/__test__/List.spec.tsx index 15be8055ab..b685485b55 100644 --- a/kafka-ui-react-app/src/components/KsqlDb/List/__test__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/KsqlDb/List/__test__/List.spec.tsx @@ -3,15 +3,14 @@ import List from 'components/KsqlDb/List/List'; import { render } from 'lib/testHelpers'; import fetchMock from 'fetch-mock'; import { screen } from '@testing-library/dom'; - -const renderComponent = () => { - render(); -}; +import { act } from '@testing-library/react'; describe('KsqlDb List', () => { afterEach(() => fetchMock.reset()); it('renders List component with Tables and Streams tabs', async () => { - renderComponent(); + await act(() => { + render(); + }); const Tables = screen.getByTitle('Tables'); const Streams = screen.getByTitle('Streams'); diff --git a/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.tsx b/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.tsx index 3054057382..c68380d373 100644 --- a/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.tsx +++ b/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.tsx @@ -2,22 +2,21 @@ import React from 'react'; import Select from 'components/common/Select/Select'; import { CompatibilityLevelCompatibilityEnum } from 'generated-sources'; import { useAppDispatch } from 'lib/hooks/redux'; -import usePagination from 'lib/hooks/usePagination'; -import useSearch from 'lib/hooks/useSearch'; import useAppParams from 'lib/hooks/useAppParams'; import { fetchSchemas } from 'redux/reducers/schemas/schemasSlice'; import { ClusterNameRoute } from 'lib/paths'; import { schemasApiClient } from 'lib/api'; import { showServerError } from 'lib/errorHandling'; import { useConfirm } from 'lib/hooks/useConfirm'; +import { useSearchParams } from 'react-router-dom'; +import { PER_PAGE } from 'lib/constants'; import * as S from './GlobalSchemaSelector.styled'; const GlobalSchemaSelector: React.FC = () => { const { clusterName } = useAppParams(); const dispatch = useAppDispatch(); - const [searchText] = useSearch(); - const { page, perPage } = usePagination(); + const [searchParams] = useSearchParams(); const confirm = useConfirm(); const [currentCompatibilityLevel, setCurrentCompatibilityLevel] = @@ -61,7 +60,12 @@ const GlobalSchemaSelector: React.FC = () => { }); setCurrentCompatibilityLevel(nextLevel); dispatch( - fetchSchemas({ clusterName, page, perPage, search: searchText }) + fetchSchemas({ + clusterName, + page: Number(searchParams.get('page') || 1), + perPage: Number(searchParams.get('perPage') || PER_PAGE), + search: searchParams.get('q') || '', + }) ); } catch (e) { showServerError(e as Response); diff --git a/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/__test__/GlobalSchemaSelector.spec.tsx b/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/__test__/GlobalSchemaSelector.spec.tsx index 34da3886d7..e8e8bcc453 100644 --- a/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/__test__/GlobalSchemaSelector.spec.tsx +++ b/kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/__test__/GlobalSchemaSelector.spec.tsx @@ -82,7 +82,7 @@ describe('GlobalSchemaSelector', () => { } ); const getSchemasMock = fetchMock.getOnce( - `api/clusters/${clusterName}/schemas`, + `api/clusters/${clusterName}/schemas?page=1&perPage=25`, 200 ); await waitFor(() => { diff --git a/kafka-ui-react-app/src/components/Schemas/List/List.tsx b/kafka-ui-react-app/src/components/Schemas/List/List.tsx index f40171a1d4..8a1344f55e 100644 --- a/kafka-ui-react-app/src/components/Schemas/List/List.tsx +++ b/kafka-ui-react-app/src/components/Schemas/List/List.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import { ClusterNameRoute, clusterSchemaNewRelativePath } from 'lib/paths'; +import { + ClusterNameRoute, + clusterSchemaNewRelativePath, + clusterSchemaPath, +} from 'lib/paths'; import ClusterContext from 'components/contexts/ClusterContext'; -import * as C from 'components/common/table/Table/Table.styled'; -import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell'; import { Button } from 'components/common/Button/Button'; import PageHeading from 'components/common/PageHeading/PageHeading'; import { useAppDispatch, useAppSelector } from 'lib/hooks/redux'; @@ -13,36 +15,53 @@ import { getAreSchemasFulfilled, SCHEMAS_FETCH_ACTION, } from 'redux/reducers/schemas/schemasSlice'; -import usePagination from 'lib/hooks/usePagination'; import PageLoader from 'components/common/PageLoader/PageLoader'; -import Pagination from 'components/common/Pagination/Pagination'; import { resetLoaderById } from 'redux/reducers/loader/loaderSlice'; import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled'; import Search from 'components/common/Search/Search'; import useSearch from 'lib/hooks/useSearch'; import PlusIcon from 'components/common/Icons/PlusIcon'; +import Table, { LinkCell } from 'components/common/NewTable'; +import { ColumnDef } from '@tanstack/react-table'; +import { SchemaSubject } from 'generated-sources'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { PER_PAGE } from 'lib/constants'; -import ListItem from './ListItem'; import GlobalSchemaSelector from './GlobalSchemaSelector/GlobalSchemaSelector'; const List: React.FC = () => { const dispatch = useAppDispatch(); const { isReadOnly } = React.useContext(ClusterContext); const { clusterName } = useAppParams(); - + const navigate = useNavigate(); const schemas = useAppSelector(selectAllSchemas); const isFetched = useAppSelector(getAreSchemasFulfilled); const totalPages = useAppSelector((state) => state.schemas.totalPages); - + const [searchParams] = useSearchParams(); const [searchText, handleSearchText] = useSearch(); - const { page, perPage } = usePagination(); React.useEffect(() => { - dispatch(fetchSchemas({ clusterName, page, perPage, search: searchText })); + dispatch( + fetchSchemas({ + clusterName, + page: Number(searchParams.get('page') || 1), + perPage: Number(searchParams.get('perPage') || PER_PAGE), + search: searchParams.get('q') || '', + }) + ); return () => { dispatch(resetLoaderById(SCHEMAS_FETCH_ACTION)); }; - }, [clusterName, dispatch, page, perPage, searchText]); + }, [clusterName, dispatch, searchParams]); + + const columns = React.useMemo[]>( + () => [ + { header: 'Subject', accessorKey: 'subject', cell: LinkCell }, + { header: 'Version', accessorKey: 'version' }, + { header: 'Compatibility', accessorKey: 'compatibilityLevel' }, + ], + [] + ); return ( <> @@ -68,31 +87,16 @@ const List: React.FC = () => { /> {isFetched ? ( - <> - - - - - - - - - - {schemas.length === 0 && ( - - - - )} - {schemas.map((subject) => ( - - ))} - - - - +
No schemas found
+ navigate(clusterSchemaPath(clusterName, row.original.subject)) + } + serverSideProcessing + /> ) : ( )} diff --git a/kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx b/kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx deleted file mode 100644 index be642fd6f1..0000000000 --- a/kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { SchemaSubject } from 'generated-sources'; -import { NavLink } from 'react-router-dom'; -import * as S from 'components/common/table/Table/TableKeyLink.styled'; - -export interface ListItemProps { - subject: SchemaSubject; -} - -const ListItem: React.FC = ({ - subject: { subject, version, compatibilityLevel }, -}) => { - return ( - - - - {subject} - - - - - - ); -}; - -export default ListItem; diff --git a/kafka-ui-react-app/src/components/Schemas/List/__test__/List.spec.tsx b/kafka-ui-react-app/src/components/Schemas/List/__test__/List.spec.tsx index 2f66047229..cefb32819a 100644 --- a/kafka-ui-react-app/src/components/Schemas/List/__test__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/Schemas/List/__test__/List.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import List from 'components/Schemas/List/List'; import { render, WithRoute } from 'lib/testHelpers'; -import { clusterSchemasPath } from 'lib/paths'; +import { clusterSchemaPath, clusterSchemasPath } from 'lib/paths'; import { act, screen } from '@testing-library/react'; import { schemasFulfilledState, @@ -15,12 +15,20 @@ import ClusterContext, { } from 'components/contexts/ClusterContext'; import { RootState } from 'redux/interfaces'; import fetchMock from 'fetch-mock'; +import userEvent from '@testing-library/user-event'; import { schemasPayload, schemasEmptyPayload } from './fixtures'; +const mockedUsedNavigate = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockedUsedNavigate, +})); + const clusterName = 'testClusterName'; -const schemasAPIUrl = `/api/clusters/${clusterName}/schemas`; -const schemasAPICompabilityUrl = `${schemasAPIUrl}/compatibility`; +const schemasAPIUrl = `/api/clusters/${clusterName}/schemas?page=1&perPage=25`; +const schemasAPICompabilityUrl = `/api/clusters/${clusterName}/schemas/compatibility`; const renderComponent = ( initialState: RootState['schemas'] = schemasInitialState, context: ContextProps = contextInitialValue @@ -101,6 +109,17 @@ describe('List', () => { expect(screen.getByText(schemaVersion1.subject)).toBeInTheDocument(); expect(screen.getByText(schemaVersion2.subject)).toBeInTheDocument(); }); + it('handles onRowClick', () => { + const { subject, version, compatibilityLevel } = schemaVersion2; + const row = screen.getByRole('row', { + name: `${subject} ${version} ${compatibilityLevel}`, + }); + expect(row).toBeInTheDocument(); + userEvent.click(row); + expect(mockedUsedNavigate).toHaveBeenCalledWith( + clusterSchemaPath(clusterName, subject) + ); + }); }); describe('responded with readonly cluster schemas', () => { @@ -109,6 +128,7 @@ describe('List', () => { schemasAPIUrl, schemasPayload ); + fetchMock.getOnce(schemasAPICompabilityUrl, 200); await act(() => { renderComponent(schemasFulfilledState, { ...contextInitialValue, diff --git a/kafka-ui-react-app/src/components/Schemas/List/__test__/ListItem.spec.tsx b/kafka-ui-react-app/src/components/Schemas/List/__test__/ListItem.spec.tsx deleted file mode 100644 index 3f3213faae..0000000000 --- a/kafka-ui-react-app/src/components/Schemas/List/__test__/ListItem.spec.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import ListItem, { ListItemProps } from 'components/Schemas/List/ListItem'; -import { screen } from '@testing-library/react'; -import { render } from 'lib/testHelpers'; - -import { schemas } from './fixtures'; - -describe('ListItem', () => { - const setupComponent = (props: ListItemProps = { subject: schemas[0] }) => - render( -
{version}{compatibilityLevel}
- - - -
- ); - - it('renders schemas', () => { - setupComponent(); - expect(screen.getAllByRole('link').length).toEqual(1); - expect(screen.getAllByRole('cell').length).toEqual(3); - }); -}); diff --git a/kafka-ui-react-app/src/components/Schemas/List/__test__/fixtures.ts b/kafka-ui-react-app/src/components/Schemas/List/__test__/fixtures.ts index 61cb7383e8..65bdd99741 100644 --- a/kafka-ui-react-app/src/components/Schemas/List/__test__/fixtures.ts +++ b/kafka-ui-react-app/src/components/Schemas/List/__test__/fixtures.ts @@ -3,7 +3,7 @@ import { schemaVersion2, } from 'redux/reducers/schemas/__test__/fixtures'; -export const schemas = [schemaVersion1, schemaVersion2]; +const schemas = [schemaVersion1, schemaVersion2]; export const schemasPayload = { pageCount: 1, diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx index a5153806c5..82551ac250 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx @@ -44,7 +44,7 @@ const TopicConsumerGroups: React.FC = () => {
{consumer.coordinator?.id} {consumer.state && ( - {`${consumer.state + {`${consumer.state .charAt(0) .toUpperCase()}${consumer.state .slice(1) diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/MessageContent.styled.ts b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/MessageContent.styled.ts index 2f3efd7b53..8b238ab66d 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/MessageContent.styled.ts +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/MessageContent.styled.ts @@ -69,22 +69,6 @@ export const MetadataMeta = styled.p` font-size: 12px; `; -export const PaginationButton = styled.button` - display: flex; - align-items: center; - padding: 6px 12px; - height: 32px; - border: 1px solid ${({ theme }) => theme.pagination.borderColor.normal}; - box-sizing: border-box; - border-radius: 4px; - color: ${({ theme }) => theme.pagination.color.normal}; - background: none; - font-family: Inter; - margin-right: 13px; - cursor: pointer; - font-size: 14px; -`; - export const Tab = styled.button<{ $active?: boolean }>( ({ theme, $active }) => css` background-color: ${theme.secondaryTab.backgroundColor[ diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/__tests__/MessageContent.styled.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/__tests__/MessageContent.styled.spec.tsx deleted file mode 100644 index 526f7c9032..0000000000 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/__tests__/MessageContent.styled.spec.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import * as S from 'components/Topics/Topic/Details/Messages/MessageContent/MessageContent.styled'; -import { render } from 'lib/testHelpers'; -import { screen } from '@testing-library/react'; -import theme from 'theme/theme'; - -describe('MessageContent Styled Components', () => { - describe('PaginationComponent', () => { - beforeEach(() => { - render(); - }); - it('should test the Pagination Button theme related Props', () => { - const button = screen.getByRole('button'); - expect(button).toHaveStyle(`color: ${theme.pagination.color.normal}`); - expect(button).toHaveStyle( - `border: 1px solid ${theme.pagination.borderColor.normal}` - ); - }); - }); -}); diff --git a/kafka-ui-react-app/src/components/common/NewTable/ExpanderCell.tsx b/kafka-ui-react-app/src/components/common/NewTable/ExpanderCell.tsx index e1975abb6a..d53c3d7902 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/ExpanderCell.tsx +++ b/kafka-ui-react-app/src/components/common/NewTable/ExpanderCell.tsx @@ -3,8 +3,7 @@ import React from 'react'; import * as S from './Table.styled'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const ExpanderCell: React.FC> = ({ row }) => ( +const ExpanderCell: React.FC> = ({ row }) => ( > = ({ getValue }) => { + const value = `${getValue()}`; + const handleClick: React.MouseEventHandler = (e) => e.stopPropagation(); + return ( + + {value} + + ); +}; + +export default LinkCell; diff --git a/kafka-ui-react-app/src/components/common/NewTable/SelectRowCell.tsx b/kafka-ui-react-app/src/components/common/NewTable/SelectRowCell.tsx index 3f12583f01..5c212e1d56 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/SelectRowCell.tsx +++ b/kafka-ui-react-app/src/components/common/NewTable/SelectRowCell.tsx @@ -2,8 +2,7 @@ import { CellContext } from '@tanstack/react-table'; import React from 'react'; import IndeterminateCheckbox from 'components/common/IndeterminateCheckbox/IndeterminateCheckbox'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const SelectRowCell: React.FC> = ({ row }) => ( +const SelectRowCell: React.FC> = ({ row }) => ( > = ({ table }) => ( +const SelectRowHeader: React.FC> = ({ + table, +}) => ( > = ({ getValue }) => ( - +const SizeCell: React.FC> = ({ getValue }) => ( + ()} /> ); export default SizeCell; diff --git a/kafka-ui-react-app/src/components/common/NewTable/Table.styled.ts b/kafka-ui-react-app/src/components/common/NewTable/Table.styled.ts index 83f4fdda5c..5fc02176ee 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/Table.styled.ts +++ b/kafka-ui-react-app/src/components/common/NewTable/Table.styled.ts @@ -99,13 +99,13 @@ export const Th = styled.th( ); interface RowProps { - expandable?: boolean; + clickable?: boolean; expanded?: boolean; } export const Row = styled.tr( - ({ theme: { table }, expanded, expandable }) => ` - cursor: ${expandable ? 'pointer' : 'default'}; + ({ theme: { table }, expanded, clickable }) => ` + cursor: ${clickable ? 'pointer' : 'default'}; background-color: ${table.tr.backgroundColor[expanded ? 'hover' : 'normal']}; &:hover { background-color: ${table.tr.backgroundColor.hover}; diff --git a/kafka-ui-react-app/src/components/common/NewTable/Table.tsx b/kafka-ui-react-app/src/components/common/NewTable/Table.tsx index 80f5477fd1..aaf02ea408 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/Table.tsx +++ b/kafka-ui-react-app/src/components/common/NewTable/Table.tsx @@ -28,13 +28,26 @@ export interface TableProps { data: TData[]; pageCount?: number; columns: ColumnDef[]; - renderSubComponent?: React.FC<{ row: Row }>; - getRowCanExpand?: (row: Row) => boolean; + + // Server-side processing: sorting, pagination serverSideProcessing?: boolean; - enableSorting?: boolean; - enableRowSelection?: boolean | ((row: Row) => boolean); - batchActionsBar?: React.FC<{ rows: Row[]; resetRowSelection(): void }>; + + // Expandeble rows + getRowCanExpand?: (row: Row) => boolean; // Enables the ability to expand row. Use `() => true` when want to expand all rows. + renderSubComponent?: React.FC<{ row: Row }>; // Component to render expanded row. + + // Selectable rows + enableRowSelection?: boolean | ((row: Row) => boolean); // Enables the ability to select row. + batchActionsBar?: React.FC<{ rows: Row[]; resetRowSelection(): void }>; // Component to render batch actions bar for slected rows + + // Sorting. + enableSorting?: boolean; // Enables sorting for table. + + // Placeholder for empty table emptyMessage?: string; + + // Handles row click. Can not be combined with `enableRowSelection` && expandable rows. + onRowClick?: (row: Row) => void; } type UpdaterFn = (previousState: T) => T; @@ -72,6 +85,7 @@ const getSortingFromSearchParams = (searchParams: URLSearchParams) => { * `enableSorting = false` to the column def. * - table component stores the sorting state in URLSearchParams. Use `sortBy` and `sortDirection` * search param to set default sortings. + * - use `id` property of the column def to set the sortBy for server side sorting. * * 2. Pagination * - pagination enabled by default. @@ -107,6 +121,7 @@ const Table: React.FC> = ({ enableRowSelection = false, batchActionsBar, emptyMessage, + onRowClick, }) => { const [searchParams, setSearchParams] = useSearchParams(); const [rowSelection, setRowSelection] = React.useState({}); @@ -157,6 +172,24 @@ const Table: React.FC> = ({ const Bar = batchActionsBar; + const handleRowClick = (row: Row) => (e: React.MouseEvent) => { + // If row selection is enabled do not handle row click. + if (enableRowSelection) return undefined; + + // If row can be expanded do not handle row click. + if (row.getCanExpand()) { + e.stopPropagation(); + return row.toggleExpanded(); + } + + if (onRowClick) { + e.stopPropagation(); + return onRowClick(row); + } + + return undefined; + }; + return ( <> {table.getSelectedRowModel().flatRows.length > 0 && Bar && ( @@ -205,9 +238,12 @@ const Table: React.FC> = ({ {table.getRowModel().rows.map((row) => ( row.getCanExpand() && row.toggleExpanded()} + onClick={handleRowClick(row)} + clickable={ + !enableRowSelection && + (row.getCanExpand() || onRowClick !== undefined) + } > {!!enableRowSelection && ( diff --git a/kafka-ui-react-app/src/components/common/NewTable/TagCell.tsx b/kafka-ui-react-app/src/components/common/NewTable/TagCell.tsx new file mode 100644 index 0000000000..c78d64a2de --- /dev/null +++ b/kafka-ui-react-app/src/components/common/NewTable/TagCell.tsx @@ -0,0 +1,12 @@ +import { CellContext } from '@tanstack/react-table'; +import React from 'react'; +import getTagColor from 'components/common/Tag/getTagColor'; +import { Tag } from 'components/common/Tag/Tag.styled'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const TagCell: React.FC> = ({ getValue }) => { + const value = getValue(); + return {value}; +}; + +export default TagCell; diff --git a/kafka-ui-react-app/src/components/common/NewTable/TimestampCell.tsx b/kafka-ui-react-app/src/components/common/NewTable/TimestampCell.tsx index 9c7475274c..f0ff3d5f65 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/TimestampCell.tsx +++ b/kafka-ui-react-app/src/components/common/NewTable/TimestampCell.tsx @@ -5,8 +5,8 @@ import React from 'react'; import * as S from './Table.styled'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -const TimestampCell: React.FC> = ({ getValue }) => ( - {formatTimestamp(getValue())} +const TimestampCell: React.FC> = ({ getValue }) => ( + {formatTimestamp(getValue())} ); export default TimestampCell; diff --git a/kafka-ui-react-app/src/components/common/NewTable/__test__/Table.spec.tsx b/kafka-ui-react-app/src/components/common/NewTable/__test__/Table.spec.tsx index 3e4dddc410..b6ca981141 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/__test__/Table.spec.tsx +++ b/kafka-ui-react-app/src/components/common/NewTable/__test__/Table.spec.tsx @@ -4,20 +4,54 @@ import Table, { TableProps, TimestampCell, SizeCell, + LinkCell, + TagCell, } from 'components/common/NewTable'; import { screen, waitFor } from '@testing-library/dom'; import { ColumnDef, Row } from '@tanstack/react-table'; import userEvent from '@testing-library/user-event'; import { formatTimestamp } from 'lib/dateTimeHelpers'; import { act } from '@testing-library/react'; +import { ConnectorState, ConsumerGroupState } from 'generated-sources'; + +const mockedUsedNavigate = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockedUsedNavigate, +})); type Datum = typeof data[0]; const data = [ - { timestamp: 1660034383725, text: 'lorem', selectable: false, size: 1234 }, - { timestamp: 1660034399999, text: 'ipsum', selectable: true, size: 3 }, - { timestamp: 1660034399922, text: 'dolor', selectable: true, size: 50000 }, - { timestamp: 1660034199922, text: 'sit', selectable: false, size: 1_312_323 }, + { + timestamp: 1660034383725, + text: 'lorem', + selectable: false, + size: 1234, + tag: ConnectorState.RUNNING, + }, + { + timestamp: 1660034399999, + text: 'ipsum', + selectable: true, + size: 3, + tag: ConnectorState.FAILED, + }, + { + timestamp: 1660034399922, + text: 'dolor', + selectable: true, + size: 50000, + tag: ConsumerGroupState.EMPTY, + }, + { + timestamp: 1660034199922, + text: 'sit', + selectable: false, + size: 1_312_323, + tag: 'some_string', + }, ]; const columns: ColumnDef[] = [ @@ -29,12 +63,18 @@ const columns: ColumnDef[] = [ { header: 'Text', accessorKey: 'text', + cell: LinkCell, }, { header: 'Size', accessorKey: 'size', cell: SizeCell, }, + { + header: 'Tag', + accessorKey: 'tag', + cell: TagCell, + }, ]; const ExpandedRow: React.FC = () =>
I am expanded row
; @@ -45,7 +85,7 @@ interface Props extends TableProps { const renderComponent = (props: Partial = {}) => { render( - + { ).toBeInTheDocument(); }); + describe('LinkCell', () => { + it('renders link', () => { + renderComponent(); + expect(screen.getByRole('link', { name: 'lorem' })).toBeInTheDocument(); + }); + + it('link click stops propagation', () => { + const onRowClick = jest.fn(); + renderComponent({ onRowClick }); + const link = screen.getByRole('link', { name: 'lorem' }); + userEvent.click(link); + expect(onRowClick).not.toHaveBeenCalled(); + }); + }); + describe('ExpanderCell', () => { it('renders button', () => { renderComponent({ getRowCanExpand: () => true }); @@ -116,6 +171,14 @@ describe('Table', () => { }); }); + it('renders TagCell', () => { + renderComponent(); + expect(screen.getByText(data[0].tag)).toBeInTheDocument(); + expect(screen.getByText(data[1].tag)).toBeInTheDocument(); + expect(screen.getByText(data[2].tag)).toBeInTheDocument(); + expect(screen.getByText(data[3].tag)).toBeInTheDocument(); + }); + describe('Pagination', () => { it('does not render page buttons', () => { renderComponent(); @@ -240,4 +303,34 @@ describe('Table', () => { expect(screen.getByText('I am Action Bar')).toBeInTheDocument(); }); }); + describe('Clickable Row', () => { + const onRowClick = jest.fn(); + it('handles onRowClick', () => { + renderComponent({ onRowClick }); + const rows = screen.getAllByRole('row'); + expect(rows.length).toEqual(data.length + 1); + userEvent.click(rows[1]); + expect(onRowClick).toHaveBeenCalledTimes(1); + }); + it('does nothing unless onRowClick is provided', () => { + renderComponent(); + const rows = screen.getAllByRole('row'); + expect(rows.length).toEqual(data.length + 1); + userEvent.click(rows[1]); + }); + it('does not handle onRowClick if enableRowSelection', () => { + renderComponent({ onRowClick, enableRowSelection: true }); + const rows = screen.getAllByRole('row'); + expect(rows.length).toEqual(data.length + 1); + userEvent.click(rows[1]); + expect(onRowClick).not.toHaveBeenCalled(); + }); + it('does not handle onRowClick if expandable rows', () => { + renderComponent({ onRowClick, getRowCanExpand: () => true }); + const rows = screen.getAllByRole('row'); + expect(rows.length).toEqual(data.length + 1); + userEvent.click(rows[1]); + expect(onRowClick).not.toHaveBeenCalled(); + }); + }); }); diff --git a/kafka-ui-react-app/src/components/common/NewTable/index.ts b/kafka-ui-react-app/src/components/common/NewTable/index.ts index 08a5f85ad1..4584db2a56 100644 --- a/kafka-ui-react-app/src/components/common/NewTable/index.ts +++ b/kafka-ui-react-app/src/components/common/NewTable/index.ts @@ -1,9 +1,11 @@ import Table, { TableProps } from './Table'; import TimestampCell from './TimestampCell'; import SizeCell from './SizeCell'; +import LinkCell from './LinkCell'; +import TagCell from './TagCell'; export type { TableProps }; -export { TimestampCell, SizeCell }; +export { TimestampCell, SizeCell, LinkCell, TagCell }; export default Table; diff --git a/kafka-ui-react-app/src/components/common/Pagination/PageControl.tsx b/kafka-ui-react-app/src/components/common/Pagination/PageControl.tsx deleted file mode 100644 index 87254a7222..0000000000 --- a/kafka-ui-react-app/src/components/common/Pagination/PageControl.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -import { PaginationLink } from './Pagination.styled'; - -export interface PageControlProps { - current: boolean; - url: string; - page: number; -} - -const PageControl: React.FC = ({ current, url, page }) => { - return ( -
  • - - {page} - -
  • - ); -}; - -export default PageControl; diff --git a/kafka-ui-react-app/src/components/common/Pagination/Pagination.styled.ts b/kafka-ui-react-app/src/components/common/Pagination/Pagination.styled.ts deleted file mode 100644 index 5551e311a2..0000000000 --- a/kafka-ui-react-app/src/components/common/Pagination/Pagination.styled.ts +++ /dev/null @@ -1,87 +0,0 @@ -import styled from 'styled-components'; -import { Link } from 'react-router-dom'; -import theme from 'theme/theme'; - -export const Wrapper = styled.nav` - display: flex; - align-items: flex-end; - padding: 25px 16px; - gap: 15px; - - & > ul { - display: flex; - align-items: flex-end; - - & > li:not(:last-child) { - margin-right: 12px; - } - } -`; - -export const PaginationLink = styled(Link)<{ $isCurrent: boolean }>` - display: flex; - justify-content: center; - align-items: center; - - height: 32px; - width: 33px; - - border-radius: 4px; - border: 1px solid - ${({ $isCurrent }) => - $isCurrent - ? theme.pagination.currentPage - : theme.pagination.borderColor.normal}; - background-color: ${({ $isCurrent }) => - $isCurrent - ? theme.pagination.currentPage - : theme.pagination.backgroundColor}; - color: ${theme.pagination.color.normal}; - - &:hover { - border: 1px solid - ${({ $isCurrent }) => - $isCurrent - ? theme.pagination.currentPage - : theme.pagination.borderColor.hover}; - color: ${(props) => props.theme.pagination.color.hover}; - cursor: ${({ $isCurrent }) => ($isCurrent ? 'default' : 'pointer')}; - } -`; - -export const PaginationButton = styled(Link)` - display: flex; - align-items: center; - padding: 6px 12px; - height: 32px; - border: 1px solid ${theme.pagination.borderColor.normal}; - border-radius: 4px; - color: ${theme.pagination.color.normal}; - - &:hover { - border: 1px solid ${theme.pagination.borderColor.hover}; - color: ${theme.pagination.color.hover}; - cursor: pointer; - } - &:active { - border: 1px solid ${theme.pagination.borderColor.active}; - color: ${theme.pagination.color.active}; - } - &:disabled { - border: 1px solid ${theme.pagination.borderColor.disabled}; - color: ${theme.pagination.color.disabled}; - cursor: not-allowed; - } -`; - -export const DisabledButton = styled.button` - display: flex; - align-items: center; - padding: 6px 12px; - height: 32px; - border: 1px solid ${theme.pagination.borderColor.disabled}; - background-color: ${theme.pagination.backgroundColor}; - border-radius: 4px; - font-size: 16px; - color: ${theme.pagination.color.disabled}; -`; diff --git a/kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx b/kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx deleted file mode 100644 index 46322856a2..0000000000 --- a/kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { PER_PAGE } from 'lib/constants'; -import usePagination from 'lib/hooks/usePagination'; -import range from 'lodash/range'; -import React from 'react'; -import PageControl from 'components/common/Pagination/PageControl'; -import { useSearchParams } from 'react-router-dom'; - -import * as S from './Pagination.styled'; - -export interface PaginationProps { - totalPages: number; -} - -const NEIGHBOURS = 2; - -const Pagination: React.FC = ({ totalPages }) => { - const { page, perPage, pathname } = usePagination(); - const [searchParams] = useSearchParams(); - - const currentPage = page || 1; - const currentPerPage = perPage || PER_PAGE; - - const getPath = (newPage: number) => { - searchParams.set('page', Math.max(newPage, 1).toString()); - searchParams.set('perPage', currentPerPage.toString()); - return `${pathname}?${searchParams.toString()}`; - }; - - const pages = React.useMemo(() => { - // Total visible numbers: neighbours, current, first & last - const totalNumbers = NEIGHBOURS * 2 + 3; - // totalNumbers + `...`*2 - const totalBlocks = totalNumbers + 2; - - if (totalPages <= totalBlocks) { - return range(1, totalPages + 1); - } - - const startPage = Math.max( - 2, - Math.min(currentPage - NEIGHBOURS, totalPages) - ); - const endPage = Math.min( - totalPages - 1, - Math.min(currentPage + NEIGHBOURS, totalPages) - ); - - let p = range(startPage, endPage + 1); - - const hasLeftSpill = startPage > 2; - const hasRightSpill = totalPages - endPage > 1; - const spillOffset = totalNumbers - (p.length + 1); - - switch (true) { - case hasLeftSpill && !hasRightSpill: { - p = [...range(startPage - spillOffset - 1, startPage - 1), ...p]; - break; - } - - case !hasLeftSpill && hasRightSpill: { - p = [...p, ...range(endPage + 1, endPage + spillOffset + 1)]; - break; - } - - default: - break; - } - - return p; - }, [currentPage, totalPages]); - - return ( - - {currentPage > 1 ? ( - - Previous - - ) : ( - Previous - )} - {totalPages > 1 && ( -
      - {!pages.includes(1) && ( - - )} - {!pages.includes(2) && ( -
    • - -
    • - )} - {pages.map((p) => ( - - ))} - {!pages.includes(totalPages - 1) && ( -
    • - -
    • - )} - {!pages.includes(totalPages) && ( - - )} -
    - )} - {currentPage < totalPages ? ( - - Next - - ) : ( - Next - )} -
    - ); -}; - -export default Pagination; diff --git a/kafka-ui-react-app/src/components/common/Pagination/__tests__/PageControl.spec.tsx b/kafka-ui-react-app/src/components/common/Pagination/__tests__/PageControl.spec.tsx deleted file mode 100644 index 5e2c37471c..0000000000 --- a/kafka-ui-react-app/src/components/common/Pagination/__tests__/PageControl.spec.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import PageControl, { - PageControlProps, -} from 'components/common/Pagination/PageControl'; -import { screen } from '@testing-library/react'; -import { render } from 'lib/testHelpers'; -import theme from 'theme/theme'; - -const page = 138; - -describe('PageControl', () => { - const setupComponent = (props: Partial = {}) => - render(); - - const getButton = () => screen.getByRole('button'); - - it('renders current page', () => { - setupComponent({ current: true }); - expect(getButton()).toHaveStyle( - `background-color: ${theme.pagination.currentPage}` - ); - }); - - it('renders non-current page', () => { - setupComponent({ current: false }); - expect(getButton()).toHaveStyle( - `background-color: ${theme.pagination.backgroundColor}` - ); - }); - - it('renders page number', () => { - setupComponent({ current: false }); - expect(getButton()).toHaveTextContent(String(page)); - }); -}); diff --git a/kafka-ui-react-app/src/components/common/Pagination/__tests__/Pagination.spec.tsx b/kafka-ui-react-app/src/components/common/Pagination/__tests__/Pagination.spec.tsx deleted file mode 100644 index 52fbd355bf..0000000000 --- a/kafka-ui-react-app/src/components/common/Pagination/__tests__/Pagination.spec.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; -import Pagination, { - PaginationProps, -} from 'components/common/Pagination/Pagination'; -import theme from 'theme/theme'; -import { render } from 'lib/testHelpers'; -import { screen } from '@testing-library/react'; - -describe('Pagination', () => { - const setupComponent = ( - search = '', - props: Partial = {} - ) => { - const defaultPath = '/my/test/path/23'; - const pathName = search ? `${defaultPath}${search}` : defaultPath; - return render(, { - initialEntries: [pathName], - }); - }; - - describe('next & prev buttons', () => { - it('renders disable prev button and enabled next link', () => { - setupComponent('?page=1'); - expect(screen.getByText('Previous')).toBeDisabled(); - expect(screen.getByText('Next')).toBeInTheDocument(); - }); - - it('renders disable next button and enabled prev link', () => { - setupComponent('?page=11'); - expect(screen.getByText('Previous')).toBeInTheDocument(); - expect(screen.getByText('Next')).toBeDisabled(); - }); - - it('renders next & prev links with correct path', () => { - setupComponent('?page=5&perPage=20'); - expect(screen.getByText('Previous')).toBeInTheDocument(); - expect(screen.getByText('Next')).toBeInTheDocument(); - expect(screen.getByText('Previous')).toHaveAttribute( - 'href', - '/my/test/path/23?page=4&perPage=20' - ); - expect(screen.getByText('Next')).toHaveAttribute( - 'href', - '/my/test/path/23?page=6&perPage=20' - ); - }); - }); - - describe('spread', () => { - it('renders 1 spread element after first page control', () => { - setupComponent('?page=8'); - expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('…'); - }); - - it('renders 1 spread element before last spread control', () => { - setupComponent('?page=2'); - expect(screen.getAllByRole('listitem')[7]).toHaveTextContent('…'); - }); - - it('renders 2 spread elements', () => { - setupComponent('?page=6'); - expect(screen.getAllByText('…').length).toEqual(2); - expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('1'); - expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('…'); - expect(screen.getAllByRole('listitem')[7]).toHaveTextContent('…'); - expect(screen.getAllByRole('listitem')[8]).toHaveTextContent('11'); - }); - - it('renders 0 spread elements', () => { - setupComponent('?page=2', { totalPages: 8 }); - expect(screen.queryAllByText('…').length).toEqual(0); - expect(screen.getAllByRole('listitem').length).toEqual(8); - }); - }); - - describe('current page', () => { - it('check if it sets page 8 as current when page param is set', () => { - setupComponent('?page=8'); - expect(screen.getByText('8')).toHaveStyle( - `background-color: ${theme.pagination.currentPage}` - ); - }); - - it('check if it sets first page as current when page param not set', () => { - setupComponent('', { totalPages: 8 }); - expect(screen.getByText('1')).toHaveStyle( - `background-color: ${theme.pagination.currentPage}` - ); - }); - }); -}); diff --git a/kafka-ui-react-app/src/components/common/SmartTable/SmartTable.tsx b/kafka-ui-react-app/src/components/common/SmartTable/SmartTable.tsx deleted file mode 100644 index 2d1dfdda13..0000000000 --- a/kafka-ui-react-app/src/components/common/SmartTable/SmartTable.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import Pagination from 'components/common/Pagination/Pagination'; -import { Table } from 'components/common/table/Table/Table.styled'; -import * as S from 'components/common/table/TableHeaderCell/TableHeaderCell.styled'; -import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell'; -import { TableState } from 'lib/hooks/useTableState'; - -import { - isColumnElement, - SelectCell, - TableHeaderCellProps, -} from './TableColumn'; -import { TableRow } from './TableRow'; - -interface SmartTableProps { - tableState: TableState; - allSelectable?: boolean; - selectable?: boolean; - className?: string; - placeholder?: string; - isFullwidth?: boolean; - paginated?: boolean; - hoverable?: boolean; -} - -export const SmartTable = ({ - children, - tableState, - selectable = false, - allSelectable = false, - placeholder = 'No Data Found', - isFullwidth = false, - paginated = false, - hoverable = false, -}: React.PropsWithChildren>) => { - const handleRowSelection = (row: T, checked: boolean) => { - tableState.setRowsSelection([row], checked); - }; - - const headerRow = React.useMemo(() => { - const headerCells = React.Children.map(children, (child) => { - if (!isColumnElement(child)) { - return child; - } - - const { headerCell, title, orderValue } = child.props; - - const HeaderCell = headerCell as React.FC>; - return HeaderCell ? ( - - - - ) : ( - // TODO types will be changed after fixing TableHeaderCell - - ); - }); - let checkboxElement = null; - - if (selectable) { - checkboxElement = allSelectable ? ( - - ) : ( - - ); - } - - return ( -
    - {checkboxElement} - {headerCells} - - ); - }, [children, allSelectable, tableState]); - - const bodyRows = React.useMemo(() => { - if (tableState.data.length === 0) { - const colspan = React.Children.count(children) + +selectable; - return ( - - - - ); - } - return tableState.data.map((dataItem, index) => { - return ( - - {children} - - ); - }); - }, [ - children, - handleRowSelection, - hoverable, - placeholder, - selectable, - tableState, - ]); - - return ( - <> -
    {placeholder}
    - {headerRow} - {bodyRows} -
    - {paginated && tableState.totalPages !== undefined && ( - - )} - - ); -}; diff --git a/kafka-ui-react-app/src/components/common/SmartTable/TableColumn.tsx b/kafka-ui-react-app/src/components/common/SmartTable/TableColumn.tsx deleted file mode 100644 index 8044999ee2..0000000000 --- a/kafka-ui-react-app/src/components/common/SmartTable/TableColumn.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; -import { TableState } from 'lib/hooks/useTableState'; -import { SortOrder } from 'generated-sources'; -import * as S from 'components/common/table/TableHeaderCell/TableHeaderCell.styled'; -import { DefaultTheme, StyledComponent } from 'styled-components'; - -export interface OrderableProps { - orderBy: string | null; - sortOrder: SortOrder; - handleOrderBy: (orderBy: string | null) => void; -} - -interface TableCellPropsBase { - tableState: TableState; -} - -export interface TableHeaderCellProps - extends TableCellPropsBase { - orderable?: OrderableProps; - orderValue?: string; -} - -export interface TableCellProps - extends TableCellPropsBase { - rowIndex: number; - dataItem: T; - hovered?: boolean; -} - -interface TableColumnProps { - cell?: React.FC>; - children?: React.ReactElement; - headerCell?: React.FC>; - field?: string; - title?: string; - maxWidth?: string; - className?: string; - orderValue?: string; - customTd?: typeof S.Td; -} - -export const TableColumn = ( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _props: React.PropsWithChildren> -): React.ReactElement => { - return
    ; -}; - -export function isColumnElement( - element: React.ReactNode -): element is React.ReactElement> { - if (!React.isValidElement(element)) { - return false; - } - - const elementType = (element as React.ReactElement).type; - return ( - elementType === TableColumn || - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (elementType as any).originalType === TableColumn - ); -} - -interface SelectCellProps { - selected: boolean; - selectable: boolean; - el: 'td' | 'th'; - rowIndex: number; - onChange: (checked: boolean) => void; -} - -export const SelectCell: React.FC = ({ - selected, - selectable, - rowIndex, - onChange, - el, -}) => { - const handleChange = (e: React.ChangeEvent) => { - onChange(e.target.checked); - }; - - let El: 'td' | StyledComponent<'th', DefaultTheme>; - if (el === 'th') { - El = S.TableHeaderCell; - } else { - El = el; - } - - return ( - - {selectable && ( - - )} - - ); -}; diff --git a/kafka-ui-react-app/src/components/common/SmartTable/TableRow.tsx b/kafka-ui-react-app/src/components/common/SmartTable/TableRow.tsx deleted file mode 100644 index 3adb30ce33..0000000000 --- a/kafka-ui-react-app/src/components/common/SmartTable/TableRow.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import { propertyLookup } from 'lib/propertyLookup'; -import { TableState } from 'lib/hooks/useTableState'; -import { Td } from 'components/common/table/TableHeaderCell/TableHeaderCell.styled'; - -import { isColumnElement, SelectCell, TableCellProps } from './TableColumn'; - -interface TableRowProps { - index: number; - id?: TId; - hoverable?: boolean; - tableState: TableState; - dataItem: T; - selectable: boolean; - onSelectChange?: (row: T, checked: boolean) => void; -} - -export const TableRow = ({ - children, - hoverable = false, - id, - index, - dataItem, - selectable, - tableState, - onSelectChange, -}: React.PropsWithChildren>): React.ReactElement => { - const [hovered, setHovered] = React.useState(false); - - const handleMouseEnter = () => { - setHovered(true); - }; - - const handleMouseLeave = () => { - setHovered(false); - }; - - const handleSelectChange = (checked: boolean) => { - onSelectChange?.(dataItem, checked); - }; - - return ( -