✨(frontend) display button to re-run fetch domain from dimail
Add the button in the modal which describes actions required to make the domain work
This commit is contained in:
@@ -10,6 +10,7 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(domains) allow user to re-run all fetch domain data from dimail
|
||||
- ✨(domains) display DNS config expected for domain with required actions
|
||||
- ✨(domains) check status after creation
|
||||
- ✨(domains) display required actions to do on domain
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { VariantType } from '@openfun/cunningham-react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
|
||||
import { MailDomain } from '@/features/mail-domains/domains';
|
||||
@@ -6,14 +7,6 @@ import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
import { MailDomainView } from '../components/MailDomainView';
|
||||
|
||||
const toast = jest.fn();
|
||||
jest.mock('@openfun/cunningham-react', () => ({
|
||||
...jest.requireActual('@openfun/cunningham-react'),
|
||||
useToastProvider: () => ({
|
||||
toast,
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockMailDomain: MailDomain = {
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
name: 'example.com',
|
||||
@@ -56,7 +49,17 @@ const mockMailDomain: MailDomain = {
|
||||
],
|
||||
};
|
||||
|
||||
const toast = jest.fn();
|
||||
jest.mock('@openfun/cunningham-react', () => ({
|
||||
...jest.requireActual('@openfun/cunningham-react'),
|
||||
useToastProvider: () => ({
|
||||
toast,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('<MailDomainView />', () => {
|
||||
const apiUrl = `end:/mail-domains/${mockMailDomain.slug}/fetch/`;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -64,6 +67,7 @@ describe('<MailDomainView />', () => {
|
||||
afterEach(() => {
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('display action required button and open modal with information when domain status is action_required', () => {
|
||||
render(<MailDomainView mailDomain={mockMailDomain} />, {
|
||||
wrapper: AppWrapper,
|
||||
@@ -79,7 +83,7 @@ describe('<MailDomainView />', () => {
|
||||
expect(screen.getByText('Required actions on domain')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Je veux que le MX du domaine soit mx.ox.numerique.gouv.fr/,
|
||||
/Je veux que le MX du domaine soit mx.ox.numerique.gouv.fr./i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
@@ -92,4 +96,45 @@ describe('<MailDomainView />', () => {
|
||||
screen.getByText(/webmail.ox.numerique.gouv.fr./i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
it('allows re-running domain check when clicking re-run button', async () => {
|
||||
// Mock the fetch call
|
||||
fetchMock.postOnce(apiUrl, {
|
||||
status: 200,
|
||||
body: mockMailDomain,
|
||||
});
|
||||
|
||||
render(
|
||||
<MailDomainView
|
||||
mailDomain={mockMailDomain}
|
||||
onMailDomainUpdate={jest.fn()}
|
||||
/>,
|
||||
{ wrapper: AppWrapper },
|
||||
);
|
||||
|
||||
// Check if action required button is displayed
|
||||
const actionButton = screen.getByText('Actions required');
|
||||
expect(actionButton).toBeInTheDocument();
|
||||
|
||||
// Click the button and verify modal content
|
||||
fireEvent.click(actionButton);
|
||||
|
||||
// Verify modal title and content
|
||||
expect(screen.getByText('Required actions on domain')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Je veux que le MX du domaine soit mx.ox.numerique.gouv.fr./,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Find and click re-run button
|
||||
const reRunButton = screen.getByText('Re-run check');
|
||||
fireEvent.click(reRunButton);
|
||||
await waitFor(() => {
|
||||
expect(fetchMock.called(apiUrl)).toBeTruthy();
|
||||
});
|
||||
expect(toast).toHaveBeenCalledWith(
|
||||
'Domain data fetched successfully',
|
||||
VariantType.SUCCESS,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import fetchMock from 'fetch-mock';
|
||||
|
||||
import { APIError } from '@/api';
|
||||
import { fetchMailDomain } from '@/features/mail-domains/domains/api/useFetchMailDomain';
|
||||
import { MailDomain } from '@/features/mail-domains/domains/types';
|
||||
|
||||
const mockMailDomain: MailDomain = {
|
||||
id: '123e4567-e89b-12d3-a456-426614174000',
|
||||
name: 'example.com',
|
||||
status: 'enabled',
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
slug: 'example-com',
|
||||
support_email: 'support@example.com',
|
||||
abilities: {
|
||||
delete: false,
|
||||
manage_accesses: true,
|
||||
get: true,
|
||||
patch: true,
|
||||
put: true,
|
||||
post: true,
|
||||
},
|
||||
action_required_details: {
|
||||
mx: 'Je veux que le MX du domaine soit mx.ox.numerique.gouv.fr.',
|
||||
},
|
||||
expected_config: [
|
||||
{ target: '', type: 'mx', value: 'mx.ox.numerique.gouv.fr.' },
|
||||
{
|
||||
target: 'dimail._domainkey',
|
||||
type: 'txt',
|
||||
value: 'v=DKIM1; h=sha256; k=rsa; p=X...X',
|
||||
},
|
||||
{ target: 'imap', type: 'cname', value: 'imap.ox.numerique.gouv.fr.' },
|
||||
{ target: 'smtp', type: 'cname', value: 'smtp.ox.numerique.gouv.fr.' },
|
||||
{
|
||||
target: '',
|
||||
type: 'txt',
|
||||
value: 'v=spf1 include:_spf.ox.numerique.gouv.fr -all',
|
||||
},
|
||||
{
|
||||
target: 'webmail',
|
||||
type: 'cname',
|
||||
value: 'webmail.ox.numerique.gouv.fr.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('fetchMailDomain', () => {
|
||||
afterEach(() => {
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('fetch the domain successfully', async () => {
|
||||
fetchMock.postOnce('end:/mail-domains/example-slug/fetch/', {
|
||||
status: 200,
|
||||
body: mockMailDomain,
|
||||
});
|
||||
|
||||
const result = await fetchMailDomain('example-slug');
|
||||
|
||||
expect(result).toEqual(mockMailDomain);
|
||||
expect(fetchMock.calls()).toHaveLength(1);
|
||||
expect(fetchMock.lastUrl()).toContain('/mail-domains/example-slug/fetch/');
|
||||
});
|
||||
|
||||
it('throw an error when the domain is not found', async () => {
|
||||
fetchMock.postOnce('end:/mail-domains/example-slug/fetch/', {
|
||||
status: 404,
|
||||
body: { cause: ['Domain not found'] },
|
||||
});
|
||||
|
||||
await expect(fetchMailDomain('example-slug')).rejects.toThrow(APIError);
|
||||
expect(fetchMock.calls()).toHaveLength(1);
|
||||
expect(fetchMock.lastUrl()).toContain('/mail-domains/example-slug/fetch/');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
|
||||
import { MailDomain } from '../types';
|
||||
|
||||
export const fetchMailDomain = async (slug: string): Promise<MailDomain> => {
|
||||
const response = await fetchAPI(`mail-domains/${slug}/fetch/`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIError(
|
||||
'Failed to fetch domain from Dimail',
|
||||
await errorCauses(response),
|
||||
);
|
||||
}
|
||||
|
||||
return response.json() as Promise<MailDomain>;
|
||||
};
|
||||
|
||||
export const useFetchFromDimail = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess: (data: MailDomain) => void;
|
||||
onError: (error: APIError) => void;
|
||||
}) => {
|
||||
return useMutation<MailDomain, APIError, string>({
|
||||
mutationFn: fetchMailDomain,
|
||||
onSuccess: (data) => {
|
||||
onSuccess(data);
|
||||
},
|
||||
onError: (error) => {
|
||||
onError(error);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalSize,
|
||||
VariantType,
|
||||
@@ -15,10 +16,14 @@ import MailDomainsLogo from '@/features/mail-domains/assets/mail-domains-logo.sv
|
||||
import { MailDomain, Role } from '@/features/mail-domains/domains';
|
||||
import { MailDomainsContent } from '@/features/mail-domains/mailboxes';
|
||||
|
||||
import { useFetchFromDimail } from '../api/useFetchMailDomain';
|
||||
|
||||
type Props = {
|
||||
mailDomain: MailDomain;
|
||||
onMailDomainUpdate?: (updatedDomain: MailDomain) => void;
|
||||
};
|
||||
export const MailDomainView = ({ mailDomain }: Props) => {
|
||||
|
||||
export const MailDomainView = ({ mailDomain, onMailDomainUpdate }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToastProvider();
|
||||
const [showModal, setShowModal] = React.useState(false);
|
||||
@@ -57,6 +62,19 @@ export const MailDomainView = ({ mailDomain }: Props) => {
|
||||
toast(t('copy done'), VariantType.SUCCESS);
|
||||
};
|
||||
|
||||
const { mutate: fetchMailDomain } = useFetchFromDimail({
|
||||
onSuccess: (data: MailDomain) => {
|
||||
console.info('fetchMailDomain success', data);
|
||||
setShowModal(false);
|
||||
toast(t('Domain data fetched successfully'), VariantType.SUCCESS);
|
||||
onMailDomainUpdate?.(data);
|
||||
},
|
||||
onError: () => {
|
||||
console.error('fetchMailDomain error');
|
||||
toast(t('Failed to fetch domain data'), VariantType.ERROR);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal && (
|
||||
@@ -135,6 +153,17 @@ export const MailDomainView = ({ mailDomain }: Props) => {
|
||||
</pre>
|
||||
</Box>
|
||||
)}
|
||||
<pre>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
void fetchMailDomain(mailDomain.slug);
|
||||
}}
|
||||
>
|
||||
{t('Re-run check')}
|
||||
</Button>
|
||||
</div>
|
||||
</pre>
|
||||
</Modal>
|
||||
)}
|
||||
<Box $padding="big">
|
||||
@@ -155,6 +184,25 @@ export const MailDomainView = ({ mailDomain }: Props) => {
|
||||
<Text $margin="none" as="h3" $size="h3">
|
||||
{mailDomain?.name}
|
||||
</Text>
|
||||
{/* TODO: remove when pending status will be removed */}
|
||||
{mailDomain?.status === 'pending' && (
|
||||
<button
|
||||
onClick={handleShowModal}
|
||||
style={{
|
||||
padding: '5px 10px',
|
||||
marginLeft: '10px',
|
||||
backgroundColor: '#cccccc',
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '500',
|
||||
borderRadius: '5px',
|
||||
}}
|
||||
data-modal="mail-domain-status"
|
||||
>
|
||||
{t('Pending')}
|
||||
</button>
|
||||
)}
|
||||
{mailDomain?.status === 'action_required' && (
|
||||
<button
|
||||
onClick={handleShowModal}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"Deleting the {{teamName}} team": "Suppression du groupe {{teamName}}",
|
||||
"Disable": "Désactiver",
|
||||
"Disable mailbox": "Désactiver la boîte mail",
|
||||
"Domain data fetched successfully": "Les données de domaine ont été récupérées avec succès",
|
||||
"Domain name": "Nom de domaine",
|
||||
"E-mail:": "E-mail:",
|
||||
"E.g. : jean.dupont@mail.fr": "Ex. : jean.dupont@mail.fr",
|
||||
@@ -86,6 +87,7 @@
|
||||
"Example: saint-laurent.fr": "Exemple : saint-laurent.fr",
|
||||
"Failed to add {{name}} in the team": "Impossible d'ajouter {{name}} au groupe",
|
||||
"Failed to create the invitation for {{email}}": "Impossible de créer l'invitation pour {{email}}",
|
||||
"Failed to fetch domain data": "Impossible de récupérer les données du domaine",
|
||||
"Failed to update mailbox status": "Impossible de mettre à jour le statut de la boîte mail",
|
||||
"Filter member list": "Filtrer la liste des membres",
|
||||
"Find a member to add to the team": "Trouver un membre à ajouter au groupe",
|
||||
@@ -145,6 +147,7 @@
|
||||
"Open the teams panel": "Ouvrir le panneau des groupes",
|
||||
"Ouch!": "Aïe !",
|
||||
"Owner": "Propriétaire",
|
||||
"Pending": "En attente",
|
||||
"Personal data and cookies": "Données personnelles et cookies",
|
||||
"Please enter a valid email address": "Merci de saisir une adresse e-mail valide",
|
||||
"Please enter a valid email address.\nE.g. : jean.dupont@mail.fr": "Veuillez entrer une adresse e-mail valide.\nEx. : jean.dupont@mail.fr",
|
||||
@@ -153,6 +156,7 @@
|
||||
"Publication Director": "Directeur de la publication",
|
||||
"Publisher": "Éditeur",
|
||||
"Radio buttons to update the roles": "Boutons radio pour mettre à jour les rôles",
|
||||
"Re-run check": "Ré-exécuter la vérification",
|
||||
"Remedy": "Voie de recours",
|
||||
"Remove from domain": "Retirer du domaine",
|
||||
"Remove from group": "Retirer du groupe",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Loader } from '@openfun/cunningham-react';
|
||||
import { useRouter as useNavigate } from 'next/navigation';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { ReactElement } from 'react';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { TextErrors } from '@/components/TextErrors';
|
||||
import {
|
||||
MailDomain,
|
||||
MailDomainsLayout,
|
||||
useMailDomain,
|
||||
} from '@/features/mail-domains/domains';
|
||||
@@ -14,13 +15,15 @@ import { NextPageWithLayout } from '@/types/next';
|
||||
|
||||
const MailboxesPage: NextPageWithLayout = () => {
|
||||
const router = useRouter();
|
||||
const [currentMailDomain, setCurrentMailDomain] = useState<MailDomain | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
if (router?.query?.slug && typeof router.query.slug !== 'string') {
|
||||
throw new Error('Invalid mail domain slug');
|
||||
}
|
||||
|
||||
const { slug } = router.query;
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
@@ -30,6 +33,13 @@ const MailboxesPage: NextPageWithLayout = () => {
|
||||
isLoading,
|
||||
} = useMailDomain({ slug: String(slug) });
|
||||
|
||||
// Update currentMailDomain when mailDomain changes
|
||||
React.useEffect(() => {
|
||||
if (mailDomain) {
|
||||
setCurrentMailDomain(mailDomain);
|
||||
}
|
||||
}, [mailDomain]);
|
||||
|
||||
if (error?.status === 404) {
|
||||
navigate.replace(`/404`);
|
||||
return null;
|
||||
@@ -47,11 +57,16 @@ const MailboxesPage: NextPageWithLayout = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!mailDomain) {
|
||||
if (!currentMailDomain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <MailDomainView mailDomain={mailDomain} />;
|
||||
return (
|
||||
<MailDomainView
|
||||
mailDomain={currentMailDomain}
|
||||
onMailDomainUpdate={setCurrentMailDomain}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
MailboxesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
|
||||
Reference in New Issue
Block a user