From 64c5ac3babe36507df271e67c0e8c40d40f631a9 Mon Sep 17 00:00:00 2001 From: Alejandro Visiedo Date: Tue, 23 Apr 2024 22:53:36 +0200 Subject: [PATCH] feat(HMS-2994): add delete confirmation modal This change introduce the delete confirmation modal dialog. This dialog will show up when we click on the kebab menu of the list domains view to delete the item, or when we click on the kebab menu from the detail view of the domain to delete the domain that is being displayed. In both cases, the deletion confirmation modal is showed up, and when we press Delete button the item is eventually deleted from the database. Making click on the modal cross icon or cancel link will dismiss the deletion confirmation modal with no effect on the data stored. Signed-off-by: Alejandro Visiedo --- .../ConfirmDeleteDomain.scss | 1 + .../ConfirmDeleteDomain.test.tsx | 50 ++++++++++++++++++ .../ConfirmDeleteDomain.tsx | 44 ++++++++++++++++ src/Components/DomainList/DomainList.tsx | 20 ++++++-- src/Routes/DetailPage/DetailPage.tsx | 51 ++++++++++++------- 5 files changed, 146 insertions(+), 20 deletions(-) create mode 100644 src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.scss create mode 100644 src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.test.tsx create mode 100644 src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.tsx diff --git a/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.scss b/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.scss new file mode 100644 index 0000000..acd9576 --- /dev/null +++ b/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.scss @@ -0,0 +1 @@ +@import '~@redhat-cloud-services/frontend-components-utilities/styles/variables'; diff --git a/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.test.tsx b/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.test.tsx new file mode 100644 index 0000000..e3b7371 --- /dev/null +++ b/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import ConfirmDeleteDomain from './ConfirmDeleteDomain'; +import '@testing-library/jest-dom'; +import { Domain } from '../../Api'; + +const domain: Domain = { + domain_name: 'mydomain.test', +} as unknown as Domain; + +test('expect empty when isOpen is false', () => { + const root = render(); + expect(root.container).toBeEmptyDOMElement(); +}); + +test('expect modal displayed', () => { + render(); + expect(screen.getByRole('heading')).toHaveTextContent(/^Warning alert:Delete identity domain registration\?$/); + expect(screen.getByRole('button', { name: 'Close' })).toHaveTextContent(/^$/); + expect(screen.getByRole('button', { name: 'Delete' })).toHaveTextContent(/^Delete$/); + expect(screen.getByRole('button', { name: 'Cancel' })).toHaveTextContent(/^Cancel$/); +}); + +test('expect handler onDelete to be called', () => { + // given + const confirmHandler = jest.fn(); + const cancelHandler = jest.fn(); + render(); + + // when the OK button is clicked + screen.getByRole('button', { name: 'Delete' }).click(); + + // then the confirmHandler should be called with the domain as argument and cancelHandler should not + expect(confirmHandler).toBeCalledWith(domain); + expect(cancelHandler).toBeCalledTimes(0); +}); + +test('expect handler onCancel to be called', () => { + // given + const confirmHandler = jest.fn(); + const cancelHandler = jest.fn(); + render(); + + // when the OK button is clicked + screen.getByRole('button', { name: 'Cancel' }).click(); + + // then the confirmHandler should be called with the domain as argument and cancelHandler should not + expect(cancelHandler).toBeCalledTimes(1); + expect(confirmHandler).toBeCalledTimes(0); +}); diff --git a/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.tsx b/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.tsx new file mode 100644 index 0000000..c792177 --- /dev/null +++ b/src/Components/ConfirmDeleteDomain/ConfirmDeleteDomain.tsx @@ -0,0 +1,44 @@ +import { Button, Modal } from '@patternfly/react-core'; +import './ConfirmDeleteDomain.scss'; +import React from 'react'; +import { Domain } from '../../Api/api'; + +interface ConfirmDeleteDomainProps { + domain?: Domain; + isOpen?: boolean; + onDelete?: (domain?: Domain) => void; + onCancel?: () => void; +} + +/** + * Modal dialog to confirm a domain deletion. + * + * @param props the props given by the smart component. + */ +const ConfirmDeleteDomain: React.FC = (props) => { + const onDeleteWrapper = () => { + props.onDelete && props.onDelete(props.domain); + }; + return ( + + Delete + , + , + ]} + > + No new host enrollment from HCC will be allowed on {props.domain?.title || ''} domain after registration deletion. + + ); +}; + +export default ConfirmDeleteDomain; diff --git a/src/Components/DomainList/DomainList.tsx b/src/Components/DomainList/DomainList.tsx index d3c9c6a..335083c 100644 --- a/src/Components/DomainList/DomainList.tsx +++ b/src/Components/DomainList/DomainList.tsx @@ -8,6 +8,7 @@ import { useNavigate } from 'react-router-dom'; import { AppContext, AppContextType } from '../../AppContext'; import { Button } from '@patternfly/react-core'; import AutoJoinChangeConfirmDialog from '../AutoJoinChangeConfirmDialog/AutoJoinChangeConfirmDialog'; +import ConfirmDeleteDomain from '../ConfirmDeleteDomain/ConfirmDeleteDomain'; export interface IColumnType { key: string; @@ -108,7 +109,9 @@ export const DomainList = () => { const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc'>('asc'); const domains = context?.domains || ([] as Domain[]); + const [isOpenAutoJoinChangeConfirm, setIsOpenAutoJoinChangeConfirm] = useState(false); + const [isOpenConfirmDelete, setIsOpenConfirmDelete] = useState(false); const [currentDomain, setCurrentDomain] = useState(); const enabledText = 'Enabled'; @@ -166,8 +169,18 @@ export const DomainList = () => { } }; - const onDelete = (domain: Domain) => { - if (domain.domain_id !== undefined) { + const OnShowConfirmDelete = (domain: Domain) => { + setIsOpenConfirmDelete(true); + setCurrentDomain(domain); + }; + + const onDismissConfirmDelete = () => { + setIsOpenConfirmDelete(false); + }; + + const onDelete = (domain?: Domain) => { + setIsOpenConfirmDelete(false); + if (domain?.domain_id !== undefined) { const domainId = domain.domain_id; resources_api .deleteDomain(domainId) @@ -198,7 +211,7 @@ export const DomainList = () => { }, { title: 'Delete', - onClick: () => onDelete(domain), + onClick: () => OnShowConfirmDelete(domain), ouiaId: 'ButtonActionDelete', }, ]; @@ -269,6 +282,7 @@ export const DomainList = () => { onConfirm={onConfirmAutoJoinChange} onCancel={() => setIsOpenAutoJoinChangeConfirm(false)} /> + ); }; diff --git a/src/Routes/DetailPage/DetailPage.tsx b/src/Routes/DetailPage/DetailPage.tsx index 3eb62e3..ac2aec0 100644 --- a/src/Routes/DetailPage/DetailPage.tsx +++ b/src/Routes/DetailPage/DetailPage.tsx @@ -22,6 +22,7 @@ import { Domain, ResourcesApiFactory } from '../../Api/api'; import { AppContext, AppContextType } from '../../AppContext'; import { DetailGeneral } from './Components/DetailGeneral/DetailGeneral'; import { DetailServers } from './Components/DetailServers/DetailServers'; +import ConfirmDeleteDomain from '../../Components/ConfirmDeleteDomain/ConfirmDeleteDomain'; /** * It represents the detail page to show the information about a @@ -40,6 +41,7 @@ const DetailPage = () => { // States const [domain, setDomain] = useState(appContext?.getDomain(domain_id || '')); + const [isOpenConfirmDelete, setIsOpenConfirmDelete] = useState(false); console.log('INFO:DetailPage render:domain_id=' + domain_id); @@ -82,26 +84,40 @@ const DetailPage = () => { setIsKebabOpen(!isKebabOpen); }; + const OnShowConfirmDelete = () => { + setIsOpenConfirmDelete(true); + }; + + const onDismissConfirmDelete = () => { + setIsOpenConfirmDelete(false); + }; + + const onDelete = (domain?: Domain) => { + if (domain?.domain_id !== undefined) { + const domainId = domain.domain_id; + resources_api + .deleteDomain(domainId) + .then((response) => { + if (response.status == 204) { + appContext?.deleteDomain(domainId); + navigate('/domains', { replace: true }); + } else { + // TODO show-up notification with error message + console.error(`response.status=${response.status}; response.data=${response.data}`); + } + }) + .catch((error) => { + // TODO show-up notification with error message + console.log('error onClose: ' + error); + }); + } + }; + const dropdownItems: JSX.Element[] = [ { - console.log('Deleting domain: ' + value); - if (domain_id !== undefined) { - resources_api - .deleteDomain(domain_id) - .then((res) => { - if (res.status === 204) { - console.info('Domain ' + value + ' was deleted'); - appContext?.deleteDomain(domain_id); - navigate('/domains', { replace: true }); - } - }) - .catch((reason) => { - // TODO Send error notification to chrome - console.log(reason); - }); - } + onClick={() => { + domain !== undefined && OnShowConfirmDelete(); }} ouiaId="ButtonDetailsDelete" > @@ -168,6 +184,7 @@ const DetailPage = () => { + ); };