[ISSUE-1512]Added sorting by topics size

* [ISSUE-1512]Added sorting by topics size

* Add sort by Size.Refactoring sort order

* correct a little mistake

* Improve test coverage

* got rid code dupliction

* refactoring

Co-authored-by: ValentinPrischepa <valentin.prischepa@gmail.com>
Co-authored-by: Anton Zorin <ant.zorin@gmail.com>
Co-authored-by: Oleg Shur <workshur@gmail.com>
This commit is contained in:
Zorii4 2022-02-09 15:46:11 +03:00 committed by GitHub
parent 529cd0bd6e
commit 4eaf8ea2c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 101 additions and 18 deletions

View file

@ -44,6 +44,8 @@ exports[`Tasks view matches snapshot 1`] = `
background: #FFFFFF; background: #FFFFFF;
cursor: default; cursor: default;
color: #73848C; color: #73848C;
padding-right: 18px;
position: relative;
} }
.c1 { .c1 {
@ -217,6 +219,8 @@ exports[`Tasks view matches snapshot when no tasks 1`] = `
background: #FFFFFF; background: #FFFFFF;
cursor: default; cursor: default;
color: #73848C; color: #73848C;
padding-right: 18px;
position: relative;
} }
.c1 { .c1 {

View file

@ -12,7 +12,11 @@ import ClusterContext from 'components/contexts/ClusterContext';
import PageLoader from 'components/common/PageLoader/PageLoader'; import PageLoader from 'components/common/PageLoader/PageLoader';
import Pagination from 'components/common/Pagination/Pagination'; import Pagination from 'components/common/Pagination/Pagination';
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal'; import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
import { GetTopicsRequest, TopicColumnsToSort } from 'generated-sources'; import {
GetTopicsRequest,
SortOrder,
TopicColumnsToSort,
} from 'generated-sources';
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell'; import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
import Search from 'components/common/Search/Search'; import Search from 'components/common/Search/Search';
import { PER_PAGE } from 'lib/constants'; import { PER_PAGE } from 'lib/constants';
@ -39,6 +43,7 @@ export interface TopicsListProps {
): void; ): void;
search: string; search: string;
orderBy: TopicColumnsToSort | null; orderBy: TopicColumnsToSort | null;
sortOrder: SortOrder;
setTopicsSearch(search: string): void; setTopicsSearch(search: string): void;
setTopicsOrderBy(orderBy: TopicColumnsToSort | null): void; setTopicsOrderBy(orderBy: TopicColumnsToSort | null): void;
} }
@ -54,6 +59,7 @@ const List: React.FC<TopicsListProps> = ({
clearTopicsMessages, clearTopicsMessages,
search, search,
orderBy, orderBy,
sortOrder,
setTopicsSearch, setTopicsSearch,
setTopicsOrderBy, setTopicsOrderBy,
}) => { }) => {
@ -69,6 +75,7 @@ const List: React.FC<TopicsListProps> = ({
page, page,
perPage, perPage,
orderBy: orderBy || undefined, orderBy: orderBy || undefined,
sortOrder,
search, search,
showInternal, showInternal,
}); });
@ -78,6 +85,7 @@ const List: React.FC<TopicsListProps> = ({
page, page,
perPage, perPage,
orderBy, orderBy,
sortOrder,
search, search,
showInternal, showInternal,
]); ]);
@ -215,24 +223,32 @@ const List: React.FC<TopicsListProps> = ({
title="Topic Name" title="Topic Name"
orderValue={TopicColumnsToSort.NAME} orderValue={TopicColumnsToSort.NAME}
orderBy={orderBy} orderBy={orderBy}
sortOrder={sortOrder}
handleOrderBy={setTopicsOrderBy} handleOrderBy={setTopicsOrderBy}
/> />
<TableHeaderCell <TableHeaderCell
title="Total Partitions" title="Total Partitions"
orderValue={TopicColumnsToSort.TOTAL_PARTITIONS} orderValue={TopicColumnsToSort.TOTAL_PARTITIONS}
orderBy={orderBy} orderBy={orderBy}
sortOrder={sortOrder}
handleOrderBy={setTopicsOrderBy} handleOrderBy={setTopicsOrderBy}
/> />
<TableHeaderCell <TableHeaderCell
title="Out of sync replicas" title="Out of sync replicas"
orderValue={TopicColumnsToSort.OUT_OF_SYNC_REPLICAS} orderValue={TopicColumnsToSort.OUT_OF_SYNC_REPLICAS}
orderBy={orderBy} orderBy={orderBy}
sortOrder={sortOrder}
handleOrderBy={setTopicsOrderBy} handleOrderBy={setTopicsOrderBy}
/> />
<TableHeaderCell title="Replication Factor" /> <TableHeaderCell title="Replication Factor" />
<TableHeaderCell title="Number of messages" /> <TableHeaderCell title="Number of messages" />
<TableHeaderCell title="Size" /> <TableHeaderCell
<TableHeaderCell /> title="Size"
orderValue={TopicColumnsToSort.SIZE}
orderBy={orderBy}
sortOrder={sortOrder}
handleOrderBy={setTopicsOrderBy}
/>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -15,6 +15,7 @@ import {
getTopicListTotalPages, getTopicListTotalPages,
getTopicsSearch, getTopicsSearch,
getTopicsOrderBy, getTopicsOrderBy,
getTopicsSortOrder,
} from 'redux/reducers/topics/selectors'; } from 'redux/reducers/topics/selectors';
import List from './List'; import List from './List';
@ -25,6 +26,7 @@ const mapStateToProps = (state: RootState) => ({
totalPages: getTopicListTotalPages(state), totalPages: getTopicListTotalPages(state),
search: getTopicsSearch(state), search: getTopicsSearch(state),
orderBy: getTopicsOrderBy(state), orderBy: getTopicsOrderBy(state),
sortOrder: getTopicsSortOrder(state),
}); });
const mapDispatchToProps = { const mapDispatchToProps = {

View file

@ -13,6 +13,7 @@ import { externalTopicPayload } from 'redux/reducers/topics/__test__/fixtures';
import { ConfirmationModalProps } from 'components/common/ConfirmationModal/ConfirmationModal'; import { ConfirmationModalProps } from 'components/common/ConfirmationModal/ConfirmationModal';
import theme from 'theme/theme'; import theme from 'theme/theme';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { SortOrder } from 'generated-sources';
jest.mock( jest.mock(
'components/common/ConfirmationModal/ConfirmationModal', 'components/common/ConfirmationModal/ConfirmationModal',
@ -33,6 +34,7 @@ describe('List', () => {
clearTopicMessages={jest.fn()} clearTopicMessages={jest.fn()}
search="" search=""
orderBy={null} orderBy={null}
sortOrder={SortOrder.ASC}
setTopicsSearch={jest.fn()} setTopicsSearch={jest.fn()}
setTopicsOrderBy={jest.fn()} setTopicsOrderBy={jest.fn()}
{...props} {...props}
@ -104,6 +106,7 @@ describe('List', () => {
expect(fetchTopicsList).toHaveBeenLastCalledWith({ expect(fetchTopicsList).toHaveBeenLastCalledWith({
search: '', search: '',
showInternal: false, showInternal: false,
sortOrder: SortOrder.ASC,
}); });
}); });

View file

@ -1,8 +1,10 @@
import { SortOrder } from 'generated-sources';
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
interface TitleProps { interface TitleProps {
isOrderable?: boolean; isOrderable?: boolean;
isOrdered?: boolean; isOrdered?: boolean;
sortOrder?: SortOrder;
} }
const orderableMixin = css( const orderableMixin = css(
@ -45,20 +47,48 @@ const orderableMixin = css(
` `
); );
const orderedMixin = css( const ASCMixin = css(
({ theme: { table } }) => ` ({ theme: { table } }) => `
color: ${table.th.color.active}; color: ${table.th.color.active};
&::before { cursor: pointer;
border-bottom-color: ${table.th.color.active};
}
&::after { &::after {
cursor: pointer;
border: 4px solid transparent;
content: '';
display: block;
height: 0;
right: 5px;
top: 50%;
position: absolute;
border-top-color: ${table.th.color.active}; border-top-color: ${table.th.color.active};
margin-top: 1px;
}
`
);
const DESCMixin = css(
({ theme: { table } }) => `
color: ${table.th.color.active};
cursor: pointer;
&::before {
border: 4px solid transparent;
cursor: pointer;
content: '';
display: block;
height: 0;
right: 5px;
top: 50%;
position: absolute;
margin-top: -9px;
border-bottom-color: ${table.th.color.active};
} }
` `
); );
export const Title = styled.span<TitleProps>( export const Title = styled.span<TitleProps>(
({ isOrderable, isOrdered, theme: { table } }) => css` ({ isOrderable, isOrdered, sortOrder, theme: { table } }) => css`
font-family: Inter, sans-serif; font-family: Inter, sans-serif;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
@ -72,10 +102,14 @@ export const Title = styled.span<TitleProps>(
background: ${table.th.backgroundColor.normal}; background: ${table.th.backgroundColor.normal};
cursor: default; cursor: default;
color: ${table.th.color.normal}; color: ${table.th.color.normal};
padding-right: 18px;
position: relative;
${isOrderable && orderableMixin} ${isOrderable && !isOrdered && orderableMixin}
${isOrderable && isOrdered && orderedMixin} ${isOrderable && isOrdered && sortOrder === SortOrder.ASC && ASCMixin}
${isOrderable && isOrdered && sortOrder === SortOrder.DESC && DESCMixin}
` `
); );

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { TopicColumnsToSort } from 'generated-sources'; import { SortOrder, TopicColumnsToSort } from 'generated-sources';
import * as S from 'components/common/table/TableHeaderCell/TableHeaderCell.styled'; import * as S from 'components/common/table/TableHeaderCell/TableHeaderCell.styled';
export interface TableHeaderCellProps { export interface TableHeaderCellProps {
@ -7,6 +7,7 @@ export interface TableHeaderCellProps {
previewText?: string; previewText?: string;
onPreview?: () => void; onPreview?: () => void;
orderBy?: TopicColumnsToSort | null; orderBy?: TopicColumnsToSort | null;
sortOrder?: SortOrder;
orderValue?: TopicColumnsToSort | null; orderValue?: TopicColumnsToSort | null;
handleOrderBy?: (orderBy: TopicColumnsToSort | null) => void; handleOrderBy?: (orderBy: TopicColumnsToSort | null) => void;
} }
@ -17,6 +18,7 @@ const TableHeaderCell: React.FC<TableHeaderCellProps> = (props) => {
previewText, previewText,
onPreview, onPreview,
orderBy, orderBy,
sortOrder,
orderValue, orderValue,
handleOrderBy, handleOrderBy,
...restProps ...restProps
@ -38,6 +40,7 @@ const TableHeaderCell: React.FC<TableHeaderCellProps> = (props) => {
}; };
const orderableProps = isOrderable && { const orderableProps = isOrderable && {
isOrderable, isOrderable,
sortOrder,
onClick: handleOnClick, onClick: handleOnClick,
onKeyDown: handleOnKeyDown, onKeyDown: handleOnKeyDown,
role: 'button', role: 'button',

View file

@ -4,7 +4,7 @@ import { render } from 'lib/testHelpers';
import TableHeaderCell, { import TableHeaderCell, {
TableHeaderCellProps, TableHeaderCellProps,
} from 'components/common/table/TableHeaderCell/TableHeaderCell'; } from 'components/common/table/TableHeaderCell/TableHeaderCell';
import { TopicColumnsToSort } from 'generated-sources'; import { SortOrder, TopicColumnsToSort } from 'generated-sources';
import theme from 'theme/theme'; import theme from 'theme/theme';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
@ -50,6 +50,7 @@ describe('TableHeaderCell', () => {
title: testTitle, title: testTitle,
orderBy: TopicColumnsToSort.NAME, orderBy: TopicColumnsToSort.NAME,
orderValue: TopicColumnsToSort.NAME, orderValue: TopicColumnsToSort.NAME,
sortOrder: SortOrder.ASC,
handleOrderBy, handleOrderBy,
}); });
const columnheader = screen.getByRole('columnheader'); const columnheader = screen.getByRole('columnheader');
@ -59,7 +60,6 @@ describe('TableHeaderCell', () => {
expect(title).toHaveStyle(`color: ${theme.table.th.color.active};`); expect(title).toHaveStyle(`color: ${theme.table.th.color.active};`);
expect(title).toHaveStyle('cursor: pointer;'); expect(title).toHaveStyle('cursor: pointer;');
}); });
it('renders click on title triggers handler', () => { it('renders click on title triggers handler', () => {
setupComponent({ setupComponent({
title: testTitle, title: testTitle,
@ -129,6 +129,7 @@ describe('TableHeaderCell', () => {
title: testTitle, title: testTitle,
orderBy: TopicColumnsToSort.NAME, orderBy: TopicColumnsToSort.NAME,
orderValue: TopicColumnsToSort.NAME, orderValue: TopicColumnsToSort.NAME,
sortOrder: SortOrder.ASC,
handleOrderBy: jest.fn(), handleOrderBy: jest.fn(),
}); });
const columnheader = screen.getByRole('columnheader'); const columnheader = screen.getByRole('columnheader');

View file

@ -4,6 +4,7 @@ import {
NewSchemaSubject, NewSchemaSubject,
SchemaSubject, SchemaSubject,
SchemaType, SchemaType,
SortOrder,
} from 'generated-sources'; } from 'generated-sources';
export const clusterStats: ClusterStats = { export const clusterStats: ClusterStats = {
@ -42,5 +43,6 @@ export const mockTopicsState = {
messages: [], messages: [],
search: '', search: '',
orderBy: null, orderBy: null,
sortOrder: SortOrder.ASC,
consumerGroups: [], consumerGroups: [],
}; };

View file

@ -9,6 +9,7 @@ import {
TopicMessage, TopicMessage,
TopicMessageConsuming, TopicMessageConsuming,
TopicMessageSchema, TopicMessageSchema,
SortOrder,
} from 'generated-sources'; } from 'generated-sources';
export type TopicName = Topic['name']; export type TopicName = Topic['name'];
@ -53,6 +54,7 @@ export interface TopicsState {
totalPages: number; totalPages: number;
search: string; search: string;
orderBy: TopicColumnsToSort | null; orderBy: TopicColumnsToSort | null;
sortOrder: SortOrder;
consumerGroups: ConsumerGroup[]; consumerGroups: ConsumerGroup[];
} }

View file

@ -1,4 +1,8 @@
import { MessageSchemaSourceEnum, TopicColumnsToSort } from 'generated-sources'; import {
MessageSchemaSourceEnum,
SortOrder,
TopicColumnsToSort,
} from 'generated-sources';
import { import {
deleteTopicAction, deleteTopicAction,
clearMessagesTopicAction, clearMessagesTopicAction,
@ -72,6 +76,7 @@ let state = {
totalPages: 1, totalPages: 1,
search: '', search: '',
orderBy: null, orderBy: null,
sortOrder: SortOrder.ASC,
consumerGroups: [], consumerGroups: [],
}; };
@ -130,6 +135,7 @@ describe('topics reducer', () => {
totalPages: 1, totalPages: 1,
search: '', search: '',
orderBy: null, orderBy: null,
sortOrder: SortOrder.ASC,
consumerGroups: [], consumerGroups: [],
}; };
expect( expect(

View file

@ -2,7 +2,7 @@ import { Action, TopicsState } from 'redux/interfaces';
import { getType } from 'typesafe-actions'; import { getType } from 'typesafe-actions';
import * as actions from 'redux/actions'; import * as actions from 'redux/actions';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { TopicColumnsToSort } from 'generated-sources'; import { SortOrder, TopicColumnsToSort } from 'generated-sources';
export const initialState: TopicsState = { export const initialState: TopicsState = {
byName: {}, byName: {},
@ -10,6 +10,7 @@ export const initialState: TopicsState = {
totalPages: 1, totalPages: 1,
search: '', search: '',
orderBy: TopicColumnsToSort.NAME, orderBy: TopicColumnsToSort.NAME,
sortOrder: SortOrder.ASC,
consumerGroups: [], consumerGroups: [],
}; };
@ -41,6 +42,10 @@ const reducer = (state = initialState, action: Action): TopicsState => {
return { return {
...state, ...state,
orderBy: action.payload, orderBy: action.payload,
sortOrder:
state.orderBy === action.payload && state.sortOrder === SortOrder.ASC
? SortOrder.DESC
: SortOrder.ASC,
}; };
} }
case getType(actions.fetchTopicMessageSchemaAction.success): { case getType(actions.fetchTopicMessageSchemaAction.success): {

View file

@ -157,6 +157,11 @@ export const getTopicsOrderBy = createSelector(
(state) => state.orderBy (state) => state.orderBy
); );
export const getTopicsSortOrder = createSelector(
topicsState,
(state) => state.sortOrder
);
export const getIsTopicInternal = createSelector( export const getIsTopicInternal = createSelector(
getTopicByName, getTopicByName,
(topic) => !!topic?.internal (topic) => !!topic?.internal