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

Send email notification on mention #9339

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion h/emails/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from h.emails import reply_notification, reset_password, signup

__all__ = ("reply_notification", "reset_password", "signup")
__all__ = ("mention_notification", "reply_notification", "reset_password", "signup")
30 changes: 30 additions & 0 deletions h/emails/mention_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pyramid.renderers import render
from pyramid.request import Request

from h import links
from h.emails.util import get_user_url
from h.notification.mention import Notification


def generate(request: Request, notification: Notification):
context = {
"user_url": get_user_url(notification.mentioning_user, request),
"user_display_name": notification.mentioning_user.display_name
or notification.mentioning_user.username,
"annotation_url": links.incontext_link(request, notification.annotation)
or request.route_url("annotation", id=notification.annotation.id),
"document_title": notification.document.title
or notification.annotation.target_uri,
"document_url": notification.annotation.target_uri,
"annotation": notification.annotation,
}

subject = f"{context['user_display_name']} has mentioned you in an annotation"
text = render(
"h:templates/emails/mention_notification.txt.jinja2", context, request=request
)
html = render(
"h:templates/emails/mention_notification.html.jinja2", context, request=request
)

return [notification.mentioned_user.email], subject, text, html
12 changes: 3 additions & 9 deletions h/emails/reply_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pyramid.request import Request

from h import links
from h.emails.util import get_user_url
from h.models import Subscriptions
from h.notification.reply import Notification
from h.services import SubscriptionService
Expand All @@ -27,7 +28,7 @@ def generate(request: Request, notification: Notification):
"parent": notification.parent,
"parent_user_display_name": notification.parent_user.display_name
or notification.parent_user.username,
"parent_user_url": _get_user_url(notification.parent_user, request),
"parent_user_url": get_user_url(notification.parent_user, request),
"unsubscribe_url": request.route_url(
"unsubscribe",
token=unsubscribe_token,
Expand All @@ -38,7 +39,7 @@ def generate(request: Request, notification: Notification):
or request.route_url("annotation", id=notification.reply.id),
"reply_user_display_name": notification.reply_user.display_name
or notification.reply_user.username,
"reply_user_url": _get_user_url(notification.reply_user, request),
"reply_user_url": get_user_url(notification.reply_user, request),
}

subject = f"{context['reply_user_display_name']} has replied to your annotation"
Expand All @@ -50,10 +51,3 @@ def generate(request: Request, notification: Notification):
)

return [notification.parent_user.email], subject, text, html


def _get_user_url(user, request):
if user.authority == request.default_authority:
return request.route_url("stream.user_query", user=user.username)

return None
10 changes: 10 additions & 0 deletions h/emails/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pyramid.request import Request

from h.models import User


def get_user_url(user: User, request: Request) -> str | None:
if user.authority == request.default_authority:
return request.route_url("stream.user_query", user=user.username)

return None
61 changes: 61 additions & 0 deletions h/notification/mention.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import logging
from dataclasses import dataclass

from h.models import Annotation, Document, User

logger = logging.getLogger(__name__)


@dataclass
class Notification:
"""A data structure representing a mention notification in an annotation."""

mentioning_user: User
mentioned_user: User
annotation: Annotation
document: Document


def get_notifications(request, annotation: Annotation, action) -> list[Notification]:
# Only send notifications when new annotations are created
if action != "create":
return []

user_service = request.find_service(name="user")

# If the mentioning user doesn't exist (anymore), we can't send emails, but
# this would be super weird, so log a warning.
mentioning_user = user_service.fetch(annotation.userid)
if mentioning_user is None:
logger.warning(
"user who just mentioned another user no longer exists: %s",
annotation.userid,
)
return []

notifications = []
for mention in annotation.mentions:
# If the mentioning user doesn't exist (anymore), we can't send emails
mentioned_user = user_service.fetch(mention.user.userid)
if mentioned_user is None:
continue

# If mentioned user doesn't have an email address we can't email them.
if not mention.user.email:
continue

# Do not notify users about their own replies
if mentioning_user == mentioned_user:
continue

# If the annotation doesn't have a document, we can't send an email.
if annotation.document is None:
continue

notifications.append(
Notification(
mentioning_user, mentioned_user, annotation, annotation.document
)
)

return notifications
27 changes: 26 additions & 1 deletion h/subscribers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from h import __version__, emails
from h.events import AnnotationEvent
from h.exceptions import RealtimeMessageQueueError
from h.notification import reply
from h.notification import mention, reply
from h.services.annotation_read import AnnotationReadService
from h.tasks import mailer

Expand Down Expand Up @@ -89,3 +89,28 @@ def send_reply_notifications(event):
except OperationalError as err: # pragma: no cover
# We could not connect to rabbit! So carry on
report_exception(err)


@subscriber(AnnotationEvent)
def send_mention_notifications(event):
"""Send mention notifications triggered by a mention event."""

request = event.request

with request.tm:
annotation = request.find_service(AnnotationReadService).get_annotation_by_id(
event.annotation_id,
)
notifications = mention.get_notifications(request, annotation, event.action)

if not notifications:
return

for notification in notifications:
send_params = emails.mention_notification.generate(request, notification)

try:
mailer.send.delay(*send_params)
except OperationalError as err: # pragma: no cover
# We could not connect to rabbit! So carry on
report_exception(err)
26 changes: 26 additions & 0 deletions h/templates/emails/mention_notification.html.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<p>
{% if user_url %}
<a href="{{ user_url }}">{{ user_display_name }}</a>
{% else %}
{{ user_display_name }}
{% endif %}
has
<a href="{{ annotation_url }}">mentioned you</a>
on
<a href="{{ document_url }}">&ldquo;{{ document_title }}&rdquo;</a>:
</p>

<p>
On
{{ annotation.updated | human_timestamp }}
{% if user_url %}
<a href="{{ user_url }}">{{ user_display_name }}</a>
{% else %}
{{ user_display_name }}
{% endif %}
commented:
</p>

<blockquote>{{ (annotation.text or "")|striptags }}</blockquote>

<p><a href="{{ annotation_url }}">View the thread and respond</a>.</p>
7 changes: 7 additions & 0 deletions h/templates/emails/mention_notification.txt.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{ user_display_name }} has replied to your annotation on "{{ document_title }}":

On {{ annotation.updated | human_timestamp }} {{ user_display_name }} replied:

> {{ (annotation.text or "")|striptags }}

View the thread and respond: {{ annotation_url }}
4 changes: 2 additions & 2 deletions h/templates/emails/reply_notification.html.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
commented:
</p>

<blockquote>{{ parent.text or "" }}</blockquote>
<blockquote>{{ (parent.text or "")|striptags }}</blockquote>

<p>
On
Expand All @@ -34,7 +34,7 @@
replied:
</p>

<blockquote>{{ reply.text or "" }}</blockquote>
<blockquote>{{ (reply.text or "")|striptags }}</blockquote>

<p><a href="{{ reply_url }}">View the thread and respond</a>.</p>

Expand Down
4 changes: 2 additions & 2 deletions h/templates/emails/reply_notification.txt.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

On {{ parent.created | human_timestamp }} {{ parent_user_display_name }} commented:

> {{ parent.text or "" }}
> {{ (parent.text or "")|striptags }}

On {{ reply.created | human_timestamp }} {{ reply_user_display_name }} replied:

> {{ reply.text or "" }}
> {{ (reply.text or "")|striptags }}

View the thread and respond: {{ reply_url }}

Expand Down
Loading
Loading