فهرست منبع

Issues/921 pagination consumer groups (#1802)

* Add Pagination to the ConsumerGroups Page

* Consumer Groups test suites minor code modifications

* Consumer Groups test Search results code

* Consumer Groups test Search with Api request

* Consumer Groups Search Logic
Mgrdich 3 سال پیش
والد
کامیت
c79905ce32

+ 19 - 47
kafka-ui-react-app/src/components/ConsumerGroups/ConsumerGroups.tsx

@@ -1,57 +1,29 @@
 import React from 'react';
-import { ClusterName } from 'redux/interfaces';
-import { Switch, useParams } from 'react-router-dom';
-import PageLoader from 'components/common/PageLoader/PageLoader';
+import { Switch } from 'react-router-dom';
 import Details from 'components/ConsumerGroups/Details/Details';
 import ListContainer from 'components/ConsumerGroups/List/ListContainer';
 import ResetOffsets from 'components/ConsumerGroups/Details/ResetOffsets/ResetOffsets';
-import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
-import {
-  fetchConsumerGroupsPaged,
-  getAreConsumerGroupsPagedFulfilled,
-  getConsumerGroupsOrderBy,
-  getConsumerGroupsSortOrder,
-} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
 import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
 
 const ConsumerGroups: React.FC = () => {
-  const dispatch = useAppDispatch();
-  const { clusterName } = useParams<{ clusterName: ClusterName }>();
-  const isFetched = useAppSelector(getAreConsumerGroupsPagedFulfilled);
-  const orderBy = useAppSelector(getConsumerGroupsOrderBy);
-  const sortOrder = useAppSelector(getConsumerGroupsSortOrder);
-  React.useEffect(() => {
-    dispatch(
-      fetchConsumerGroupsPaged({
-        clusterName,
-        orderBy: orderBy || undefined,
-        sortOrder,
-      })
-    );
-  }, [clusterName, orderBy, sortOrder, dispatch]);
-
-  if (isFetched) {
-    return (
-      <Switch>
-        <BreadcrumbRoute
-          exact
-          path="/ui/clusters/:clusterName/consumer-groups"
-          component={ListContainer}
-        />
-        <BreadcrumbRoute
-          exact
-          path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID"
-          component={Details}
-        />
-        <BreadcrumbRoute
-          path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID/reset-offsets"
-          component={ResetOffsets}
-        />
-      </Switch>
-    );
-  }
-
-  return <PageLoader />;
+  return (
+    <Switch>
+      <BreadcrumbRoute
+        exact
+        path="/ui/clusters/:clusterName/consumer-groups"
+        component={ListContainer}
+      />
+      <BreadcrumbRoute
+        exact
+        path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID"
+        component={Details}
+      />
+      <BreadcrumbRoute
+        path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID/reset-offsets"
+        component={ResetOffsets}
+      />
+    </Switch>
+  );
 };
 
 export default ConsumerGroups;

+ 31 - 12
kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx

@@ -1,4 +1,5 @@
-import React, { useMemo } from 'react';
+import React from 'react';
+import { useParams } from 'react-router-dom';
 import PageHeading from 'components/common/PageHeading/PageHeading';
 import Search from 'components/common/Search/Search';
 import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled';
@@ -14,12 +15,19 @@ 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 { ClusterName } from 'redux/interfaces';
+import { fetchConsumerGroupsPaged } from 'redux/reducers/consumerGroups/consumerGroupsSlice';
+import PageLoader from 'components/common/PageLoader/PageLoader';
 
 export interface Props {
   consumerGroups: ConsumerGroupDetails[];
   orderBy: ConsumerGroupOrdering | null;
   sortOrder: SortOrder;
   totalPages: number;
+  isFetched: boolean;
   setConsumerGroupsSortOrderBy(orderBy: ConsumerGroupOrdering | null): void;
 }
 
@@ -28,23 +36,33 @@ const List: React.FC<Props> = ({
   sortOrder,
   orderBy,
   totalPages,
+  isFetched,
   setConsumerGroupsSortOrderBy,
 }) => {
-  const [searchText, setSearchText] = React.useState<string>('');
+  const { page, perPage } = usePagination();
+  const [searchText, handleSearchText] = useSearch();
+  const dispatch = useAppDispatch();
+  const { clusterName } = useParams<{ clusterName: ClusterName }>();
 
-  const tableData = useMemo(() => {
-    return consumerGroups.filter(
-      (consumerGroup) =>
-        !searchText || consumerGroup?.groupId?.indexOf(searchText) >= 0
+  React.useEffect(() => {
+    dispatch(
+      fetchConsumerGroupsPaged({
+        clusterName,
+        orderBy: orderBy || undefined,
+        sortOrder,
+        page,
+        perPage,
+        search: searchText,
+      })
     );
-  }, [searchText, consumerGroups]);
+  }, [clusterName, orderBy, searchText, sortOrder, page, perPage, dispatch]);
 
   const tableState = useTableState<
     ConsumerGroupDetails,
     string,
     ConsumerGroupOrdering
   >(
-    tableData,
+    consumerGroups,
     {
       totalPages,
       idSelector: (consumerGroup) => consumerGroup.groupId,
@@ -56,9 +74,9 @@ const List: React.FC<Props> = ({
     }
   );
 
-  const handleInputChange = (search: string) => {
-    setSearchText(search);
-  };
+  if (!isFetched) {
+    return <PageLoader />;
+  }
 
   return (
     <div>
@@ -67,7 +85,7 @@ const List: React.FC<Props> = ({
         <Search
           placeholder="Search by Consumer Group ID"
           value={searchText}
-          handleSearch={handleInputChange}
+          handleSearch={handleSearchText}
         />
       </ControlPanelWrapper>
       <SmartTable
@@ -75,6 +93,7 @@ const List: React.FC<Props> = ({
         isFullwidth
         placeholder="No active consumer groups"
         hoverable
+        paginated
       >
         <TableColumn
           title="Consumer Group ID"

+ 2 - 0
kafka-ui-react-app/src/components/ConsumerGroups/List/ListContainer.tsx

@@ -6,6 +6,7 @@ import {
   getConsumerGroupsTotalPages,
   sortBy,
   selectAll,
+  getAreConsumerGroupsPagedFulfilled,
 } from 'redux/reducers/consumerGroups/consumerGroupsSlice';
 import List from 'components/ConsumerGroups/List/List';
 
@@ -14,6 +15,7 @@ const mapStateToProps = (state: RootState) => ({
   orderBy: getConsumerGroupsOrderBy(state),
   sortOrder: getConsumerGroupsSortOrder(state),
   totalPages: getConsumerGroupsTotalPages(state),
+  isFetched: getAreConsumerGroupsPagedFulfilled(state),
 });
 
 const mapDispatchToProps = {

+ 2 - 33
kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import List, { Props } from 'components/ConsumerGroups/List/List';
-import { screen, waitFor } from '@testing-library/react';
+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';
@@ -23,6 +23,7 @@ describe('List', () => {
         sortOrder={sortOrder || SortOrder.ASC}
         setConsumerGroupsSortOrderBy={setConsumerGroupsSortOrderBy || jest.fn()}
         totalPages={totalPages || 1}
+        isFetched={'isFetched' in props ? !!props.isFetched : true}
       />
     );
   };
@@ -41,38 +42,6 @@ describe('List', () => {
       expect(screen.getByText('groupId2')).toBeInTheDocument();
     });
 
-    describe('when searched', () => {
-      it('renders only searched consumers', async () => {
-        await waitFor(() => {
-          userEvent.type(
-            screen.getByPlaceholderText('Search by Consumer Group ID'),
-            consumerGroupMock[0].groupId
-          );
-        });
-
-        expect(
-          screen.getByText(consumerGroupMock[0].groupId)
-        ).toBeInTheDocument();
-        expect(
-          screen.getByText(consumerGroupMock[1].groupId)
-        ).toBeInTheDocument();
-      });
-
-      it('renders will not render a list since not found in the list', async () => {
-        await waitFor(() => {
-          userEvent.type(
-            screen.getByPlaceholderText('Search by Consumer Group ID'),
-            'NotFoundedText'
-          );
-        });
-        await waitFor(() => {
-          expect(
-            screen.getByText(/No active consumer groups/i)
-          ).toBeInTheDocument();
-        });
-      });
-    });
-
     describe('Testing the Ordering', () => {
       it('should test the sort order functionality', async () => {
         const thElement = screen.getByText(/consumer group id/i);

+ 91 - 14
kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx

@@ -6,38 +6,69 @@ import {
   waitForElementToBeRemoved,
 } from '@testing-library/react';
 import ConsumerGroups from 'components/ConsumerGroups/ConsumerGroups';
-import { consumerGroups } from 'redux/reducers/consumerGroups/__test__/fixtures';
+import {
+  consumerGroups,
+  noConsumerGroupsResponse,
+} from 'redux/reducers/consumerGroups/__test__/fixtures';
 import { render } from 'lib/testHelpers';
 import fetchMock from 'fetch-mock';
-import { Route } from 'react-router';
+import { Route, Router } from 'react-router';
 import { ConsumerGroupOrdering, SortOrder } from 'generated-sources';
+import { createMemoryHistory } from 'history';
 
 const clusterName = 'cluster1';
 
-const renderComponent = () =>
+const historyMock = createMemoryHistory({
+  initialEntries: [clusterConsumerGroupsPath(clusterName)],
+});
+
+const renderComponent = (history = historyMock) =>
   render(
-    <Route path={clusterConsumerGroupsPath(':clusterName')}>
-      <ConsumerGroups />
-    </Route>,
+    <Router history={history}>
+      <Route path={clusterConsumerGroupsPath(':clusterName')}>
+        <ConsumerGroups />
+      </Route>
+    </Router>,
     {
       pathname: clusterConsumerGroupsPath(clusterName),
     }
   );
 
-describe('ConsumerGroup', () => {
+describe('ConsumerGroups', () => {
   it('renders with initial state', async () => {
     renderComponent();
 
     expect(screen.getByRole('progressbar')).toBeInTheDocument();
   });
 
-  describe('Fetching Mock', () => {
-    const url = `/api/clusters/${clusterName}/consumer-groups/paged?orderBy=${ConsumerGroupOrdering.NAME}&sortOrder=${SortOrder.ASC}`;
+  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,
+        },
+      });
+
+      renderComponent();
+      await waitFor(() => 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);
+      const consumerGroupsMock = fetchMock.getOnce(url, 404, {
+        query: {
+          orderBy: ConsumerGroupOrdering.NAME,
+          sortOrder: SortOrder.ASC,
+        },
+      });
 
       renderComponent();
 
@@ -48,10 +79,19 @@ describe('ConsumerGroup', () => {
     });
 
     it('renders with 200 from consumer groups', async () => {
-      const consumerGroupsMock = fetchMock.getOnce(url, {
-        pagedCount: 1,
-        consumerGroups,
-      });
+      const consumerGroupsMock = fetchMock.getOnce(
+        url,
+        {
+          pagedCount: 1,
+          consumerGroups,
+        },
+        {
+          query: {
+            orderBy: ConsumerGroupOrdering.NAME,
+            sortOrder: SortOrder.ASC,
+          },
+        }
+      );
 
       renderComponent();
 
@@ -60,6 +100,43 @@ describe('ConsumerGroup', () => {
 
       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,
+          },
+        }
+      );
+
+      const mockedHistory = createMemoryHistory({
+        initialEntries: [
+          `${clusterConsumerGroupsPath(clusterName)}?q=${searchText}`,
+        ],
+      });
+      renderComponent(mockedHistory);
+
+      await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
+      await waitFor(() => expect(consumerGroupsMock.called()).toBeTruthy());
+
+      expect(screen.getByText(searchText)).toBeInTheDocument();
+      expect(
+        screen.queryByText(consumerGroups[1].groupId)
+      ).not.toBeInTheDocument();
     });
   });
 });

+ 5 - 0
kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts

@@ -25,6 +25,11 @@ export const consumerGroups = [
   },
 ];
 
+export const noConsumerGroupsResponse = {
+  pageCount: 1,
+  consumerGroups: [],
+};
+
 export const consumerGroupsPage = {
   totalPages: 1,
   consumerGroups,

+ 10 - 1
kafka-ui-react-app/src/redux/reducers/consumerGroups/consumerGroupsSlice.ts

@@ -33,15 +33,24 @@ export const fetchConsumerGroupsPaged = createAsyncThunk<
     clusterName: ClusterName;
     orderBy?: ConsumerGroupOrdering;
     sortOrder?: SortOrder;
+    page?: number;
+    perPage?: number;
+    search: string;
   }
 >(
   'consumerGroups/fetchConsumerGroupsPaged',
-  async ({ clusterName, orderBy, sortOrder }, { rejectWithValue }) => {
+  async (
+    { clusterName, orderBy, sortOrder, page, perPage, search },
+    { rejectWithValue }
+  ) => {
     try {
       const response = await api.getConsumerGroupsPageRaw({
         clusterName,
         orderBy,
         sortOrder,
+        page,
+        perPage,
+        search,
       });
       return await response.value();
     } catch (error) {