Skip to content

Commit

Permalink
Trigger an email to admins each time a translation lands back in in t…
Browse files Browse the repository at this point in the history
…he CMS (#15594)

This will probably be a little spammy to start with - emailing each admin
once per locale per page/snippet translated, but we will improve this
with something custom on the dashboard soon
  • Loading branch information
stevejalim authored Nov 28, 2024
1 parent 811948f commit aa1e969
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 0 deletions.
2 changes: 2 additions & 0 deletions bedrock/cms/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from django.apps import AppConfig

from bedrock.cms.signal_handlers import * # noqa: F403 F406


class CmsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
Expand Down
73 changes: 73 additions & 0 deletions bedrock/cms/signal_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import logging
from typing import TYPE_CHECKING, Type

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.mail import send_mail
from django.template.loader import render_to_string

from wagtail_localize_smartling.signals import translation_imported

if TYPE_CHECKING:
from wagtail_localize.models import Translation
from wagtail_localize_smartling.models import Job


logger = logging.getLogger(__name__)


def notify_of_imported_translation(
sender: Type["Job"],
instance: "Job",
translation: "Translation",
**kwargs,
):
"""
Signal handler for receiving news that a translation has landed from
Smartling.
For now, sends a notification email to all Admins
"""
UserModel = get_user_model()

admin_emails = UserModel.objects.filter(
is_superuser=True,
is_active=True,
).values_list("email", flat=True)
admin_emails = [email for email in admin_emails if email] # Safety check to ensure no empty email addresses are included

if not admin_emails:
logger.warning("Unable to send translation-imported email alerts: no admins in system")
return

email_subject = "New translations imported into Bedrock CMS"

job_name = instance.name
translation_source_name = str(instance.translation_source.get_source_instance())

smartling_cms_dashboard_url = f"{settings.WAGTAILADMIN_BASE_URL}/cms-admin/smartling-jobs/inspect/{instance.pk}/"

email_body = render_to_string(
template_name="cms/email/notifications/translation_imported__body.txt",
context={
"job_name": job_name,
"translation_source_name": translation_source_name,
"translation_target_language_code": translation.target_locale.language_code,
"smartling_cms_dashboard_url": smartling_cms_dashboard_url,
},
)

send_mail(
subject=email_subject,
message=email_body,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=admin_emails,
)
logger.info(f"Translation-imported notification sent to {len(admin_emails)} admins")


translation_imported.connect(notify_of_imported_translation, weak=False)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Hi there

ACTION REQUIRED: A new translation has been synced back from Smartling
and needs to be published.

It is Job '{{job_name}}' for '{{translation_source_name}}' into {{translation_target_language_code}}.

You can view the job at {{smartling_cms_dashboard_url}}

From there, for each item listed under "Translations", please open it in a new tab, review and publish it.

Please note that you may need to publish things in a particular order (e.g. Snippet before the Page that uses it; )

Many thanks

Bedrock-bot


P.S. If you find this process painful, please come and tell us about it in #www!
98 changes: 98 additions & 0 deletions bedrock/cms/tests/test_signal_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import logging

from django.test import override_settings

import pytest
from wagtail_localize.models import Translation
from wagtail_localize_smartling.models import Job
from wagtail_localize_smartling.signals import translation_imported

from bedrock.cms.signal_handlers import notify_of_imported_translation
from bedrock.cms.tests.factories import WagtailUserFactory

pytestmark = [pytest.mark.django_db]


def test_translation_imported_is_connected_to_the_expected_handler():
assert len(translation_imported.receivers) == 1
assert translation_imported.receivers[0][1] == notify_of_imported_translation


@override_settings(
DEFAULT_FROM_EMAIL="from@example.com",
WAGTAILADMIN_BASE_URL="https://cms.example.com",
)
def test_notify_of_imported_translation__happy_path(mocker, caplog):
caplog.set_level(logging.INFO)

WagtailUserFactory(
username="admin_1",
email="admin_1@example.com",
is_superuser=True,
)
WagtailUserFactory(
username="user_1",
email="user_1@example.com",
is_superuser=False,
)
WagtailUserFactory(
username="admin_2",
email="admin_2@example.com",
is_superuser=True,
)

mock_job = mocker.MagicMock(spec=Job)
mock_job.name = "Test Job"
mock_job.pk = 9876
mock_source = mocker.Mock(name="test-source")
mock_job.translation_source.get_source_instance.return_value = mock_source

mock_translation = mocker.MagicMock(spec=Translation, name="mock-translation")
mock_translation.target_locale.language_code = "fr-CA"

mock_send_mail = mocker.patch("bedrock.cms.signal_handlers.send_mail")

translation_imported.send(
sender=Job,
instance=mock_job,
translation=mock_translation,
)
assert mock_send_mail.call_count == 1
assert mock_send_mail.call_args[1]["subject"] == "New translations imported into Bedrock CMS"
assert mock_send_mail.call_args[1]["from_email"] == "from@example.com"
assert mock_send_mail.call_args[1]["recipient_list"] == ["admin_1@example.com", "admin_2@example.com"]

for expected_string in [
"ACTION REQUIRED: A new translation has been synced back from Smartling",
"It is Job 'Test Job'",
"https://cms.example.com/cms-admin/smartling-jobs/inspect/9876/",
]:
assert expected_string in mock_send_mail.call_args[1]["message"]
assert caplog.records[0].message == "Translation-imported notification sent to 2 admins"


def test_notify_of_imported_translation__no_admins_in_system(mocker, caplog):
caplog.set_level(logging.INFO)

mock_job = mocker.MagicMock(spec=Job)
mock_job.name = "Test Job"
mock_job.pk = 9876
mock_source = mocker.Mock(name="test-source")
mock_job.translation_source.get_source_instance.return_value = mock_source

mock_translation = mocker.MagicMock(spec=Translation, name="mock-translation")
mock_translation.target_locale.language_code = "fr-CA"

mock_send_mail = mocker.patch("bedrock.cms.signal_handlers.send_mail")

translation_imported.send(
sender=Job,
instance=mock_job,
translation=mock_translation,
)
assert mock_send_mail.call_count == 0
assert caplog.records[0].message == "Unable to send translation-imported email alerts: no admins in system"

0 comments on commit aa1e969

Please sign in to comment.