From 29d0bbb6928b4c725dfaa91fc24908bc6a517e73 Mon Sep 17 00:00:00 2001 From: Sabrina Demagny Date: Sun, 16 Feb 2025 17:57:36 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20display=20button=20to=20r?= =?UTF-8?q?e-run=20fetch=20domain=20from=20dimail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the button in the modal which describes actions required to make the domain work --- CHANGELOG.md | 1 + .../domains/__tests__/MailDomainView.test.tsx | 65 +++++++++++++--- .../__tests__/useFetchMailDomain.test.tsx | 76 +++++++++++++++++++ .../domains/api/useFetchMailDomain.tsx | 38 ++++++++++ .../domains/components/MailDomainView.tsx | 50 +++++++++++- .../apps/desk/src/i18n/translations.json | 4 + .../src/pages/mail-domains/[slug]/index.tsx | 23 +++++- 7 files changed, 242 insertions(+), 15 deletions(-) create mode 100644 src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/useFetchMailDomain.test.tsx create mode 100644 src/frontend/apps/desk/src/features/mail-domains/domains/api/useFetchMailDomain.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ed6b4..d02d172 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/MailDomainView.test.tsx b/src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/MailDomainView.test.tsx index 457b370..87d617a 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/MailDomainView.test.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/MailDomainView.test.tsx @@ -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('', () => { + const apiUrl = `end:/mail-domains/${mockMailDomain.slug}/fetch/`; + beforeEach(() => { jest.clearAllMocks(); }); @@ -64,6 +67,7 @@ describe('', () => { afterEach(() => { fetchMock.restore(); }); + it('display action required button and open modal with information when domain status is action_required', () => { render(, { wrapper: AppWrapper, @@ -79,7 +83,7 @@ describe('', () => { 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('', () => { 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( + , + { 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, + ); + }); }); diff --git a/src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/useFetchMailDomain.test.tsx b/src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/useFetchMailDomain.test.tsx new file mode 100644 index 0000000..07aae45 --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/domains/__tests__/useFetchMailDomain.test.tsx @@ -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/'); + }); +}); diff --git a/src/frontend/apps/desk/src/features/mail-domains/domains/api/useFetchMailDomain.tsx b/src/frontend/apps/desk/src/features/mail-domains/domains/api/useFetchMailDomain.tsx new file mode 100644 index 0000000..184c591 --- /dev/null +++ b/src/frontend/apps/desk/src/features/mail-domains/domains/api/useFetchMailDomain.tsx @@ -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 => { + 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; +}; + +export const useFetchFromDimail = ({ + onSuccess, + onError, +}: { + onSuccess: (data: MailDomain) => void; + onError: (error: APIError) => void; +}) => { + return useMutation({ + mutationFn: fetchMailDomain, + onSuccess: (data) => { + onSuccess(data); + }, + onError: (error) => { + onError(error); + }, + }); +}; diff --git a/src/frontend/apps/desk/src/features/mail-domains/domains/components/MailDomainView.tsx b/src/frontend/apps/desk/src/features/mail-domains/domains/components/MailDomainView.tsx index 72f1fd6..6156ee5 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/domains/components/MailDomainView.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/domains/components/MailDomainView.tsx @@ -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) => { )} +
+            
+ +
+
)} @@ -155,6 +184,25 @@ export const MailDomainView = ({ mailDomain }: Props) => { {mailDomain?.name} + {/* TODO: remove when pending status will be removed */} + {mailDomain?.status === 'pending' && ( + + )} {mailDomain?.status === 'action_required' && (