diff --git a/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx b/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx index 85b65d016d7..1019a1bca37 100644 --- a/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx +++ b/kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx @@ -18,8 +18,9 @@ const mapStateToProps = (state: RootState) => ({ isFetching: getIsSchemaListFetching(state), schemas: getSchemaList(state), globalSchemaCompatibilityLevel: getGlobalSchemaCompatibilityLevel(state), - isGlobalSchemaCompatibilityLevelFetched: - getGlobalSchemaCompatibilityLevelFetched(state), + isGlobalSchemaCompatibilityLevelFetched: getGlobalSchemaCompatibilityLevelFetched( + state + ), }); const mapDispatchToProps = { 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 54d4b7854ad..99784bb869f 100644 --- a/kafka-ui-react-app/src/components/Topics/List/List.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/List.tsx @@ -12,6 +12,9 @@ import { FetchTopicsListParams } from 'redux/actions'; import ClusterContext from 'components/contexts/ClusterContext'; import PageLoader from 'components/common/PageLoader/PageLoader'; import Pagination from 'components/common/Pagination/Pagination'; +import { TopicColumnsToSort } from 'generated-sources'; +import SortableColumnHeader from 'components/common/table/SortableCulumnHeader/SortableColumnHeader'; +import Search from 'components/common/Search/Search'; import ListItem from './ListItem'; @@ -27,6 +30,10 @@ interface Props { clusterName: ClusterName, partitions?: number[] ): void; + search: string; + orderBy: TopicColumnsToSort | null; + setTopicsSearch(search: string): void; + setTopicsOrderBy(orderBy: TopicColumnsToSort | null): void; } const List: React.FC = ({ @@ -37,14 +44,24 @@ const List: React.FC = ({ fetchTopicsList, deleteTopic, clearTopicMessages, + search, + orderBy, + setTopicsSearch, + setTopicsOrderBy, }) => { const { isReadOnly } = React.useContext(ClusterContext); const { clusterName } = useParams<{ clusterName: ClusterName }>(); const { page, perPage } = usePagination(); React.useEffect(() => { - fetchTopicsList({ clusterName, page, perPage }); - }, [fetchTopicsList, clusterName, page, perPage]); + fetchTopicsList({ + clusterName, + page, + perPage, + orderBy: orderBy || undefined, + search, + }); + }, [fetchTopicsList, clusterName, page, perPage, orderBy, search]); const [showInternal, setShowInternal] = React.useState(true); @@ -52,14 +69,16 @@ const List: React.FC = ({ setShowInternal(!showInternal); }, [showInternal]); + const handleSearch = (value: string) => setTopicsSearch(value); + const items = showInternal ? topics : externalTopics; return (
{showInternal ? `All Topics` : `External Topics`}
-
-
+
+
= ({
-
+
+ +
+
{!isReadOnly && ( = ({ - - - + + + 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 d5d5c5f416d..029f26aea5b 100644 --- a/kafka-ui-react-app/src/components/Topics/List/ListContainer.ts +++ b/kafka-ui-react-app/src/components/Topics/List/ListContainer.ts @@ -4,12 +4,16 @@ import { fetchTopicsList, deleteTopic, clearTopicMessages, + setTopicsSearchAction, + setTopicsOrderByAction, } from 'redux/actions'; import { getTopicList, getExternalTopicList, getAreTopicsFetching, getTopicListTotalPages, + getTopicsSearch, + getTopicsOrderBy, } from 'redux/reducers/topics/selectors'; import List from './List'; @@ -19,12 +23,16 @@ const mapStateToProps = (state: RootState) => ({ topics: getTopicList(state), externalTopics: getExternalTopicList(state), totalPages: getTopicListTotalPages(state), + search: getTopicsSearch(state), + orderBy: getTopicsOrderBy(state), }); const mapDispatchToProps = { fetchTopicsList, deleteTopic, clearTopicMessages, + setTopicsSearch: setTopicsSearchAction, + setTopicsOrderBy: setTopicsOrderByAction, }; export default connect(mapStateToProps, mapDispatchToProps)(List); diff --git a/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx b/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx index ca92c22f5a6..299e6aa1b93 100644 --- a/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx +++ b/kafka-ui-react-app/src/components/Topics/List/ListItem.tsx @@ -23,8 +23,10 @@ const ListItem: React.FC = ({ clusterName, clearTopicMessages, }) => { - const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] = - React.useState(false); + const [ + isDeleteTopicConfirmationVisible, + setDeleteTopicConfirmationVisible, + ] = React.useState(false); const outOfSyncReplicas = React.useMemo(() => { if (partitions === undefined || partitions.length === 0) { 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 11dd2b00b65..9ebfdc680dc 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 @@ -24,6 +24,10 @@ describe('List', () => { fetchTopicsList={jest.fn()} deleteTopic={jest.fn()} clearTopicMessages={jest.fn()} + search="" + orderBy={null} + setTopicsSearch={jest.fn()} + setTopicsOrderBy={jest.fn()} /> @@ -33,29 +37,48 @@ describe('List', () => { }); describe('when it does not have readonly flag', () => { + const mockFetch = jest.fn(); + jest.useFakeTimers(); + const component = mount( + + + + + + ); it('renders the Add a Topic button', () => { - const component = mount( - - - - - - ); expect(component.exists('Link')).toBeTruthy(); }); + it('matches the snapshot', () => { + expect(component).toMatchSnapshot(); + }); + + it('calls fetchTopicsList on input', () => { + const input = component.find('input').at(1); + input.simulate('change', { target: { value: 't' } }); + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 300); + setTimeout(() => { + expect(mockFetch).toHaveBeenCalledTimes(1); + }, 301); + }); }); }); diff --git a/kafka-ui-react-app/src/components/Topics/List/__tests__/__snapshots__/List.spec.tsx.snap b/kafka-ui-react-app/src/components/Topics/List/__tests__/__snapshots__/List.spec.tsx.snap new file mode 100644 index 00000000000..27706ba2831 --- /dev/null +++ b/kafka-ui-react-app/src/components/Topics/List/__tests__/__snapshots__/List.spec.tsx.snap @@ -0,0 +1,255 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`List when it does not have readonly flag matches the snapshot 1`] = ` + + + +
+ + + +
+
+
+
+ + +
+
+
+ +

+ + + + +

+
+
+
+ + + + Add a Topic + + + +
+
+
+
+
Topic NameTotal PartitionsOut of sync replicasType
+ + + + + + + + + + + + + + + + + + + + +
+ Topic Name + + + + + Total Partitions + + + + + Out of sync replicas + + + + + Type + + +
+ No topics found +
+ + + +
+
+ + + +`; diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx index 62fcca38821..ab1965250bd 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Details.tsx @@ -33,8 +33,10 @@ const Details: React.FC = ({ }) => { const history = useHistory(); const { isReadOnly } = React.useContext(ClusterContext); - const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] = - React.useState(false); + const [ + isDeleteTopicConfirmationVisible, + setDeleteTopicConfirmationVisible, + ] = React.useState(false); const deleteTopicHandler = React.useCallback(() => { deleteTopic(clusterName, topicName); history.push(clusterTopicsPath(clusterName)); diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx index 034d942c68a..fd0d4499f86 100644 --- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx +++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Messages.tsx @@ -50,8 +50,9 @@ const Messages: React.FC = ({ fetchTopicMessages, }) => { const [searchQuery, setSearchQuery] = React.useState(''); - const [searchTimestamp, setSearchTimestamp] = - React.useState(null); + const [searchTimestamp, setSearchTimestamp] = React.useState( + null + ); const [filterProps, setFilterProps] = React.useState([]); const [selectedSeekType, setSelectedSeekType] = React.useState( SeekType.OFFSET diff --git a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx index 2eb11cf8b50..31965e372b1 100644 --- a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx +++ b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx @@ -39,11 +39,12 @@ const CustomParamSelect: React.FC = ({ return valid || 'Custom Parameter must be unique'; }; - const onChange = - (inputName: string) => (event: React.ChangeEvent) => { - trigger(inputName); - onNameChange(index, event.target.value); - }; + const onChange = (inputName: string) => ( + event: React.ChangeEvent + ) => { + trigger(inputName); + onNameChange(index, event.target.value); + }; return ( <> diff --git a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx index f05e33382a3..570d0154f40 100644 --- a/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx +++ b/kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx @@ -35,11 +35,13 @@ const CustomParams: React.FC = ({ isSubmitting, config }) => { ) : {}; - const [formCustomParams, setFormCustomParams] = - React.useState({ - byIndex, - allIndexes: Object.keys(byIndex), - }); + const [ + formCustomParams, + setFormCustomParams, + ] = React.useState({ + byIndex, + allIndexes: Object.keys(byIndex), + }); const onAdd = (event: React.MouseEvent) => { event.preventDefault(); diff --git a/kafka-ui-react-app/src/components/common/Search/Search.tsx b/kafka-ui-react-app/src/components/common/Search/Search.tsx new file mode 100644 index 00000000000..2c72b7b6bd6 --- /dev/null +++ b/kafka-ui-react-app/src/components/common/Search/Search.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { useDebouncedCallback } from 'use-debounce'; + +interface SearchProps { + handleSearch: (value: string) => void; + placeholder?: string; + value: string; +} + +const Search: React.FC = ({ + handleSearch, + placeholder = 'Search', + value, +}) => { + const onChange = useDebouncedCallback( + (e) => handleSearch(e.target.value), + 300 + ); + return ( +

+ + + + +

+ ); +}; + +export default Search; diff --git a/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx b/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx new file mode 100644 index 00000000000..017a30c8feb --- /dev/null +++ b/kafka-ui-react-app/src/components/common/Search/__tests__/Search.spec.tsx @@ -0,0 +1,35 @@ +import { shallow } from 'enzyme'; +import Search from 'components/common/Search/Search'; +import React from 'react'; + +jest.mock('use-debounce', () => ({ + useDebouncedCallback: (fn: (e: Event) => void) => fn, +})); + +describe('Search', () => { + const handleSearch = jest.fn(); + let component = shallow( + + ); + it('calls handleSearch on input', () => { + component.find('input').simulate('change', { target: { value: 'test' } }); + expect(handleSearch).toHaveBeenCalledTimes(1); + }); + + describe('when placeholder is provided', () => { + it('matches the snapshot', () => { + expect(component).toMatchSnapshot(); + }); + }); + + describe('when placeholder is not provided', () => { + component = shallow(); + it('matches the snapshot', () => { + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/kafka-ui-react-app/src/components/common/Search/__tests__/__snapshots__/Search.spec.tsx.snap b/kafka-ui-react-app/src/components/common/Search/__tests__/__snapshots__/Search.spec.tsx.snap new file mode 100644 index 00000000000..c2026138ee0 --- /dev/null +++ b/kafka-ui-react-app/src/components/common/Search/__tests__/__snapshots__/Search.spec.tsx.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Search when placeholder is not provided matches the snapshot 1`] = ` +

+ + + + +

+`; + +exports[`Search when placeholder is provided matches the snapshot 1`] = ` +

+ + + + +

+`; diff --git a/kafka-ui-react-app/src/components/common/table/SortableCulumnHeader/SortableColumnHeader.tsx b/kafka-ui-react-app/src/components/common/table/SortableCulumnHeader/SortableColumnHeader.tsx new file mode 100644 index 00000000000..5fc7fca68c0 --- /dev/null +++ b/kafka-ui-react-app/src/components/common/table/SortableCulumnHeader/SortableColumnHeader.tsx @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React from 'react'; +import cx from 'classnames'; + +export interface ListHeaderProps { + value: any; + title: string; + orderBy: any; + setOrderBy: React.Dispatch>; +} + +const ListHeaderCell: React.FC = ({ + value, + title, + orderBy, + setOrderBy, +}) => ( + setOrderBy(value)} + > + {title} + + + + +); + +export default ListHeaderCell; diff --git a/kafka-ui-react-app/src/components/common/table/__tests__/SortableColumnHeader.spec.tsx b/kafka-ui-react-app/src/components/common/table/__tests__/SortableColumnHeader.spec.tsx new file mode 100644 index 00000000000..a9421ac7fad --- /dev/null +++ b/kafka-ui-react-app/src/components/common/table/__tests__/SortableColumnHeader.spec.tsx @@ -0,0 +1,37 @@ +import SortableColumnHeader from 'components/common/table/SortableCulumnHeader/SortableColumnHeader'; +import { mount } from 'enzyme'; +import { TopicColumnsToSort } from 'generated-sources'; +import React from 'react'; + +describe('ListHeader', () => { + const setOrderBy = jest.fn(); + const component = mount( + + + + + + +
+ ); + it('matches the snapshot', () => { + expect(component).toMatchSnapshot(); + }); + + describe('on column click', () => { + it('calls setOrderBy', () => { + component.find('th').simulate('click'); + expect(setOrderBy).toHaveBeenCalledTimes(1); + expect(setOrderBy).toHaveBeenCalledWith(TopicColumnsToSort.NAME); + }); + + it('matches the snapshot', () => { + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/kafka-ui-react-app/src/components/common/table/__tests__/__snapshots__/SortableColumnHeader.spec.tsx.snap b/kafka-ui-react-app/src/components/common/table/__tests__/__snapshots__/SortableColumnHeader.spec.tsx.snap new file mode 100644 index 00000000000..48e0159cc04 --- /dev/null +++ b/kafka-ui-react-app/src/components/common/table/__tests__/__snapshots__/SortableColumnHeader.spec.tsx.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ListHeader matches the snapshot 1`] = ` + + + + + + + + +
+ Name + + + +
+`; + +exports[`ListHeader on column click matches the snapshot 1`] = ` + + + + + + + + +
+ Name + + + +
+`; diff --git a/kafka-ui-react-app/src/redux/actions/__test__/actions.spec.ts b/kafka-ui-react-app/src/redux/actions/__test__/actions.spec.ts index e39fdb2a9c4..4b23d249b30 100644 --- a/kafka-ui-react-app/src/redux/actions/__test__/actions.spec.ts +++ b/kafka-ui-react-app/src/redux/actions/__test__/actions.spec.ts @@ -3,6 +3,7 @@ import { schemaVersionsPayload, } from 'redux/reducers/schemas/__test__/fixtures'; import * as actions from 'redux/actions'; +import { TopicColumnsToSort } from 'generated-sources'; describe('Actions', () => { describe('fetchClusterStatsAction', () => { @@ -131,4 +132,22 @@ describe('Actions', () => { }); }); }); + + describe('setTopicsSearchAction', () => { + it('creartes SET_TOPICS_SEARCH', () => { + expect(actions.setTopicsSearchAction('test')).toEqual({ + type: 'SET_TOPICS_SEARCH', + payload: 'test', + }); + }); + }); + + describe('setTopicsOrderByAction', () => { + it('creartes SET_TOPICS_ORDER_BY', () => { + expect(actions.setTopicsOrderByAction(TopicColumnsToSort.NAME)).toEqual({ + type: 'SET_TOPICS_ORDER_BY', + payload: TopicColumnsToSort.NAME, + }); + }); + }); }); diff --git a/kafka-ui-react-app/src/redux/actions/actions.ts b/kafka-ui-react-app/src/redux/actions/actions.ts index 8c78b418d31..d9d5a08e871 100644 --- a/kafka-ui-react-app/src/redux/actions/actions.ts +++ b/kafka-ui-react-app/src/redux/actions/actions.ts @@ -18,6 +18,7 @@ import { ConsumerGroupDetails, SchemaSubject, CompatibilityLevelCompatibilityEnum, + TopicColumnsToSort, Connector, FullConnectorInfo, Connect, @@ -233,3 +234,11 @@ export const updateConnectorConfigAction = createAsyncAction( 'PATCH_CONNECTOR_CONFIG__SUCCESS', 'PATCH_CONNECTOR_CONFIG__FAILURE' )(); + +export const setTopicsSearchAction = createAction( + 'SET_TOPICS_SEARCH' +)(); + +export const setTopicsOrderByAction = createAction( + 'SET_TOPICS_ORDER_BY' +)(); diff --git a/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts b/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts index 025929b08f8..f698f45c29d 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/brokers.ts @@ -6,29 +6,30 @@ import * as actions from 'redux/actions/actions'; const apiClientConf = new Configuration(BASE_PARAMS); export const brokersApiClient = new BrokersApi(apiClientConf); -export const fetchBrokers = - (clusterName: ClusterName): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchBrokersAction.request()); - try { - const payload = await brokersApiClient.getBrokers({ clusterName }); - dispatch(actions.fetchBrokersAction.success(payload)); - } catch (e) { - dispatch(actions.fetchBrokersAction.failure()); - } - }; +export const fetchBrokers = ( + clusterName: ClusterName +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchBrokersAction.request()); + try { + const payload = await brokersApiClient.getBrokers({ clusterName }); + dispatch(actions.fetchBrokersAction.success(payload)); + } catch (e) { + dispatch(actions.fetchBrokersAction.failure()); + } +}; -export const fetchBrokerMetrics = - (clusterName: ClusterName, brokerId: BrokerId): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchBrokerMetricsAction.request()); - try { - const payload = await brokersApiClient.getBrokersMetrics({ - clusterName, - id: brokerId, - }); - dispatch(actions.fetchBrokerMetricsAction.success(payload)); - } catch (e) { - dispatch(actions.fetchBrokerMetricsAction.failure()); - } - }; +export const fetchBrokerMetrics = ( + clusterName: ClusterName, + brokerId: BrokerId +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchBrokerMetricsAction.request()); + try { + const payload = await brokersApiClient.getBrokersMetrics({ + clusterName, + id: brokerId, + }); + dispatch(actions.fetchBrokerMetricsAction.success(payload)); + } catch (e) { + dispatch(actions.fetchBrokerMetricsAction.failure()); + } +}; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts b/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts index 14d04b01f87..996da2b016b 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/clusters.ts @@ -16,28 +16,28 @@ export const fetchClustersList = (): PromiseThunkResult => async (dispatch) => { } }; -export const fetchClusterStats = - (clusterName: ClusterName): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchClusterStatsAction.request()); - try { - const payload = await clustersApiClient.getClusterStats({ clusterName }); - dispatch(actions.fetchClusterStatsAction.success(payload)); - } catch (e) { - dispatch(actions.fetchClusterStatsAction.failure()); - } - }; +export const fetchClusterStats = ( + clusterName: ClusterName +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchClusterStatsAction.request()); + try { + const payload = await clustersApiClient.getClusterStats({ clusterName }); + dispatch(actions.fetchClusterStatsAction.success(payload)); + } catch (e) { + dispatch(actions.fetchClusterStatsAction.failure()); + } +}; -export const fetchClusterMetrics = - (clusterName: ClusterName): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchClusterMetricsAction.request()); - try { - const payload = await clustersApiClient.getClusterMetrics({ - clusterName, - }); - dispatch(actions.fetchClusterMetricsAction.success(payload)); - } catch (e) { - dispatch(actions.fetchClusterMetricsAction.failure()); - } - }; +export const fetchClusterMetrics = ( + clusterName: ClusterName +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchClusterMetricsAction.request()); + try { + const payload = await clustersApiClient.getClusterMetrics({ + clusterName, + }); + dispatch(actions.fetchClusterMetricsAction.success(payload)); + } catch (e) { + dispatch(actions.fetchClusterMetricsAction.failure()); + } +}; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts b/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts index fd3aa6b911d..939960adc93 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/connectors.ts @@ -20,7 +20,6 @@ import { getResponse } from 'lib/errorHandling'; const apiClientConf = new Configuration(BASE_PARAMS); export const kafkaConnectApiClient = new KafkaConnectApi(apiClientConf); - export const fetchConnects = (clusterName: ClusterName): PromiseThunkResult => async (dispatch) => { diff --git a/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts b/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts index 37ce7b9aeda..4a5445cb3b6 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/consumerGroups.ts @@ -10,40 +10,39 @@ import * as actions from 'redux/actions/actions'; const apiClientConf = new Configuration(BASE_PARAMS); export const consumerGroupsApiClient = new ConsumerGroupsApi(apiClientConf); -export const fetchConsumerGroupsList = - (clusterName: ClusterName): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchConsumerGroupsAction.request()); - try { - const consumerGroups = await consumerGroupsApiClient.getConsumerGroups({ - clusterName, - }); - dispatch(actions.fetchConsumerGroupsAction.success(consumerGroups)); - } catch (e) { - dispatch(actions.fetchConsumerGroupsAction.failure()); - } - }; +export const fetchConsumerGroupsList = ( + clusterName: ClusterName +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchConsumerGroupsAction.request()); + try { + const consumerGroups = await consumerGroupsApiClient.getConsumerGroups({ + clusterName, + }); + dispatch(actions.fetchConsumerGroupsAction.success(consumerGroups)); + } catch (e) { + dispatch(actions.fetchConsumerGroupsAction.failure()); + } +}; -export const fetchConsumerGroupDetails = - ( - clusterName: ClusterName, - consumerGroupID: ConsumerGroupID - ): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchConsumerGroupDetailsAction.request()); - try { - const consumerGroupDetails = - await consumerGroupsApiClient.getConsumerGroup({ - clusterName, - id: consumerGroupID, - }); - dispatch( - actions.fetchConsumerGroupDetailsAction.success({ - consumerGroupID, - details: consumerGroupDetails, - }) - ); - } catch (e) { - dispatch(actions.fetchConsumerGroupDetailsAction.failure()); - } - }; +export const fetchConsumerGroupDetails = ( + clusterName: ClusterName, + consumerGroupID: ConsumerGroupID +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchConsumerGroupDetailsAction.request()); + try { + const consumerGroupDetails = await consumerGroupsApiClient.getConsumerGroup( + { + clusterName, + id: consumerGroupID, + } + ); + dispatch( + actions.fetchConsumerGroupDetailsAction.success({ + consumerGroupID, + details: consumerGroupDetails, + }) + ); + } catch (e) { + dispatch(actions.fetchConsumerGroupDetailsAction.failure()); + } +}; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts b/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts index 03f1c20960a..bc98688afbc 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/schemas.ts @@ -20,164 +20,160 @@ import { isEqual } from 'lodash'; const apiClientConf = new Configuration(BASE_PARAMS); export const schemasApiClient = new SchemasApi(apiClientConf); -export const fetchSchemasByClusterName = - (clusterName: ClusterName): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchSchemasByClusterNameAction.request()); - try { - const schemas = await schemasApiClient.getSchemas({ clusterName }); - dispatch(actions.fetchSchemasByClusterNameAction.success(schemas)); - } catch (e) { - dispatch(actions.fetchSchemasByClusterNameAction.failure()); - } - }; +export const fetchSchemasByClusterName = ( + clusterName: ClusterName +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchSchemasByClusterNameAction.request()); + try { + const schemas = await schemasApiClient.getSchemas({ clusterName }); + dispatch(actions.fetchSchemasByClusterNameAction.success(schemas)); + } catch (e) { + dispatch(actions.fetchSchemasByClusterNameAction.failure()); + } +}; -export const fetchSchemaVersions = - (clusterName: ClusterName, subject: SchemaName): PromiseThunkResult => - async (dispatch) => { - if (!subject) return; - dispatch(actions.fetchSchemaVersionsAction.request()); - try { - const versions = await schemasApiClient.getAllVersionsBySubject({ - clusterName, - subject, - }); - dispatch(actions.fetchSchemaVersionsAction.success(versions)); - } catch (e) { - dispatch(actions.fetchSchemaVersionsAction.failure()); - } - }; +export const fetchSchemaVersions = ( + clusterName: ClusterName, + subject: SchemaName +): PromiseThunkResult => async (dispatch) => { + if (!subject) return; + dispatch(actions.fetchSchemaVersionsAction.request()); + try { + const versions = await schemasApiClient.getAllVersionsBySubject({ + clusterName, + subject, + }); + dispatch(actions.fetchSchemaVersionsAction.success(versions)); + } catch (e) { + dispatch(actions.fetchSchemaVersionsAction.failure()); + } +}; -export const fetchGlobalSchemaCompatibilityLevel = - (clusterName: ClusterName): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request()); - try { - const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({ - clusterName, - }); - dispatch( - actions.fetchGlobalSchemaCompatibilityLevelAction.success( - result.compatibility - ) - ); - } catch (e) { - dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.failure()); - } - }; +export const fetchGlobalSchemaCompatibilityLevel = ( + clusterName: ClusterName +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request()); + try { + const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({ + clusterName, + }); + dispatch( + actions.fetchGlobalSchemaCompatibilityLevelAction.success( + result.compatibility + ) + ); + } catch (e) { + dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.failure()); + } +}; -export const updateGlobalSchemaCompatibilityLevel = - ( - clusterName: ClusterName, - compatibilityLevel: CompatibilityLevelCompatibilityEnum - ): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request()); - try { - await schemasApiClient.updateGlobalSchemaCompatibilityLevel({ - clusterName, - compatibilityLevel: { compatibility: compatibilityLevel }, - }); - dispatch( - actions.updateGlobalSchemaCompatibilityLevelAction.success( - compatibilityLevel - ) - ); - } catch (e) { - dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.failure()); - } - }; +export const updateGlobalSchemaCompatibilityLevel = ( + clusterName: ClusterName, + compatibilityLevel: CompatibilityLevelCompatibilityEnum +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request()); + try { + await schemasApiClient.updateGlobalSchemaCompatibilityLevel({ + clusterName, + compatibilityLevel: { compatibility: compatibilityLevel }, + }); + dispatch( + actions.updateGlobalSchemaCompatibilityLevelAction.success( + compatibilityLevel + ) + ); + } catch (e) { + dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.failure()); + } +}; -export const createSchema = - ( - clusterName: ClusterName, - newSchemaSubject: NewSchemaSubject - ): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.createSchemaAction.request()); - try { - const schema: SchemaSubject = await schemasApiClient.createNewSchema({ +export const createSchema = ( + clusterName: ClusterName, + newSchemaSubject: NewSchemaSubject +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.createSchemaAction.request()); + try { + const schema: SchemaSubject = await schemasApiClient.createNewSchema({ + clusterName, + newSchemaSubject, + }); + dispatch(actions.createSchemaAction.success(schema)); + } catch (error) { + const response = await getResponse(error); + const alert: FailurePayload = { + subject: ['schema', newSchemaSubject.subject].join('-'), + title: `Schema ${newSchemaSubject.subject}`, + response, + }; + dispatch(actions.createSchemaAction.failure({ alert })); + throw error; + } +}; + +export const updateSchema = ( + latestSchema: SchemaSubject, + newSchema: string, + newSchemaType: SchemaType, + newCompatibilityLevel: CompatibilityLevelCompatibilityEnum, + clusterName: string, + subject: string +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.updateSchemaAction.request()); + try { + let schema: SchemaSubject = latestSchema; + if ( + (newSchema && + !isEqual(JSON.parse(latestSchema.schema), JSON.parse(newSchema))) || + newSchemaType !== latestSchema.schemaType + ) { + schema = await schemasApiClient.createNewSchema({ clusterName, - newSchemaSubject, + newSchemaSubject: { + ...latestSchema, + schema: newSchema || latestSchema.schema, + schemaType: newSchemaType || latestSchema.schemaType, + }, }); - dispatch(actions.createSchemaAction.success(schema)); - } catch (error) { - const response = await getResponse(error); - const alert: FailurePayload = { - subject: ['schema', newSchemaSubject.subject].join('-'), - title: `Schema ${newSchemaSubject.subject}`, - response, - }; - dispatch(actions.createSchemaAction.failure({ alert })); - throw error; - } - }; - -export const updateSchema = - ( - latestSchema: SchemaSubject, - newSchema: string, - newSchemaType: SchemaType, - newCompatibilityLevel: CompatibilityLevelCompatibilityEnum, - clusterName: string, - subject: string - ): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.updateSchemaAction.request()); - try { - let schema: SchemaSubject = latestSchema; - if ( - (newSchema && - !isEqual(JSON.parse(latestSchema.schema), JSON.parse(newSchema))) || - newSchemaType !== latestSchema.schemaType - ) { - schema = await schemasApiClient.createNewSchema({ - clusterName, - newSchemaSubject: { - ...latestSchema, - schema: newSchema || latestSchema.schema, - schemaType: newSchemaType || latestSchema.schemaType, - }, - }); - } - if (newCompatibilityLevel !== latestSchema.compatibilityLevel) { - await schemasApiClient.updateSchemaCompatibilityLevel({ - clusterName, - subject, - compatibilityLevel: { - compatibility: newCompatibilityLevel, - }, - }); - } - actions.updateSchemaAction.success(schema); - } catch (e) { - const response = await getResponse(e); - const alert: FailurePayload = { - subject: ['schema', subject].join('-'), - title: `Schema ${subject}`, - response, - }; - dispatch(actions.updateSchemaAction.failure({ alert })); - throw e; } - }; -export const deleteSchema = - (clusterName: ClusterName, subject: string): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.deleteSchemaAction.request()); - try { - await schemasApiClient.deleteSchema({ + if (newCompatibilityLevel !== latestSchema.compatibilityLevel) { + await schemasApiClient.updateSchemaCompatibilityLevel({ clusterName, subject, + compatibilityLevel: { + compatibility: newCompatibilityLevel, + }, }); - dispatch(actions.deleteSchemaAction.success(subject)); - } catch (error) { - const response = await getResponse(error); - const alert: FailurePayload = { - subject: ['schema', subject].join('-'), - title: `Schema ${subject}`, - response, - }; - dispatch(actions.deleteSchemaAction.failure({ alert })); } - }; + actions.updateSchemaAction.success(schema); + } catch (e) { + const response = await getResponse(e); + const alert: FailurePayload = { + subject: ['schema', subject].join('-'), + title: `Schema ${subject}`, + response, + }; + dispatch(actions.updateSchemaAction.failure({ alert })); + throw e; + } +}; +export const deleteSchema = ( + clusterName: ClusterName, + subject: string +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.deleteSchemaAction.request()); + try { + await schemasApiClient.deleteSchema({ + clusterName, + subject, + }); + dispatch(actions.deleteSchemaAction.success(subject)); + } catch (error) { + const response = await getResponse(error); + const alert: FailurePayload = { + subject: ['schema', subject].join('-'), + title: `Schema ${subject}`, + response, + }; + dispatch(actions.deleteSchemaAction.failure({ alert })); + } +}; diff --git a/kafka-ui-react-app/src/redux/actions/thunks/topics.ts b/kafka-ui-react-app/src/redux/actions/thunks/topics.ts index 22e9fb359e9..52409911fc3 100644 --- a/kafka-ui-react-app/src/redux/actions/thunks/topics.ts +++ b/kafka-ui-react-app/src/redux/actions/thunks/topics.ts @@ -7,6 +7,7 @@ import { TopicCreation, TopicUpdate, TopicConfig, + TopicColumnsToSort, } from 'generated-sources'; import { PromiseThunkResult, @@ -30,140 +31,141 @@ export interface FetchTopicsListParams { clusterName: ClusterName; page?: number; perPage?: number; + showInternal?: boolean; + search?: string; + orderBy?: TopicColumnsToSort; } -export const fetchTopicsList = - (params: FetchTopicsListParams): PromiseThunkResult => - async (dispatch, getState) => { - dispatch(actions.fetchTopicsListAction.request()); - try { - const { topics, pageCount } = await topicsApiClient.getTopics(params); - const newState = (topics || []).reduce( - (memo: TopicsState, topic) => ({ - ...memo, - byName: { - ...memo.byName, - [topic.name]: { - ...memo.byName[topic.name], - ...topic, - id: v4(), - }, +export const fetchTopicsList = ( + params: FetchTopicsListParams +): PromiseThunkResult => async (dispatch, getState) => { + dispatch(actions.fetchTopicsListAction.request()); + try { + const { topics, pageCount } = await topicsApiClient.getTopics(params); + const newState = (topics || []).reduce( + (memo: TopicsState, topic) => ({ + ...memo, + byName: { + ...memo.byName, + [topic.name]: { + ...memo.byName[topic.name], + ...topic, + id: v4(), }, - allNames: [...memo.allNames, topic.name], - }), - { - ...getState().topics, - allNames: [], - totalPages: pageCount || 1, - } - ); - dispatch(actions.fetchTopicsListAction.success(newState)); - } catch (e) { - dispatch(actions.fetchTopicsListAction.failure()); - } - }; + }, + allNames: [...memo.allNames, topic.name], + }), + { + ...getState().topics, + allNames: [], + totalPages: pageCount || 1, + } + ); + dispatch(actions.fetchTopicsListAction.success(newState)); + } catch (e) { + dispatch(actions.fetchTopicsListAction.failure()); + } +}; -export const fetchTopicMessages = - ( - clusterName: ClusterName, - topicName: TopicName, - queryParams: Partial - ): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.fetchTopicMessagesAction.request()); - try { - const messages = await messagesApiClient.getTopicMessages({ - clusterName, - topicName, - ...queryParams, - }); - dispatch(actions.fetchTopicMessagesAction.success(messages)); - } catch (e) { - dispatch(actions.fetchTopicMessagesAction.failure()); - } - }; +export const fetchTopicMessages = ( + clusterName: ClusterName, + topicName: TopicName, + queryParams: Partial +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.fetchTopicMessagesAction.request()); + try { + const messages = await messagesApiClient.getTopicMessages({ + clusterName, + topicName, + ...queryParams, + }); + dispatch(actions.fetchTopicMessagesAction.success(messages)); + } catch (e) { + dispatch(actions.fetchTopicMessagesAction.failure()); + } +}; -export const clearTopicMessages = - ( - clusterName: ClusterName, - topicName: TopicName, - partitions?: number[] - ): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.clearMessagesTopicAction.request()); - try { - await messagesApiClient.deleteTopicMessages({ - clusterName, - topicName, - partitions, - }); - dispatch(actions.clearMessagesTopicAction.success(topicName)); - } catch (e) { - const response = await getResponse(e); - const alert: FailurePayload = { - subject: [clusterName, topicName, partitions].join('-'), - title: `Clear Topic Messages`, - response, - }; - dispatch(actions.clearMessagesTopicAction.failure({ alert })); - } - }; +export const clearTopicMessages = ( + clusterName: ClusterName, + topicName: TopicName, + partitions?: number[] +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.clearMessagesTopicAction.request()); + try { + await messagesApiClient.deleteTopicMessages({ + clusterName, + topicName, + partitions, + }); + dispatch(actions.clearMessagesTopicAction.success(topicName)); + } catch (e) { + const response = await getResponse(e); + const alert: FailurePayload = { + subject: [clusterName, topicName, partitions].join('-'), + title: `Clear Topic Messages`, + response, + }; + dispatch(actions.clearMessagesTopicAction.failure({ alert })); + } +}; -export const fetchTopicDetails = - (clusterName: ClusterName, topicName: TopicName): PromiseThunkResult => - async (dispatch, getState) => { - dispatch(actions.fetchTopicDetailsAction.request()); - try { - const topicDetails = await topicsApiClient.getTopicDetails({ - clusterName, - topicName, - }); - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topicName]: { - ...state.byName[topicName], - ...topicDetails, - }, +export const fetchTopicDetails = ( + clusterName: ClusterName, + topicName: TopicName +): PromiseThunkResult => async (dispatch, getState) => { + dispatch(actions.fetchTopicDetailsAction.request()); + try { + const topicDetails = await topicsApiClient.getTopicDetails({ + clusterName, + topicName, + }); + const state = getState().topics; + const newState = { + ...state, + byName: { + ...state.byName, + [topicName]: { + ...state.byName[topicName], + ...topicDetails, }, - }; - dispatch(actions.fetchTopicDetailsAction.success(newState)); - } catch (e) { - dispatch(actions.fetchTopicDetailsAction.failure()); - } - }; + }, + }; + dispatch(actions.fetchTopicDetailsAction.success(newState)); + } catch (e) { + dispatch(actions.fetchTopicDetailsAction.failure()); + } +}; -export const fetchTopicConfig = - (clusterName: ClusterName, topicName: TopicName): PromiseThunkResult => - async (dispatch, getState) => { - dispatch(actions.fetchTopicConfigAction.request()); - try { - const config = await topicsApiClient.getTopicConfigs({ - clusterName, - topicName, - }); +export const fetchTopicConfig = ( + clusterName: ClusterName, + topicName: TopicName +): PromiseThunkResult => async (dispatch, getState) => { + dispatch(actions.fetchTopicConfigAction.request()); + try { + const config = await topicsApiClient.getTopicConfigs({ + clusterName, + topicName, + }); - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topicName]: { - ...state.byName[topicName], - config: config.map((inputConfig) => ({ - ...inputConfig, - })), - }, + const state = getState().topics; + const newState = { + ...state, + byName: { + ...state.byName, + [topicName]: { + ...state.byName[topicName], + config: config.map((inputConfig) => ({ + ...inputConfig, + })), }, - }; + }, + }; - dispatch(actions.fetchTopicConfigAction.success(newState)); - } catch (e) { - dispatch(actions.fetchTopicConfigAction.failure()); - } - }; + dispatch(actions.fetchTopicConfigAction.success(newState)); + } catch (e) { + dispatch(actions.fetchTopicConfigAction.failure()); + } +}; const formatTopicCreation = (form: TopicFormDataRaw): TopicCreation => { const { @@ -231,84 +233,84 @@ const formatTopicUpdate = (form: TopicFormDataRaw): TopicUpdate => { }; }; -export const createTopic = - (clusterName: ClusterName, form: TopicFormDataRaw): PromiseThunkResult => - async (dispatch, getState) => { - dispatch(actions.createTopicAction.request()); - try { - const topic: Topic = await topicsApiClient.createTopic({ - clusterName, - topicCreation: formatTopicCreation(form), - }); +export const createTopic = ( + clusterName: ClusterName, + form: TopicFormDataRaw +): PromiseThunkResult => async (dispatch, getState) => { + dispatch(actions.createTopicAction.request()); + try { + const topic: Topic = await topicsApiClient.createTopic({ + clusterName, + topicCreation: formatTopicCreation(form), + }); - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topic.name]: { - ...topic, - }, + const state = getState().topics; + const newState = { + ...state, + byName: { + ...state.byName, + [topic.name]: { + ...topic, }, - allNames: [...state.allNames, topic.name], - }; + }, + allNames: [...state.allNames, topic.name], + }; - dispatch(actions.createTopicAction.success(newState)); - } catch (error) { - const response = await getResponse(error); - const alert: FailurePayload = { - subject: ['schema', form.name].join('-'), - title: `Schema ${form.name}`, - response, - }; - dispatch(actions.createTopicAction.failure({ alert })); - } - }; + dispatch(actions.createTopicAction.success(newState)); + } catch (error) { + const response = await getResponse(error); + const alert: FailurePayload = { + subject: ['schema', form.name].join('-'), + title: `Schema ${form.name}`, + response, + }; + dispatch(actions.createTopicAction.failure({ alert })); + } +}; -export const updateTopic = - ( - clusterName: ClusterName, - topicName: TopicName, - form: TopicFormDataRaw - ): PromiseThunkResult => - async (dispatch, getState) => { - dispatch(actions.updateTopicAction.request()); - try { - const topic: Topic = await topicsApiClient.updateTopic({ - clusterName, - topicName, - topicUpdate: formatTopicUpdate(form), - }); +export const updateTopic = ( + clusterName: ClusterName, + topicName: TopicName, + form: TopicFormDataRaw +): PromiseThunkResult => async (dispatch, getState) => { + dispatch(actions.updateTopicAction.request()); + try { + const topic: Topic = await topicsApiClient.updateTopic({ + clusterName, + topicName, + topicUpdate: formatTopicUpdate(form), + }); - const state = getState().topics; - const newState = { - ...state, - byName: { - ...state.byName, - [topic.name]: { - ...state.byName[topic.name], - ...topic, - }, + const state = getState().topics; + const newState = { + ...state, + byName: { + ...state.byName, + [topic.name]: { + ...state.byName[topic.name], + ...topic, }, - }; + }, + }; - dispatch(actions.updateTopicAction.success(newState)); - } catch (e) { - dispatch(actions.updateTopicAction.failure()); - } - }; + dispatch(actions.updateTopicAction.success(newState)); + } catch (e) { + dispatch(actions.updateTopicAction.failure()); + } +}; -export const deleteTopic = - (clusterName: ClusterName, topicName: TopicName): PromiseThunkResult => - async (dispatch) => { - dispatch(actions.deleteTopicAction.request()); - try { - await topicsApiClient.deleteTopic({ - clusterName, - topicName, - }); - dispatch(actions.deleteTopicAction.success(topicName)); - } catch (e) { - dispatch(actions.deleteTopicAction.failure()); - } - }; +export const deleteTopic = ( + clusterName: ClusterName, + topicName: TopicName +): PromiseThunkResult => async (dispatch) => { + dispatch(actions.deleteTopicAction.request()); + try { + await topicsApiClient.deleteTopic({ + clusterName, + topicName, + }); + dispatch(actions.deleteTopicAction.success(topicName)); + } catch (e) { + dispatch(actions.deleteTopicAction.failure()); + } +}; diff --git a/kafka-ui-react-app/src/redux/interfaces/topic.ts b/kafka-ui-react-app/src/redux/interfaces/topic.ts index e2835c4b5d3..fb5278c1704 100644 --- a/kafka-ui-react-app/src/redux/interfaces/topic.ts +++ b/kafka-ui-react-app/src/redux/interfaces/topic.ts @@ -5,6 +5,7 @@ import { TopicConfig, TopicCreation, GetTopicMessagesRequest, + TopicColumnsToSort, } from 'generated-sources'; export type TopicName = Topic['name']; @@ -45,6 +46,8 @@ export interface TopicsState { allNames: TopicName[]; totalPages: number; messages: TopicMessage[]; + search: string; + orderBy: TopicColumnsToSort | null; } export type TopicFormFormattedParams = TopicCreation['configs']; 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 d4841b137b2..0e064db94fa 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,10 @@ -import { deleteTopicAction, clearMessagesTopicAction } from 'redux/actions'; +import { TopicColumnsToSort } from 'generated-sources'; +import { + deleteTopicAction, + clearMessagesTopicAction, + setTopicsSearchAction, + setTopicsOrderByAction, +} from 'redux/actions'; import reducer from 'redux/reducers/topics/reducer'; const topic = { @@ -13,21 +19,44 @@ const state = { allNames: [topic.name], messages: [], totalPages: 1, + search: '', + orderBy: null, }; describe('topics reducer', () => { - it('deletes the topic from the list on DELETE_TOPIC__SUCCESS', () => { - expect(reducer(state, deleteTopicAction.success(topic.name))).toEqual({ - byName: {}, - allNames: [], - messages: [], - totalPages: 1, + describe('delete topic', () => { + it('deletes the topic from the list on DELETE_TOPIC__SUCCESS', () => { + expect(reducer(state, deleteTopicAction.success(topic.name))).toEqual({ + ...state, + byName: {}, + allNames: [], + }); + }); + + it('delete topic messages on CLEAR_TOPIC_MESSAGES__SUCCESS', () => { + expect( + reducer(state, clearMessagesTopicAction.success(topic.name)) + ).toEqual(state); + }); + }); + + describe('search topics', () => { + it('sets the search string', () => { + expect(reducer(state, setTopicsSearchAction('test'))).toEqual({ + ...state, + search: 'test', + }); }); }); - it('delete topic messages on CLEAR_TOPIC_MESSAGES__SUCCESS', () => { - expect( - reducer(state, clearMessagesTopicAction.success(topic.name)) - ).toEqual(state); + describe('order topics', () => { + it('sets the orderBy', () => { + expect( + reducer(state, setTopicsOrderByAction(TopicColumnsToSort.NAME)) + ).toEqual({ + ...state, + orderBy: TopicColumnsToSort.NAME, + }); + }); }); }); 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 0e3f7366e3c..85e1956f749 100644 --- a/kafka-ui-react-app/src/redux/reducers/topics/reducer.ts +++ b/kafka-ui-react-app/src/redux/reducers/topics/reducer.ts @@ -8,6 +8,8 @@ export const initialState: TopicsState = { allNames: [], totalPages: 1, messages: [], + search: '', + orderBy: null, }; const transformTopicMessages = ( @@ -59,6 +61,18 @@ const reducer = (state = initialState, action: Action): TopicsState => { messages: [], }; } + case getType(actions.setTopicsSearchAction): { + return { + ...state, + search: action.payload, + }; + } + case getType(actions.setTopicsOrderByAction): { + return { + ...state, + orderBy: action.payload, + }; + } default: return state; } 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 6aa5a6f2288..c7084f761da 100644 --- a/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts +++ b/kafka-ui-react-app/src/redux/reducers/topics/selectors.ts @@ -18,10 +18,12 @@ export const getTopicListTotalPages = (state: RootState) => topicsState(state).totalPages; const getTopicListFetchingStatus = createFetchingSelector('GET_TOPICS'); -const getTopicDetailsFetchingStatus = - createFetchingSelector('GET_TOPIC_DETAILS'); -const getTopicMessagesFetchingStatus = - createFetchingSelector('GET_TOPIC_MESSAGES'); +const getTopicDetailsFetchingStatus = createFetchingSelector( + 'GET_TOPIC_DETAILS' +); +const getTopicMessagesFetchingStatus = createFetchingSelector( + 'GET_TOPIC_MESSAGES' +); const getTopicConfigFetchingStatus = createFetchingSelector('GET_TOPIC_CONFIG'); const getTopicCreationStatus = createFetchingSelector('POST_TOPIC'); const getTopicUpdateStatus = createFetchingSelector('PATCH_TOPIC'); @@ -122,6 +124,16 @@ export const getTopicConfigByParamName = createSelector( } ); +export const getTopicsSearch = createSelector( + topicsState, + (state) => state.search +); + +export const getTopicsOrderBy = createSelector( + topicsState, + (state) => state.orderBy +); + export const getIsTopicInternal = createSelector( getTopicByName, ({ internal }) => !!internal diff --git a/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts b/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts index 9c20ad7f1af..373edbe3a09 100644 --- a/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts +++ b/kafka-ui-react-app/src/redux/store/configureStore/mockStoreCreator.ts @@ -6,7 +6,9 @@ import { RootState, Action } from 'redux/interfaces'; const middlewares: Array = [thunk]; type DispatchExts = ThunkDispatch; -const mockStoreCreator: MockStoreCreator = - configureMockStore(middlewares); +const mockStoreCreator: MockStoreCreator< + RootState, + DispatchExts +> = configureMockStore(middlewares); export default mockStoreCreator();