[issues-357] - User should not be able to update/delete (#392)
* [issues-357] - User should not be able to update/delete internal topics. Add confirmation for all delete/reset actions * [issues-357] - User should not be able to update/delete internal topics. Add confirmation for all delete/reset actions * [issues-357] - User should not be able to update/delete internal topics. Add confirmation for all delete/reset actions * [issues-357] - User should not be able to update/delete internal topics. Add confirmation for all delete/reset actions * [issues-357] - User should not be able to update/delete internal topics. Add confirmation for all delete/reset actions * [issues-357] - User should not be able to update/delete Co-authored-by: mbovtryuk <mbovtryuk@provectus.com>
This commit is contained in:
parent
c8829d6fcf
commit
c3ff5a2c6b
8 changed files with 180 additions and 49 deletions
|
@ -64,7 +64,9 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
{internal ? 'Internal' : 'External'}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<td className="topic-action-block">
|
||||
{!internal ? (
|
||||
<>
|
||||
<div className="has-text-right">
|
||||
<Dropdown
|
||||
label={
|
||||
|
@ -91,6 +93,8 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
>
|
||||
Are you sure want to remove <b>{name}</b> topic?
|
||||
</ConfirmationModal>
|
||||
</>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -28,28 +28,31 @@ describe('ListItem', () => {
|
|||
);
|
||||
|
||||
it('triggers the deleting messages when clicked on the delete messages button', () => {
|
||||
const component = shallow(setupComponent());
|
||||
const component = shallow(setupComponent({ topic: externalTopicPayload }));
|
||||
expect(component.exists('.topic-action-block')).toBeTruthy();
|
||||
component.find('DropdownItem').at(0).simulate('click');
|
||||
expect(mockDeleteMessages).toBeCalledTimes(1);
|
||||
expect(mockDeleteMessages).toBeCalledWith(
|
||||
clusterName,
|
||||
internalTopicPayload.name
|
||||
externalTopicPayload.name
|
||||
);
|
||||
});
|
||||
|
||||
it('triggers the deleteTopic when clicked on the delete button', () => {
|
||||
const wrapper = shallow(setupComponent());
|
||||
const wrapper = shallow(setupComponent({ topic: externalTopicPayload }));
|
||||
expect(wrapper.exists('.topic-action-block')).toBeTruthy();
|
||||
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
|
||||
wrapper.find('DropdownItem').at(1).simulate('click');
|
||||
const modal = wrapper.find('mock-ConfirmationModal');
|
||||
expect(modal.prop('isOpen')).toBeTruthy();
|
||||
modal.simulate('confirm');
|
||||
expect(mockDelete).toBeCalledTimes(1);
|
||||
expect(mockDelete).toBeCalledWith(clusterName, internalTopicPayload.name);
|
||||
expect(mockDelete).toBeCalledWith(clusterName, externalTopicPayload.name);
|
||||
});
|
||||
|
||||
it('closes ConfirmationModal when clicked on the cancel button', () => {
|
||||
const wrapper = shallow(setupComponent());
|
||||
const wrapper = shallow(setupComponent({ topic: externalTopicPayload }));
|
||||
expect(wrapper.exists('.topic-action-block')).toBeTruthy();
|
||||
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
|
||||
wrapper.find('DropdownItem').last().simulate('click');
|
||||
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeTruthy();
|
||||
|
|
|
@ -19,6 +19,7 @@ import SettingsContainer from './Settings/SettingsContainer';
|
|||
interface Props extends Topic, TopicDetails {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
isInternal: boolean;
|
||||
deleteTopic: (clusterName: ClusterName, topicName: TopicName) => void;
|
||||
clearTopicMessages(clusterName: ClusterName, topicName: TopicName): void;
|
||||
}
|
||||
|
@ -26,6 +27,7 @@ interface Props extends Topic, TopicDetails {
|
|||
const Details: React.FC<Props> = ({
|
||||
clusterName,
|
||||
topicName,
|
||||
isInternal,
|
||||
deleteTopic,
|
||||
clearTopicMessages,
|
||||
}) => {
|
||||
|
@ -72,8 +74,8 @@ const Details: React.FC<Props> = ({
|
|||
</NavLink>
|
||||
</div>
|
||||
<div className="navbar-end">
|
||||
{!isReadOnly && !isInternal ? (
|
||||
<div className="buttons">
|
||||
{!isReadOnly && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -105,8 +107,8 @@ const Details: React.FC<Props> = ({
|
|||
Are you sure want to remove <b>{topicName}</b> topic?
|
||||
</ConfirmationModal>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</nav>
|
||||
<br />
|
||||
|
|
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
|||
import { ClusterName, RootState, TopicName } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { deleteTopic, clearTopicMessages } from 'redux/actions';
|
||||
import { getIsTopicInternal } from 'redux/reducers/topics/selectors';
|
||||
|
||||
import Details from './Details';
|
||||
|
||||
|
@ -22,6 +23,7 @@ const mapStateToProps = (
|
|||
) => ({
|
||||
clusterName,
|
||||
topicName,
|
||||
isInternal: getIsTopicInternal(state, topicName),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
|
|
@ -75,6 +75,7 @@ const Overview: React.FC<Props> = ({
|
|||
<td>{offsetMin}</td>
|
||||
<td>{offsetMax}</td>
|
||||
<td className="has-text-right">
|
||||
{!internal ? (
|
||||
<Dropdown
|
||||
label={
|
||||
<span className="icon">
|
||||
|
@ -91,6 +92,7 @@ const Overview: React.FC<Props> = ({
|
|||
<span className="has-text-danger">Clear Messages</span>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import Overview from 'components/Topics/Topic/Details/Overview/Overview';
|
||||
|
||||
describe('Overview', () => {
|
||||
const mockInternal = false;
|
||||
const mockClusterName = 'local';
|
||||
const mockTopicName = 'topic';
|
||||
const mockClearTopicMessages = jest.fn();
|
||||
const mockPartitions = [
|
||||
{
|
||||
partition: 1,
|
||||
leader: 1,
|
||||
replicas: [
|
||||
{
|
||||
broker: 1,
|
||||
leader: false,
|
||||
inSync: true,
|
||||
},
|
||||
],
|
||||
offsetMax: 0,
|
||||
offsetMin: 0,
|
||||
},
|
||||
];
|
||||
|
||||
describe('when it has internal flag', () => {
|
||||
it('does not render the Action button a Topic', () => {
|
||||
const component = shallow(
|
||||
<Overview
|
||||
name={mockTopicName}
|
||||
partitions={mockPartitions}
|
||||
internal={mockInternal}
|
||||
clusterName={mockClusterName}
|
||||
topicName={mockTopicName}
|
||||
clearTopicMessages={mockClearTopicMessages}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component.exists('Dropdown')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
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 {
|
||||
internalTopicPayload,
|
||||
externalTopicPayload,
|
||||
} from 'redux/reducers/topics/__test__/fixtures';
|
||||
|
||||
describe('Details', () => {
|
||||
const mockDelete = jest.fn();
|
||||
const mockClusterName = 'local';
|
||||
const mockClearTopicMessages = jest.fn();
|
||||
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(
|
||||
<StaticRouter>
|
||||
<ClusterContext.Provider
|
||||
value={{
|
||||
isReadOnly: true,
|
||||
hasKafkaConnectConfigured: true,
|
||||
hasSchemaRegistryConfigured: true,
|
||||
}}
|
||||
>
|
||||
<Details
|
||||
clusterName={mockClusterName}
|
||||
topicName={internalTopicPayload.name}
|
||||
name={internalTopicPayload.name}
|
||||
isInternal={mockInternalTopicPayload}
|
||||
deleteTopic={mockDelete}
|
||||
clearTopicMessages={mockClearTopicMessages}
|
||||
/>
|
||||
</ClusterContext.Provider>
|
||||
</StaticRouter>
|
||||
);
|
||||
|
||||
expect(component.exists('button')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when it does not have readonly flag', () => {
|
||||
it('renders the Action button a Topic', () => {
|
||||
const component = mount(
|
||||
<StaticRouter>
|
||||
<ClusterContext.Provider
|
||||
value={{
|
||||
isReadOnly: false,
|
||||
hasKafkaConnectConfigured: true,
|
||||
hasSchemaRegistryConfigured: true,
|
||||
}}
|
||||
>
|
||||
<Details
|
||||
clusterName={mockClusterName}
|
||||
topicName={internalTopicPayload.name}
|
||||
name={internalTopicPayload.name}
|
||||
isInternal={mockExternalTopicPayload}
|
||||
deleteTopic={mockDelete}
|
||||
clearTopicMessages={mockClearTopicMessages}
|
||||
/>
|
||||
</ClusterContext.Provider>
|
||||
</StaticRouter>
|
||||
);
|
||||
|
||||
expect(component.exists('button')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -121,3 +121,8 @@ export const getTopicConfigByParamName = createSelector(
|
|||
return byParamName;
|
||||
}
|
||||
);
|
||||
|
||||
export const getIsTopicInternal = createSelector(
|
||||
getTopicByName,
|
||||
({ internal }) => !!internal
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue