Issues/1525 add sort order consumer group page (#1747)
* Adding Consumer Groups Paged Reducer and action creator and get Selector logic * Adding fetchConsumerGroupsPaged to the ConsumerGroups Paged * Code refactoring and adding general fixtures For Consumer Group List * Adding Container Redux Connect structure to the List of Consumer Groups * Adding Selectors and action creators for orderBy and Sort Order * Adding All Necessary components to render the smart Tables in Consumer Groups * Adding SmartTable to the Consumer Groups page + fixing allSelectable Checkbox bug in the SmartTable Component * Primitive Tests for ConsumerGroupsTableCells to test rendering and mockup the component and table data * Finalizing Tests for consumer Groups table , minor code bug fix in the Consumer Groups table structure * Adding Order By to the Consumer Groups Table , View part * Adding order By to the Consumer Groups pages * Adding order By to the Consumer Groups pages with SortBy functionality * Code refactor in the ConsumerGroups component and its related tests * Code refactor in the ConsumerGroups component and its related tests * adding Tests in the Consumer Groups List * Fixing the Sorting styling Bug in the Table order * Adding additional Tests to the ConsumerGroups List tests * Adding additional Tests for TableHeaderCell styled component * Deleting obsolete codes from the consumer Groups Slice + minor table header test type fix * Adding Tests for the consumerGroupSlice * Adding Tests for the consumerGroupSlice * Consumer Groups table minor code modifications * Minor Code bug fixes in the SmartTable Component
This commit is contained in:
parent
8908d6839c
commit
73266f86af
15 changed files with 588 additions and 127 deletions
|
@ -3,22 +3,32 @@ import { ClusterName } from 'redux/interfaces';
|
|||
import { Switch, useParams } from 'react-router-dom';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import Details from 'components/ConsumerGroups/Details/Details';
|
||||
import List from 'components/ConsumerGroups/List/List';
|
||||
import ListContainer from 'components/ConsumerGroups/List/ListContainer';
|
||||
import ResetOffsets from 'components/ConsumerGroups/Details/ResetOffsets/ResetOffsets';
|
||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
||||
import {
|
||||
fetchConsumerGroups,
|
||||
getAreConsumerGroupsFulfilled,
|
||||
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(getAreConsumerGroupsFulfilled);
|
||||
const isFetched = useAppSelector(getAreConsumerGroupsPagedFulfilled);
|
||||
const orderBy = useAppSelector(getConsumerGroupsOrderBy);
|
||||
const sortOrder = useAppSelector(getConsumerGroupsSortOrder);
|
||||
React.useEffect(() => {
|
||||
dispatch(fetchConsumerGroups(clusterName));
|
||||
}, [clusterName, dispatch]);
|
||||
dispatch(
|
||||
fetchConsumerGroupsPaged({
|
||||
clusterName,
|
||||
orderBy: orderBy || undefined,
|
||||
sortOrder,
|
||||
})
|
||||
);
|
||||
}, [clusterName, orderBy, sortOrder, dispatch]);
|
||||
|
||||
if (isFetched) {
|
||||
return (
|
||||
|
@ -26,7 +36,7 @@ const ConsumerGroups: React.FC = () => {
|
|||
<BreadcrumbRoute
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/consumer-groups"
|
||||
component={List}
|
||||
component={ListContainer}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
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 getTagColor from 'components/ConsumerGroups/Utils/TagColor';
|
||||
import { ConsumerGroup } from 'generated-sources';
|
||||
import { SmartTableKeyLink } from 'components/common/table/Table/TableKeyLink.styled';
|
||||
|
||||
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={`consumer-groups/${groupId}`}>{groupId}</Link>
|
||||
</SmartTableKeyLink>
|
||||
);
|
||||
};
|
|
@ -1,18 +1,61 @@
|
|||
import React from 'react';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
import React, { useMemo } from 'react';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import Search from 'components/common/Search/Search';
|
||||
import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import { selectAll } from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||
import {
|
||||
ConsumerGroupDetails,
|
||||
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 ListItem from './ListItem';
|
||||
export interface Props {
|
||||
consumerGroups: ConsumerGroupDetails[];
|
||||
orderBy: ConsumerGroupOrdering | null;
|
||||
sortOrder: SortOrder;
|
||||
totalPages: number;
|
||||
setConsumerGroupsSortOrderBy(orderBy: ConsumerGroupOrdering | null): void;
|
||||
}
|
||||
|
||||
const List: React.FC = () => {
|
||||
const consumerGroups = useAppSelector(selectAll);
|
||||
const List: React.FC<Props> = ({
|
||||
consumerGroups,
|
||||
sortOrder,
|
||||
orderBy,
|
||||
totalPages,
|
||||
setConsumerGroupsSortOrderBy,
|
||||
}) => {
|
||||
const [searchText, setSearchText] = React.useState<string>('');
|
||||
|
||||
const tableData = useMemo(() => {
|
||||
return consumerGroups.filter(
|
||||
(consumerGroup) =>
|
||||
!searchText || consumerGroup?.groupId?.indexOf(searchText) >= 0
|
||||
);
|
||||
}, [searchText, consumerGroups]);
|
||||
|
||||
const tableState = useTableState<
|
||||
ConsumerGroupDetails,
|
||||
string,
|
||||
ConsumerGroupOrdering
|
||||
>(
|
||||
tableData,
|
||||
{
|
||||
totalPages,
|
||||
idSelector: (consumerGroup) => consumerGroup.groupId,
|
||||
},
|
||||
{
|
||||
handleOrderBy: setConsumerGroupsSortOrderBy,
|
||||
orderBy,
|
||||
sortOrder,
|
||||
}
|
||||
);
|
||||
|
||||
const handleInputChange = (search: string) => {
|
||||
setSearchText(search);
|
||||
};
|
||||
|
@ -27,36 +70,31 @@ const List: React.FC = () => {
|
|||
handleSearch={handleInputChange}
|
||||
/>
|
||||
</ControlPanelWrapper>
|
||||
<Table isFullwidth>
|
||||
<thead>
|
||||
<tr>
|
||||
<TableHeaderCell title="Consumer Group ID" />
|
||||
<TableHeaderCell title="Num Of Members" />
|
||||
<TableHeaderCell title="Num Of Topics" />
|
||||
<TableHeaderCell title="Messages Behind" />
|
||||
<TableHeaderCell title="Coordinator" />
|
||||
<TableHeaderCell title="State" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{consumerGroups
|
||||
.filter(
|
||||
(consumerGroup) =>
|
||||
!searchText || consumerGroup?.groupId?.indexOf(searchText) >= 0
|
||||
)
|
||||
.map((consumerGroup) => (
|
||||
<ListItem
|
||||
key={consumerGroup.groupId}
|
||||
consumerGroup={consumerGroup}
|
||||
<SmartTable
|
||||
tableState={tableState}
|
||||
isFullwidth
|
||||
placeholder="No active consumer groups"
|
||||
hoverable
|
||||
>
|
||||
<TableColumn
|
||||
title="Consumer Group ID"
|
||||
cell={GroupIDCell}
|
||||
orderValue={ConsumerGroupOrdering.NAME}
|
||||
/>
|
||||
))}
|
||||
{consumerGroups.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={10}>No active consumer groups</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
getConsumerGroupsOrderBy,
|
||||
getConsumerGroupsSortOrder,
|
||||
getConsumerGroupsTotalPages,
|
||||
sortBy,
|
||||
selectAll,
|
||||
} 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),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setConsumerGroupsSortOrderBy: sortBy,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(List);
|
|
@ -0,0 +1,64 @@
|
|||
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, never> = {
|
||||
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',
|
||||
`/consumer-groups/${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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,27 +1,40 @@
|
|||
import React from 'react';
|
||||
import List from 'components/ConsumerGroups/List/List';
|
||||
import List, { Props } from 'components/ConsumerGroups/List/List';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { store } from 'redux/store';
|
||||
import { fetchConsumerGroups } from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||
import { consumerGroups } from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
import { consumerGroups as consumerGroupMock } from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
import { ConsumerGroupOrdering, SortOrder } from 'generated-sources';
|
||||
import theme from 'theme/theme';
|
||||
|
||||
describe('List', () => {
|
||||
beforeEach(() => render(<List />, { store }));
|
||||
const setUpComponent = (props: Partial<Props> = {}) => {
|
||||
const {
|
||||
consumerGroups,
|
||||
orderBy,
|
||||
sortOrder,
|
||||
totalPages,
|
||||
setConsumerGroupsSortOrderBy,
|
||||
} = props;
|
||||
return render(
|
||||
<List
|
||||
consumerGroups={consumerGroups || []}
|
||||
orderBy={orderBy || ConsumerGroupOrdering.NAME}
|
||||
sortOrder={sortOrder || SortOrder.ASC}
|
||||
setConsumerGroupsSortOrderBy={setConsumerGroupsSortOrderBy || jest.fn()}
|
||||
totalPages={totalPages || 1}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders empty table', () => {
|
||||
setUpComponent();
|
||||
expect(screen.getByRole('table')).toBeInTheDocument();
|
||||
expect(screen.getByText('No active consumer groups')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('consumerGroups are fetched', () => {
|
||||
beforeEach(() => {
|
||||
store.dispatch({
|
||||
type: fetchConsumerGroups.fulfilled.type,
|
||||
payload: consumerGroups,
|
||||
});
|
||||
});
|
||||
beforeEach(() => setUpComponent({ consumerGroups: consumerGroupMock }));
|
||||
|
||||
it('renders all rows with consumers', () => {
|
||||
expect(screen.getByText('groupId1')).toBeInTheDocument();
|
||||
|
@ -33,13 +46,64 @@ describe('List', () => {
|
|||
await waitFor(() => {
|
||||
userEvent.type(
|
||||
screen.getByPlaceholderText('Search by Consumer Group ID'),
|
||||
'groupId1'
|
||||
consumerGroupMock[0].groupId
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByText('groupId1')).toBeInTheDocument();
|
||||
expect(screen.getByText('groupId2')).toBeInTheDocument();
|
||||
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);
|
||||
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}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { consumerGroups } from 'redux/reducers/consumerGroups/__test__/fixtures'
|
|||
import { render } from 'lib/testHelpers';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { Route } from 'react-router';
|
||||
import { ConsumerGroupOrdering, SortOrder } from 'generated-sources';
|
||||
|
||||
const clusterName = 'cluster1';
|
||||
|
||||
|
@ -24,21 +25,19 @@ const renderComponent = () =>
|
|||
);
|
||||
|
||||
describe('ConsumerGroup', () => {
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
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}`;
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
});
|
||||
it('renders with 404 from consumer groups', async () => {
|
||||
const consumerGroupsMock = fetchMock.getOnce(
|
||||
`/api/clusters/${clusterName}/consumer-groups`,
|
||||
404
|
||||
);
|
||||
const consumerGroupsMock = fetchMock.getOnce(url, 404);
|
||||
|
||||
renderComponent();
|
||||
|
||||
|
@ -49,10 +48,10 @@ describe('ConsumerGroup', () => {
|
|||
});
|
||||
|
||||
it('renders with 200 from consumer groups', async () => {
|
||||
const consumerGroupsMock = fetchMock.getOnce(
|
||||
`/api/clusters/${clusterName}/consumer-groups`,
|
||||
consumerGroups
|
||||
);
|
||||
const consumerGroupsMock = fetchMock.getOnce(url, {
|
||||
pagedCount: 1,
|
||||
consumerGroups,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
|
||||
|
@ -63,3 +62,4 @@ describe('ConsumerGroup', () => {
|
|||
expect(screen.getByRole('table')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -69,9 +69,10 @@ export const SmartTable = <T, TId extends IdType, OT = never>({
|
|||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<tr>
|
||||
{allSelectable ? (
|
||||
let checkboxElement = null;
|
||||
|
||||
if (selectable) {
|
||||
checkboxElement = allSelectable ? (
|
||||
<SelectCell
|
||||
rowIndex={-1}
|
||||
el="th"
|
||||
|
@ -81,7 +82,12 @@ export const SmartTable = <T, TId extends IdType, OT = never>({
|
|||
/>
|
||||
) : (
|
||||
<S.TableHeaderCell />
|
||||
)}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
{checkboxElement}
|
||||
{headerCells}
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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<OT> {
|
||||
orderBy: OT | null;
|
||||
|
@ -77,7 +79,12 @@ export const SelectCell: React.FC<SelectCellProps> = ({
|
|||
onChange(e.target.checked);
|
||||
};
|
||||
|
||||
const El = el;
|
||||
let El: 'td' | StyledComponent<'th', DefaultTheme>;
|
||||
if (el === 'th') {
|
||||
El = S.TableHeaderCell;
|
||||
} else {
|
||||
El = el;
|
||||
}
|
||||
|
||||
return (
|
||||
<El>
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
export const TableKeyLink = styled.td`
|
||||
const tableLinkMixin = css(
|
||||
({ theme }) => `
|
||||
& > a {
|
||||
color: ${({ theme }) => theme.table.link.color};
|
||||
color: ${theme.table.link.color};
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const TableKeyLink = styled.td`
|
||||
${tableLinkMixin}
|
||||
`;
|
||||
|
||||
export const SmartTableKeyLink = styled.div`
|
||||
${tableLinkMixin}
|
||||
`;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import styled, { css } from 'styled-components';
|
||||
import { SortOrder } from 'generated-sources';
|
||||
|
||||
interface TitleProps {
|
||||
export interface TitleProps {
|
||||
isOrderable?: boolean;
|
||||
isOrdered?: boolean;
|
||||
sortOrder?: SortOrder;
|
||||
}
|
||||
|
||||
const orderableMixin = css(
|
||||
|
@ -45,20 +47,28 @@ const orderableMixin = css(
|
|||
`
|
||||
);
|
||||
|
||||
const orderedMixin = css(
|
||||
const ASCMixin = css(
|
||||
({ theme: { table } }) => `
|
||||
color: ${table.th.color.active};
|
||||
&::before {
|
||||
|
||||
&:before {
|
||||
border-bottom-color: ${table.th.color.active};
|
||||
}
|
||||
&::after {
|
||||
`
|
||||
);
|
||||
|
||||
const DESCMixin = css(
|
||||
({ theme: { table } }) => `
|
||||
color: ${table.th.color.active};
|
||||
|
||||
&:after {
|
||||
border-top-color: ${table.th.color.active};
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const Title = styled.span<TitleProps>(
|
||||
({ isOrderable, isOrdered, theme: { table } }) => css`
|
||||
({ isOrderable, isOrdered, sortOrder, theme: { table } }) => css`
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
|
@ -75,7 +85,9 @@ export const Title = styled.span<TitleProps>(
|
|||
|
||||
${isOrderable && orderableMixin}
|
||||
|
||||
${isOrderable && isOrdered && orderedMixin}
|
||||
${isOrderable && isOrdered && sortOrder === SortOrder.ASC && ASCMixin}
|
||||
|
||||
${isOrderable && isOrdered && sortOrder === SortOrder.DESC && DESCMixin}
|
||||
`
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import * as S from 'components/common/table/TableHeaderCell/TableHeaderCell.styled';
|
||||
import { SortOrder } from 'generated-sources';
|
||||
import { screen } from '@testing-library/react';
|
||||
import theme from 'theme/theme';
|
||||
|
||||
describe('TableHeaderCell.Styled', () => {
|
||||
describe('Title Component', () => {
|
||||
const DEFAULT_TITLE_TEXT = 'Text';
|
||||
const setUpComponent = (
|
||||
props: Partial<S.TitleProps> = {},
|
||||
text: string = DEFAULT_TITLE_TEXT
|
||||
) => {
|
||||
render(
|
||||
<S.Title
|
||||
isOrderable={'isOrderable' in props ? props.isOrderable : true}
|
||||
isOrdered={'isOrdered' in props ? props.isOrdered : true}
|
||||
sortOrder={props.sortOrder || SortOrder.ASC}
|
||||
>
|
||||
{text || DEFAULT_TITLE_TEXT}
|
||||
</S.Title>
|
||||
);
|
||||
};
|
||||
describe('test the default Parameters', () => {
|
||||
beforeEach(() => {
|
||||
setUpComponent();
|
||||
});
|
||||
it('should test the props of Title Component', () => {
|
||||
const titleElement = screen.getByText(DEFAULT_TITLE_TEXT);
|
||||
expect(titleElement).toBeInTheDocument();
|
||||
expect(titleElement).toHaveStyle(
|
||||
`color: ${theme.table.th.color.active};`
|
||||
);
|
||||
expect(titleElement).toHaveStyleRule(
|
||||
'border-bottom-color',
|
||||
theme.table.th.color.active,
|
||||
{
|
||||
modifier: '&:before',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom props', () => {
|
||||
it('should test the sort order styling of Title Component', () => {
|
||||
setUpComponent({
|
||||
sortOrder: SortOrder.DESC,
|
||||
});
|
||||
|
||||
const titleElement = screen.getByText(DEFAULT_TITLE_TEXT);
|
||||
expect(titleElement).toBeInTheDocument();
|
||||
expect(titleElement).toHaveStyleRule(
|
||||
'color',
|
||||
theme.table.th.color.active
|
||||
);
|
||||
expect(titleElement).toHaveStyleRule(
|
||||
'border-top-color',
|
||||
theme.table.th.color.active,
|
||||
{
|
||||
modifier: '&:after',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should test the Title Component styling without the ordering', () => {
|
||||
setUpComponent({
|
||||
isOrderable: false,
|
||||
isOrdered: false,
|
||||
});
|
||||
|
||||
const titleElement = screen.getByText(DEFAULT_TITLE_TEXT);
|
||||
expect(titleElement).toHaveStyleRule('cursor', 'default');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Preview Component', () => {
|
||||
const DEFAULT_TEXT = 'DEFAULT_TEXT';
|
||||
it('should render the preview and check themes values', () => {
|
||||
render(<S.Preview>{DEFAULT_TEXT}</S.Preview>);
|
||||
const element = screen.getByText(DEFAULT_TEXT);
|
||||
expect(element).toBeInTheDocument();
|
||||
expect(element).toHaveStyleRule(
|
||||
'background',
|
||||
theme.table.th.backgroundColor.normal
|
||||
);
|
||||
expect(element).toHaveStyleRule(
|
||||
'color',
|
||||
theme.table.th.previewColor.normal
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
import { store } from 'redux/store';
|
||||
import {
|
||||
sortBy,
|
||||
getConsumerGroupsOrderBy,
|
||||
getConsumerGroupsSortOrder,
|
||||
getAreConsumerGroupsPagedFulfilled,
|
||||
fetchConsumerGroupsPaged,
|
||||
selectAll,
|
||||
} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||
import { ConsumerGroupOrdering, SortOrder } from 'generated-sources';
|
||||
import { consumerGroups } from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
|
||||
describe('Consumer Groups Slice', () => {
|
||||
describe('Actions', () => {
|
||||
it('should test the sortBy actions', () => {
|
||||
expect(store.getState().consumerGroups.sortOrder).toBe(SortOrder.ASC);
|
||||
|
||||
store.dispatch(sortBy(ConsumerGroupOrdering.STATE));
|
||||
expect(getConsumerGroupsOrderBy(store.getState())).toBe(
|
||||
ConsumerGroupOrdering.STATE
|
||||
);
|
||||
expect(getConsumerGroupsSortOrder(store.getState())).toBe(SortOrder.DESC);
|
||||
store.dispatch(sortBy(ConsumerGroupOrdering.STATE));
|
||||
expect(getConsumerGroupsSortOrder(store.getState())).toBe(SortOrder.ASC);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Thunk Actions', () => {
|
||||
it('should check the fetchConsumerPaged ', () => {
|
||||
store.dispatch({
|
||||
type: fetchConsumerGroupsPaged.fulfilled.type,
|
||||
payload: {
|
||||
consumerGroups,
|
||||
},
|
||||
});
|
||||
|
||||
expect(getAreConsumerGroupsPagedFulfilled(store.getState())).toBeTruthy();
|
||||
expect(selectAll(store.getState())).toEqual(consumerGroups);
|
||||
|
||||
store.dispatch({
|
||||
type: fetchConsumerGroupsPaged.fulfilled.type,
|
||||
payload: {
|
||||
consumerGroups: null,
|
||||
},
|
||||
});
|
||||
expect(selectAll(store.getState())).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -25,6 +25,11 @@ export const consumerGroups = [
|
|||
},
|
||||
];
|
||||
|
||||
export const consumerGroupsPage = {
|
||||
totalPages: 1,
|
||||
consumerGroups,
|
||||
};
|
||||
|
||||
export const consumerGroupPayload = {
|
||||
groupId: 'amazon.msk.canary.group.broker-1',
|
||||
members: 0,
|
||||
|
|
|
@ -3,12 +3,15 @@ import {
|
|||
createEntityAdapter,
|
||||
createSlice,
|
||||
createSelector,
|
||||
PayloadAction,
|
||||
} from '@reduxjs/toolkit';
|
||||
import {
|
||||
Configuration,
|
||||
ConsumerGroup,
|
||||
ConsumerGroupDetails,
|
||||
ConsumerGroupOrdering,
|
||||
ConsumerGroupsApi,
|
||||
ConsumerGroupsPageResponse,
|
||||
SortOrder,
|
||||
} from 'generated-sources';
|
||||
import { BASE_PARAMS } from 'lib/constants';
|
||||
import { getResponse } from 'lib/errorHandling';
|
||||
|
@ -19,20 +22,28 @@ import {
|
|||
RootState,
|
||||
} from 'redux/interfaces';
|
||||
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
||||
import { EntityState } from '@reduxjs/toolkit/src/entities/models';
|
||||
|
||||
const apiClientConf = new Configuration(BASE_PARAMS);
|
||||
export const api = new ConsumerGroupsApi(apiClientConf);
|
||||
|
||||
export const fetchConsumerGroups = createAsyncThunk<
|
||||
ConsumerGroup[],
|
||||
ClusterName
|
||||
export const fetchConsumerGroupsPaged = createAsyncThunk<
|
||||
ConsumerGroupsPageResponse,
|
||||
{
|
||||
clusterName: ClusterName;
|
||||
orderBy?: ConsumerGroupOrdering;
|
||||
sortOrder?: SortOrder;
|
||||
}
|
||||
>(
|
||||
'consumerGroups/fetchConsumerGroups',
|
||||
async (clusterName: ClusterName, { rejectWithValue }) => {
|
||||
'consumerGroups/fetchConsumerGroupsPaged',
|
||||
async ({ clusterName, orderBy, sortOrder }, { rejectWithValue }) => {
|
||||
try {
|
||||
return await api.getConsumerGroups({
|
||||
const response = await api.getConsumerGroupsPageRaw({
|
||||
clusterName,
|
||||
orderBy,
|
||||
sortOrder,
|
||||
});
|
||||
return await response.value();
|
||||
} catch (error) {
|
||||
return rejectWithValue(await getResponse(error as Response));
|
||||
}
|
||||
|
@ -105,19 +116,45 @@ export const resetConsumerGroupOffsets = createAsyncThunk<
|
|||
}
|
||||
}
|
||||
);
|
||||
const SCHEMAS_PAGE_COUNT = 1;
|
||||
|
||||
const consumerGroupsAdapter = createEntityAdapter<ConsumerGroupDetails>({
|
||||
selectId: (consumerGroup) => consumerGroup.groupId,
|
||||
});
|
||||
|
||||
const consumerGroupsSlice = createSlice({
|
||||
interface ConsumerGroupState extends EntityState<ConsumerGroupDetails> {
|
||||
orderBy: ConsumerGroupOrdering | null;
|
||||
sortOrder: SortOrder;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
const initialState: ConsumerGroupState = {
|
||||
orderBy: ConsumerGroupOrdering.NAME,
|
||||
sortOrder: SortOrder.ASC,
|
||||
totalPages: SCHEMAS_PAGE_COUNT,
|
||||
...consumerGroupsAdapter.getInitialState(),
|
||||
};
|
||||
|
||||
export const consumerGroupsSlice = createSlice({
|
||||
name: 'consumerGroups',
|
||||
initialState: consumerGroupsAdapter.getInitialState(),
|
||||
reducers: {},
|
||||
initialState,
|
||||
reducers: {
|
||||
sortBy: (state, action: PayloadAction<ConsumerGroupOrdering>) => {
|
||||
state.orderBy = action.payload;
|
||||
state.sortOrder =
|
||||
state.orderBy === action.payload && state.sortOrder === SortOrder.ASC
|
||||
? SortOrder.DESC
|
||||
: SortOrder.ASC;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(fetchConsumerGroups.fulfilled, (state, { payload }) => {
|
||||
consumerGroupsAdapter.setAll(state, payload);
|
||||
});
|
||||
builder.addCase(
|
||||
fetchConsumerGroupsPaged.fulfilled,
|
||||
(state, { payload }) => {
|
||||
state.totalPages = payload.pageCount || SCHEMAS_PAGE_COUNT;
|
||||
consumerGroupsAdapter.setAll(state, payload.consumerGroups || []);
|
||||
}
|
||||
);
|
||||
builder.addCase(fetchConsumerGroupDetails.fulfilled, (state, { payload }) =>
|
||||
consumerGroupsAdapter.upsertOne(state, payload)
|
||||
);
|
||||
|
@ -127,13 +164,17 @@ const consumerGroupsSlice = createSlice({
|
|||
},
|
||||
});
|
||||
|
||||
export const { selectAll, selectById } =
|
||||
consumerGroupsAdapter.getSelectors<RootState>(
|
||||
({ consumerGroups }) => consumerGroups
|
||||
);
|
||||
export const { sortBy } = consumerGroupsSlice.actions;
|
||||
|
||||
export const getAreConsumerGroupsFulfilled = createSelector(
|
||||
createFetchingSelector('consumerGroups/fetchConsumerGroups'),
|
||||
const consumerGroupsState = ({
|
||||
consumerGroups,
|
||||
}: RootState): ConsumerGroupState => consumerGroups;
|
||||
|
||||
export const { selectAll, selectById } =
|
||||
consumerGroupsAdapter.getSelectors<RootState>(consumerGroupsState);
|
||||
|
||||
export const getAreConsumerGroupsPagedFulfilled = createSelector(
|
||||
createFetchingSelector('consumerGroups/fetchConsumerGroupsPaged'),
|
||||
(status) => status === 'fulfilled'
|
||||
);
|
||||
|
||||
|
@ -152,4 +193,19 @@ export const getIsOffsetReseted = createSelector(
|
|||
(status) => status === 'fulfilled'
|
||||
);
|
||||
|
||||
export const getConsumerGroupsOrderBy = createSelector(
|
||||
consumerGroupsState,
|
||||
(state) => state.orderBy
|
||||
);
|
||||
|
||||
export const getConsumerGroupsSortOrder = createSelector(
|
||||
consumerGroupsState,
|
||||
(state) => state.sortOrder
|
||||
);
|
||||
|
||||
export const getConsumerGroupsTotalPages = createSelector(
|
||||
consumerGroupsState,
|
||||
(state) => state.totalPages
|
||||
);
|
||||
|
||||
export default consumerGroupsSlice.reducer;
|
||||
|
|
Loading…
Add table
Reference in a new issue