Add ConfirmationModal common component (#383)
* Add ConfirmationModal common component * Update specs
This commit is contained in:
parent
ab57772329
commit
9d62670eef
6 changed files with 236 additions and 76 deletions
|
@ -37,9 +37,11 @@ const List: React.FC<ListProps> = ({
|
||||||
return (
|
return (
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<Breadcrumb>All Connectors</Breadcrumb>
|
<Breadcrumb>All Connectors</Breadcrumb>
|
||||||
<div className="box has-background-danger has-text-centered has-text-light">
|
<article className="message is-warning">
|
||||||
|
<div className="message-body">
|
||||||
Kafka Connect section is under construction.
|
Kafka Connect section is under construction.
|
||||||
</div>
|
</div>
|
||||||
|
</article>
|
||||||
<MetricsWrapper>
|
<MetricsWrapper>
|
||||||
<Indicator
|
<Indicator
|
||||||
className="level-left is-one-third"
|
className="level-left is-one-third"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { deleteConnector } from 'redux/actions';
|
||||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||||
import DropdownDivider from 'components/common/Dropdown/DropdownDivider';
|
import DropdownDivider from 'components/common/Dropdown/DropdownDivider';
|
||||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||||
|
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||||
import StatusTag from '../StatusTag';
|
import StatusTag from '../StatusTag';
|
||||||
|
|
||||||
export interface ListItemProps {
|
export interface ListItemProps {
|
||||||
|
@ -30,11 +31,16 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const [
|
||||||
|
isDeleteConnectorConfirmationVisible,
|
||||||
|
setDeleteConnectorConfirmationVisible,
|
||||||
|
] = React.useState(false);
|
||||||
|
|
||||||
const handleDelete = React.useCallback(() => {
|
const handleDelete = React.useCallback(() => {
|
||||||
if (clusterName && connect && name) {
|
if (clusterName && connect && name) {
|
||||||
dispatch(deleteConnector(clusterName, connect, name));
|
dispatch(deleteConnector(clusterName, connect, name));
|
||||||
}
|
}
|
||||||
|
setDeleteConnectorConfirmationVisible(false);
|
||||||
}, [clusterName, connect, name]);
|
}, [clusterName, connect, name]);
|
||||||
|
|
||||||
const runningTasks = React.useMemo(() => {
|
const runningTasks = React.useMemo(() => {
|
||||||
|
@ -67,7 +73,8 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="has-text-right">
|
<td>
|
||||||
|
<div className="has-text-right">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label={
|
label={
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
|
@ -77,10 +84,20 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||||
right
|
right
|
||||||
>
|
>
|
||||||
<DropdownDivider />
|
<DropdownDivider />
|
||||||
<DropdownItem onClick={handleDelete}>
|
<DropdownItem
|
||||||
|
onClick={() => setDeleteConnectorConfirmationVisible(true)}
|
||||||
|
>
|
||||||
<span className="has-text-danger">Remove Connector</span>
|
<span className="has-text-danger">Remove Connector</span>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
<ConfirmationModal
|
||||||
|
isOpen={isDeleteConnectorConfirmationVisible}
|
||||||
|
onCancel={() => setDeleteConnectorConfirmationVisible(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
>
|
||||||
|
Are you sure want to remove <b>{name}</b> connector?
|
||||||
|
</ConfirmationModal>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,6 +8,11 @@ import ListItem, { ListItemProps } from '../ListItem';
|
||||||
|
|
||||||
const store = configureStore();
|
const store = configureStore();
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'components/common/ConfirmationModal/ConfirmationModal',
|
||||||
|
() => 'mock-ConfirmationModal'
|
||||||
|
);
|
||||||
|
|
||||||
describe('Connectors ListItem', () => {
|
describe('Connectors ListItem', () => {
|
||||||
const connector = connectorsPayload[0];
|
const connector = connectorsPayload[0];
|
||||||
const setupWrapper = (props: Partial<ListItemProps> = {}) => (
|
const setupWrapper = (props: Partial<ListItemProps> = {}) => (
|
||||||
|
@ -57,7 +62,12 @@ describe('Connectors ListItem', () => {
|
||||||
|
|
||||||
it('handles delete', () => {
|
it('handles delete', () => {
|
||||||
const wrapper = mount(setupWrapper());
|
const wrapper = mount(setupWrapper());
|
||||||
wrapper.find('DropdownItem a').last().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('cancel');
|
||||||
|
expect(wrapper.find('mock-ConfirmationModal').prop('isOpen')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
|
|
|
@ -110,7 +110,8 @@ exports[`Connectors ListItem matches snapshot 1`] = `
|
||||||
2
|
2
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td>
|
||||||
|
<div
|
||||||
className="has-text-right"
|
className="has-text-right"
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
@ -181,6 +182,18 @@ exports[`Connectors ListItem matches snapshot 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
<mock-ConfirmationModal
|
||||||
|
isOpen={false}
|
||||||
|
onCancel={[Function]}
|
||||||
|
onConfirm={[Function]}
|
||||||
|
>
|
||||||
|
Are you sure want to remove
|
||||||
|
<b>
|
||||||
|
hdfs-source-connector
|
||||||
|
</b>
|
||||||
|
connector?
|
||||||
|
</mock-ConfirmationModal>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface ConfirmationModalProps {
|
||||||
|
isOpen?: boolean;
|
||||||
|
title?: React.ReactNode;
|
||||||
|
onConfirm(): void;
|
||||||
|
onCancel(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
}) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal is-active">
|
||||||
|
<div className="modal-background" onClick={onCancel} aria-hidden="true" />
|
||||||
|
<div className="modal-card">
|
||||||
|
<header className="modal-card-head">
|
||||||
|
<p className="modal-card-title">{title || 'Confirm the action'}</p>
|
||||||
|
<button
|
||||||
|
onClick={onCancel}
|
||||||
|
type="button"
|
||||||
|
className="delete"
|
||||||
|
aria-label="close"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<section className="modal-card-body">{children}</section>
|
||||||
|
<footer className="modal-card-foot is-justify-content-flex-end">
|
||||||
|
<button
|
||||||
|
onClick={onConfirm}
|
||||||
|
type="button"
|
||||||
|
className="button is-danger"
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
<button onClick={onCancel} type="button" className="button">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmationModal;
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { mount, ReactWrapper } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import ConfirmationModal, {
|
||||||
|
ConfirmationModalProps,
|
||||||
|
} from '../ConfirmationModal';
|
||||||
|
|
||||||
|
const confirmMock = jest.fn();
|
||||||
|
const cancelMock = jest.fn();
|
||||||
|
const body = 'Please Confirm the action!';
|
||||||
|
describe('ConfiramationModal', () => {
|
||||||
|
const setupWrapper = (props: Partial<ConfirmationModalProps> = {}) => (
|
||||||
|
<ConfirmationModal onCancel={cancelMock} onConfirm={confirmMock} {...props}>
|
||||||
|
{body}
|
||||||
|
</ConfirmationModal>
|
||||||
|
);
|
||||||
|
|
||||||
|
it('renders nothing', () => {
|
||||||
|
const wrapper = mount(setupWrapper({ isOpen: false }));
|
||||||
|
expect(wrapper.exists(ConfirmationModal)).toBeTruthy();
|
||||||
|
expect(wrapper.exists('.modal.is-active')).toBeFalsy();
|
||||||
|
});
|
||||||
|
it('renders modal', () => {
|
||||||
|
const wrapper = mount(setupWrapper({ isOpen: true }));
|
||||||
|
expect(wrapper.exists(ConfirmationModal)).toBeTruthy();
|
||||||
|
expect(wrapper.exists('.modal.is-active')).toBeTruthy();
|
||||||
|
expect(wrapper.find('.modal-card-body').text()).toEqual(body);
|
||||||
|
});
|
||||||
|
it('renders modal with default header', () => {
|
||||||
|
const wrapper = mount(setupWrapper({ isOpen: true }));
|
||||||
|
expect(wrapper.find('.modal-card-title').text()).toEqual(
|
||||||
|
'Confirm the action'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('renders modal with custom header', () => {
|
||||||
|
const title = 'My Custom Header';
|
||||||
|
const wrapper = mount(setupWrapper({ isOpen: true, title }));
|
||||||
|
expect(wrapper.find('.modal-card-title').text()).toEqual(title);
|
||||||
|
});
|
||||||
|
it('handles onConfirm when user clicks confirm button', () => {
|
||||||
|
const wrapper = mount(setupWrapper({ isOpen: true }));
|
||||||
|
expect(wrapper.find('.modal-card-foot button').length).toEqual(2);
|
||||||
|
const cancelBtn = wrapper.find('.modal-card-foot button').at(0);
|
||||||
|
expect(cancelBtn.text()).toEqual('Confirm');
|
||||||
|
cancelBtn.simulate('click');
|
||||||
|
expect(cancelMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(confirmMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cancellation', () => {
|
||||||
|
let wrapper: ReactWrapper;
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = mount(setupWrapper({ isOpen: true }));
|
||||||
|
});
|
||||||
|
it('handles onCancel when user clicks on modal-background', () => {
|
||||||
|
wrapper.find('.modal-background').simulate('click');
|
||||||
|
expect(cancelMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(confirmMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
it('handles onCancel when user clicks on Cancel button', () => {
|
||||||
|
expect(wrapper.find('.modal-card-foot button').length).toEqual(2);
|
||||||
|
const cancelBtn = wrapper.find('.modal-card-foot button').at(1);
|
||||||
|
expect(cancelBtn.text()).toEqual('Cancel');
|
||||||
|
cancelBtn.simulate('click');
|
||||||
|
expect(cancelMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(confirmMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue