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 (
|
||||
<div className="section">
|
||||
<Breadcrumb>All Connectors</Breadcrumb>
|
||||
<div className="box has-background-danger has-text-centered has-text-light">
|
||||
Kafka Connect section is under construction.
|
||||
</div>
|
||||
<article className="message is-warning">
|
||||
<div className="message-body">
|
||||
Kafka Connect section is under construction.
|
||||
</div>
|
||||
</article>
|
||||
<MetricsWrapper>
|
||||
<Indicator
|
||||
className="level-left is-one-third"
|
||||
|
|
|
@ -9,6 +9,7 @@ import { deleteConnector } from 'redux/actions';
|
|||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownDivider from 'components/common/Dropdown/DropdownDivider';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import StatusTag from '../StatusTag';
|
||||
|
||||
export interface ListItemProps {
|
||||
|
@ -30,11 +31,16 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
},
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [
|
||||
isDeleteConnectorConfirmationVisible,
|
||||
setDeleteConnectorConfirmationVisible,
|
||||
] = React.useState(false);
|
||||
|
||||
const handleDelete = React.useCallback(() => {
|
||||
if (clusterName && connect && name) {
|
||||
dispatch(deleteConnector(clusterName, connect, name));
|
||||
}
|
||||
setDeleteConnectorConfirmationVisible(false);
|
||||
}, [clusterName, connect, name]);
|
||||
|
||||
const runningTasks = React.useMemo(() => {
|
||||
|
@ -67,20 +73,31 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
</span>
|
||||
)}
|
||||
</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
|
||||
>
|
||||
<DropdownDivider />
|
||||
<DropdownItem
|
||||
onClick={() => setDeleteConnectorConfirmationVisible(true)}
|
||||
>
|
||||
<span className="has-text-danger">Remove Connector</span>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
isOpen={isDeleteConnectorConfirmationVisible}
|
||||
onCancel={() => setDeleteConnectorConfirmationVisible(false)}
|
||||
onConfirm={handleDelete}
|
||||
>
|
||||
<DropdownDivider />
|
||||
<DropdownItem onClick={handleDelete}>
|
||||
<span className="has-text-danger">Remove Connector</span>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
Are you sure want to remove <b>{name}</b> connector?
|
||||
</ConfirmationModal>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -8,6 +8,11 @@ import ListItem, { ListItemProps } from '../ListItem';
|
|||
|
||||
const store = configureStore();
|
||||
|
||||
jest.mock(
|
||||
'components/common/ConfirmationModal/ConfirmationModal',
|
||||
() => 'mock-ConfirmationModal'
|
||||
);
|
||||
|
||||
describe('Connectors ListItem', () => {
|
||||
const connector = connectorsPayload[0];
|
||||
const setupWrapper = (props: Partial<ListItemProps> = {}) => (
|
||||
|
@ -57,7 +62,12 @@ describe('Connectors ListItem', () => {
|
|||
|
||||
it('handles delete', () => {
|
||||
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', () => {
|
||||
|
|
|
@ -110,77 +110,90 @@ exports[`Connectors ListItem matches snapshot 1`] = `
|
|||
2
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
className="has-text-right"
|
||||
>
|
||||
<Dropdown
|
||||
label={
|
||||
<span
|
||||
className="icon"
|
||||
>
|
||||
<i
|
||||
className="fas fa-cog"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
right={true}
|
||||
<td>
|
||||
<div
|
||||
className="has-text-right"
|
||||
>
|
||||
<div
|
||||
className="dropdown is-right"
|
||||
<Dropdown
|
||||
label={
|
||||
<span
|
||||
className="icon"
|
||||
>
|
||||
<i
|
||||
className="fas fa-cog"
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
right={true}
|
||||
>
|
||||
<div
|
||||
className="dropdown-trigger"
|
||||
>
|
||||
<button
|
||||
aria-controls="dropdown-menu"
|
||||
aria-haspopup="true"
|
||||
className="button is-small"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon"
|
||||
>
|
||||
<i
|
||||
className="fas fa-cog"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
id="dropdown-menu"
|
||||
role="menu"
|
||||
className="dropdown is-right"
|
||||
>
|
||||
<div
|
||||
className="dropdown-content has-text-left"
|
||||
className="dropdown-trigger"
|
||||
>
|
||||
<DropdownDivider>
|
||||
<hr
|
||||
className="dropdown-divider"
|
||||
/>
|
||||
</DropdownDivider>
|
||||
<DropdownItem
|
||||
<button
|
||||
aria-controls="dropdown-menu"
|
||||
aria-haspopup="true"
|
||||
className="button is-small"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<a
|
||||
className="dropdown-item is-link"
|
||||
href="#end"
|
||||
onClick={[Function]}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
<span
|
||||
className="icon"
|
||||
>
|
||||
<span
|
||||
className="has-text-danger"
|
||||
<i
|
||||
className="fas fa-cog"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
id="dropdown-menu"
|
||||
role="menu"
|
||||
>
|
||||
<div
|
||||
className="dropdown-content has-text-left"
|
||||
>
|
||||
<DropdownDivider>
|
||||
<hr
|
||||
className="dropdown-divider"
|
||||
/>
|
||||
</DropdownDivider>
|
||||
<DropdownItem
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="dropdown-item is-link"
|
||||
href="#end"
|
||||
onClick={[Function]}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
Remove Connector
|
||||
</span>
|
||||
</a>
|
||||
</DropdownItem>
|
||||
<span
|
||||
className="has-text-danger"
|
||||
>
|
||||
Remove Connector
|
||||
</span>
|
||||
</a>
|
||||
</DropdownItem>
|
||||
</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>
|
||||
</tr>
|
||||
</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