diff --git a/kafka-ui-react-app/src/components/Connect/Details/Tasks/__tests__/__snapshots__/Tasks.spec.tsx.snap b/kafka-ui-react-app/src/components/Connect/Details/Tasks/__tests__/__snapshots__/Tasks.spec.tsx.snap index 65abb230e3..2226883229 100644 --- a/kafka-ui-react-app/src/components/Connect/Details/Tasks/__tests__/__snapshots__/Tasks.spec.tsx.snap +++ b/kafka-ui-react-app/src/components/Connect/Details/Tasks/__tests__/__snapshots__/Tasks.spec.tsx.snap @@ -44,6 +44,8 @@ exports[`Tasks view matches snapshot 1`] = ` background: #FFFFFF; cursor: default; color: #73848C; + padding-right: 18px; + position: relative; } .c1 { @@ -217,6 +219,8 @@ exports[`Tasks view matches snapshot when no tasks 1`] = ` background: #FFFFFF; cursor: default; color: #73848C; + padding-right: 18px; + position: relative; } .c1 { diff --git a/kafka-ui-react-app/src/components/Topics/List/List.tsx b/kafka-ui-react-app/src/components/Topics/List/List.tsx index dfceed7fc2..fc54ae61ba 100644 --- a/kafka-ui-react-app/src/components/Topics/List/List.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/List.tsx @@ -12,7 +12,11 @@ import ClusterContext from 'components/contexts/ClusterContext'; import PageLoader from 'components/common/PageLoader/PageLoader'; import Pagination from 'components/common/Pagination/Pagination'; 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 Search from 'components/common/Search/Search'; import { PER_PAGE } from 'lib/constants'; @@ -39,6 +43,7 @@ export interface TopicsListProps { ): void; search: string; orderBy: TopicColumnsToSort | null; + sortOrder: SortOrder; setTopicsSearch(search: string): void; setTopicsOrderBy(orderBy: TopicColumnsToSort | null): void; } @@ -54,6 +59,7 @@ const List: React.FC = ({ clearTopicsMessages, search, orderBy, + sortOrder, setTopicsSearch, setTopicsOrderBy, }) => { @@ -69,6 +75,7 @@ const List: React.FC = ({ page, perPage, orderBy: orderBy || undefined, + sortOrder, search, showInternal, }); @@ -78,6 +85,7 @@ const List: React.FC = ({ page, perPage, orderBy, + sortOrder, search, showInternal, ]); @@ -215,24 +223,32 @@ const List: React.FC = ({ title="Topic Name" orderValue={TopicColumnsToSort.NAME} orderBy={orderBy} + sortOrder={sortOrder} handleOrderBy={setTopicsOrderBy} /> - - + diff --git a/kafka-ui-react-app/src/components/Topics/List/ListContainer.ts b/kafka-ui-react-app/src/components/Topics/List/ListContainer.ts index 2c556d8f3e..f18111d415 100644 --- a/kafka-ui-react-app/src/components/Topics/List/ListContainer.ts +++ b/kafka-ui-react-app/src/components/Topics/List/ListContainer.ts @@ -15,6 +15,7 @@ import { getTopicListTotalPages, getTopicsSearch, getTopicsOrderBy, + getTopicsSortOrder, } from 'redux/reducers/topics/selectors'; import List from './List'; @@ -25,6 +26,7 @@ const mapStateToProps = (state: RootState) => ({ totalPages: getTopicListTotalPages(state), search: getTopicsSearch(state), orderBy: getTopicsOrderBy(state), + sortOrder: getTopicsSortOrder(state), }); const mapDispatchToProps = { diff --git a/kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx b/kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx index 88e611fc34..eceee72ce1 100644 --- a/kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx @@ -13,6 +13,7 @@ import { externalTopicPayload } from 'redux/reducers/topics/__test__/fixtures'; import { ConfirmationModalProps } from 'components/common/ConfirmationModal/ConfirmationModal'; import theme from 'theme/theme'; import { ThemeProvider } from 'styled-components'; +import { SortOrder } from 'generated-sources'; jest.mock( 'components/common/ConfirmationModal/ConfirmationModal', @@ -33,6 +34,7 @@ describe('List', () => { clearTopicMessages={jest.fn()} search="" orderBy={null} + sortOrder={SortOrder.ASC} setTopicsSearch={jest.fn()} setTopicsOrderBy={jest.fn()} {...props} @@ -104,6 +106,7 @@ describe('List', () => { expect(fetchTopicsList).toHaveBeenLastCalledWith({ search: '', showInternal: false, + sortOrder: SortOrder.ASC, }); }); diff --git a/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts b/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts index b18cc06b5a..3c7d510555 100644 --- a/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts +++ b/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.styled.ts @@ -1,8 +1,10 @@ +import { SortOrder } from 'generated-sources'; import styled, { css } from 'styled-components'; interface TitleProps { isOrderable?: boolean; isOrdered?: boolean; + sortOrder?: SortOrder; } const orderableMixin = css( @@ -45,20 +47,48 @@ const orderableMixin = css( ` ); -const orderedMixin = css( +const ASCMixin = css( ({ theme: { table } }) => ` color: ${table.th.color.active}; - &::before { - border-bottom-color: ${table.th.color.active}; - } - &::after { - border-top-color: ${table.th.color.active}; - } + cursor: pointer; + + &::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}; + 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( - ({ isOrderable, isOrdered, theme: { table } }) => css` + ({ isOrderable, isOrdered, sortOrder, theme: { table } }) => css` font-family: Inter, sans-serif; font-size: 12px; font-style: normal; @@ -72,10 +102,14 @@ export const Title = styled.span( background: ${table.th.backgroundColor.normal}; cursor: default; 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} ` ); diff --git a/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.tsx b/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.tsx index 07a2c4e124..74d0dfddf3 100644 --- a/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.tsx +++ b/kafka-ui-react-app/src/components/common/table/TableHeaderCell/TableHeaderCell.tsx @@ -1,5 +1,5 @@ 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'; export interface TableHeaderCellProps { @@ -7,6 +7,7 @@ export interface TableHeaderCellProps { previewText?: string; onPreview?: () => void; orderBy?: TopicColumnsToSort | null; + sortOrder?: SortOrder; orderValue?: TopicColumnsToSort | null; handleOrderBy?: (orderBy: TopicColumnsToSort | null) => void; } @@ -17,6 +18,7 @@ const TableHeaderCell: React.FC = (props) => { previewText, onPreview, orderBy, + sortOrder, orderValue, handleOrderBy, ...restProps @@ -38,6 +40,7 @@ const TableHeaderCell: React.FC = (props) => { }; const orderableProps = isOrderable && { isOrderable, + sortOrder, onClick: handleOnClick, onKeyDown: handleOnKeyDown, role: 'button', diff --git a/kafka-ui-react-app/src/components/common/table/__tests__/TableHeaderCell.spec.tsx b/kafka-ui-react-app/src/components/common/table/__tests__/TableHeaderCell.spec.tsx index 7797b900ff..1ce54fc44b 100644 --- a/kafka-ui-react-app/src/components/common/table/__tests__/TableHeaderCell.spec.tsx +++ b/kafka-ui-react-app/src/components/common/table/__tests__/TableHeaderCell.spec.tsx @@ -4,7 +4,7 @@ import { render } from 'lib/testHelpers'; import TableHeaderCell, { TableHeaderCellProps, } from 'components/common/table/TableHeaderCell/TableHeaderCell'; -import { TopicColumnsToSort } from 'generated-sources'; +import { SortOrder, TopicColumnsToSort } from 'generated-sources'; import theme from 'theme/theme'; import userEvent from '@testing-library/user-event'; @@ -50,6 +50,7 @@ describe('TableHeaderCell', () => { title: testTitle, orderBy: TopicColumnsToSort.NAME, orderValue: TopicColumnsToSort.NAME, + sortOrder: SortOrder.ASC, handleOrderBy, }); const columnheader = screen.getByRole('columnheader'); @@ -59,7 +60,6 @@ describe('TableHeaderCell', () => { expect(title).toHaveStyle(`color: ${theme.table.th.color.active};`); expect(title).toHaveStyle('cursor: pointer;'); }); - it('renders click on title triggers handler', () => { setupComponent({ title: testTitle, @@ -129,6 +129,7 @@ describe('TableHeaderCell', () => { title: testTitle, orderBy: TopicColumnsToSort.NAME, orderValue: TopicColumnsToSort.NAME, + sortOrder: SortOrder.ASC, handleOrderBy: jest.fn(), }); const columnheader = screen.getByRole('columnheader'); diff --git a/kafka-ui-react-app/src/redux/actions/__test__/fixtures.ts b/kafka-ui-react-app/src/redux/actions/__test__/fixtures.ts index d2617f2301..d4a007aaaa 100644 --- a/kafka-ui-react-app/src/redux/actions/__test__/fixtures.ts +++ b/kafka-ui-react-app/src/redux/actions/__test__/fixtures.ts @@ -4,6 +4,7 @@ import { NewSchemaSubject, SchemaSubject, SchemaType, + SortOrder, } from 'generated-sources'; export const clusterStats: ClusterStats = { @@ -42,5 +43,6 @@ export const mockTopicsState = { messages: [], search: '', orderBy: null, + sortOrder: SortOrder.ASC, consumerGroups: [], }; diff --git a/kafka-ui-react-app/src/redux/interfaces/topic.ts b/kafka-ui-react-app/src/redux/interfaces/topic.ts index 168faca764..647b0e69f9 100644 --- a/kafka-ui-react-app/src/redux/interfaces/topic.ts +++ b/kafka-ui-react-app/src/redux/interfaces/topic.ts @@ -9,6 +9,7 @@ import { TopicMessage, TopicMessageConsuming, TopicMessageSchema, + SortOrder, } from 'generated-sources'; export type TopicName = Topic['name']; @@ -53,6 +54,7 @@ export interface TopicsState { totalPages: number; search: string; orderBy: TopicColumnsToSort | null; + sortOrder: SortOrder; consumerGroups: ConsumerGroup[]; } diff --git a/kafka-ui-react-app/src/redux/reducers/topics/__test__/reducer.spec.ts b/kafka-ui-react-app/src/redux/reducers/topics/__test__/reducer.spec.ts index b7e8d5c308..ce05db0506 100644 --- a/kafka-ui-react-app/src/redux/reducers/topics/__test__/reducer.spec.ts +++ b/kafka-ui-react-app/src/redux/reducers/topics/__test__/reducer.spec.ts @@ -1,4 +1,8 @@ -import { MessageSchemaSourceEnum, TopicColumnsToSort } from 'generated-sources'; +import { + MessageSchemaSourceEnum, + SortOrder, + TopicColumnsToSort, +} from 'generated-sources'; import { deleteTopicAction, clearMessagesTopicAction, @@ -72,6 +76,7 @@ let state = { totalPages: 1, search: '', orderBy: null, + sortOrder: SortOrder.ASC, consumerGroups: [], }; @@ -130,6 +135,7 @@ describe('topics reducer', () => { totalPages: 1, search: '', orderBy: null, + sortOrder: SortOrder.ASC, consumerGroups: [], }; expect( diff --git a/kafka-ui-react-app/src/redux/reducers/topics/reducer.ts b/kafka-ui-react-app/src/redux/reducers/topics/reducer.ts index 7c7aa07e99..e8fe50fad9 100644 --- a/kafka-ui-react-app/src/redux/reducers/topics/reducer.ts +++ b/kafka-ui-react-app/src/redux/reducers/topics/reducer.ts @@ -2,7 +2,7 @@ import { Action, TopicsState } from 'redux/interfaces'; import { getType } from 'typesafe-actions'; import * as actions from 'redux/actions'; import * as _ from 'lodash'; -import { TopicColumnsToSort } from 'generated-sources'; +import { SortOrder, TopicColumnsToSort } from 'generated-sources'; export const initialState: TopicsState = { byName: {}, @@ -10,6 +10,7 @@ export const initialState: TopicsState = { totalPages: 1, search: '', orderBy: TopicColumnsToSort.NAME, + sortOrder: SortOrder.ASC, consumerGroups: [], }; @@ -41,6 +42,10 @@ const reducer = (state = initialState, action: Action): TopicsState => { return { ...state, orderBy: action.payload, + sortOrder: + state.orderBy === action.payload && state.sortOrder === SortOrder.ASC + ? SortOrder.DESC + : SortOrder.ASC, }; } case getType(actions.fetchTopicMessageSchemaAction.success): { diff --git a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts index a363a958a9..ee59bc9735 100644 --- a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts +++ b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts @@ -157,6 +157,11 @@ export const getTopicsOrderBy = createSelector( (state) => state.orderBy ); +export const getTopicsSortOrder = createSelector( + topicsState, + (state) => state.sortOrder +); + export const getIsTopicInternal = createSelector( getTopicByName, (topic) => !!topic?.internal