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
This commit is contained in:
Mgrdich 2022-04-05 15:48:22 +04:00 committed by GitHub
parent b4df8a73c8
commit c79905ce32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 160 additions and 107 deletions

View file

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

View file

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

View file

@ -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 = {

View file

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

View file

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

View file

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

View file

@ -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) {