Skip to content

Commit

Permalink
scheduled-mails: Migrate existing scheduled emails to new templates.
Browse files Browse the repository at this point in the history
Migrates existing ScheduledEmails for onboarding emails that have
either "zerver/emails/followup_day1" or "zerver/emails/followup_day2"
as the email template prefix to instead use the new template
prefixes "zerver/emails/account_registered" and
"zerver/emails/zulip_onboarding_topics".
  • Loading branch information
laurynmm authored and timabbott committed Aug 18, 2023
1 parent 438bcc1 commit 0df1be9
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 45 deletions.
95 changes: 95 additions & 0 deletions zerver/migrations/0468_rename_followup_day_email_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Generated by Django 4.2.2 on 2023-07-11 10:45

from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
from django.db.models import F, Func, JSONField, TextField, Value
from django.db.models.functions import Cast

# ScheduledMessage.type for onboarding emails from zerver/models.py
WELCOME = 1


def update_for_followup_day_email_templates_rename(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
ScheduledEmail = apps.get_model("zerver", "ScheduledEmail")

account_registered_emails = ScheduledEmail.objects.annotate(
as_jsonb=Cast("data", JSONField())
).filter(type=WELCOME, as_jsonb__template_prefix="zerver/emails/followup_day1")
account_registered_emails.update(
data=Cast(
Func(
F("as_jsonb"),
Value(["template_prefix"]),
Value("zerver/emails/account_registered", JSONField()),
function="jsonb_set",
),
TextField(),
)
)

onboarding_zulip_topics_emails = ScheduledEmail.objects.annotate(
as_jsonb=Cast("data", JSONField())
).filter(type=WELCOME, as_jsonb__template_prefix="zerver/emails/followup_day2")
onboarding_zulip_topics_emails.update(
data=Cast(
Func(
F("as_jsonb"),
Value(["template_prefix"]),
Value("zerver/emails/onboarding_zulip_topics", JSONField()),
function="jsonb_set",
),
TextField(),
)
)


def revert_followup_day_email_templates_rename(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
ScheduledEmail = apps.get_model("zerver", "ScheduledEmail")

rename_extradata_realmauditlog_extra_data_json = ScheduledEmail.objects.annotate(
as_jsonb=Cast("data", JSONField())
).filter(type=WELCOME, as_jsonb__template_prefix="zerver/emails/account_registered")
rename_extradata_realmauditlog_extra_data_json.update(
data=Cast(
Func(
F("as_jsonb"),
Value(["template_prefix"]),
Value("zerver/emails/followup_day1", JSONField()),
function="jsonb_set",
),
TextField(),
)
)

rename_extradata_realmauditlog_extra_data_json = ScheduledEmail.objects.annotate(
as_jsonb=Cast("data", JSONField())
).filter(type=WELCOME, as_jsonb__template_prefix="zerver/emails/onboarding_zulip_topics")
rename_extradata_realmauditlog_extra_data_json.update(
data=Cast(
Func(
F("as_jsonb"),
Value(["template_prefix"]),
Value("zerver/emails/followup_day2", JSONField()),
function="jsonb_set",
),
TextField(),
)
)


class Migration(migrations.Migration):
dependencies = [
("zerver", "0467_rename_extradata_realmauditlog_extra_data_json"),
]

operations = [
migrations.RunPython(
update_for_followup_day_email_templates_rename,
reverse_code=revert_followup_day_email_templates_rename,
),
]
111 changes: 66 additions & 45 deletions zerver/tests/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# You can also read
# https://www.caktusgroup.com/blog/2016/02/02/writing-unit-tests-django-migrations/
# to get a tutorial on the framework that inspired this feature.
from datetime import datetime, timezone

import orjson
from django.db.migrations.state import StateApps

from zerver.lib.test_classes import MigrationsTestCase
Expand All @@ -26,56 +28,75 @@
# been tested for a migration being merged.


class RealmPlaygroundURLPrefix(MigrationsTestCase):
migrate_from = "0462_realmplayground_url_template"
migrate_to = "0463_backfill_realmplayground_url_template"
class ScheduledEmailData(MigrationsTestCase):
migrate_from = "0466_realmfilter_order"
migrate_to = "0467_rename_followup_day_email_templates"

@use_db_models
def setUpBeforeMigration(self, apps: StateApps) -> None:
iago = self.example_user("iago")
RealmPlayground = apps.get_model("zerver", "RealmPlayground")

urls = [
"http://example.com/",
"https://example.com/{",
"https://example.com/{}",
"https://example.com/{val}",
"https://example.com/{code}",
ScheduledEmail = apps.get_model("zerver", "ScheduledEmail")
send_date = datetime(2025, 1, 1, 1, 0, 0, 0, tzinfo=timezone.utc)

templates = [
["zerver/emails/followup_day1", "a", True, 10],
["zerver/emails/followup_day2", "b", False, 20],
["zerver/emails/onboarding_zulip_guide", "c", True, 30],
]
self.realm_playground_ids = []

for index, url in enumerate(urls):
self.realm_playground_ids.append(
RealmPlayground.objects.create(
realm=iago.realm,
name=f"Playground {index}",
pygments_language="Python",
url_prefix=url,
url_template=None,
).id
)
self.realm_playground_ids.append(
RealmPlayground.objects.create(

for template in templates:
email_fields = {
"template_prefix": template[0],
"string_context": template[1],
"boolean_context": template[2],
"integer_context": template[3],
}

email = ScheduledEmail.objects.create(
type=1,
realm=iago.realm,
name="Existing Playground",
pygments_language="Python",
url_prefix="https://example.com",
url_template="https://example.com/{code}",
).id
)

def test_converted_url_templates(self) -> None:
RealmPlayground = self.apps.get_model("zerver", "RealmPlayground")

expected_urls = [
"http://example.com/{code}",
"https://example.com/%7B{code}",
"https://example.com/%7B%7D{code}",
"https://example.com/%7Bval%7D{code}",
"https://example.com/%7Bcode%7D{code}",
"https://example.com/{code}",
scheduled_timestamp=send_date,
data=orjson.dumps(email_fields).decode(),
)
email.users.add(iago.id)

def test_updated_email_templates(self) -> None:
ScheduledEmail = self.apps.get_model("zerver", "ScheduledEmail")
send_date = datetime(2025, 1, 1, 1, 0, 0, 0, tzinfo=timezone.utc)

old_templates = [
"zerver/emails/followup_day1",
"zerver/emails/followup_day2",
]

for realm_playground_id, expected_url in zip(self.realm_playground_ids, expected_urls):
realm_playground = RealmPlayground.objects.get(id=realm_playground_id)
self.assertEqual(realm_playground.url_template, expected_url)
current_templates = [
"zerver/emails/account_registered",
"zerver/emails/onboarding_zulip_guide",
"zerver/emails/onboarding_zulip_topics",
]

email_data = [
["zerver/emails/account_registered", "a", True, 10],
["zerver/emails/onboarding_zulip_topics", "b", False, 20],
["zerver/emails/onboarding_zulip_guide", "c", True, 30],
]

scheduled_emails = ScheduledEmail.objects.all()
self.assert_length(scheduled_emails, 3)

checked_emails = []
for email in scheduled_emails:
self.assertEqual(email.type, 1)
self.assertEqual(email.scheduled_timestamp, send_date)

updated_data = orjson.loads(email.data)
template_prefix = updated_data["template_prefix"]
self.assertFalse(template_prefix in old_templates)
for data in email_data:
if template_prefix == data[0]:
self.assertEqual(updated_data["string_context"], data[1])
self.assertEqual(updated_data["boolean_context"], data[2])
self.assertEqual(updated_data["integer_context"], data[3])
checked_emails.append(template_prefix)

self.assertEqual(current_templates, sorted(checked_emails))

0 comments on commit 0df1be9

Please sign in to comment.