Skip to content

Commit

Permalink
Confirmation modal for topic & schema delete actions (provectus#384)
Browse files Browse the repository at this point in the history
  • Loading branch information
workshur committed Apr 21, 2021
1 parent 45fdabd commit 3bc1ff8
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 52 deletions.
27 changes: 20 additions & 7 deletions kafka-ui-react-app/src/components/Schemas/Details/Details.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import { useHistory } from 'react-router';
import { SchemaSubject } from 'generated-sources';
import { ClusterName, SchemaName } from 'redux/interfaces';
import { clusterSchemasPath } from 'lib/paths';
import ClusterContext from 'components/contexts/ClusterContext';
import { useHistory } from 'react-router';
import Breadcrumb from '../../common/Breadcrumb/Breadcrumb';
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
import PageLoader from 'components/common/PageLoader/PageLoader';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import SchemaVersion from './SchemaVersion';
import LatestVersionItem from './LatestVersionItem';
import PageLoader from '../../common/PageLoader/PageLoader';

export interface DetailsProps {
subject: SchemaName;
Expand All @@ -32,15 +33,20 @@ const Details: React.FC<DetailsProps> = ({
isFetched,
}) => {
const { isReadOnly } = React.useContext(ClusterContext);
const [
isDeleteSchemaConfirmationVisible,
setDeleteSchemaConfirmationVisible,
] = React.useState(false);

React.useEffect(() => {
fetchSchemaVersions(clusterName, subject);
}, [fetchSchemaVersions, clusterName]);

const history = useHistory();
const onDelete = async () => {
await deleteSchema(clusterName, subject);
const onDelete = React.useCallback(() => {
deleteSchema(clusterName, subject);
history.push(clusterSchemasPath(clusterName));
};
}, [deleteSchema, clusterName, subject]);

return (
<div className="section">
Expand Down Expand Up @@ -84,10 +90,17 @@ const Details: React.FC<DetailsProps> = ({
className="button is-danger is-small level-item"
type="button"
title="in development"
onClick={onDelete}
onClick={() => setDeleteSchemaConfirmationVisible(true)}
>
Remove
</button>
<ConfirmationModal
isOpen={isDeleteSchemaConfirmationVisible}
onCancel={() => setDeleteSchemaConfirmationVisible(false)}
onConfirm={onDelete}
>
Are you sure want to remove <b>{subject}</b> schema?
</ConfirmationModal>
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import { shallow, mount } from 'enzyme';
import { shallow, mount, ReactWrapper } from 'enzyme';
import configureStore from 'redux/store/configureStore';
import { StaticRouter } from 'react-router';
import ClusterContext from 'components/contexts/ClusterContext';
Expand All @@ -11,6 +11,11 @@ import { schema, versions } from './fixtures';
const clusterName = 'testCluster';
const fetchSchemaVersionsMock = jest.fn();

jest.mock(
'components/common/ConfirmationModal/ConfirmationModal',
() => 'mock-ConfirmationModal'
);

describe('Details', () => {
describe('Container', () => {
const store = configureStore();
Expand Down Expand Up @@ -92,29 +97,53 @@ describe('Details', () => {
});

describe('when schema has versions', () => {
const wrapper = shallow(setupWrapper({ versions }));

it('renders table heading with SchemaVersion', () => {
const wrapper = shallow(setupWrapper({ versions }));
expect(wrapper.exists('LatestVersionItem')).toBeTruthy();
expect(wrapper.exists('button')).toBeTruthy();
expect(wrapper.exists('thead')).toBeTruthy();
expect(wrapper.find('SchemaVersion').length).toEqual(2);
});

it('calls deleteSchema on button click', () => {
const mockDelete = jest.fn();
const component = mount(
<StaticRouter>
{setupWrapper({ versions, deleteSchema: mockDelete })}
</StaticRouter>
);
component.find('button').at(1).simulate('click');
expect(mockDelete).toHaveBeenCalledTimes(1);
});

it('matches snapshot', () => {
expect(shallow(setupWrapper({ versions }))).toMatchSnapshot();
});

describe('confirmation', () => {
let wrapper: ReactWrapper;
let confirmationModal: ReactWrapper;
const mockDelete = jest.fn();

const findConfirmationModal = () =>
wrapper.find('mock-ConfirmationModal');

beforeEach(() => {
wrapper = mount(
<StaticRouter>
{setupWrapper({ versions, deleteSchema: mockDelete })}
</StaticRouter>
);
confirmationModal = findConfirmationModal();
});

it('calls deleteSchema after confirmation', () => {
expect(confirmationModal.prop('isOpen')).toBeFalsy();
wrapper.find('button').at(1).simulate('click');
expect(findConfirmationModal().prop('isOpen')).toBeTruthy();
// @ts-expect-error lack of typing of enzyme#invoke
confirmationModal.invoke('onConfirm')();
expect(mockDelete).toHaveBeenCalledTimes(1);
});

it('calls deleteSchema after confirmation', () => {
expect(confirmationModal.prop('isOpen')).toBeFalsy();
wrapper.find('button').at(1).simulate('click');
expect(findConfirmationModal().prop('isOpen')).toBeTruthy();
// @ts-expect-error lack of typing of enzyme#invoke
wrapper.find('mock-ConfirmationModal').invoke('onCancel')();
expect(findConfirmationModal().prop('isOpen')).toBeFalsy();
});
});
});

describe('when the readonly flag is set', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ exports[`Details View Initial state matches snapshot 1`] = `
>
Remove
</button>
<mock-ConfirmationModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
>
Are you sure want to remove
<b>
test
</b>
schema?
</mock-ConfirmationModal>
</div>
</div>
<LatestVersionItem
Expand Down Expand Up @@ -202,6 +213,17 @@ exports[`Details View when page with schema versions loaded when schema has vers
>
Remove
</button>
<mock-ConfirmationModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
>
Are you sure want to remove
<b>
test
</b>
schema?
</mock-ConfirmationModal>
</div>
</div>
<LatestVersionItem
Expand Down Expand Up @@ -340,6 +362,17 @@ exports[`Details View when page with schema versions loaded when versions are em
>
Remove
</button>
<mock-ConfirmationModal
isOpen={false}
onCancel={[Function]}
onConfirm={[Function]}
>
Are you sure want to remove
<b>
test
</b>
schema?
</mock-ConfirmationModal>
</div>
</div>
<LatestVersionItem
Expand Down
41 changes: 29 additions & 12 deletions kafka-ui-react-app/src/components/Topics/List/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from 'redux/interfaces';
import DropdownItem from 'components/common/Dropdown/DropdownItem';
import Dropdown from 'components/common/Dropdown/Dropdown';
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';

export interface ListItemProps {
topic: TopicWithDetailedInfo;
Expand All @@ -20,6 +21,11 @@ const ListItem: React.FC<ListItemProps> = ({
deleteTopic,
clusterName,
}) => {
const [
isDeleteTopicConfirmationVisible,
setDeleteTopicConfirmationVisible,
] = React.useState(false);

const outOfSyncReplicas = React.useMemo(() => {
if (partitions === undefined || partitions.length === 0) {
return 0;
Expand Down Expand Up @@ -54,19 +60,30 @@ const ListItem: React.FC<ListItemProps> = ({
{internal ? 'Internal' : 'External'}
</div>
</td>
<td className="has-text-right">
<Dropdown
label={
<span className="icon">
<i className="fas fa-cog" />
</span>
}
right
<td>
<div className="has-text-right">
<Dropdown
label={
<span className="icon">
<i className="fas fa-cog" />
</span>
}
right
>
<DropdownItem
onClick={() => setDeleteTopicConfirmationVisible(true)}
>
<span className="has-text-danger">Remove Topic</span>
</DropdownItem>
</Dropdown>
</div>
<ConfirmationModal
isOpen={isDeleteTopicConfirmationVisible}
onCancel={() => setDeleteTopicConfirmationVisible(false)}
onConfirm={deleteTopicHandler}
>
<DropdownItem onClick={deleteTopicHandler}>
<span className="has-text-danger">Remove Topic</span>
</DropdownItem>
</Dropdown>
Are you sure want to remove <b>{name}</b> topic?
</ConfirmationModal>
</td>
</tr>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import ListItem, { ListItemProps } from '../ListItem';
const mockDelete = jest.fn();
const clusterName = 'local';

jest.mock(
'components/common/ConfirmationModal/ConfirmationModal',
() => 'mock-ConfirmationModal'
);

describe('ListItem', () => {
const setupComponent = (props: Partial<ListItemProps> = {}) => (
<ListItem
Expand All @@ -22,11 +27,25 @@ describe('ListItem', () => {

it('triggers the deleteTopic when clicked on the delete button', () => {
const wrapper = shallow(setupComponent());
wrapper.find('DropdownItem').simulate('click');
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
wrapper.find('DropdownItem').last().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);
});

it('closes ConfirmationModal when clicked on the cancel button', () => {
const wrapper = shallow(setupComponent());
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
wrapper.find('DropdownItem').last().simulate('click');
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeTruthy();
wrapper.find('mock-ConfirmationModal').simulate('cancel');
expect(mockDelete).toBeCalledTimes(0);
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
});

it('renders correct tags for internal topic', () => {
const wrapper = mount(
<StaticRouter>
Expand All @@ -50,4 +69,20 @@ describe('ListItem', () => {

expect(wrapper.find('.tag.is-primary').text()).toEqual('External');
});

it('renders correct out of sync replicas number', () => {
const wrapper = mount(
<StaticRouter>
<table>
<tbody>
{setupComponent({
topic: { ...externalTopicPayload, partitions: undefined },
})}
</tbody>
</table>
</StaticRouter>
);

expect(wrapper.find('td').at(2).text()).toEqual('0');
});
});
Loading

0 comments on commit 3bc1ff8

Please sign in to comment.