Skip to content

Commit

Permalink
🥅(frontend) catch mailbox creation errors
Browse files Browse the repository at this point in the history
- display error message for error 500 and 400 for
missing mail domain secret and force focus on
first name form field when api error is caught
- update related e2e test

**Attention**
Writing e2e tests to assert this behavior checking both
the frontend and the backend is complex. So for the
moment, the requests interceptions are kept. To write
proper tests, it takes to use a mail domain already
enabled and with a valid secret, which are actions
performed manually on Django Admin.
To test these behaviors manually, it takes to use
a mail domain with an empty secret for the error 400,
and a invalid secret for the error 500.
  • Loading branch information
daproclaima committed Aug 22, 2024
1 parent 5362f13 commit 2ebd036
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 27 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ and this project adheres to

### Fixed

- 🐛(frontend) user can submit form to add mail domain by pressing "Enter" key
🐛(frontend) fix mai domain addition submission #355
🥅(frontend) improve api error handling on mailbox creation form #355

## [1.0.1] - 2024-08-19

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export const createMailbox = async ({
});

if (!response.ok) {
// TODO: extend errorCauses to return the name of the invalid field names to highlight in the form?
throw new APIError(
'Failed to create the mailbox',
await errorCauses(response),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { z } from 'zod';

import { APIError } from '@/api';
import { Box, Text, TextErrors } from '@/components';

import { CreateMailboxParams, useCreateMailbox } from '../../api';
Expand All @@ -32,6 +33,73 @@ const GlobalStyle = createGlobalStyle`
}
`;

const useCreateMailboxApiError = ({
error,
methods,
}: {
error: APIError | null;
methods: UseFormReturn<CreateMailboxParams> | null;
}): string[] | undefined => {
const [errorCauses, setErrorCauses] = React.useState<undefined | string[]>(
undefined,
);
const { t } = useTranslation();

React.useEffect(() => {
if (methods && t && error) {
let causes = undefined;

if (error.cause?.length) {
const parseCauses = (causes: string[]) =>
causes.reduce((arrayCauses, cause) => {
switch (cause) {
case 'Mailbox with this Local_part and Domain already exists.':
methods.setError('local_part', {
type: 'manual',
message: t('This email prefix is already used.'),
});
break;

case "Please configure your domain's secret before creating any mailbox.":
arrayCauses.push(
t(
'The mail domain secret is misconfigured. Please, contact our support team to solve the issue: ' +
'suiteterritoriale@anct.gouv.fr.',
),
);
break;
default:
arrayCauses.push(cause);
}

return arrayCauses;
}, [] as string[]);

causes = parseCauses(error.cause);
}

if (error.status === 500 || !error.cause) {
causes = [
t(
'Your request cannot be processed because the server is experiencing an error. If the problem ' +
'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr.',
),
];
}

setErrorCauses(causes);
}
}, [error, methods, t]);

React.useEffect(() => {
if (errorCauses && methods) {
methods.setFocus('first_name');
}
}, [methods, errorCauses]);

return errorCauses;
};

export const CreateMailboxForm = ({
mailDomain,
closeModal,
Expand Down Expand Up @@ -88,27 +156,15 @@ export const CreateMailboxForm = ({
},
});

const errorCauses = useCreateMailboxApiError({ error, methods });

const onSubmitCallback = (event: React.FormEvent) => {
event.preventDefault();
void methods.handleSubmit((data) =>
createMailbox({ ...data, mailDomainSlug: mailDomain.slug }),
)();
};

const causes = error?.cause?.filter((cause) => {
const isFound =
cause === 'Mailbox with this Local_part and Domain already exists.';

if (isFound) {
methods.setError('local_part', {
type: 'manual',
message: t('This email prefix is already used.'),
});
}

return !isFound;
});

return (
<FormProvider {...methods}>
<Modal
Expand Down Expand Up @@ -152,8 +208,12 @@ export const CreateMailboxForm = ({
>
<GlobalStyle />
<Box $width="100%" $margin={{ top: 'none', bottom: 'xl' }}>
{!!causes?.length && (
<TextErrors $margin={{ bottom: 'small' }} causes={causes} />
{!!errorCauses?.length && (
<TextErrors
$margin={{ bottom: 'small' }}
causes={errorCauses}
$textAlign="left"
/>
)}
<Text
$margin={{ horizontal: 'none', vertical: 'big' }}
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/apps/desk/src/i18n/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
"Teams": "Équipes",
"The National Agency for Territorial Cohesion undertakes to make its\n service accessible, in accordance with article 47 of law no. 2005-102\n of February 11, 2005.": "L'Agence Nationale de la Cohésion des Territoires s’engage à rendre son service accessible, conformément à l’article 47 de la loi n° 2005-102 du 11 février 2005.",
"The domain name encounters an error. Please contact our support team to solve the problem:": "Le nom de domaine rencontre une erreur. Veuillez contacter notre support pour résoudre le problème :",
"The mail domain secret is misconfigured. Please, contact our support team to solve the issue: suiteterritoriale@anct.gouv.fr.": "Le secret du domaine de messagerie est mal configuré. Veuillez contacter notre support pour résoudre le problème : suiteterritoriale@anct.gouv.fr.",
"The member has been removed from the team": "Le membre a été supprimé de votre groupe",
"The role has been updated": "Le rôle a bien été mis à jour",
"The team has been removed.": "Le groupe a été supprimé.",
Expand Down Expand Up @@ -164,6 +165,7 @@
"You cannot update the role of other owner.": "Vous ne pouvez pas mettre à jour les rôles d'autre propriétaire.",
"You must have minimum 1 character": "Vous devez entrer au moins 1 caractère",
"Your domain name is being validated. You will not be able to create mailboxes until your domain name has been validated by our team.": "Votre nom de domaine est en cours de validation. Vous ne pourrez créer de boîtes mail que lorsque votre nom de domaine sera validé par notre équipe.",
"Your request cannot be processed because the server is experiencing an error. If the problem persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr.": "Votre demande ne peut pas être traitée car le serveur rencontre une erreur. Si le problème persiste, veuillez contacter notre support pour résoudre le problème : suiteterritoriale@anct.gouv.fr.",
"[disabled]": "[désactivé]",
"[enabled]": "[actif]",
"[failed]": "[erroné]",
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/apps/desk/src/pages/mail-domains/add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import React, { ReactElement } from 'react';

import { Box } from '@/components';
import { MailDomainsLayout } from '@/features/mail-domains';
import { ModalCreateMailDomain } from '@/features/mail-domains/components/ModalAddMailDomain';
import { ModalAddMailDomain } from '@/features/mail-domains/components/ModalAddMailDomain';
import { NextPageWithLayout } from '@/types/next';

const Page: NextPageWithLayout = () => {
return (
<Box $padding="large" $height="inherit">
<ModalCreateMailDomain />
<ModalAddMailDomain />
</Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,7 @@ test.describe('Mail domain create mailbox', () => {
await expect(textAlreadyUsedLocalPart).toBeVisible();
});

test('checks unknown api error causes are displayed above form when they are not related with invalid field', async ({
page,
}) => {
test('checks unknown api error causes are displayed', async ({ page }) => {
const interceptRequests = async (page: Page) => {
void interceptCommonApiRequests(page);

Expand Down Expand Up @@ -465,6 +463,66 @@ test.describe('Mail domain create mailbox', () => {

await page.getByRole('button', { name: 'Create the mailbox' }).click();

await expect(page.getByText('Unknown error from server')).toBeVisible();
await expect(
page.getByText(
'Your request cannot be processed because the server is experiencing an error. If the problem persists,' +
' please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr.',
),
).toBeVisible();

await expect(inputFirstName).toBeFocused();
});

test('checks mail domain misconfiguration api error cause are displayed', async ({
page,
}) => {
const interceptRequests = async (page: Page) => {
void interceptCommonApiRequests(page);

await page.route(
'**/api/v1.0/mail-domains/domainfr/mailboxes/',
async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 400,
json: {
__all__: [
"Please configure your domain's secret before creating any mailbox.",
],
},
});
}
},
{ times: 1 },
);
};

void interceptRequests(page);

await navigateToMailboxCreationFormForMailDomainFr(page);

const inputFirstName = page.getByLabel('First name');
const inputLastName = page.getByLabel('Last name');
const inputLocalPart = page.getByLabel('Email address prefix');
const inputSecondaryEmailAddress = page.getByLabel(
'Secondary email address',
);

await inputFirstName.fill('John');
await inputLastName.fill('Doe');
await inputLocalPart.fill('john.doe');

await inputSecondaryEmailAddress.fill('john.do@mail.fr');

await page.getByRole('button', { name: 'Create the mailbox' }).click();

await expect(
page.getByText(
'The mail domain secret is misconfigured. Please, contact our support team to solve the issue:' +
' suiteterritoriale@anct.gouv.fr.',
),
).toBeVisible();

await expect(inputFirstName).toBeFocused();
});
});
31 changes: 28 additions & 3 deletions src/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8734,7 +8734,16 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"

"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -8828,7 +8837,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -9682,7 +9698,16 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand Down

0 comments on commit 2ebd036

Please sign in to comment.