Hide 'Clear All Messages' button on topics with non-delete policy

* #992: hide 'Clear All Messages' button on topics which are not delete policy, as it's not supported by AdminClient

* #992 remove console.log and use type
This commit is contained in:
Si Tang 2021-10-27 21:10:57 +09:00 committed by GitHub
parent 94112f151b
commit 7b62af1fa2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 830 additions and 63 deletions

View file

@ -21,20 +21,22 @@ import TopicConsumerGroupsContainer from './ConsumerGroups/TopicConsumerGroupsCo
import SettingsContainer from './Settings/SettingsContainer';
import Messages from './Messages/Messages';
interface Props extends Topic, TopicDetails {
export interface DetailsProps extends Topic, TopicDetails {
clusterName: ClusterName;
topicName: TopicName;
isInternal: boolean;
isDeleted: boolean;
isDeletePolicy: boolean;
deleteTopic: (clusterName: ClusterName, topicName: TopicName) => void;
clearTopicMessages(clusterName: ClusterName, topicName: TopicName): void;
}
const Details: React.FC<Props> = ({
const Details: React.FC<DetailsProps> = ({
clusterName,
topicName,
isInternal,
isDeleted,
isDeletePolicy,
deleteTopic,
clearTopicMessages,
}) => {
@ -100,13 +102,15 @@ const Details: React.FC<Props> = ({
{!isReadOnly && !isInternal ? (
<div className="buttons">
<>
<button
type="button"
className="button is-danger"
onClick={clearTopicMessagesHandler}
>
Clear All Messages
</button>
{isDeletePolicy && (
<button
type="button"
className="button is-danger"
onClick={clearTopicMessagesHandler}
>
Clear All Messages
</button>
)}
{isTopicDeletionAllowed && (
<button
className="button is-danger"

View file

@ -4,6 +4,7 @@ import { withRouter, RouteComponentProps } from 'react-router-dom';
import { deleteTopic, clearTopicMessages } from 'redux/actions';
import {
getIsTopicDeleted,
getIsTopicDeletePolicy,
getIsTopicInternal,
} from 'redux/reducers/topics/selectors';
@ -28,6 +29,7 @@ const mapStateToProps = (
topicName,
isInternal: getIsTopicInternal(state, topicName),
isDeleted: getIsTopicDeleted(state),
isDeletePolicy: getIsTopicDeletePolicy(state, topicName),
});
const mapDispatchToProps = {

View file

@ -1,8 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import { StaticRouter } from 'react-router-dom';
import ClusterContext from 'components/contexts/ClusterContext';
import Details from 'components/Topics/Topic/Details/Details';
import ClusterContext, {
ContextProps,
} from 'components/contexts/ClusterContext';
import Details, { DetailsProps } from 'components/Topics/Topic/Details/Details';
import {
internalTopicPayload,
externalTopicPayload,
@ -19,65 +21,75 @@ describe('Details', () => {
const mockInternalTopicPayload = internalTopicPayload.internal;
const mockExternalTopicPayload = externalTopicPayload.internal;
describe('when it has readonly flag', () => {
it('does not render the Action button a Topic', () => {
const component = mount(
<Provider store={store}>
<StaticRouter>
<ClusterContext.Provider
value={{
isReadOnly: true,
hasKafkaConnectConfigured: true,
hasSchemaRegistryConfigured: true,
isTopicDeletionAllowed: true,
}}
>
<Details
clusterName={mockClusterName}
topicName={internalTopicPayload.name}
name={internalTopicPayload.name}
isInternal={mockInternalTopicPayload}
deleteTopic={mockDelete}
clearTopicMessages={mockClearTopicMessages}
isDeleted={false}
/>
</ClusterContext.Provider>
</StaticRouter>
</Provider>
);
const setupWrapper = (
contextProps: Partial<ContextProps> = {},
detailsProps: Partial<DetailsProps> = {}
) => (
<Provider store={store}>
<StaticRouter>
<ClusterContext.Provider
value={{
isReadOnly: true,
hasKafkaConnectConfigured: true,
hasSchemaRegistryConfigured: true,
isTopicDeletionAllowed: true,
...contextProps,
}}
>
<Details
clusterName={mockClusterName}
topicName={internalTopicPayload.name}
name={internalTopicPayload.name}
isInternal={mockInternalTopicPayload}
deleteTopic={mockDelete}
clearTopicMessages={mockClearTopicMessages}
isDeleted={false}
isDeletePolicy
{...detailsProps}
/>
</ClusterContext.Provider>
</StaticRouter>
</Provider>
);
describe('when it has readonly flag', () => {
const component = mount(setupWrapper({ isReadOnly: true }));
it('does not render the Action button a Topic', () => {
expect(component.exists('button')).toBeFalsy();
});
it('matches the snapshot', () => {
expect(component).toMatchSnapshot();
});
});
describe('when it does not have readonly flag', () => {
it('renders the Action button a Topic', () => {
const component = mount(
<Provider store={store}>
<StaticRouter>
<ClusterContext.Provider
value={{
isReadOnly: false,
hasKafkaConnectConfigured: true,
hasSchemaRegistryConfigured: true,
isTopicDeletionAllowed: true,
}}
>
<Details
clusterName={mockClusterName}
topicName={internalTopicPayload.name}
name={internalTopicPayload.name}
isInternal={mockExternalTopicPayload}
deleteTopic={mockDelete}
clearTopicMessages={mockClearTopicMessages}
isDeleted={false}
/>
</ClusterContext.Provider>
</StaticRouter>
</Provider>
);
const component = mount(
setupWrapper(
{ isReadOnly: false },
{ isInternal: mockExternalTopicPayload }
)
);
it('renders the Action buttons on a Topic', () => {
expect(component.exists('button')).toBeTruthy();
expect(component.find('button').length).toEqual(2);
});
it('matches the snapshot', () => {
expect(component).toMatchSnapshot();
});
});
describe('when it shows on compact topic page', () => {
const component = mount(
setupWrapper(
{ isReadOnly: false },
{ isInternal: mockExternalTopicPayload, isDeletePolicy: false }
)
);
it('not render clear message button on a topic', () => {
expect(component.find('button').length).toEqual(1);
});
it('matches the snapshot', () => {
expect(component).toMatchSnapshot();
});
});
});

View file

@ -0,0 +1,741 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Details when it does not have readonly flag matches the snapshot 1`] = `
<Provider
store={
Object {
"@@observable": [Function],
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
}
}
>
<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 {}}
>
<Details
clearTopicMessages={[MockFunction]}
clusterName="local"
deleteTopic={[MockFunction]}
isDeletePolicy={true}
isDeleted={false}
isInternal={false}
name="__internal.topic"
topicName="__internal.topic"
>
<div
className="box"
>
<nav
className="navbar"
role="navigation"
>
<div
className="navbar-start"
>
<NavLink
activeClassName="is-active is-primary"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic"
onClick={[Function]}
>
Overview
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/messages"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/messages",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/messages"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/messages"
onClick={[Function]}
>
Messages
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/consumergroups"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/consumergroups",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/consumergroups"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/consumergroups"
onClick={[Function]}
>
Consumers
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/settings"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/settings",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/settings"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/settings"
onClick={[Function]}
>
Settings
</a>
</LinkAnchor>
</Link>
</NavLink>
</div>
<div
className="navbar-end"
>
<div
className="buttons"
>
<button
className="button is-danger"
onClick={[Function]}
type="button"
>
Clear All Messages
</button>
<button
className="button is-danger"
onClick={[Function]}
type="button"
>
Delete Topic
</button>
<Link
className="button"
to="/ui/clusters/local/topics/__internal.topic/message"
>
<LinkAnchor
className="button"
href="/ui/clusters/local/topics/__internal.topic/message"
navigate={[Function]}
>
<a
className="button"
href="/ui/clusters/local/topics/__internal.topic/message"
onClick={[Function]}
>
Produce message
</a>
</LinkAnchor>
</Link>
<Link
className="button"
to="/ui/clusters/local/topics/__internal.topic/edit"
>
<LinkAnchor
className="button"
href="/ui/clusters/local/topics/__internal.topic/edit"
navigate={[Function]}
>
<a
className="button"
href="/ui/clusters/local/topics/__internal.topic/edit"
onClick={[Function]}
>
Edit settings
</a>
</LinkAnchor>
</Link>
<ConfirmationModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
/>
</div>
</div>
</nav>
<br />
<Switch />
</div>
</Details>
</Router>
</StaticRouter>
</Provider>
`;
exports[`Details when it has readonly flag matches the snapshot 1`] = `
<Provider
store={
Object {
"@@observable": [Function],
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
}
}
>
<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 {}}
>
<Details
clearTopicMessages={[MockFunction]}
clusterName="local"
deleteTopic={[MockFunction]}
isDeletePolicy={true}
isDeleted={false}
isInternal={true}
name="__internal.topic"
topicName="__internal.topic"
>
<div
className="box"
>
<nav
className="navbar"
role="navigation"
>
<div
className="navbar-start"
>
<NavLink
activeClassName="is-active is-primary"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic"
onClick={[Function]}
>
Overview
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/messages"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/messages",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/messages"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/messages"
onClick={[Function]}
>
Messages
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/consumergroups"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/consumergroups",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/consumergroups"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/consumergroups"
onClick={[Function]}
>
Consumers
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/settings"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/settings",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/settings"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/settings"
onClick={[Function]}
>
Settings
</a>
</LinkAnchor>
</Link>
</NavLink>
</div>
<div
className="navbar-end"
/>
</nav>
<br />
<Switch />
</div>
</Details>
</Router>
</StaticRouter>
</Provider>
`;
exports[`Details when it shows on compact topic page matches the snapshot 1`] = `
<Provider
store={
Object {
"@@observable": [Function],
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
}
}
>
<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 {}}
>
<Details
clearTopicMessages={[MockFunction]}
clusterName="local"
deleteTopic={[MockFunction]}
isDeletePolicy={false}
isDeleted={false}
isInternal={false}
name="__internal.topic"
topicName="__internal.topic"
>
<div
className="box"
>
<nav
className="navbar"
role="navigation"
>
<div
className="navbar-start"
>
<NavLink
activeClassName="is-active is-primary"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic"
onClick={[Function]}
>
Overview
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/messages"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/messages",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/messages"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/messages"
onClick={[Function]}
>
Messages
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/consumergroups"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/consumergroups",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/consumergroups"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/consumergroups"
onClick={[Function]}
>
Consumers
</a>
</LinkAnchor>
</Link>
</NavLink>
<NavLink
activeClassName="is-active"
className="navbar-item is-tab"
exact={true}
to="/ui/clusters/local/topics/__internal.topic/settings"
>
<Link
aria-current={null}
className="navbar-item is-tab"
to={
Object {
"hash": "",
"pathname": "/ui/clusters/local/topics/__internal.topic/settings",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/settings"
navigate={[Function]}
>
<a
aria-current={null}
className="navbar-item is-tab"
href="/ui/clusters/local/topics/__internal.topic/settings"
onClick={[Function]}
>
Settings
</a>
</LinkAnchor>
</Link>
</NavLink>
</div>
<div
className="navbar-end"
>
<div
className="buttons"
>
<button
className="button is-danger"
onClick={[Function]}
type="button"
>
Delete Topic
</button>
<Link
className="button"
to="/ui/clusters/local/topics/__internal.topic/message"
>
<LinkAnchor
className="button"
href="/ui/clusters/local/topics/__internal.topic/message"
navigate={[Function]}
>
<a
className="button"
href="/ui/clusters/local/topics/__internal.topic/message"
onClick={[Function]}
>
Produce message
</a>
</LinkAnchor>
</Link>
<Link
className="button"
to="/ui/clusters/local/topics/__internal.topic/edit"
>
<LinkAnchor
className="button"
href="/ui/clusters/local/topics/__internal.topic/edit"
navigate={[Function]}
>
<a
className="button"
href="/ui/clusters/local/topics/__internal.topic/edit"
onClick={[Function]}
>
Edit settings
</a>
</LinkAnchor>
</Link>
<ConfirmationModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
/>
</div>
</div>
</nav>
<br />
<Switch />
</div>
</Details>
</Router>
</StaticRouter>
</Provider>
`;

View file

@ -5,6 +5,7 @@ import {
TopicsState,
TopicConfigByName,
} from 'redux/interfaces';
import { TopicCleanUpPolicyEnum } from 'generated-sources';
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
const topicsState = ({ topics }: RootState): TopicsState => topics;
@ -145,6 +146,13 @@ export const getTopicConfigByParamName = createSelector(
}
);
export const getIsTopicDeletePolicy = createSelector(
getTopicByName,
(topic) => {
return topic?.cleanUpPolicy === TopicCleanUpPolicyEnum.DELETE;
}
);
export const getTopicsSearch = createSelector(
topicsState,
(state) => state.search