Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#3725] Add problems to the email digest #4093

Merged
merged 5 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 34 additions & 26 deletions src/openforms/emails/tasks.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,59 @@
from datetime import timedelta
from datetime import datetime, timedelta
from typing import Any

from django.conf import settings
from django.template.loader import render_to_string
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.logging.service import (
collect_failed_emails,
collect_failed_registrations,
)

from .utils import send_mail_html


class Digest:
def __init__(self, since: datetime) -> None:
self.since = since

def get_context_data(self) -> dict[str, Any]:
failed_emails = collect_failed_emails(self.since)
failed_registrations = collect_failed_registrations(self.since)

if not (failed_emails or failed_registrations):
return {}

return {
"failed_emails": failed_emails,
"failed_registrations": failed_registrations,
}

def render(self) -> str:
if not (context := self.get_context_data()):
return ""

return render_to_string("emails/admin_digest.html", context)


@app.task
def send_email_digest() -> None:
config = GlobalConfiguration.get_solo()
if not (recipients := config.recipients_email_digest):
return

period_start = 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")
yesterday = timezone.now() - timedelta(days=1)
digest = Digest(since=yesterday)
content = digest.render()

if not logs:
if not content:
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
],
},
)

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

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

{% if failed_emails %}
<h5>{% trans "Emails that failed to send" %}</h5>
<ul>
{% for email in failed_emails %}
<li>
{% blocktranslate with event=email.event submission_uuid=email.submission_uuid %}
Email for the event "{{ event }}" for submission {{ submission_uuid }}.
{% endblocktranslate %}
vaszig marked this conversation as resolved.
Show resolved Hide resolved
</li>
{% endfor %}
</ul>
{% endif %}

{% if failed_registrations %}
<h5>{% trans "Registrations" %}</h5>
<ul>
{% for registration in failed_registrations %}
<li>
{% blocktranslate with form_name=registration.form_name counter=registration.failed_submissions_counter first_failure_at=registration.initial_failure_at|time:"H:i" last_failure_at=registration.last_failure_at|time:"H:i" %}
vaszig marked this conversation as resolved.
Show resolved Hide resolved
Form '{{ form_name }}' failed {{ counter }} times between {{ first_failure_at }} and {{ last_failure_at }}.
{% endblocktranslate %}
</li>
<a href="{{ registration.admin_link }}">
{% blocktranslate with form_name=registration.form_name %}
View failed '{{ form_name }}' submissions
{% endblocktranslate %}
</a>
vaszig marked this conversation as resolved.
Show resolved Hide resolved
{% endfor %}
</ul>
{% endif %}
Loading
Loading