Skip to content

Commit

Permalink
[#3725] Added failed registrations to the email digest
Browse files Browse the repository at this point in the history
  • Loading branch information
vaszig committed Apr 4, 2024
1 parent f51704e commit 183f152
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 26 deletions.
28 changes: 9 additions & 19 deletions src/openforms/emails/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from django_yubin.models import Message

from openforms.celery import app
from openforms.config.models import GlobalConfiguration
from openforms.logging.models import TimelineLogProxy
from openforms.registrations.utils import collect_registrations_failures

from .utils import send_mail_html
from .utils import collect_failed_emails, send_mail_html


@app.task
Expand All @@ -20,32 +18,24 @@ def send_email_digest() -> None:
if not (recipients := config.recipients_email_digest):
return

period_start = timezone.now() - timedelta(days=1)
desired_period = timezone.now() - timedelta(days=1)

logs = TimelineLogProxy.objects.filter(
timestamp__gt=period_start,
extra_data__status=Message.STATUS_FAILED,
extra_data__include_in_daily_digest=True,
).distinct("content_type", "extra_data__status", "extra_data__event")
failed_emails = collect_failed_emails(desired_period)
failed_registrations = collect_registrations_failures(desired_period)

if not logs:
if not (failed_emails or failed_registrations):
return

content = render_to_string(
"emails/admin_digest.html",
{
"logs": [
{
"submission_uuid": log.content_object.uuid,
"event": log.extra_data["event"],
}
for log in logs
],
"failed_emails": failed_emails,
"failed_registrations": failed_registrations,
},
)

send_mail_html(
_("[Open Forms] Daily summary of failed emails"),
_("[Open Forms] Daily summary of failed procedures"),
content,
settings.DEFAULT_FROM_EMAIL,
recipients,
Expand Down
24 changes: 19 additions & 5 deletions src/openforms/emails/templates/emails/admin_digest.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
{% load static i18n %}

<p>
{% blocktranslate %}Here is a summary of the emails that failed to send yesterday:{% endblocktranslate %}
{% blocktranslate %}Here is a summary of the failed procedures in the past 24 hours:{% endblocktranslate %}
</p>
<ul>
{% for log in logs %}<li>- Email for the event "{{ log.event }}" for submission {{ log.submission_uuid }}.</li>
{% endfor %}
</ul>

{% if failed_emails %}
<h5>{% trans "Emails" %}</h5>
<ul>
{% for email in failed_emails %}
<li>- Email for the event "{{ email.event }}" for submission {{ email.submission_uuid }}.</li>
{% endfor %}
</ul>
{% endif %}

{% if failed_registrations %}
<h5>{% trans "Registrations" %}</h5>
<ul>
{% for registration in failed_registrations %}
<li>- {{ registration.form_name }} failed {{ registration.failed_submissions_counter }} times between {{ registration.initial_failure_at|date:"H:i" }} and {{ registration.last_failure_at|date:"H:i" }}.</li>
<a href="{{ registration.admin_link }}">Click here to see all the failed submissions for form {{ registration.form_name }} in the admin</a>
{% endfor %}
</ul>
{% endif %}
97 changes: 95 additions & 2 deletions src/openforms/emails/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
from freezegun import freeze_time

from openforms.config.models import GlobalConfiguration
from openforms.forms.tests.factories import FormFactory
from openforms.logging.tests.factories import TimelineLogProxyFactory
from openforms.submissions.constants import RegistrationStatuses
from openforms.submissions.tests.factories import SubmissionFactory

from ..tasks import send_email_digest
Expand Down Expand Up @@ -72,8 +74,9 @@ def test_create_digest_email(self):
expected_content = dedent(
f"""
<p>
Here is a summary of the emails that failed to send yesterday:
Here is a summary of the failed procedures in the past 24 hours:
</p>
<h5>Emails</h5>
<ul>
<li>- Email for the event "registration" for submission {submission.uuid}.</li>
Expand Down Expand Up @@ -128,8 +131,9 @@ def test_that_repeated_failures_are_not_mentioned_multiple_times(self):
expected_content = dedent(
f"""
<p>
Here is a summary of the emails that failed to send yesterday:
Here is a summary of the failed procedures in the past 24 hours:
</p>
<h5>Emails</h5>
<ul>
<li>- Email for the event "registration" for submission {submission.uuid}.</li>
Expand Down Expand Up @@ -177,3 +181,92 @@ def test_no_recipients(self):
send_email_digest()

self.assertEqual(0, len(mail.outbox))

def test_failed_submissions_are_correctly_sent(self):
# 1st form with 2 failures in the past 24 hours
form_1 = FormFactory.create()
failed_submission_1 = SubmissionFactory.create(
form=form_1, registration_status=RegistrationStatuses.failed
)
failed_submission_2 = SubmissionFactory.create(
form=form_1, registration_status=RegistrationStatuses.failed
)

# 1st failure
with freeze_time("2023-01-02T12:30:00+01:00"):
TimelineLogProxyFactory.create(
template="logging/events/registration_failure.txt",
content_object=failed_submission_1,
extra_data={
"log_event": "registration_failure",
"include_in_daily_digest": True,
},
)

# 2nd failure
with freeze_time("2023-01-02T18:30:00+01:00"):
TimelineLogProxyFactory.create(
template="logging/events/registration_failure.txt",
content_object=failed_submission_2,
extra_data={
"log_event": "registration_failure",
"include_in_daily_digest": True,
},
)

# 2nd form with 1 failure in the past 24 hours
form_2 = FormFactory.create()
failed_submission = SubmissionFactory.create(
form=form_2, registration_status=RegistrationStatuses.failed
)

# failure
with freeze_time("2023-01-02T12:30:00+01:00"):
TimelineLogProxyFactory.create(
template="logging/events/registration_failure.txt",
content_object=failed_submission,
extra_data={
"log_event": "registration_failure",
"include_in_daily_digest": True,
},
)

with (
freeze_time("2023-01-03T01:00:00+01:00"),
patch(
"openforms.emails.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(
recipients_email_digest=["[email protected]"]
),
),
patch("openforms.emails.tasks.send_mail_html") as patch_email,
):
send_email_digest()

patch_email.assert_called_once()

args = patch_email.call_args.args
expected_content = dedent(
f"""
<p>
Here is a summary of the failed procedures in the past 24 hours:
</p>
<h5>Registrations</h5>
<ul>
<li>- {form_1.name} failed 2 times between 12:30 and 18:30.</li>
<a href="/admin/submissions/submission/?form__id__exact={form_1.id}&needs_on_completion_retry__exact=1&from_time=24hAgo">
Click here to see all the failed submissions for form {form_1.name} in the admin
</a>
<li>- {form_2.name} failed 1 times between 12:30 and 12:30.</li>
<a href="/admin/submissions/submission/?form__id__exact={form_2.id}&needs_on_completion_retry__exact=1&from_time=24hAgo">
Click here to see all the failed submissions for form {form_2.name} in the admin
</a>
</ul>
"""
).strip()

self.assertHTMLEqual(
expected_content,
args[1].strip(),
)
22 changes: 22 additions & 0 deletions src/openforms/emails/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from django.conf import settings
from django.template.loader import get_template

from django_yubin.models import Message
from mail_cleaner.mail import send_mail_plus
from mail_cleaner.sanitizer import sanitize_content as _sanitize_content
from mail_cleaner.text import strip_tags_plus

from openforms.config.models import GlobalConfiguration, Theme
from openforms.logging.models import TimelineLogProxy
from openforms.template import openforms_backend, render_from_string

from .context import get_wrapper_context
Expand Down Expand Up @@ -100,3 +102,23 @@ def render_email_template(
backend=openforms_backend,
disable_autoescape=disable_autoescape,
)


def collect_failed_emails(desired_period) -> list[dict[str, str] | None]:
logs = TimelineLogProxy.objects.filter(
timestamp__gt=desired_period,
extra_data__status=Message.STATUS_FAILED,
extra_data__include_in_daily_digest=True,
).distinct("content_type", "extra_data__status", "extra_data__event")

if not logs:
return
failed_emails = [
{
"submission_uuid": log.content_object.uuid,
"event": log.extra_data["event"],
}
for log in logs
]

return failed_emails
33 changes: 33 additions & 0 deletions src/openforms/registrations/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from glom import assign, glom

from openforms.logging.models import TimelineLogProxy
from openforms.submissions.models import Submission
from openforms.submissions.utils import get_filtered_submission_admin_url

unset = object()

Expand Down Expand Up @@ -30,3 +32,34 @@ def execute_unless_result_exists(
assign(submission.registration_result, spec, result, missing=dict)
submission.save(update_fields=["registration_result"])
return callback_result


def collect_registrations_failures(desired_period) -> list[dict[str, str] | None]:
logs = TimelineLogProxy.objects.filter(
timestamp__gt=desired_period,
extra_data__log_event="registration_failure",
extra_data__include_in_daily_digest=True,
).order_by("timestamp")

if not logs:
return

connected_forms = {}
for log in logs:
form = log.content_object.form

if form.uuid in connected_forms:
connected_forms[form.uuid]["failed_submissions_counter"] += 1
connected_forms[form.uuid]["last_failure_at"] = log.timestamp
else:
connected_forms[form.uuid] = {
"form_name": form.name,
"failed_submissions_counter": 1,
"initial_failure_at": log.timestamp,
"last_failure_at": log.timestamp,
"admin_link": get_filtered_submission_admin_url(form.id, 1, "24hAgo"),
}

failed_registrations = [form_detail for form_detail in connected_forms.values()]

return failed_registrations
19 changes: 19 additions & 0 deletions src/openforms/submissions/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from datetime import timedelta

from django import forms
from django.contrib import admin, messages
from django.contrib.contenttypes.admin import GenericTabularInline
from django.db.models import Q
from django.http import Http404
from django.template.defaultfilters import filesizeformat
from django.utils import timezone
from django.utils.translation import gettext_lazy as _, ngettext

from privates.admin import PrivateMediaMixin
Expand Down Expand Up @@ -82,6 +85,21 @@ def expected_parameters(self):
return [self.parameter_name]


class SubmissionTimeListFilter(admin.SimpleListFilter):
title = _("time")
parameter_name = "from_time"

def lookups(self, request, model_admin):
return [
("24hAgo", _("In the past 24 hours")),
]

def queryset(self, request, queryset):
desired_period = timezone.now() - timedelta(days=1)
if self.value() == "24hAgo":
return queryset.filter(last_register_date__gt=desired_period)

Check warning on line 100 in src/openforms/submissions/admin.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/submissions/admin.py#L100

Added line #L100 was not covered by tests


class SubmissionStepInline(admin.StackedInline):
model = SubmissionStep
extra = 0
Expand Down Expand Up @@ -170,6 +188,7 @@ class SubmissionAdmin(admin.ModelAdmin):
)
list_filter = (
SubmissionTypeListFilter,
SubmissionTimeListFilter,
"registration_status",
"needs_on_completion_retry",
"form",
Expand Down
11 changes: 11 additions & 0 deletions src/openforms/submissions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.http import HttpRequest
from django.utils import translation

from furl import furl
from rest_framework.permissions import SAFE_METHODS
from rest_framework.request import Request
from rest_framework.reverse import reverse
Expand Down Expand Up @@ -335,3 +336,13 @@ def get_report_download_url(request: Request, report: SubmissionReport) -> str:
kwargs={"report_id": report.id, "token": token},
)
return request.build_absolute_uri(download_url)


def get_filtered_submission_admin_url(form_id: int, state: int, from_time: str) -> str:
query_params = {
"form__id__exact": form_id,
"needs_on_completion_retry__exact": state,
"from_time": from_time,
}
submissions_admin_url = furl(reverse("admin:submissions_submission_changelist"))
return submissions_admin_url.add(query_params).url

0 comments on commit 183f152

Please sign in to comment.