瀏覽代碼

Get rid of SmartTable component (#2444)

* Get rid of SmartTable component

* Clickable rows

* Improve test coverage
Oleg Shur 2 年之前
父節點
當前提交
e1fb6bacc3
共有 57 個文件被更改,包括 591 次插入1641 次删除
  1. 0 5
      kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.style.ts
  2. 40 52
      kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.tsx
  3. 99 75
      kafka-ui-react-app/src/components/Brokers/BrokersList/__test__/BrokersList.spec.tsx
  4. 1 1
      kafka-ui-react-app/src/components/Connect/Details/Overview/Overview.tsx
  5. 3 1
      kafka-ui-react-app/src/components/Connect/Details/Tasks/Tasks.tsx
  6. 3 1
      kafka-ui-react-app/src/components/Connect/List/ListItem.tsx
  7. 3 1
      kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx
  8. 0 23
      kafka-ui-react-app/src/components/ConsumerGroups/List/ConsumerGroupsTableCells.tsx
  9. 67 71
      kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx
  10. 1 10
      kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.tsx
  11. 0 29
      kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx
  12. 0 61
      kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ConsumerGroupsTableCells.spec.tsx
  13. 30 48
      kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx
  14. 0 86
      kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ListItem.spec.tsx
  15. 28 109
      kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx
  16. 19 19
      kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/KsqlDbItem.tsx
  17. 30 33
      kafka-ui-react-app/src/components/KsqlDb/List/KsqlDbItem/__test__/KsqlDbItem.spec.tsx
  18. 4 5
      kafka-ui-react-app/src/components/KsqlDb/List/__test__/List.spec.tsx
  19. 9 5
      kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector.tsx
  20. 1 1
      kafka-ui-react-app/src/components/Schemas/List/GlobalSchemaSelector/__test__/GlobalSchemaSelector.spec.tsx
  21. 40 36
      kafka-ui-react-app/src/components/Schemas/List/List.tsx
  22. 0 26
      kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx
  23. 23 3
      kafka-ui-react-app/src/components/Schemas/List/__test__/List.spec.tsx
  24. 0 23
      kafka-ui-react-app/src/components/Schemas/List/__test__/ListItem.spec.tsx
  25. 1 1
      kafka-ui-react-app/src/components/Schemas/List/__test__/fixtures.ts
  26. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx
  27. 0 16
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/MessageContent.styled.ts
  28. 0 20
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/__tests__/MessageContent.styled.spec.tsx
  29. 1 2
      kafka-ui-react-app/src/components/common/NewTable/ExpanderCell.tsx
  30. 16 0
      kafka-ui-react-app/src/components/common/NewTable/LinkCell.tsx
  31. 1 2
      kafka-ui-react-app/src/components/common/NewTable/SelectRowCell.tsx
  32. 3 2
      kafka-ui-react-app/src/components/common/NewTable/SelectRowHeader.tsx
  33. 2 2
      kafka-ui-react-app/src/components/common/NewTable/SizeCell.tsx
  34. 3 3
      kafka-ui-react-app/src/components/common/NewTable/Table.styled.ts
  35. 43 7
      kafka-ui-react-app/src/components/common/NewTable/Table.tsx
  36. 12 0
      kafka-ui-react-app/src/components/common/NewTable/TagCell.tsx
  37. 2 2
      kafka-ui-react-app/src/components/common/NewTable/TimestampCell.tsx
  38. 98 5
      kafka-ui-react-app/src/components/common/NewTable/__test__/Table.spec.tsx
  39. 3 1
      kafka-ui-react-app/src/components/common/NewTable/index.ts
  40. 0 26
      kafka-ui-react-app/src/components/common/Pagination/PageControl.tsx
  41. 0 87
      kafka-ui-react-app/src/components/common/Pagination/Pagination.styled.ts
  42. 0 128
      kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx
  43. 0 35
      kafka-ui-react-app/src/components/common/Pagination/__tests__/PageControl.spec.tsx
  44. 0 91
      kafka-ui-react-app/src/components/common/Pagination/__tests__/Pagination.spec.tsx
  45. 0 134
      kafka-ui-react-app/src/components/common/SmartTable/SmartTable.tsx
  46. 0 102
      kafka-ui-react-app/src/components/common/SmartTable/TableColumn.tsx
  47. 0 86
      kafka-ui-react-app/src/components/common/SmartTable/TableRow.tsx
  48. 2 1
      kafka-ui-react-app/src/components/common/Tag/Tag.styled.tsx
  49. 2 10
      kafka-ui-react-app/src/components/common/Tag/getTagColor.ts
  50. 0 4
      kafka-ui-react-app/src/components/common/table/Table/TableKeyLink.styled.ts
  51. 0 6
      kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts
  52. 0 18
      kafka-ui-react-app/src/lib/__test__/propertyLookup.spec.ts
  53. 0 17
      kafka-ui-react-app/src/lib/hooks/usePagination.ts
  54. 0 78
      kafka-ui-react-app/src/lib/hooks/useTableState.ts
  55. 0 9
      kafka-ui-react-app/src/lib/propertyLookup.ts
  56. 0 5
      kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts
  57. 0 16
      kafka-ui-react-app/src/theme/theme.ts

+ 0 - 5
kafka-ui-react-app/src/components/Brokers/BrokersList/BrokersList.style.ts

@@ -1,5 +0,0 @@
-import styled from 'styled-components';
-
-export const ClickableRow = styled.tr`
-  cursor: pointer;
-`;

+ 40 - 52
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<ColumnDef<typeof rows>[]>(
+    () => [
+      { 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 = () => {
           </Metrics.Indicator>
         </Metrics.Section>
       </Metrics.Wrapper>
-      <Table isFullwidth>
-        <thead>
-          <tr>
-            <TableHeaderCell title="Broker" />
-            <TableHeaderCell title="Segment Size" />
-            <TableHeaderCell title="Segment Count" />
-            <TableHeaderCell title="Port" />
-            <TableHeaderCell title="Host" />
-          </tr>
-        </thead>
-        <tbody>
-          {(!diskUsage || diskUsage.length === 0) && (
-            <tr>
-              <td colSpan={10}>Disk usage data not available</td>
-            </tr>
-          )}
-
-          {diskUsage &&
-            diskUsage.length !== 0 &&
-            diskUsage.map(({ brokerId, segmentSize, segmentCount }) => {
-              const brokerItem = brokers?.find(({ id }) => id === brokerId);
-
-              return (
-                <ClickableRow
-                  key={brokerId}
-                  onClick={() => navigate(`${brokerId}`)}
-                >
-                  <td>
-                    <NavLink to={`${brokerId}`} role="link">
-                      {brokerId}
-                    </NavLink>
-                  </td>
-                  <td>
-                    <BytesFormatted value={segmentSize} />
-                  </td>
-                  <td>{segmentCount}</td>
-                  <td>{brokerItem?.port}</td>
-                  <td>{brokerItem?.host}</td>
-                </ClickableRow>
-              );
-            })}
-        </tbody>
-      </Table>
+      <Table
+        columns={columns}
+        data={rows}
+        enableSorting
+        onRowClick={({ original: { brokerId } }) =>
+          navigate(clusterBrokerPath(clusterName, brokerId))
+        }
+        emptyMessage="Disk usage data not available"
+      />
     </>
   );
 };

+ 99 - 75
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,
-      }));
+    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();
+      });
     });
 
-    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 diskUsage is empty', () => {
+      beforeEach(() => {
+        (useBrokers as jest.Mock).mockImplementation(() => ({
+          data: brokersPayload,
+        }));
+        (useClusterStats as jest.Mock).mockImplementation(() => ({
+          data: { ...clusterStatsPayload, diskUsage: undefined },
+        }));
       });
-      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();
+      it('renders empty table', async () => {
+        renderComponent();
+        expect(screen.getByRole('table')).toBeInTheDocument();
+        expect(
+          screen.getByRole('row', { name: 'Disk usage data not available' })
+        ).toBeInTheDocument();
+      });
     });
   });
 });

+ 1 - 1
kafka-ui-react-app/src/components/Connect/Details/Overview/Overview.tsx

@@ -35,7 +35,7 @@ const Overview: React.FC = () => {
           </Metrics.Indicator>
         )}
         <Metrics.Indicator label="State">
-          <C.Tag color={getTagColor(connector.status)}>
+          <C.Tag color={getTagColor(connector.status.state)}>
             {connector.status.state}
           </C.Tag>
         </Metrics.Indicator>

+ 3 - 1
kafka-ui-react-app/src/components/Connect/Details/Tasks/Tasks.tsx

@@ -43,7 +43,9 @@ const Tasks: React.FC = () => {
             <td>{task.status?.id}</td>
             <td>{task.status?.workerId}</td>
             <td>
-              <Tag color={getTagColor(task.status)}>{task.status.state}</Tag>
+              <Tag color={getTagColor(task.status.state)}>
+                {task.status.state}
+              </Tag>
             </td>
             <td>{task.status.trace || 'null'}</td>
             <td style={{ width: '5%' }}>

+ 3 - 1
kafka-ui-react-app/src/components/Connect/List/ListItem.tsx

@@ -72,7 +72,9 @@ const ListItem: React.FC<ListItemProps> = ({
           ))}
         </S.TagsWrapper>
       </td>
-      <td>{status && <Tag color={getTagColor(status)}>{status.state}</Tag>}</td>
+      <td>
+        {status && <Tag color={getTagColor(status.state)}>{status.state}</Tag>}
+      </td>
       <td>
         {runningTasks && (
           <span>

+ 3 - 1
kafka-ui-react-app/src/components/ConsumerGroups/Details/Details.tsx

@@ -82,7 +82,9 @@ const Details: React.FC = () => {
       <Metrics.Wrapper>
         <Metrics.Section>
           <Metrics.Indicator label="State">
-            <Tag color={getTagColor(consumerGroup)}>{consumerGroup.state}</Tag>
+            <Tag color={getTagColor(consumerGroup.state)}>
+              {consumerGroup.state}
+            </Tag>
           </Metrics.Indicator>
           <Metrics.Indicator label="Members">
             {consumerGroup.members}

+ 0 - 23
kafka-ui-react-app/src/components/ConsumerGroups/List/ConsumerGroupsTableCells.tsx

@@ -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<TableCellProps<ConsumerGroup, string>> = ({
-  dataItem,
-}) => {
-  return <Tag color={getTagColor(dataItem)}>{dataItem.state}</Tag>;
-};
-
-export const GroupIDCell: React.FC<TableCellProps<ConsumerGroup, string>> = ({
-  dataItem: { groupId },
-}) => {
-  return (
-    <SmartTableKeyLink>
-      <Link to={groupId}>{groupId}</Link>
-    </SmartTableKeyLink>
-  );
-};

+ 67 - 71
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<Props> = ({
-  consumerGroups,
-  sortOrder,
-  orderBy,
-  totalPages,
-  isFetched,
-  setConsumerGroupsSortOrderBy,
-}) => {
-  const { page, perPage } = usePagination();
+const List: React.FC<Props> = ({ consumerGroups, totalPages }) => {
   const [searchText, handleSearchText] = useSearch();
   const dispatch = useAppDispatch();
   const { clusterName } = useAppParams<ClusterNameRoute>();
+  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<ConsumerGroupDetails, string>(
-    consumerGroups,
-    {
-      totalPages,
-      idSelector: (consumerGroup) => consumerGroup.groupId,
-    },
-    {
-      handleOrderBy: setConsumerGroupsSortOrderBy,
-      orderBy,
-      sortOrder,
-    }
+  const columns = React.useMemo<ColumnDef<ConsumerGroupDetails>[]>(
+    () => [
+      {
+        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 <PageLoader />;
-  }
-
   return (
-    <div>
+    <>
       <PageHeading text="Consumers" />
       <ControlPanelWrapper hasInput>
         <Search
@@ -84,33 +93,20 @@ const List: React.FC<Props> = ({
           handleSearch={handleSearchText}
         />
       </ControlPanelWrapper>
-      <SmartTable
-        tableState={tableState}
-        isFullwidth
-        placeholder="No active consumer groups"
-        hoverable
-        paginated
-      >
-        <TableColumn
-          title="Consumer Group ID"
-          cell={GroupIDCell}
-          orderValue={ConsumerGroupOrdering.NAME}
-        />
-        <TableColumn
-          title="Num Of Members"
-          field="members"
-          orderValue={ConsumerGroupOrdering.MEMBERS}
-        />
-        <TableColumn title="Num Of Topics" field="topics" />
-        <TableColumn title="Messages Behind" field="messagesBehind" />
-        <TableColumn title="Coordinator" field="coordinator.id" />
-        <TableColumn
-          title="State"
-          cell={StatusCell}
-          orderValue={ConsumerGroupOrdering.STATE}
-        />
-      </SmartTable>
-    </div>
+      <Table
+        columns={columns}
+        pageCount={totalPages}
+        data={consumerGroups}
+        emptyMessage="No active consumer groups found"
+        serverSideProcessing
+        enableSorting
+        onRowClick={({ original }) =>
+          navigate(
+            clusterConsumerGroupDetailsPath(clusterName, original.groupId)
+          )
+        }
+      />
+    </>
   );
 };
 

+ 1 - 10
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);

+ 0 - 29
kafka-ui-react-app/src/components/ConsumerGroups/List/ListItem.tsx

@@ -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 (
-    <tr>
-      <TableKeyLink>
-        <Link to={`consumer-groups/${consumerGroup.groupId}`}>
-          {consumerGroup.groupId}
-        </Link>
-      </TableKeyLink>
-      <td>{consumerGroup.members}</td>
-      <td>{consumerGroup.topics}</td>
-      <td>{consumerGroup.messagesBehind}</td>
-      <td>{consumerGroup.coordinator?.id}</td>
-      <td>
-        <Tag color={getTagColor(consumerGroup)}>{consumerGroup.state}</Tag>
-      </td>
-    </tr>
-  );
-};
-
-export default ListItem;

+ 0 - 61
kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ConsumerGroupsTableCells.spec.tsx

@@ -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<ConsumerGroup, string> = {
-    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(
-        <GroupIDCell
-          rowIndex={1}
-          dataItem={consumerGroup}
-          tableState={mockTableState}
-        />
-      );
-      const linkElement = screen.getByRole('link');
-      expect(linkElement).toBeInTheDocument();
-      expect(linkElement).toHaveAttribute('href', `/${consumerGroup.groupId}`);
-    });
-  });
-
-  describe('GroupIdCell', () => {
-    it('should GroupIdCell props render normally', () => {
-      render(
-        <StatusCell
-          rowIndex={1}
-          dataItem={consumerGroup}
-          tableState={mockTableState}
-        />
-      );
-      expect(
-        screen.getByText(consumerGroup.state as string)
-      ).toBeInTheDocument();
-    });
-  });
-});

+ 30 - 48
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(<ListContainer />);
+    expect(screen.getByRole('table')).toBeInTheDocument();
+  });
+});
 
 describe('List', () => {
-  const setUpComponent = (props: Partial<Props> = {}) => {
-    const {
-      consumerGroups,
-      orderBy,
-      sortOrder,
-      totalPages,
-      setConsumerGroupsSortOrderBy,
-    } = props;
+  const renderComponent = (props: Partial<Props> = {}) => {
+    const { consumerGroups, totalPages } = props;
     return render(
       <List
         consumerGroups={consumerGroups || []}
-        orderBy={orderBy || ConsumerGroupOrdering.NAME}
-        sortOrder={sortOrder || SortOrder.ASC}
-        setConsumerGroupsSortOrderBy={setConsumerGroupsSortOrderBy || jest.fn()}
         totalPages={totalPages || 1}
-        isFetched={'isFetched' in props ? !!props.isFetched : true}
       />
     );
   };
 
   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')
       );
     });
   });

+ 0 - 86
kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/ListItem.spec.tsx

@@ -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) => (
-    <table>
-      <tbody>
-        <ListItem consumerGroup={consumerGroup} />
-      </tbody>
-    </table>
-  );
-
-  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);
-  });
-});

+ 28 - 109
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', () => () => (
+  <div>ListContainerMock</div>
+));
+jest.mock('components/ConsumerGroups/Details/Details', () => () => (
+  <div>DetailsMock</div>
+));
+jest.mock(
+  'components/ConsumerGroups/Details/ResetOffsets/ResetOffsets',
+  () => () => <div>ResetOffsetsMock</div>
+);
+
 const renderComponent = (path?: string) =>
   render(
     <WithRoute path={getNonExactPath(clusterConsumerGroupsPath())}>
@@ -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();
   });
 });

+ 19 - 19
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<KsqlDbItemProps> = ({ type, fetching, rows }) => {
   const preparedRows = rows[type]?.map(ksqlRowData) || [];
-  const tableState = useTableState<KsqlTableState, string>(preparedRows, {
-    idSelector: ({ name }) => name,
-    totalPages: 0,
-  });
+
+  const columns = React.useMemo<ColumnDef<KsqlTableState>[]>(
+    () => [
+      { 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 <PageLoader />;
   }
   return (
-    <SmartTable
-      tableState={tableState}
-      isFullwidth
-      placeholder="No tables or streams found"
-      hoverable
-    >
-      <TableColumn title="Name" field="name" />
-      <TableColumn title="Topic" field="topic" />
-      <TableColumn title="Key Format" field="keyFormat" />
-      <TableColumn title="Value Format" field="valueFormat" />
-      <TableColumn title="Is Windowed" field="isWindowed" />
-    </SmartTable>
+    <Table
+      data={preparedRows}
+      columns={columns}
+      emptyMessage="No tables or streams found"
+      enableSorting={false}
+    />
   );
 };
 

+ 30 - 33
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 renderComponent = (props: Partial<KsqlDbItemProps> = {}) => {
+    render(
+      <WithRoute path={tablesPathname}>
+        <KsqlDbItem
+          type={KsqlDbItemType.Tables}
+          fetching={false}
+          rows={{ tables: [], streams: [] }}
+          {...props}
+        />
+      </WithRoute>,
+      {
+        initialEntries: [clusterKsqlDbTablesPath()],
+      }
+    );
+  };
 
-  const component = (props: Partial<KsqlDbItemProps> = {}) => (
-    <WithRoute path={tablesPathname}>
-      <KsqlDbItem
-        type={KsqlDbItemType.Tables}
-        fetching={false}
-        rows={{ tables: [], streams: [] }}
-        {...props}
-      />
-    </WithRoute>
-  );
-
-  it('renders progressbar when fetching tables and streams', () => {
-    render(component({ fetching: true }), {
-      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', () => {
-    render(component({}), {
-      initialEntries: [clusterKsqlDbTablesPath()],
-    });
+
+  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', () => {
-    render(
-      component({
+
+  it('renders with tables', async () => {
+    await act(() =>
+      renderComponent({
         rows: {
           tables: fetchKsqlDbTablesPayload.tables,
           streams: [],
         },
-      }),
-      {
-        initialEntries: [clusterKsqlDbTablesPath()],
-      }
+      })
     );
     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);
   });

+ 4 - 5
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(<List />);
-};
+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(<List />);
+    });
 
     const Tables = screen.getByTitle('Tables');
     const Streams = screen.getByTitle('Streams');

+ 9 - 5
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<ClusterNameRoute>();
   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);

+ 1 - 1
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(() => {

+ 40 - 36
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<ClusterNameRoute>();
-
+  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<ColumnDef<SchemaSubject>[]>(
+    () => [
+      { header: 'Subject', accessorKey: 'subject', cell: LinkCell },
+      { header: 'Version', accessorKey: 'version' },
+      { header: 'Compatibility', accessorKey: 'compatibilityLevel' },
+    ],
+    []
+  );
 
   return (
     <>
@@ -68,31 +87,16 @@ const List: React.FC = () => {
         />
       </ControlPanelWrapper>
       {isFetched ? (
-        <>
-          <C.Table isFullwidth>
-            <thead>
-              <tr>
-                <TableHeaderCell title="Schema Name" />
-                <TableHeaderCell title="Version" />
-                <TableHeaderCell title="Compatibility" />
-              </tr>
-            </thead>
-            <tbody>
-              {schemas.length === 0 && (
-                <tr>
-                  <td colSpan={10}>No schemas found</td>
-                </tr>
-              )}
-              {schemas.map((subject) => (
-                <ListItem
-                  key={[subject.id, subject.subject].join('-')}
-                  subject={subject}
-                />
-              ))}
-            </tbody>
-          </C.Table>
-          <Pagination totalPages={totalPages} />
-        </>
+        <Table
+          columns={columns}
+          data={schemas}
+          pageCount={totalPages}
+          emptyMessage="No schemas found"
+          onRowClick={(row) =>
+            navigate(clusterSchemaPath(clusterName, row.original.subject))
+          }
+          serverSideProcessing
+        />
       ) : (
         <PageLoader />
       )}

+ 0 - 26
kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx

@@ -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<ListItemProps> = ({
-  subject: { subject, version, compatibilityLevel },
-}) => {
-  return (
-    <tr>
-      <S.TableKeyLink>
-        <NavLink to={subject} role="link">
-          {subject}
-        </NavLink>
-      </S.TableKeyLink>
-      <td role="cell">{version}</td>
-      <td role="cell">{compatibilityLevel}</td>
-    </tr>
-  );
-};
-
-export default ListItem;

+ 23 - 3
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,

+ 0 - 23
kafka-ui-react-app/src/components/Schemas/List/__test__/ListItem.spec.tsx

@@ -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(
-      <table>
-        <tbody>
-          <ListItem {...props} />
-        </tbody>
-      </table>
-    );
-
-  it('renders schemas', () => {
-    setupComponent();
-    expect(screen.getAllByRole('link').length).toEqual(1);
-    expect(screen.getAllByRole('cell').length).toEqual(3);
-  });
-});

+ 1 - 1
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,

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups.tsx

@@ -44,7 +44,7 @@ const TopicConsumerGroups: React.FC = () => {
             <td>{consumer.coordinator?.id}</td>
             <td>
               {consumer.state && (
-                <Tag color={getTagColor(consumer)}>{`${consumer.state
+                <Tag color={getTagColor(consumer.state)}>{`${consumer.state
                   .charAt(0)
                   .toUpperCase()}${consumer.state
                   .slice(1)

+ 0 - 16
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[

+ 0 - 20
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/MessageContent/__tests__/MessageContent.styled.spec.tsx

@@ -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(<S.PaginationButton />);
-    });
-    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}`
-      );
-    });
-  });
-});

+ 1 - 2
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<CellContext<any, unknown>> = ({ row }) => (
+const ExpanderCell: React.FC<CellContext<unknown, unknown>> = ({ row }) => (
   <S.ExpaderButton
     width="16"
     height="20"

+ 16 - 0
kafka-ui-react-app/src/components/common/NewTable/LinkCell.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+import { CellContext } from '@tanstack/react-table';
+import { NavLink } from 'react-router-dom';
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const LinkCell: React.FC<CellContext<any, unknown>> = ({ getValue }) => {
+  const value = `${getValue<string | number>()}`;
+  const handleClick: React.MouseEventHandler = (e) => e.stopPropagation();
+  return (
+    <NavLink to={value} title={value} onClick={handleClick}>
+      {value}
+    </NavLink>
+  );
+};
+
+export default LinkCell;

+ 1 - 2
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<CellContext<any, unknown>> = ({ row }) => (
+const SelectRowCell: React.FC<CellContext<unknown, unknown>> = ({ row }) => (
   <IndeterminateCheckbox
     checked={row.getIsSelected()}
     disabled={!row.getCanSelect()}

+ 3 - 2
kafka-ui-react-app/src/components/common/NewTable/SelectRowHeader.tsx

@@ -2,8 +2,9 @@ import { HeaderContext } 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 SelectRowHeader: React.FC<HeaderContext<any, unknown>> = ({ table }) => (
+const SelectRowHeader: React.FC<HeaderContext<unknown, unknown>> = ({
+  table,
+}) => (
   <IndeterminateCheckbox
     checked={table.getIsAllPageRowsSelected()}
     indeterminate={table.getIsSomePageRowsSelected()}

+ 2 - 2
kafka-ui-react-app/src/components/common/NewTable/SizeCell.tsx

@@ -3,8 +3,8 @@ import { CellContext } from '@tanstack/react-table';
 import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
-const SizeCell: React.FC<CellContext<any, any>> = ({ getValue }) => (
-  <BytesFormatted value={getValue()} />
+const SizeCell: React.FC<CellContext<any, unknown>> = ({ getValue }) => (
+  <BytesFormatted value={getValue<string | number>()} />
 );
 
 export default SizeCell;

+ 3 - 3
kafka-ui-react-app/src/components/common/NewTable/Table.styled.ts

@@ -99,13 +99,13 @@ export const Th = styled.th<ThProps>(
 );
 
 interface RowProps {
-  expandable?: boolean;
+  clickable?: boolean;
   expanded?: boolean;
 }
 
 export const Row = styled.tr<RowProps>(
-  ({ 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};

+ 43 - 7
kafka-ui-react-app/src/components/common/NewTable/Table.tsx

@@ -28,13 +28,26 @@ export interface TableProps<TData> {
   data: TData[];
   pageCount?: number;
   columns: ColumnDef<TData>[];
-  renderSubComponent?: React.FC<{ row: Row<TData> }>;
-  getRowCanExpand?: (row: Row<TData>) => boolean;
+
+  // Server-side processing: sorting, pagination
   serverSideProcessing?: boolean;
-  enableSorting?: boolean;
-  enableRowSelection?: boolean | ((row: Row<TData>) => boolean);
-  batchActionsBar?: React.FC<{ rows: Row<TData>[]; resetRowSelection(): void }>;
+
+  // Expandeble rows
+  getRowCanExpand?: (row: Row<TData>) => boolean; // Enables the ability to expand row. Use `() => true` when want to expand all rows.
+  renderSubComponent?: React.FC<{ row: Row<TData> }>; // Component to render expanded row.
+
+  // Selectable rows
+  enableRowSelection?: boolean | ((row: Row<TData>) => boolean); // Enables the ability to select row.
+  batchActionsBar?: React.FC<{ rows: Row<TData>[]; 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<TData>) => void;
 }
 
 type UpdaterFn<T> = (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<TableProps<any>> = ({
   enableRowSelection = false,
   batchActionsBar,
   emptyMessage,
+  onRowClick,
 }) => {
   const [searchParams, setSearchParams] = useSearchParams();
   const [rowSelection, setRowSelection] = React.useState({});
@@ -157,6 +172,24 @@ const Table: React.FC<TableProps<any>> = ({
 
   const Bar = batchActionsBar;
 
+  const handleRowClick = (row: Row<typeof data>) => (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<TableProps<any>> = ({
           {table.getRowModel().rows.map((row) => (
             <React.Fragment key={row.id}>
               <S.Row
-                expandable={row.getCanExpand()}
                 expanded={row.getIsExpanded()}
-                onClick={() => row.getCanExpand() && row.toggleExpanded()}
+                onClick={handleRowClick(row)}
+                clickable={
+                  !enableRowSelection &&
+                  (row.getCanExpand() || onRowClick !== undefined)
+                }
               >
                 {!!enableRowSelection && (
                   <td key={`${row.id}-select`}>

+ 12 - 0
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<CellContext<any, unknown>> = ({ getValue }) => {
+  const value = getValue<string>();
+  return <Tag color={getTagColor(value)}>{value}</Tag>;
+};
+
+export default TagCell;

+ 2 - 2
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<CellContext<any, any>> = ({ getValue }) => (
-  <S.Nowrap>{formatTimestamp(getValue())}</S.Nowrap>
+const TimestampCell: React.FC<CellContext<any, unknown>> = ({ getValue }) => (
+  <S.Nowrap>{formatTimestamp(getValue<string | number>())}</S.Nowrap>
 );
 
 export default TimestampCell;

+ 98 - 5
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<Datum>[] = [
@@ -29,12 +63,18 @@ const columns: ColumnDef<Datum>[] = [
   {
     header: 'Text',
     accessorKey: 'text',
+    cell: LinkCell,
   },
   {
     header: 'Size',
     accessorKey: 'size',
     cell: SizeCell,
   },
+  {
+    header: 'Tag',
+    accessorKey: 'tag',
+    cell: TagCell,
+  },
 ];
 
 const ExpandedRow: React.FC = () => <div>I am expanded row</div>;
@@ -45,7 +85,7 @@ interface Props extends TableProps<Datum> {
 
 const renderComponent = (props: Partial<Props> = {}) => {
   render(
-    <WithRoute path="/">
+    <WithRoute path="/*">
       <Table
         columns={columns}
         data={data}
@@ -94,6 +134,21 @@ describe('Table', () => {
     ).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();
+    });
+  });
 });

+ 3 - 1
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;

+ 0 - 26
kafka-ui-react-app/src/components/common/Pagination/PageControl.tsx

@@ -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<PageControlProps> = ({ current, url, page }) => {
-  return (
-    <li>
-      <PaginationLink
-        to={url}
-        aria-label={`Goto page ${page}`}
-        $isCurrent={current}
-        role="button"
-      >
-        {page}
-      </PaginationLink>
-    </li>
-  );
-};
-
-export default PageControl;

+ 0 - 87
kafka-ui-react-app/src/components/common/Pagination/Pagination.styled.ts

@@ -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};
-`;

+ 0 - 128
kafka-ui-react-app/src/components/common/Pagination/Pagination.tsx

@@ -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<PaginationProps> = ({ 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 (
-    <S.Wrapper role="navigation" aria-label="pagination">
-      {currentPage > 1 ? (
-        <S.PaginationButton to={getPath(currentPage - 1)}>
-          Previous
-        </S.PaginationButton>
-      ) : (
-        <S.DisabledButton disabled>Previous</S.DisabledButton>
-      )}
-      {totalPages > 1 && (
-        <ul>
-          {!pages.includes(1) && (
-            <PageControl
-              page={1}
-              current={currentPage === 1}
-              url={getPath(1)}
-            />
-          )}
-          {!pages.includes(2) && (
-            <li>
-              <span>&hellip;</span>
-            </li>
-          )}
-          {pages.map((p) => (
-            <PageControl
-              key={`page-${p}`}
-              page={p}
-              current={p === currentPage}
-              url={getPath(p)}
-            />
-          ))}
-          {!pages.includes(totalPages - 1) && (
-            <li>
-              <span>&hellip;</span>
-            </li>
-          )}
-          {!pages.includes(totalPages) && (
-            <PageControl
-              page={totalPages}
-              current={currentPage === totalPages}
-              url={getPath(totalPages)}
-            />
-          )}
-        </ul>
-      )}
-      {currentPage < totalPages ? (
-        <S.PaginationButton to={getPath(currentPage + 1)}>
-          Next
-        </S.PaginationButton>
-      ) : (
-        <S.DisabledButton disabled>Next</S.DisabledButton>
-      )}
-    </S.Wrapper>
-  );
-};
-
-export default Pagination;

+ 0 - 35
kafka-ui-react-app/src/components/common/Pagination/__tests__/PageControl.spec.tsx

@@ -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<PageControlProps> = {}) =>
-    render(<PageControl url="/test" page={page} current {...props} />);
-
-  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));
-  });
-});

+ 0 - 91
kafka-ui-react-app/src/components/common/Pagination/__tests__/Pagination.spec.tsx

@@ -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<PaginationProps> = {}
-  ) => {
-    const defaultPath = '/my/test/path/23';
-    const pathName = search ? `${defaultPath}${search}` : defaultPath;
-    return render(<Pagination totalPages={11} {...props} />, {
-      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}`
-      );
-    });
-  });
-});

+ 0 - 134
kafka-ui-react-app/src/components/common/SmartTable/SmartTable.tsx

@@ -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<T, TId extends IdType> {
-  tableState: TableState<T, TId>;
-  allSelectable?: boolean;
-  selectable?: boolean;
-  className?: string;
-  placeholder?: string;
-  isFullwidth?: boolean;
-  paginated?: boolean;
-  hoverable?: boolean;
-}
-
-export const SmartTable = <T, TId extends IdType>({
-  children,
-  tableState,
-  selectable = false,
-  allSelectable = false,
-  placeholder = 'No Data Found',
-  isFullwidth = false,
-  paginated = false,
-  hoverable = false,
-}: React.PropsWithChildren<SmartTableProps<T, TId>>) => {
-  const handleRowSelection = (row: T, checked: boolean) => {
-    tableState.setRowsSelection([row], checked);
-  };
-
-  const headerRow = React.useMemo(() => {
-    const headerCells = React.Children.map(children, (child) => {
-      if (!isColumnElement<T, TId>(child)) {
-        return child;
-      }
-
-      const { headerCell, title, orderValue } = child.props;
-
-      const HeaderCell = headerCell as React.FC<TableHeaderCellProps<T, TId>>;
-      return HeaderCell ? (
-        <S.TableHeaderCell>
-          <HeaderCell
-            orderValue={orderValue}
-            orderable={tableState.orderable}
-            tableState={tableState}
-          />
-        </S.TableHeaderCell>
-      ) : (
-        // TODO types will be changed after fixing TableHeaderCell
-        <TableHeaderCell
-          {...tableState.orderable}
-          orderValue={orderValue}
-          title={title}
-        />
-      );
-    });
-    let checkboxElement = null;
-
-    if (selectable) {
-      checkboxElement = allSelectable ? (
-        <SelectCell
-          rowIndex={-1}
-          el="th"
-          selectable
-          selected={tableState.selectedCount === tableState.data.length}
-          onChange={tableState.toggleSelection}
-        />
-      ) : (
-        <S.TableHeaderCell />
-      );
-    }
-
-    return (
-      <tr>
-        {checkboxElement}
-        {headerCells}
-      </tr>
-    );
-  }, [children, allSelectable, tableState]);
-
-  const bodyRows = React.useMemo(() => {
-    if (tableState.data.length === 0) {
-      const colspan = React.Children.count(children) + +selectable;
-      return (
-        <tr>
-          <td colSpan={colspan}>{placeholder}</td>
-        </tr>
-      );
-    }
-    return tableState.data.map((dataItem, index) => {
-      return (
-        <TableRow
-          key={tableState.idSelector(dataItem)}
-          index={index}
-          hoverable={hoverable}
-          dataItem={dataItem}
-          tableState={tableState}
-          selectable={selectable}
-          onSelectChange={handleRowSelection}
-        >
-          {children}
-        </TableRow>
-      );
-    });
-  }, [
-    children,
-    handleRowSelection,
-    hoverable,
-    placeholder,
-    selectable,
-    tableState,
-  ]);
-
-  return (
-    <>
-      <Table isFullwidth={isFullwidth}>
-        <thead>{headerRow}</thead>
-        <tbody>{bodyRows}</tbody>
-      </Table>
-      {paginated && tableState.totalPages !== undefined && (
-        <Pagination totalPages={tableState.totalPages} />
-      )}
-    </>
-  );
-};

+ 0 - 102
kafka-ui-react-app/src/components/common/SmartTable/TableColumn.tsx

@@ -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<T, TId extends IdType> {
-  tableState: TableState<T, TId>;
-}
-
-export interface TableHeaderCellProps<T, TId extends IdType>
-  extends TableCellPropsBase<T, TId> {
-  orderable?: OrderableProps;
-  orderValue?: string;
-}
-
-export interface TableCellProps<T, TId extends IdType>
-  extends TableCellPropsBase<T, TId> {
-  rowIndex: number;
-  dataItem: T;
-  hovered?: boolean;
-}
-
-interface TableColumnProps<T, TId extends IdType> {
-  cell?: React.FC<TableCellProps<T, TId>>;
-  children?: React.ReactElement;
-  headerCell?: React.FC<TableHeaderCellProps<T, TId>>;
-  field?: string;
-  title?: string;
-  maxWidth?: string;
-  className?: string;
-  orderValue?: string;
-  customTd?: typeof S.Td;
-}
-
-export const TableColumn = <T, TId extends IdType>(
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  _props: React.PropsWithChildren<TableColumnProps<T, TId>>
-): React.ReactElement => {
-  return <td />;
-};
-
-export function isColumnElement<T, TId extends IdType>(
-  element: React.ReactNode
-): element is React.ReactElement<TableColumnProps<T, TId>> {
-  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<SelectCellProps> = ({
-  selected,
-  selectable,
-  rowIndex,
-  onChange,
-  el,
-}) => {
-  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-    onChange(e.target.checked);
-  };
-
-  let El: 'td' | StyledComponent<'th', DefaultTheme>;
-  if (el === 'th') {
-    El = S.TableHeaderCell;
-  } else {
-    El = el;
-  }
-
-  return (
-    <El>
-      {selectable && (
-        <input
-          data-row={rowIndex}
-          onChange={handleChange}
-          type="checkbox"
-          checked={selected}
-        />
-      )}
-    </El>
-  );
-};

+ 0 - 86
kafka-ui-react-app/src/components/common/SmartTable/TableRow.tsx

@@ -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<T, TId extends IdType = never> {
-  index: number;
-  id?: TId;
-  hoverable?: boolean;
-  tableState: TableState<T, TId>;
-  dataItem: T;
-  selectable: boolean;
-  onSelectChange?: (row: T, checked: boolean) => void;
-}
-
-export const TableRow = <T, TId extends IdType>({
-  children,
-  hoverable = false,
-  id,
-  index,
-  dataItem,
-  selectable,
-  tableState,
-  onSelectChange,
-}: React.PropsWithChildren<TableRowProps<T, TId>>): React.ReactElement => {
-  const [hovered, setHovered] = React.useState(false);
-
-  const handleMouseEnter = () => {
-    setHovered(true);
-  };
-
-  const handleMouseLeave = () => {
-    setHovered(false);
-  };
-
-  const handleSelectChange = (checked: boolean) => {
-    onSelectChange?.(dataItem, checked);
-  };
-
-  return (
-    <tr
-      tabIndex={index}
-      id={id as string}
-      onMouseEnter={hoverable ? handleMouseEnter : undefined}
-      onMouseLeave={hoverable ? handleMouseLeave : undefined}
-    >
-      {selectable && (
-        <SelectCell
-          rowIndex={index}
-          el="td"
-          selectable={tableState.isRowSelectable(dataItem)}
-          selected={tableState.selectedIds.has(tableState.idSelector(dataItem))}
-          onChange={handleSelectChange}
-        />
-      )}
-      {React.Children.map(children, (child) => {
-        if (!isColumnElement<T, TId>(child)) {
-          return child;
-        }
-        const { cell, field, maxWidth, customTd } = child.props;
-
-        const Cell = cell as React.FC<TableCellProps<T, TId>> | undefined;
-        const TdComponent = customTd || Td;
-
-        const content = Cell ? (
-          <Cell
-            tableState={tableState}
-            hovered={hovered}
-            rowIndex={index}
-            dataItem={dataItem}
-          />
-        ) : (
-          field && propertyLookup(field, dataItem)
-        );
-
-        return (
-          <TdComponent maxWidth={maxWidth}>
-            {content as React.ReactNode}
-          </TdComponent>
-        );
-      })}
-    </tr>
-  );
-};

+ 2 - 1
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<Props>`
+export const Tag = styled.span.attrs({ role: 'widget' })<Props>`
   border: none;
   border-radius: 16px;
   height: 20px;
@@ -17,4 +17,5 @@ export const Tag = styled.p<Props>`
   padding-right: 0.75em;
   text-align: center;
   width: max-content;
+  margin: 2px 0;
 `;

+ 2 - 10
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:

+ 0 - 4
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}
-`;

+ 0 - 6
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<TitleProps>(
   ({ isOrderable, isOrdered, sortOrder, theme: { table } }) => css`
     font-family: Inter, sans-serif;

+ 0 - 18
kafka-ui-react-app/src/lib/__test__/propertyLookup.spec.ts

@@ -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);
-  });
-});

+ 0 - 17
kafka-ui-react-app/src/lib/hooks/usePagination.ts

@@ -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;

+ 0 - 78
kafka-ui-react-app/src/lib/hooks/useTableState.ts

@@ -1,78 +0,0 @@
-import React, { useCallback } from 'react';
-import { OrderableProps } from 'components/common/SmartTable/TableColumn';
-
-export interface TableState<T, TId extends IdType> {
-  data: T[];
-  selectedIds: Set<TId>;
-  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 = <T, TId extends IdType>(
-  data: T[],
-  options: {
-    totalPages: number;
-    isRowSelectable?: (row: T) => boolean;
-    idSelector: (row: T) => TId;
-  },
-  orderable?: OrderableProps
-): TableState<T, TId> => {
-  const [selectedIds, setSelectedIds] = React.useState(new Set<TId>());
-
-  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<TableState<T, TId>>(() => {
-    return {
-      data,
-      totalPages,
-      selectedIds,
-      orderable,
-      selectedCount,
-      idSelector,
-      isRowSelectable,
-      setRowsSelection,
-      toggleSelection,
-    };
-  }, [
-    data,
-    orderable,
-    selectedIds,
-    totalPages,
-    selectedCount,
-    idSelector,
-    isRowSelectable,
-    setRowsSelection,
-    toggleSelection,
-  ]);
-};

+ 0 - 9
kafka-ui-react-app/src/lib/propertyLookup.ts

@@ -1,9 +0,0 @@
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export function propertyLookup<T extends { [key: string]: any }>(
-  path: string,
-  obj: T
-) {
-  return path.split('.').reduce((prev, curr) => {
-    return prev ? prev[curr] : null;
-  }, obj);
-}

+ 0 - 5
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,

+ 0 - 16
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],