Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6907b6c
adding resource type
shanbady Jul 22, 2024
8d9c398
conditionalizing subject line
shanbady Jul 22, 2024
c0661d6
limiting list of resources to 5
shanbady Jul 22, 2024
20937dc
adding images to email
shanbady Jul 22, 2024
9503944
fixing f-string
shanbady Jul 23, 2024
831c18f
template style fixes
shanbady Jul 23, 2024
6f722d7
fixing logo
shanbady Jul 23, 2024
c328ada
formatting changes and adding footer
shanbady Jul 23, 2024
7d059fd
adding url context to email templates and some style changes
shanbady Jul 24, 2024
67dadc6
adding test
shanbady Jul 24, 2024
c582103
fixing button float and adding background
shanbady Jul 24, 2024
e86f48d
cloning model method utilities
shanbady Jul 30, 2024
d710d6a
inferring channel urls for groups
shanbady Jul 30, 2024
f138bcd
initial changes to send individual emails
shanbady Jul 30, 2024
183b29c
fixing view more url and checking for null in resource.image
shanbady Jul 31, 2024
9102231
fixing subject line
shanbady Aug 1, 2024
56df2b3
fixing footer urls
shanbady Aug 1, 2024
e069e9b
capping at 10 items
shanbady Aug 1, 2024
54cd809
fixing saved search label
shanbady Aug 1, 2024
3545793
fixing saved search type
shanbady Aug 2, 2024
cfb9f97
Merge branch 'main' into shanbady/subscription-email-template-updates
shanbady Aug 2, 2024
f85b3f0
fix spacing
shanbady Aug 2, 2024
c5ac5e8
fixing tests
shanbady Aug 2, 2024
c6816c7
adding short subject in template and some style fixes
shanbady Aug 2, 2024
bd36833
fixing method
shanbady Aug 2, 2024
d212296
Merge branch 'main' into shanbady/subscription-email-template-updates
shanbady Aug 2, 2024
c5f9705
updating dashboard settings url to match upcoming changes in pr #1348
shanbady Aug 5, 2024
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 22 additions & 20 deletions learning_resources_search/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from django.db import models
from django.db.models import JSONField

from channels.constants import ChannelType
from channels.models import Channel
from main.models import TimestampedModel

Expand All @@ -29,11 +28,25 @@ class PercolateQuery(TimestampedModel):
)
users = models.ManyToManyField(User, related_name="percolate_queries")

def __str__(self):
return f"Percolate query {self.id}: {self.query}"
def source_label(self):
source_channel = self.source_channel()
if source_channel:
return source_channel.channel_type
else:
return "saved_search"

class Meta:
unique_together = (("source_type", "original_query"),)
def source_description(self):
channel = self.source_channel()
if channel:
return channel.title
return self.original_url_params()

def source_channel(self):
original_query_params = self.original_url_params()
channels_filtered = Channel.objects.filter(search_filter=original_query_params)
if channels_filtered.exists():
return channels_filtered.first()
return None

def original_url_params(self):
ignore_params = ["endpoint"]
Expand All @@ -43,19 +56,8 @@ def original_url_params(self):
}
return urlencode(defined_params, doseq=True)

def source_label(self):
original_query_params = self.original_url_params()
channels_filtered = Channel.objects.filter(search_filter=original_query_params)
if channels_filtered.exists():
return channels_filtered.first().channel_type
else:
return "saved_search"

def source_description(self):
original_query_params = self.original_url_params()
source_label = self.source_label()
def __str__(self):
return f"Percolate query {self.id}: {self.query}"

if source_label in ChannelType:
channel = Channel.objects.get(search_filter=original_query_params)
return channel.title
return self.original_url_params()
class Meta:
unique_together = (("source_type", "original_query"),)
99 changes: 83 additions & 16 deletions learning_resources_search/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.template.defaultfilters import pluralize
from opensearchpy.exceptions import NotFoundError, RequestError
from requests.models import PreparedRequest

from learning_resources.etl.constants import RESOURCE_FILE_ETL_SOURCES
from learning_resources.models import (
Expand Down Expand Up @@ -120,6 +122,8 @@ def _infer_percolate_group(percolate_query):
Infer the heading name for the percolate query to be
grouped under in the email
"""
if percolate_query.source_label() != "saved_search":
return percolate_query.source_description()
group_keys = ["department", "topic", "offered_by"]
original_query = OrderedDict(percolate_query.original_query)
for key, val in original_query.items():
Expand All @@ -132,10 +136,13 @@ def _infer_percolate_group(percolate_query):
return None


def _infer_search_url(percolate_query):
def _infer_percolate_group_url(percolate_query):
"""
Infer the search URL for the percolate query
"""
source_channel = percolate_query.source_channel()
if source_channel:
return frontend_absolute_url(source_channel.channel_url)
original_query = OrderedDict(percolate_query.original_query)
query_string_params = {k: v for k, v in original_query.items() if v}
if "endpoint" in query_string_params:
Expand Down Expand Up @@ -178,14 +185,27 @@ def _get_percolated_rows(resources, subscription_type):
percolated_users = set(percolated.values_list("users", flat=True))
all_users.update(percolated_users)
query = percolated.first()
search_url = _infer_percolate_group_url(query)
req = PreparedRequest()
req.prepare_url(search_url, {"resource": resource.id})
resource_url = req.url
source_channel = query.source_channel()
rows.extend(
[
{
"resource_url": resource.url,
"resource_url": resource_url,
"resource_title": resource.title,
"resource_image_url": resource.image.url
if resource.image
else "",
"resource_type": resource.resource_type,
"user_id": user,
"source_label": query.source_label(),
"source_channel_type": source_channel.channel_type
if source_channel
else "saved_search",
"group": _infer_percolate_group(query),
"search_url": _infer_search_url(query),
"search_url": search_url,
}
for user in percolated_users
]
Expand All @@ -209,7 +229,6 @@ def send_subscription_emails(self, subscription_type, period="daily"):
)
rows = _get_percolated_rows(new_learning_resources, subscription_type)
template_data = _group_percolated_rows(rows)

email_tasks = celery.group(
[
attempt_send_digest_email_batch.si(user_template_items)
Expand Down Expand Up @@ -816,6 +835,30 @@ def finish_recreate_index(results, backing_indices):
log.info("recreate_index has finished successfully!")


def _generate_subscription_digest_subject(
sample_course, source_name, unique_resource_types, total_count, shortform
):
prefix = "" if shortform else "MIT Learn: "

if sample_course["source_channel_type"] == "saved_search":
return (
f"{prefix}New"
f' "{source_name}" '
f"{unique_resource_types.pop().capitalize()}{pluralize(total_count)}"
)
preposition = "from"
if sample_course["source_channel_type"] == "topic":
preposition = "in"

suffix = "" if shortform else f": {sample_course['resource_title']}"

return (
f"{prefix}New"
f" {unique_resource_types.pop().capitalize()}{pluralize(total_count)} "
f"{preposition} {source_name}{suffix}"
)


@app.task(
acks_late=True,
reject_on_worker_lost=True,
Expand All @@ -824,16 +867,40 @@ def finish_recreate_index(results, backing_indices):
def attempt_send_digest_email_batch(user_template_items):
for user_id, template_data in user_template_items:
log.info("Sending email to user %s", user_id)
if not user_id:
continue
user = User.objects.get(id=user_id)
total_count = sum([len(template_data[group]) for group in template_data])
subject = f"{settings.MITOPEN_TITLE} New Learning Resources for You"
send_template_email(
[user.email],
subject,
"email/subscribed_channel_digest.html",
context={
"documents": template_data,
"total_count": total_count,
"subject": subject,
},
)

unique_resource_types = set()
for group in template_data:
total_count = len(template_data[group])
unique_resource_types.update(
[resource["resource_type"] for resource in template_data[group]]
)
subject = _generate_subscription_digest_subject(
template_data[group][0],
group,
list(unique_resource_types),
total_count,
shortform=False,
)
# generate a shorter subject for use in the template
short_subject = _generate_subscription_digest_subject(
template_data[group][0],
group,
list(unique_resource_types),
total_count,
shortform=True,
)
send_template_email(
[user.email],
subject,
"email/subscribed_channel_digest.html",
context={
"documents": template_data[group],
"total_count": total_count,
"subject": subject,
"resource_group": group,
"short_subject": short_subject,
},
)
47 changes: 47 additions & 0 deletions learning_resources_search/tasks_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
serialize_learning_resource_for_update,
)
from learning_resources_search.tasks import (
_generate_subscription_digest_subject,
_get_percolated_rows,
_group_percolated_rows,
_infer_percolate_group,
Expand Down Expand Up @@ -929,3 +930,49 @@ def get_percolator(res):
assert user.id == task_args[0]
for topic in topics:
assert topic in template_data


def test_subscription_digest_subject():
"""
Test that email generates a dynamic subject based
on the unique resource types included
"""
resource_types = {"program"}
sample_course = {"source_channel_type": "topic", "resource_title": "robotics"}

subject_line = _generate_subscription_digest_subject(
sample_course,
"electronics",
resource_types,
total_count=1,
shortform=False,
)
assert subject_line == "MIT Learn: New Program in electronics: robotics"

sample_course = {"source_channel_type": "podcast", "resource_title": "robotics"}
resource_types = {"program"}

subject_line = _generate_subscription_digest_subject(
sample_course,
"xpro",
resource_types,
total_count=9,
shortform=False,
)
assert subject_line == "MIT Learn: New Programs from xpro: robotics"

resource_types = {"podcast"}
subject_line = _generate_subscription_digest_subject(
sample_course,
"engineering",
resource_types,
total_count=19,
shortform=False,
)
assert subject_line == "MIT Learn: New Podcasts from engineering: robotics"

resource_types = {"course"}
subject_line = _generate_subscription_digest_subject(
sample_course, "management", resource_types, 19, shortform=True
)
assert subject_line == "New Courses from management"
64 changes: 54 additions & 10 deletions main/templates/email/email_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@
}
.button-td-primary:hover,
.button-a-primary:hover {
background: #ffffff !important;
border-color: #a31f34 !important;
}

Expand Down Expand Up @@ -231,17 +230,20 @@
margin: 0;
padding: 0 !important;
mso-line-height-rule: exactly;
background-color: #ffffff;
background-color: #f3f4f8;
"
>
<center style="width: 100%; background-color: #ffffff">
<center style="background-color: #f3f4f8">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #ffffff;">
<tr>
<td>
<![endif]-->

<div style="max-width: 680px; margin: 0 auto" class="email-container">
<div
style="max-width: 680px; background: #fff; margin: 0 auto"
class="email-container"
>
<!--[if mso]>
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="680">
<tr>
Expand All @@ -261,9 +263,9 @@
<tr>
<td style="padding: 20px">
{% block logo %}
<h1>
<h1 style="text-align: center">
<img
src="https://mitopen.odl.mit.edu/static/images/mit-logo.jpg"
src="{{STATIC_URL}}/static/images/mit-logo-transparent5.jpg"
width="113"
height="60"
alt="MIT-Open"
Expand All @@ -274,7 +276,9 @@ <h1>
font-family: sans-serif;
font-size: 15px;
line-height: 15px;
display: block;
color: #a31f34;
margin: 0 auto;
margin-bottom: -10px;
"
/>
Expand All @@ -297,13 +301,53 @@ <h1>
</td>
</tr>
<!-- Clear Spacer : END -->

<tr>
<td style="padding: 0 20px; text-align: left">--</td>
</tr>
</table>
<!-- Email Body : END -->
<!-- Email Footer : BEGIN -->
<table
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
width="100%"
style="font-size: 12px"
>
<tr>
<td
style="
padding: 20px;
font-family: sans-serif;
font-size: 12px;
line-height: 15px;
color: #000;
text-align: center;
"
>
{% block footer %}

If you don't want to receive these emails in the future, you can
<a
href="{{APP_BASE_URL}}/dashboard/settings"
style="text-decoration: underline"
>edit your settings</a
>
or
<a
href="{{APP_BASE_URL}}/dashboard/settings"
style="text-decoration: underline"
>unsubscribe</a
>. <br /><br />

{% endblock %}
{% block footer-address %}
<b>MIT Open Learning</b>&nbsp;&bull;&nbsp;600 Technology Square,
NE49-2000&nbsp;&bull;&nbsp;Cambridge, MA
02139&nbsp;&bull;&nbsp;USA
{% endblock %}
</td>
</tr>
</table>
<!-- Email Footer : END -->
<!--[if mso]>
</td>
</tr>
Expand Down
Loading