Skip to content

Commit 8521b2f

Browse files
feat(web-domains-data): hosts - delete (#20413)
ref: #DCE-25 Signed-off-by: Louis BENSI <[email protected]>
1 parent 7dec08b commit 8521b2f

File tree

13 files changed

+307
-39
lines changed

13 files changed

+307
-39
lines changed

packages/manager/apps/web-domains/public/translations/domain/Messages_fr_FR.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"domain_tab_name_host": "Hosts",
4848
"domain_tab_name_ds_records": "DS Records",
4949
"domain_tab_name_contact_management": "Gestion des contacts",
50+
"domain_tab_name_not_supported": "La fonctionnalité n’est pas supportée pour cette extension.",
5051
"domain_back_to_service_list": "Retour à la liste de services",
5152
"domain_back_to_service_details": "Retour aux informations générales",
5253
"domain_back_to_previous_page": "Retour à la page précédente",
@@ -278,6 +279,10 @@
278279
"domain_tab_hosts_drawer_add_invalid_ips": "Une ou plusieurs adresses IP sont invalides. Veuillez saisir des adresses IPv4 ou IPv6 valides.",
279280
"domain_tab_hosts_drawer_add_duplicate_ips": "Il y a une ou plusieurs IP dupliqué(es) dans la saisie",
280281
"domain_tab_hosts_drawer_add_success_message": "{{ host }} a été ajouté avec succès",
281-
"domain_tab_hosts_drawer_modify_success_message": "{{ host }} est en cours de modification",
282-
"domain_tab_hosts_drawer_add_error_message": "Une erreur a été détectée lors de l'ajout de l'hôte : {{ error }}"
282+
"domain_tab_hosts_drawer_modify_success_message": "{{ host }} a été modifié avec succès",
283+
"domain_tab_hosts_drawer_add_error_message": "Une erreur a été détectée lors de l'ajout de l'hôte : {{ error }}",
284+
"domain_tab_hosts_modal_delete_title": "Supprimer l'hôte {{hostname}}",
285+
"domain_tab_hosts_modal_delete_information_message": "Êtes-vous sur de voulour supprimer cet hôte ?",
286+
"domain_tab_hosts_modal_delete_success_message": "L'hôte est en cours de suppression",
287+
"domain_tab_hosts_modal_delete_error_message": "Une erreur a été détectée lors de la suppression de l'hôte"
283288
}

packages/manager/apps/web-domains/src/domain/components/DatagridColumns/Domain/DatagridColumnActions.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
22
import { ActionMenu, ActionMenuItem } from '@ovh-ux/manager-react-components';
3-
import {
4-
useNavigation,
5-
useNavigationGetUrl,
6-
} from '@ovh-ux/manager-react-shell-client';
3+
import { useNavigation } from '@ovh-ux/manager-react-shell-client';
74
import { useMemo } from 'react';
85
import { useTranslation } from 'react-i18next';
96
import { ODS_BUTTON_COLOR } from '@ovhcloud/ods-components';
@@ -15,6 +12,9 @@ import {
1512
} from '@/common/enum/common.enum';
1613
import { DomainServiceStateEnum } from '@/domain/types/domainResource';
1714
import { DOMAIN } from '@/common/constants';
15+
import { urls } from '@/domain/routes/routes.constant';
16+
import { useGenerateUrl } from '@/common/hooks/generateUrl/useGenerateUrl';
17+
import { useNavigate } from 'react-router-dom';
1818

1919
interface DatagridColumnActionsProps {
2020
readonly serviceName: string;
@@ -32,7 +32,11 @@ export default function DatagridColumnActions({
3232
NAMESPACES.ACTIONS,
3333
NAMESPACES.CONTACT,
3434
]);
35+
const domainDetailUrl = useGenerateUrl(urls.domainDetail, 'path', {
36+
serviceName,
37+
});
3538

39+
const navigate = useNavigate();
3640
const { navigateTo } = useNavigation();
3741

3842
const { serviceInfo, isServiceInfoLoading } = useGetServiceInformation(
@@ -46,7 +50,7 @@ export default function DatagridColumnActions({
4650
{
4751
id: 1,
4852
label: t(`${NAMESPACES.ACTIONS}:see_details`),
49-
href: `/domain/${serviceName}/information`,
53+
onClick: () => navigate(domainDetailUrl),
5054
},
5155
];
5256

packages/manager/apps/web-domains/src/domain/components/Host/HostDrawer.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,21 @@ export default function HostDrawer({
5555
const formData = useForm({
5656
mode: 'onChange',
5757
values: {
58-
host:
59-
drawer.action === DrawerActionEnum.Modify
60-
? hostData.host.split('.')[0]
61-
: '',
62-
ips:
63-
drawer.action === DrawerActionEnum.Modify ? String(hostData.ips) : '',
58+
host: isAddAction ? '' : hostData.host.split('.')[0],
59+
ips: isAddAction ? '' : String(hostData.ips),
6460
},
6561
});
6662

67-
const { handleSubmit, formState, clearErrors } = formData;
63+
const { handleSubmit, formState, clearErrors, reset } = formData;
6864

6965
const onDismiss = () => {
66+
// We added the if here to allow the input to be empty if you re-open the drawer on add mode.
67+
if (isAddAction) {
68+
reset({
69+
host: '',
70+
ips: '',
71+
});
72+
}
7073
setDrawer({
7174
isOpen: false,
7275
action: null,

packages/manager/apps/web-domains/src/domain/components/ServiceDetail/ServiceDetailTabs.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import React, { useEffect, useState } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useLocation, useNavigate } from 'react-router-dom';
44
import {
5+
Icon,
6+
ICON_NAME,
57
Tab,
68
TabContent,
79
TabList,
810
Tabs,
911
TabsValueChangeEvent,
12+
Tooltip,
13+
TooltipContent,
14+
TooltipTrigger,
1015
} from '@ovhcloud/ods-react';
1116
import { useNotifications } from '@ovh-ux/manager-react-components';
1217
import { ServiceDetailTabsProps } from '@/domain/constants/serviceDetail';
@@ -44,13 +49,44 @@ export default function ServiceDetailsTabs({
4449
}
4550
}, [location.pathname]);
4651

52+
const isDisabled = (value: string, name: string, bool: boolean) => {
53+
if (value.includes(name) && bool) {
54+
return true;
55+
}
56+
return false;
57+
};
58+
4759
return (
4860
<Tabs defaultValue={value} onValueChange={handleValueChange} value={value}>
4961
<TabList>
5062
{ServiceDetailTabsProps.map((tab) => {
5163
return (
52-
<Tab key={tab.id} value={tab.value} data-testid={tab.id}>
64+
<Tab
65+
key={tab.id}
66+
value={tab.value}
67+
data-testid={tab.id}
68+
disabled={isDisabled(
69+
tab.value,
70+
'hosts',
71+
!domainResource.currentState.hostsConfiguration.hostSupported,
72+
)}
73+
className="flex items-center gap-x-4"
74+
>
5375
{t(tab.name)}
76+
{isDisabled(
77+
tab.value,
78+
'hosts',
79+
!domainResource.currentState.hostsConfiguration.hostSupported,
80+
) && (
81+
<Tooltip>
82+
<TooltipTrigger asChild>
83+
<Icon name={ICON_NAME.circleInfo} />
84+
</TooltipTrigger>
85+
<TooltipContent>
86+
{t('domain_tab_name_not_supported')}
87+
</TooltipContent>
88+
</Tooltip>
89+
)}
5490
</Tab>
5591
);
5692
})}

packages/manager/apps/web-domains/src/domain/hooks/domainTabs/useHostsDatagridColumns.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ActionMenu, DataGridTextCell } from '@ovh-ux/manager-react-components';
33
import { ODS_BUTTON_COLOR, ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components';
44
import { useTranslation } from 'react-i18next';
55
import { Dispatch, SetStateAction } from 'react';
6+
import { useNavigate } from 'react-router-dom';
67
import { THost } from '@/domain/types/host';
78
import DatagridColumnStatus from '@/domain/components/DatagridColumns/DatagridColumnStatus';
89
import { StatusEnum } from '@/domain/enum/Status.enum';
@@ -24,6 +25,7 @@ export const useHostsDatagridColumns = ({
2425
NAMESPACES.ACTIONS,
2526
NAMESPACES.STATUS,
2627
]);
28+
const navigate = useNavigate();
2729

2830
const columns = [
2931
{
@@ -66,6 +68,7 @@ export const useHostsDatagridColumns = ({
6668
label: t(`${NAMESPACES.ACTIONS}:delete`),
6769
color: ODS_BUTTON_COLOR.critical,
6870
isDisabled: props.status === StatusEnum.DELETING,
71+
onClick: () => navigate(`${props.host}/delete`),
6972
},
7073
]}
7174
id={props.host}

packages/manager/apps/web-domains/src/domain/pages/domainTabs/dns/dnsModify.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ import {
3232
} from '@/domain/hooks/data/query';
3333
import DnsConfigurationRadio from '@/domain/components/ModifyNameServer/DnsConfigurationRadio';
3434
import { TNameServer } from '@/domain/types/domainResource';
35+
import { NAMESPACES } from '@ovh-ux/manager-common-translations';
3536

3637
export default function DnsModifyPage() {
37-
const { t, i18n } = useTranslation('domain');
38+
const { t, i18n } = useTranslation(['domain', NAMESPACES.ONBOARDING]);
3839
const { serviceName } = useParams<{ serviceName: string }>();
3940
const navigate = useNavigate();
4041
const langCode = getLanguageKey(i18n.language);
@@ -108,7 +109,7 @@ export default function DnsModifyPage() {
108109
className="text-[--ods-color-primary-500]"
109110
target="_blank"
110111
>
111-
{t('domain_tab_DNS_modification_warning_message_guides')}
112+
{t(`${NAMESPACES.ONBOARDING}:find_out_more`)}
112113
<Icon name={ICON_NAME.externalLink} />
113114
</Link>
114115
</MessageBody>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import '@/common/setupTests';
2+
import { describe, it, expect, vi, beforeEach } from 'vitest';
3+
import { render, screen, fireEvent } from '@testing-library/react';
4+
import HostDelete from '@/domain/pages/domainTabs/hosts/hostDelete';
5+
import { wrapper } from '@/common/utils/test.provider';
6+
import { urls } from '@/domain/routes/routes.constant';
7+
8+
const updateDomain = vi.fn();
9+
const navigate = vi.fn();
10+
const addSuccess = vi.fn();
11+
const addError = vi.fn();
12+
13+
vi.mock('@ovh-ux/manager-react-components', async () => {
14+
const actual = await vi.importActual('@ovh-ux/manager-react-components');
15+
16+
type ModalProps = {
17+
isOpen: boolean;
18+
heading: string;
19+
primaryLabel: string;
20+
secondaryLabel: string;
21+
onPrimaryButtonClick: () => void;
22+
onSecondaryButtonClick: () => void;
23+
children?: React.ReactNode;
24+
};
25+
26+
return {
27+
...actual,
28+
Modal: ({
29+
isOpen,
30+
heading,
31+
primaryLabel,
32+
secondaryLabel,
33+
onPrimaryButtonClick,
34+
onSecondaryButtonClick,
35+
children,
36+
}: ModalProps) =>
37+
isOpen ? (
38+
<div data-testid="modal">
39+
<h1>{heading}</h1>
40+
<button onClick={onSecondaryButtonClick}>{secondaryLabel}</button>
41+
<button onClick={onPrimaryButtonClick}>{primaryLabel}</button>
42+
{children}
43+
</div>
44+
) : null,
45+
useNotifications: () => ({
46+
addSuccess,
47+
addError,
48+
}),
49+
};
50+
});
51+
52+
vi.mock('react-router-dom', () => ({
53+
useNavigate: () => navigate,
54+
useParams: () => ({
55+
serviceName: 'foobar',
56+
hostname: 'ns1.foobar',
57+
}),
58+
}));
59+
60+
vi.mock('@/domain/hooks/data/query', () => ({
61+
useGetDomainResource: vi.fn(() => ({
62+
domainResource: {
63+
checksum: 'xyz',
64+
targetSpec: {
65+
dnsConfiguration: {
66+
nameServers: ['ns1.example.com', 'ns2.example.com'],
67+
},
68+
hostsConfiguration: {
69+
hosts: [
70+
{ host: 'ns1.foobar', ips: ['1.1.1.1'] },
71+
{ host: 'ns2.foobar', ips: ['2.2.2.2'] },
72+
],
73+
},
74+
},
75+
isFetchingDomainResource: false,
76+
},
77+
})),
78+
useUpdateDomainResource: vi.fn(() => ({
79+
updateDomain,
80+
isUpdateDomainPending: false,
81+
})),
82+
}));
83+
84+
vi.mock('@/common/hooks/generateUrl/useGenerateUrl', () => ({
85+
useGenerateUrl: () => '/domain/foobar/hosts',
86+
}));
87+
88+
describe('HostDelete', () => {
89+
beforeEach(() => {
90+
vi.clearAllMocks();
91+
});
92+
93+
it('renders modal with correct heading', () => {
94+
render(<HostDelete />, { wrapper });
95+
expect(
96+
screen.getByText('domain_tab_hosts_modal_delete_title'),
97+
).toBeInTheDocument();
98+
});
99+
100+
it('calls updateDomain with filtered hosts on delete click', () => {
101+
render(<HostDelete />, { wrapper });
102+
fireEvent.click(screen.getByRole('button', { name: /actions:delete/i }));
103+
104+
expect(updateDomain).toHaveBeenCalledTimes(1);
105+
const call = updateDomain.mock.calls[0][0];
106+
expect(call.hosts).toEqual([{ host: 'ns2.foobar', ips: ['2.2.2.2'] }]);
107+
expect(call.checksum).toBe('xyz');
108+
});
109+
110+
it('calls addSuccess + navigate on delete success', () => {
111+
render(<HostDelete />, { wrapper });
112+
fireEvent.click(screen.getByRole('button', { name: /actions:delete/i }));
113+
114+
const { onSuccess, onSettled } = updateDomain.mock.calls[0][1];
115+
onSuccess();
116+
onSettled();
117+
118+
expect(addSuccess).toHaveBeenCalledTimes(1);
119+
expect(navigate).toHaveBeenCalledWith('/domain/foobar/hosts');
120+
});
121+
122+
it('calls addError on delete error', () => {
123+
render(<HostDelete />, { wrapper });
124+
fireEvent.click(screen.getByRole('button', { name: /actions:delete/i }));
125+
126+
const { onError, onSettled } = updateDomain.mock.calls[0][1];
127+
onError();
128+
onSettled();
129+
130+
expect(addError).toHaveBeenCalledTimes(1);
131+
expect(navigate).toHaveBeenCalledWith('/domain/foobar/hosts');
132+
});
133+
134+
it('navigates back on cancel click', () => {
135+
render(<HostDelete />, { wrapper });
136+
fireEvent.click(screen.getByRole('button', { name: /actions:cancel/i }));
137+
expect(navigate).toHaveBeenCalledWith('/domain/foobar/hosts');
138+
});
139+
});

0 commit comments

Comments
 (0)