parent
00da655e54
commit
98fcc90c6b
29 changed files with 1163 additions and 504 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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<Props> = ({
|
||||
|
@ -37,14 +44,24 @@ const List: React.FC<Props> = ({
|
|||
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<boolean>(true);
|
||||
|
||||
|
@ -52,14 +69,16 @@ const List: React.FC<Props> = ({
|
|||
setShowInternal(!showInternal);
|
||||
}, [showInternal]);
|
||||
|
||||
const handleSearch = (value: string) => setTopicsSearch(value);
|
||||
|
||||
const items = showInternal ? topics : externalTopics;
|
||||
|
||||
return (
|
||||
<div className="section">
|
||||
<Breadcrumb>{showInternal ? `All Topics` : `External Topics`}</Breadcrumb>
|
||||
<div className="box">
|
||||
<div className="level">
|
||||
<div className="level-item level-left">
|
||||
<div className="columns">
|
||||
<div className="column is-one-quarter is-align-items-center is-flex">
|
||||
<div className="field">
|
||||
<input
|
||||
id="switchRoundedDefault"
|
||||
|
@ -72,7 +91,14 @@ const List: React.FC<Props> = ({
|
|||
<label htmlFor="switchRoundedDefault">Show Internal Topics</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="level-item level-right">
|
||||
<div className="column">
|
||||
<Search
|
||||
handleSearch={handleSearch}
|
||||
placeholder="Search by Topic Name"
|
||||
value={search}
|
||||
/>
|
||||
</div>
|
||||
<div className="column is-2 is-justify-content-flex-end is-flex">
|
||||
{!isReadOnly && (
|
||||
<Link
|
||||
className="button is-primary"
|
||||
|
@ -91,9 +117,24 @@ const List: React.FC<Props> = ({
|
|||
<table className="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Topic Name</th>
|
||||
<th>Total Partitions</th>
|
||||
<th>Out of sync replicas</th>
|
||||
<SortableColumnHeader
|
||||
value={TopicColumnsToSort.NAME}
|
||||
title="Topic Name"
|
||||
orderBy={orderBy}
|
||||
setOrderBy={setTopicsOrderBy}
|
||||
/>
|
||||
<SortableColumnHeader
|
||||
value={TopicColumnsToSort.TOTAL_PARTITIONS}
|
||||
title="Total Partitions"
|
||||
orderBy={orderBy}
|
||||
setOrderBy={setTopicsOrderBy}
|
||||
/>
|
||||
<SortableColumnHeader
|
||||
value={TopicColumnsToSort.OUT_OF_SYNC_REPLICAS}
|
||||
title="Out of sync replicas"
|
||||
orderBy={orderBy}
|
||||
setOrderBy={setTopicsOrderBy}
|
||||
/>
|
||||
<th>Type</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -23,8 +23,10 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
clusterName,
|
||||
clearTopicMessages,
|
||||
}) => {
|
||||
const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] =
|
||||
React.useState(false);
|
||||
const [
|
||||
isDeleteTopicConfirmationVisible,
|
||||
setDeleteTopicConfirmationVisible,
|
||||
] = React.useState(false);
|
||||
|
||||
const outOfSyncReplicas = React.useMemo(() => {
|
||||
if (partitions === undefined || partitions.length === 0) {
|
||||
|
|
|
@ -24,6 +24,10 @@ describe('List', () => {
|
|||
fetchTopicsList={jest.fn()}
|
||||
deleteTopic={jest.fn()}
|
||||
clearTopicMessages={jest.fn()}
|
||||
search=""
|
||||
orderBy={null}
|
||||
setTopicsSearch={jest.fn()}
|
||||
setTopicsOrderBy={jest.fn()}
|
||||
/>
|
||||
</ClusterContext.Provider>
|
||||
</StaticRouter>
|
||||
|
@ -33,7 +37,8 @@ describe('List', () => {
|
|||
});
|
||||
|
||||
describe('when it does not have readonly flag', () => {
|
||||
it('renders the Add a Topic button', () => {
|
||||
const mockFetch = jest.fn();
|
||||
jest.useFakeTimers();
|
||||
const component = mount(
|
||||
<StaticRouter>
|
||||
<ClusterContext.Provider
|
||||
|
@ -48,14 +53,32 @@ describe('List', () => {
|
|||
topics={[]}
|
||||
externalTopics={[]}
|
||||
totalPages={1}
|
||||
fetchTopicsList={jest.fn()}
|
||||
fetchTopicsList={mockFetch}
|
||||
deleteTopic={jest.fn()}
|
||||
clearTopicMessages={jest.fn()}
|
||||
search=""
|
||||
orderBy={null}
|
||||
setTopicsSearch={jest.fn()}
|
||||
setTopicsOrderBy={jest.fn()}
|
||||
/>
|
||||
</ClusterContext.Provider>
|
||||
</StaticRouter>
|
||||
);
|
||||
it('renders the Add a Topic button', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`List when it does not have readonly flag matches the snapshot 1`] = `
|
||||
<StaticRouter>
|
||||
<Router
|
||||
history={
|
||||
Object {
|
||||
"action": "POP",
|
||||
"block": [Function],
|
||||
"createHref": [Function],
|
||||
"go": [Function],
|
||||
"goBack": [Function],
|
||||
"goForward": [Function],
|
||||
"listen": [Function],
|
||||
"location": Object {
|
||||
"hash": "",
|
||||
"pathname": "/",
|
||||
"search": "",
|
||||
"state": undefined,
|
||||
},
|
||||
"push": [Function],
|
||||
"replace": [Function],
|
||||
}
|
||||
}
|
||||
staticContext={Object {}}
|
||||
>
|
||||
<List
|
||||
areTopicsFetching={false}
|
||||
clearTopicMessages={[MockFunction]}
|
||||
deleteTopic={[MockFunction]}
|
||||
externalTopics={Array []}
|
||||
fetchTopicsList={[MockFunction]}
|
||||
orderBy={null}
|
||||
search=""
|
||||
setTopicsOrderBy={[MockFunction]}
|
||||
setTopicsSearch={[MockFunction]}
|
||||
topics={Array []}
|
||||
totalPages={1}
|
||||
>
|
||||
<div
|
||||
className="section"
|
||||
>
|
||||
<Breadcrumb>
|
||||
<nav
|
||||
aria-label="breadcrumbs"
|
||||
className="breadcrumb"
|
||||
>
|
||||
<ul>
|
||||
<li
|
||||
className="is-active"
|
||||
>
|
||||
<span
|
||||
className=""
|
||||
>
|
||||
All Topics
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</Breadcrumb>
|
||||
<div
|
||||
className="box"
|
||||
>
|
||||
<div
|
||||
className="columns"
|
||||
>
|
||||
<div
|
||||
className="column is-one-quarter is-align-items-center is-flex"
|
||||
>
|
||||
<div
|
||||
className="field"
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
className="switch is-rounded"
|
||||
id="switchRoundedDefault"
|
||||
name="switchRoundedDefault"
|
||||
onChange={[Function]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
htmlFor="switchRoundedDefault"
|
||||
>
|
||||
Show Internal Topics
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="column"
|
||||
>
|
||||
<Search
|
||||
handleSearch={[Function]}
|
||||
placeholder="Search by Topic Name"
|
||||
value=""
|
||||
>
|
||||
<p
|
||||
className="control has-icons-left"
|
||||
>
|
||||
<input
|
||||
className="input"
|
||||
defaultValue=""
|
||||
onChange={[Function]}
|
||||
placeholder="Search by Topic Name"
|
||||
type="text"
|
||||
/>
|
||||
<span
|
||||
className="icon is-small is-left"
|
||||
>
|
||||
<i
|
||||
className="fas fa-search"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
</Search>
|
||||
</div>
|
||||
<div
|
||||
className="column is-2 is-justify-content-flex-end is-flex"
|
||||
>
|
||||
<Link
|
||||
className="button is-primary"
|
||||
to="/ui/clusters/undefined/topics/create_new"
|
||||
>
|
||||
<LinkAnchor
|
||||
className="button is-primary"
|
||||
href="/ui/clusters/undefined/topics/create_new"
|
||||
navigate={[Function]}
|
||||
>
|
||||
<a
|
||||
className="button is-primary"
|
||||
href="/ui/clusters/undefined/topics/create_new"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Add a Topic
|
||||
</a>
|
||||
</LinkAnchor>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="box"
|
||||
>
|
||||
<table
|
||||
className="table is-fullwidth"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<ListHeaderCell
|
||||
orderBy={null}
|
||||
setOrderBy={[MockFunction]}
|
||||
title="Topic Name"
|
||||
value="NAME"
|
||||
>
|
||||
<th
|
||||
className="is-clickable"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Topic Name
|
||||
<span
|
||||
className="icon is-small"
|
||||
>
|
||||
<i
|
||||
className="fas fa-sort"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</ListHeaderCell>
|
||||
<ListHeaderCell
|
||||
orderBy={null}
|
||||
setOrderBy={[MockFunction]}
|
||||
title="Total Partitions"
|
||||
value="TOTAL_PARTITIONS"
|
||||
>
|
||||
<th
|
||||
className="is-clickable"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Total Partitions
|
||||
<span
|
||||
className="icon is-small"
|
||||
>
|
||||
<i
|
||||
className="fas fa-sort"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</ListHeaderCell>
|
||||
<ListHeaderCell
|
||||
orderBy={null}
|
||||
setOrderBy={[MockFunction]}
|
||||
title="Out of sync replicas"
|
||||
value="OUT_OF_SYNC_REPLICAS"
|
||||
>
|
||||
<th
|
||||
className="is-clickable"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Out of sync replicas
|
||||
<span
|
||||
className="icon is-small"
|
||||
>
|
||||
<i
|
||||
className="fas fa-sort"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</ListHeaderCell>
|
||||
<th>
|
||||
Type
|
||||
</th>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
colSpan={10}
|
||||
>
|
||||
No topics found
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Pagination
|
||||
totalPages={1}
|
||||
>
|
||||
<nav
|
||||
aria-label="pagination"
|
||||
className="pagination is-small is-right"
|
||||
role="navigation"
|
||||
>
|
||||
<button
|
||||
className="pagination-previous"
|
||||
disabled={true}
|
||||
type="button"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
className="pagination-next"
|
||||
disabled={true}
|
||||
type="button"
|
||||
>
|
||||
Next page
|
||||
</button>
|
||||
</nav>
|
||||
</Pagination>
|
||||
</div>
|
||||
</div>
|
||||
</List>
|
||||
</Router>
|
||||
</StaticRouter>
|
||||
`;
|
|
@ -33,8 +33,10 @@ const Details: React.FC<Props> = ({
|
|||
}) => {
|
||||
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));
|
||||
|
|
|
@ -50,8 +50,9 @@ const Messages: React.FC<Props> = ({
|
|||
fetchTopicMessages,
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
||||
const [searchTimestamp, setSearchTimestamp] =
|
||||
React.useState<Date | null>(null);
|
||||
const [searchTimestamp, setSearchTimestamp] = React.useState<Date | null>(
|
||||
null
|
||||
);
|
||||
const [filterProps, setFilterProps] = React.useState<FilterProps[]>([]);
|
||||
const [selectedSeekType, setSelectedSeekType] = React.useState<SeekType>(
|
||||
SeekType.OFFSET
|
||||
|
|
|
@ -39,8 +39,9 @@ const CustomParamSelect: React.FC<CustomParamSelectProps> = ({
|
|||
return valid || 'Custom Parameter must be unique';
|
||||
};
|
||||
|
||||
const onChange =
|
||||
(inputName: string) => (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const onChange = (inputName: string) => (
|
||||
event: React.ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
trigger(inputName);
|
||||
onNameChange(index, event.target.value);
|
||||
};
|
||||
|
|
|
@ -35,8 +35,10 @@ const CustomParams: React.FC<Props> = ({ isSubmitting, config }) => {
|
|||
)
|
||||
: {};
|
||||
|
||||
const [formCustomParams, setFormCustomParams] =
|
||||
React.useState<TopicFormCustomParams>({
|
||||
const [
|
||||
formCustomParams,
|
||||
setFormCustomParams,
|
||||
] = React.useState<TopicFormCustomParams>({
|
||||
byIndex,
|
||||
allIndexes: Object.keys(byIndex),
|
||||
});
|
||||
|
|
35
kafka-ui-react-app/src/components/common/Search/Search.tsx
Normal file
35
kafka-ui-react-app/src/components/common/Search/Search.tsx
Normal file
|
@ -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<SearchProps> = ({
|
||||
handleSearch,
|
||||
placeholder = 'Search',
|
||||
value,
|
||||
}) => {
|
||||
const onChange = useDebouncedCallback(
|
||||
(e) => handleSearch(e.target.value),
|
||||
300
|
||||
);
|
||||
return (
|
||||
<p className="control has-icons-left">
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
defaultValue={value}
|
||||
/>
|
||||
<span className="icon is-small is-left">
|
||||
<i className="fas fa-search" />
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
|
@ -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(
|
||||
<Search
|
||||
handleSearch={handleSearch}
|
||||
value=""
|
||||
placeholder="Search bt the Topic name"
|
||||
/>
|
||||
);
|
||||
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(<Search handleSearch={handleSearch} value="" />);
|
||||
it('matches the snapshot', () => {
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Search when placeholder is not provided matches the snapshot 1`] = `
|
||||
<p
|
||||
className="control has-icons-left"
|
||||
>
|
||||
<input
|
||||
className="input"
|
||||
defaultValue=""
|
||||
onChange={[Function]}
|
||||
placeholder="Search"
|
||||
type="text"
|
||||
/>
|
||||
<span
|
||||
className="icon is-small is-left"
|
||||
>
|
||||
<i
|
||||
className="fas fa-search"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
`;
|
||||
|
||||
exports[`Search when placeholder is provided matches the snapshot 1`] = `
|
||||
<p
|
||||
className="control has-icons-left"
|
||||
>
|
||||
<input
|
||||
className="input"
|
||||
defaultValue=""
|
||||
onChange={[Function]}
|
||||
placeholder="Search"
|
||||
type="text"
|
||||
/>
|
||||
<span
|
||||
className="icon is-small is-left"
|
||||
>
|
||||
<i
|
||||
className="fas fa-search"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
`;
|
|
@ -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<React.SetStateAction<any>>;
|
||||
}
|
||||
|
||||
const ListHeaderCell: React.FC<ListHeaderProps> = ({
|
||||
value,
|
||||
title,
|
||||
orderBy,
|
||||
setOrderBy,
|
||||
}) => (
|
||||
<th
|
||||
className={cx('is-clickable', orderBy === value && 'has-text-link-dark')}
|
||||
onClick={() => setOrderBy(value)}
|
||||
>
|
||||
{title}
|
||||
<span className="icon is-small">
|
||||
<i className="fas fa-sort" />
|
||||
</span>
|
||||
</th>
|
||||
);
|
||||
|
||||
export default ListHeaderCell;
|
|
@ -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(
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<SortableColumnHeader
|
||||
value={TopicColumnsToSort.NAME}
|
||||
title="Name"
|
||||
orderBy={null}
|
||||
setOrderBy={setOrderBy}
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
);
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ListHeader matches the snapshot 1`] = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<ListHeaderCell
|
||||
orderBy={null}
|
||||
setOrderBy={[MockFunction]}
|
||||
title="Name"
|
||||
value="NAME"
|
||||
>
|
||||
<th
|
||||
className="is-clickable"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Name
|
||||
<span
|
||||
className="icon is-small"
|
||||
>
|
||||
<i
|
||||
className="fas fa-sort"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</ListHeaderCell>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
`;
|
||||
|
||||
exports[`ListHeader on column click matches the snapshot 1`] = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<ListHeaderCell
|
||||
orderBy={null}
|
||||
setOrderBy={[MockFunction]}
|
||||
title="Name"
|
||||
value="NAME"
|
||||
>
|
||||
<th
|
||||
className="is-clickable"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Name
|
||||
<span
|
||||
className="icon is-small"
|
||||
>
|
||||
<i
|
||||
className="fas fa-sort"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
</ListHeaderCell>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
`;
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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'
|
||||
)<undefined, { connector: Connector }, { alert?: FailurePayload }>();
|
||||
|
||||
export const setTopicsSearchAction = createAction(
|
||||
'SET_TOPICS_SEARCH'
|
||||
)<string>();
|
||||
|
||||
export const setTopicsOrderByAction = createAction(
|
||||
'SET_TOPICS_ORDER_BY'
|
||||
)<TopicColumnsToSort>();
|
||||
|
|
|
@ -6,9 +6,9 @@ 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) => {
|
||||
export const fetchBrokers = (
|
||||
clusterName: ClusterName
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.fetchBrokersAction.request());
|
||||
try {
|
||||
const payload = await brokersApiClient.getBrokers({ clusterName });
|
||||
|
@ -16,11 +16,12 @@ export const fetchBrokers =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchBrokersAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchBrokerMetrics =
|
||||
(clusterName: ClusterName, brokerId: BrokerId): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
export const fetchBrokerMetrics = (
|
||||
clusterName: ClusterName,
|
||||
brokerId: BrokerId
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.fetchBrokerMetricsAction.request());
|
||||
try {
|
||||
const payload = await brokersApiClient.getBrokersMetrics({
|
||||
|
@ -31,4 +32,4 @@ export const fetchBrokerMetrics =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchBrokerMetricsAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -16,9 +16,9 @@ export const fetchClustersList = (): PromiseThunkResult => async (dispatch) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const fetchClusterStats =
|
||||
(clusterName: ClusterName): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
export const fetchClusterStats = (
|
||||
clusterName: ClusterName
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.fetchClusterStatsAction.request());
|
||||
try {
|
||||
const payload = await clustersApiClient.getClusterStats({ clusterName });
|
||||
|
@ -26,11 +26,11 @@ export const fetchClusterStats =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchClusterStatsAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchClusterMetrics =
|
||||
(clusterName: ClusterName): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
export const fetchClusterMetrics = (
|
||||
clusterName: ClusterName
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.fetchClusterMetricsAction.request());
|
||||
try {
|
||||
const payload = await clustersApiClient.getClusterMetrics({
|
||||
|
@ -40,4 +40,4 @@ export const fetchClusterMetrics =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchClusterMetricsAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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<void> =>
|
||||
async (dispatch) => {
|
||||
|
|
|
@ -10,9 +10,9 @@ 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) => {
|
||||
export const fetchConsumerGroupsList = (
|
||||
clusterName: ClusterName
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.fetchConsumerGroupsAction.request());
|
||||
try {
|
||||
const consumerGroups = await consumerGroupsApiClient.getConsumerGroups({
|
||||
|
@ -22,21 +22,20 @@ export const fetchConsumerGroupsList =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchConsumerGroupsAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchConsumerGroupDetails =
|
||||
(
|
||||
export const fetchConsumerGroupDetails = (
|
||||
clusterName: ClusterName,
|
||||
consumerGroupID: ConsumerGroupID
|
||||
): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.fetchConsumerGroupDetailsAction.request());
|
||||
try {
|
||||
const consumerGroupDetails =
|
||||
await consumerGroupsApiClient.getConsumerGroup({
|
||||
const consumerGroupDetails = await consumerGroupsApiClient.getConsumerGroup(
|
||||
{
|
||||
clusterName,
|
||||
id: consumerGroupID,
|
||||
});
|
||||
}
|
||||
);
|
||||
dispatch(
|
||||
actions.fetchConsumerGroupDetailsAction.success({
|
||||
consumerGroupID,
|
||||
|
@ -46,4 +45,4 @@ export const fetchConsumerGroupDetails =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchConsumerGroupDetailsAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,9 +20,9 @@ import { isEqual } from 'lodash';
|
|||
const apiClientConf = new Configuration(BASE_PARAMS);
|
||||
export const schemasApiClient = new SchemasApi(apiClientConf);
|
||||
|
||||
export const fetchSchemasByClusterName =
|
||||
(clusterName: ClusterName): PromiseThunkResult<void> =>
|
||||
async (dispatch) => {
|
||||
export const fetchSchemasByClusterName = (
|
||||
clusterName: ClusterName
|
||||
): PromiseThunkResult<void> => async (dispatch) => {
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.request());
|
||||
try {
|
||||
const schemas = await schemasApiClient.getSchemas({ clusterName });
|
||||
|
@ -30,11 +30,12 @@ export const fetchSchemasByClusterName =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchSchemaVersions =
|
||||
(clusterName: ClusterName, subject: SchemaName): PromiseThunkResult<void> =>
|
||||
async (dispatch) => {
|
||||
export const fetchSchemaVersions = (
|
||||
clusterName: ClusterName,
|
||||
subject: SchemaName
|
||||
): PromiseThunkResult<void> => async (dispatch) => {
|
||||
if (!subject) return;
|
||||
dispatch(actions.fetchSchemaVersionsAction.request());
|
||||
try {
|
||||
|
@ -46,11 +47,11 @@ export const fetchSchemaVersions =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchSchemaVersionsAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchGlobalSchemaCompatibilityLevel =
|
||||
(clusterName: ClusterName): PromiseThunkResult<void> =>
|
||||
async (dispatch) => {
|
||||
export const fetchGlobalSchemaCompatibilityLevel = (
|
||||
clusterName: ClusterName
|
||||
): PromiseThunkResult<void> => async (dispatch) => {
|
||||
dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.request());
|
||||
try {
|
||||
const result = await schemasApiClient.getGlobalSchemaCompatibilityLevel({
|
||||
|
@ -64,14 +65,12 @@ export const fetchGlobalSchemaCompatibilityLevel =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchGlobalSchemaCompatibilityLevelAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const updateGlobalSchemaCompatibilityLevel =
|
||||
(
|
||||
export const updateGlobalSchemaCompatibilityLevel = (
|
||||
clusterName: ClusterName,
|
||||
compatibilityLevel: CompatibilityLevelCompatibilityEnum
|
||||
): PromiseThunkResult<void> =>
|
||||
async (dispatch) => {
|
||||
): PromiseThunkResult<void> => async (dispatch) => {
|
||||
dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.request());
|
||||
try {
|
||||
await schemasApiClient.updateGlobalSchemaCompatibilityLevel({
|
||||
|
@ -86,14 +85,12 @@ export const updateGlobalSchemaCompatibilityLevel =
|
|||
} catch (e) {
|
||||
dispatch(actions.updateGlobalSchemaCompatibilityLevelAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const createSchema =
|
||||
(
|
||||
export const createSchema = (
|
||||
clusterName: ClusterName,
|
||||
newSchemaSubject: NewSchemaSubject
|
||||
): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.createSchemaAction.request());
|
||||
try {
|
||||
const schema: SchemaSubject = await schemasApiClient.createNewSchema({
|
||||
|
@ -111,18 +108,16 @@ export const createSchema =
|
|||
dispatch(actions.createSchemaAction.failure({ alert }));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const updateSchema =
|
||||
(
|
||||
export const updateSchema = (
|
||||
latestSchema: SchemaSubject,
|
||||
newSchema: string,
|
||||
newSchemaType: SchemaType,
|
||||
newCompatibilityLevel: CompatibilityLevelCompatibilityEnum,
|
||||
clusterName: string,
|
||||
subject: string
|
||||
): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.updateSchemaAction.request());
|
||||
try {
|
||||
let schema: SchemaSubject = latestSchema;
|
||||
|
@ -160,10 +155,11 @@ export const updateSchema =
|
|||
dispatch(actions.updateSchemaAction.failure({ alert }));
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
export const deleteSchema =
|
||||
(clusterName: ClusterName, subject: string): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
};
|
||||
export const deleteSchema = (
|
||||
clusterName: ClusterName,
|
||||
subject: string
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.deleteSchemaAction.request());
|
||||
try {
|
||||
await schemasApiClient.deleteSchema({
|
||||
|
@ -180,4 +176,4 @@ export const deleteSchema =
|
|||
};
|
||||
dispatch(actions.deleteSchemaAction.failure({ alert }));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
TopicCreation,
|
||||
TopicUpdate,
|
||||
TopicConfig,
|
||||
TopicColumnsToSort,
|
||||
} from 'generated-sources';
|
||||
import {
|
||||
PromiseThunkResult,
|
||||
|
@ -30,11 +31,14 @@ export interface FetchTopicsListParams {
|
|||
clusterName: ClusterName;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
showInternal?: boolean;
|
||||
search?: string;
|
||||
orderBy?: TopicColumnsToSort;
|
||||
}
|
||||
|
||||
export const fetchTopicsList =
|
||||
(params: FetchTopicsListParams): PromiseThunkResult =>
|
||||
async (dispatch, getState) => {
|
||||
export const fetchTopicsList = (
|
||||
params: FetchTopicsListParams
|
||||
): PromiseThunkResult => async (dispatch, getState) => {
|
||||
dispatch(actions.fetchTopicsListAction.request());
|
||||
try {
|
||||
const { topics, pageCount } = await topicsApiClient.getTopics(params);
|
||||
|
@ -61,15 +65,13 @@ export const fetchTopicsList =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchTopicsListAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchTopicMessages =
|
||||
(
|
||||
export const fetchTopicMessages = (
|
||||
clusterName: ClusterName,
|
||||
topicName: TopicName,
|
||||
queryParams: Partial<TopicMessageQueryParams>
|
||||
): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.fetchTopicMessagesAction.request());
|
||||
try {
|
||||
const messages = await messagesApiClient.getTopicMessages({
|
||||
|
@ -81,15 +83,13 @@ export const fetchTopicMessages =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchTopicMessagesAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const clearTopicMessages =
|
||||
(
|
||||
export const clearTopicMessages = (
|
||||
clusterName: ClusterName,
|
||||
topicName: TopicName,
|
||||
partitions?: number[]
|
||||
): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.clearMessagesTopicAction.request());
|
||||
try {
|
||||
await messagesApiClient.deleteTopicMessages({
|
||||
|
@ -107,11 +107,12 @@ export const clearTopicMessages =
|
|||
};
|
||||
dispatch(actions.clearMessagesTopicAction.failure({ alert }));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchTopicDetails =
|
||||
(clusterName: ClusterName, topicName: TopicName): PromiseThunkResult =>
|
||||
async (dispatch, getState) => {
|
||||
export const fetchTopicDetails = (
|
||||
clusterName: ClusterName,
|
||||
topicName: TopicName
|
||||
): PromiseThunkResult => async (dispatch, getState) => {
|
||||
dispatch(actions.fetchTopicDetailsAction.request());
|
||||
try {
|
||||
const topicDetails = await topicsApiClient.getTopicDetails({
|
||||
|
@ -133,11 +134,12 @@ export const fetchTopicDetails =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchTopicDetailsAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchTopicConfig =
|
||||
(clusterName: ClusterName, topicName: TopicName): PromiseThunkResult =>
|
||||
async (dispatch, getState) => {
|
||||
export const fetchTopicConfig = (
|
||||
clusterName: ClusterName,
|
||||
topicName: TopicName
|
||||
): PromiseThunkResult => async (dispatch, getState) => {
|
||||
dispatch(actions.fetchTopicConfigAction.request());
|
||||
try {
|
||||
const config = await topicsApiClient.getTopicConfigs({
|
||||
|
@ -163,7 +165,7 @@ export const fetchTopicConfig =
|
|||
} catch (e) {
|
||||
dispatch(actions.fetchTopicConfigAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const formatTopicCreation = (form: TopicFormDataRaw): TopicCreation => {
|
||||
const {
|
||||
|
@ -231,9 +233,10 @@ const formatTopicUpdate = (form: TopicFormDataRaw): TopicUpdate => {
|
|||
};
|
||||
};
|
||||
|
||||
export const createTopic =
|
||||
(clusterName: ClusterName, form: TopicFormDataRaw): PromiseThunkResult =>
|
||||
async (dispatch, getState) => {
|
||||
export const createTopic = (
|
||||
clusterName: ClusterName,
|
||||
form: TopicFormDataRaw
|
||||
): PromiseThunkResult => async (dispatch, getState) => {
|
||||
dispatch(actions.createTopicAction.request());
|
||||
try {
|
||||
const topic: Topic = await topicsApiClient.createTopic({
|
||||
|
@ -263,15 +266,13 @@ export const createTopic =
|
|||
};
|
||||
dispatch(actions.createTopicAction.failure({ alert }));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const updateTopic =
|
||||
(
|
||||
export const updateTopic = (
|
||||
clusterName: ClusterName,
|
||||
topicName: TopicName,
|
||||
form: TopicFormDataRaw
|
||||
): PromiseThunkResult =>
|
||||
async (dispatch, getState) => {
|
||||
): PromiseThunkResult => async (dispatch, getState) => {
|
||||
dispatch(actions.updateTopicAction.request());
|
||||
try {
|
||||
const topic: Topic = await topicsApiClient.updateTopic({
|
||||
|
@ -296,11 +297,12 @@ export const updateTopic =
|
|||
} catch (e) {
|
||||
dispatch(actions.updateTopicAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteTopic =
|
||||
(clusterName: ClusterName, topicName: TopicName): PromiseThunkResult =>
|
||||
async (dispatch) => {
|
||||
export const deleteTopic = (
|
||||
clusterName: ClusterName,
|
||||
topicName: TopicName
|
||||
): PromiseThunkResult => async (dispatch) => {
|
||||
dispatch(actions.deleteTopicAction.request());
|
||||
try {
|
||||
await topicsApiClient.deleteTopic({
|
||||
|
@ -311,4 +313,4 @@ export const deleteTopic =
|
|||
} catch (e) {
|
||||
dispatch(actions.deleteTopicAction.failure());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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,15 +19,17 @@ const state = {
|
|||
allNames: [topic.name],
|
||||
messages: [],
|
||||
totalPages: 1,
|
||||
search: '',
|
||||
orderBy: null,
|
||||
};
|
||||
|
||||
describe('topics reducer', () => {
|
||||
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: [],
|
||||
messages: [],
|
||||
totalPages: 1,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -30,4 +38,25 @@ describe('topics reducer', () => {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('order topics', () => {
|
||||
it('sets the orderBy', () => {
|
||||
expect(
|
||||
reducer(state, setTopicsOrderByAction(TopicColumnsToSort.NAME))
|
||||
).toEqual({
|
||||
...state,
|
||||
orderBy: TopicColumnsToSort.NAME,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,7 +6,9 @@ import { RootState, Action } from 'redux/interfaces';
|
|||
const middlewares: Array<Middleware> = [thunk];
|
||||
type DispatchExts = ThunkDispatch<RootState, undefined, Action>;
|
||||
|
||||
const mockStoreCreator: MockStoreCreator<RootState, DispatchExts> =
|
||||
configureMockStore<RootState, DispatchExts>(middlewares);
|
||||
const mockStoreCreator: MockStoreCreator<
|
||||
RootState,
|
||||
DispatchExts
|
||||
> = configureMockStore<RootState, DispatchExts>(middlewares);
|
||||
|
||||
export default mockStoreCreator();
|
||||
|
|
Loading…
Add table
Reference in a new issue