From 64850ae89df156650715ea9e1bf93d6cfd942dcf Mon Sep 17 00:00:00 2001
From: Tore Brede <Tore.Brede@uib.no>
Date: Fri, 17 Dec 2021 15:15:52 +0100
Subject: [PATCH] GREG-158: Better error reponse when e-mail sending failed for
 invite

---
 frontend/public/locales/en/common.json        |   4 +-
 frontend/public/locales/nb/common.json        |   4 +-
 frontend/public/locales/nn/common.json        |   3 +-
 .../sponsor/register/stepRegistration.tsx     | 100 ++++++++++++------
 gregui/api/views/invitation.py                |  17 ++-
 5 files changed, 92 insertions(+), 36 deletions(-)

diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json
index e54b8d16..ec71fe0f 100644
--- a/frontend/public/locales/en/common.json
+++ b/frontend/public/locales/en/common.json
@@ -168,11 +168,13 @@
     "unknown": "An unknown error has occurred. If the problem persists, contact support.",
     "invitationDataFetchFailed": "Failed to fetch invitation data",
     "guestRegistrationFailed": "Failed to register your data",
+    "partialSubmitSuccess": "Invite creation partial successful",
     "codes": {
       "invalid_invite": "Invalid invite",
       "invite_expired": "Invite has expired",
       "cannot_update_fields": "Failed to update data",
-      "update_national_id_not_allowed": "Not allowed to update verified national ID"
+      "update_national_id_not_allowed": "Not allowed to update verified national ID",
+      "invite_email_failed": "There was a problem sending the invite e-mail. Contact support."
     }
   }
 }
diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json
index 11be84cd..1444a73b 100644
--- a/frontend/public/locales/nb/common.json
+++ b/frontend/public/locales/nb/common.json
@@ -168,11 +168,13 @@
     "errorLoadOusRoleType": "Kunne ikke laste organisasjons og/eller rolletype data fra server",
     "invitationDataFetchFailed": "Klarte ikke å hente invitasjonsdata",
     "guestRegistrationFailed": "Klarte ikke å registrere dataene dine",
+    "partialSubmitSuccess": "",
     "codes": {
       "invalid_invite": "Ugyldig invitasjon",
       "invite_expired": "Invitasjonen har utløpt",
       "cannot_update_fields": "Klarte ikke å oppdatere dataene",
-      "update_national_id_not_allowed": "Ikke tillatt å endre verifisert fødselsnummer/D-nummer"
+      "update_national_id_not_allowed": "Ikke tillatt å endre verifisert fødselsnummer/D-nummer",
+      "invite_email_failed": "Klarte ikke å sende e-post med invitasjon. Kontakt support."
     }
   }
 }
diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json
index 5e721f5c..6ba68cb5 100644
--- a/frontend/public/locales/nn/common.json
+++ b/frontend/public/locales/nn/common.json
@@ -173,7 +173,8 @@
       "invalid_invite": "Ugyldig invitasjon",
       "invite_expired": "Invitasjonen har gått ut",
       "cannot_update_fields": "Klarte ikkje å oppdatere informasjonen din",
-      "update_national_id_not_allowed": "Ikkje tillate å endre verifisert fødselsnummer/D-nummer"
+      "update_national_id_not_allowed": "Ikkje tillate å endre verifisert fødselsnummer/D-nummer",
+      "invite_email_failed": "Klarte ikkje å sende e-post med invitasjon. Kontakt support."
     }
   }
 }
diff --git a/frontend/src/routes/sponsor/register/stepRegistration.tsx b/frontend/src/routes/sponsor/register/stepRegistration.tsx
index 7e965b8e..e23b8f94 100644
--- a/frontend/src/routes/sponsor/register/stepRegistration.tsx
+++ b/frontend/src/routes/sponsor/register/stepRegistration.tsx
@@ -54,6 +54,8 @@ export default function StepRegistration() {
     useState<ServerErrorReportData>()
   const [formDataErrorReport, setFormDataErrorReport] =
     useState<ServerErrorReportData>()
+  const [submitPartialSuccess, setSubmitPartialSuccess] =
+    useState<ServerErrorReportData>()
 
   // The organizational unit and role types are not used by this component, but
   // loading them here anyways to make sure that they can be loaded using the
@@ -71,6 +73,62 @@ export default function StepRegistration() {
     }
   }
 
+  function handleSubmitErrorResponse(res: Response) {
+    // Try to extract data from body of error message
+    res
+      .text()
+      .then((text) => {
+        setSubmitState(SubmitState.SubmitFailure)
+        setSubmitErrorReport({
+          errorHeading: t('error.invitationCreationFailedHeader'),
+          statusCode: res.status,
+          statusText: res.statusText,
+          errorBodyText: text,
+        })
+      })
+      .catch((error) => {
+        // Extracting data from body failed, just show an error message with no body text
+        console.error('error', error)
+        setSubmitErrorReport({
+          errorHeading: t('error.invitationCreationFailedHeader'),
+          statusCode: res.status,
+          statusText: res.statusText,
+          errorBodyText: undefined,
+        })
+        setSubmitState(SubmitState.SubmitFailure)
+      })
+  }
+
+  function handleSubmitOkResponse(res: Response) {
+    // The invite was created, but there may still be some warning
+    res
+      .json()
+      .then((jsonResponse) => {
+        // If there is a body then it should have some json-content
+        console.log('result', jsonResponse)
+
+        if ('code' in jsonResponse) {
+          // There is something in the body indicating a warning
+          setSubmitPartialSuccess({
+            errorHeading: t('error.partialSubmitSuccess'),
+            statusCode: undefined,
+            statusText: undefined,
+            errorBodyText: t(`error.codes.${jsonResponse.code}`),
+          })
+        } else {
+          console.log(JSON.stringify(jsonResponse))
+        }
+      })
+      .catch((exception) => {
+        // There was a problem when extracting the body
+        console.error(exception)
+      })
+      .finally(() => {
+        setSubmitState(SubmitState.SubmitSuccess)
+        setActiveStep(Steps.SuccessStep)
+      })
+  }
+
   const registerGuest = () => {
     const payload = {
       first_name: formData.first_name,
@@ -96,41 +154,12 @@ export default function StepRegistration() {
     fetch('/api/ui/v1/invite/', submitJsonOpts('POST', payload))
       .then((res) => {
         if (!res.ok) {
-          // Try to extract data from body of error message
-          res
-            .text()
-            .then((text) => {
-              setSubmitState(SubmitState.SubmitFailure)
-              setSubmitErrorReport({
-                errorHeading: t('error.invitationCreationFailedHeader'),
-                statusCode: res.status,
-                statusText: res.statusText,
-                errorBodyText: text,
-              })
-            })
-            .catch((error) => {
-              // Extracting data from body failed, just show an error message with no body text
-              console.error('error', error)
-              setSubmitErrorReport({
-                errorHeading: t('error.invitationCreationFailedHeader'),
-                statusCode: res.status,
-                statusText: res.statusText,
-                errorBodyText: undefined,
-              })
-              setSubmitState(SubmitState.SubmitFailure)
-            })
+          handleSubmitErrorResponse(res)
         } else {
-          return res.text()
+          handleSubmitOkResponse(res)
         }
         return null
       })
-      .then((result) => {
-        if (result !== null) {
-          console.log('result', result)
-          setSubmitState(SubmitState.SubmitSuccess)
-          setActiveStep(Steps.SuccessStep)
-        }
-      })
       .catch((error) => {
         console.error('error', error)
         setSubmitState(SubmitState.SubmitFailure)
@@ -259,6 +288,15 @@ export default function StepRegistration() {
           errorBodyText={formDataErrorReport.errorBodyText}
         />
       )}
+
+      {submitPartialSuccess !== undefined && (
+        <ServerErrorReport
+          errorHeading={submitPartialSuccess.errorHeading}
+          statusCode={undefined}
+          statusText={undefined}
+          errorBodyText={submitPartialSuccess.errorBodyText}
+        />
+      )}
     </Page>
   )
 }
diff --git a/gregui/api/views/invitation.py b/gregui/api/views/invitation.py
index 1df8e74b..0ef0add6 100644
--- a/gregui/api/views/invitation.py
+++ b/gregui/api/views/invitation.py
@@ -75,9 +75,22 @@ class InvitationView(CreateAPIView, DestroyAPIView):
             invitation__role__person_id=person.id,
             invitation__role__sponsor_id=sponsor_user.sponsor_id,
         ):
-            send_invite_mail(invitationlink)
+            try:
+                send_invite_mail(invitationlink)
+            except Exception as exception:
+                logger.error("send_invite_mail", exception=exception)
+                # The invite has been created, so send 201, but include some data saying the
+                # e-mail was not sent properly
+                return Response(
+                    status=status.HTTP_201_CREATED,
+                    data={
+                        "code": "invite_email_failed",
+                        "message": "Failed to send invite e-mail",
+                    },
+                )
 
-        return Response(status=status.HTTP_201_CREATED)
+        # Empty json-body to avoid client complaining about non-json data in response
+        return Response(status=status.HTTP_201_CREATED, data={})
 
     def delete(self, request, *args, **kwargs) -> Response:
         try:
-- 
GitLab