diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/ConsumerGroups.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/ConsumerGroups.tsx index 843e4e5bdf..0fa0a6506f 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/ConsumerGroups.tsx +++ b/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 ( - - - - - - ); - } - - return ; + return ( + + + + + + ); }; export default ConsumerGroups; diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx index 6fa0b9401f..1286268d13 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/List.tsx +++ b/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 = ({ sortOrder, orderBy, totalPages, + isFetched, setConsumerGroupsSortOrderBy, }) => { - const [searchText, setSearchText] = React.useState(''); + 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 = ({ } ); - const handleInputChange = (search: string) => { - setSearchText(search); - }; + if (!isFetched) { + return ; + } return (
@@ -67,7 +85,7 @@ const List: React.FC = ({ = ({ isFullwidth placeholder="No active consumer groups" hoverable + paginated > ({ orderBy: getConsumerGroupsOrderBy(state), sortOrder: getConsumerGroupsSortOrder(state), totalPages: getConsumerGroupsTotalPages(state), + isFetched: getAreConsumerGroupsPagedFulfilled(state), }); const mapDispatchToProps = { diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx index 69e24255e6..04b78a983b 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/List/__test__/List.spec.tsx @@ -1,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); diff --git a/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx b/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx index 59e8e4fd3d..e24d7c74d4 100644 --- a/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx +++ b/kafka-ui-react-app/src/components/ConsumerGroups/__test__/ConsumerGroups.spec.tsx @@ -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( - - - , + + + + + , { 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(); }); }); }); diff --git a/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts b/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts index 237291df20..21f1817702 100644 --- a/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts +++ b/kafka-ui-react-app/src/redux/reducers/consumerGroups/__test__/fixtures.ts @@ -25,6 +25,11 @@ export const consumerGroups = [ }, ]; +export const noConsumerGroupsResponse = { + pageCount: 1, + consumerGroups: [], +}; + export const consumerGroupsPage = { totalPages: 1, consumerGroups, diff --git a/kafka-ui-react-app/src/redux/reducers/consumerGroups/consumerGroupsSlice.ts b/kafka-ui-react-app/src/redux/reducers/consumerGroups/consumerGroupsSlice.ts index e37943cf59..88023d4e93 100644 --- a/kafka-ui-react-app/src/redux/reducers/consumerGroups/consumerGroupsSlice.ts +++ b/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) {