Refactor email summary generation. (#9260)
* Fixes a case where no summary text was returned. * The use of messages_from_person vs. messages_from_person_and_others was tweaked to depend on whether there was 1 sender or multiple senders, not based on if there was 1 room or multiple rooms.
This commit is contained in:
parent
18ab35284a
commit
5d38a3c97f
|
@ -0,0 +1 @@
|
||||||
|
Refactor the generation of summary text for email notifications.
|
|
@ -267,9 +267,21 @@ class Mailer:
|
||||||
fallback_to_members=True,
|
fallback_to_members=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
summary_text = await self.make_summary_text(
|
if len(notifs_by_room) == 1:
|
||||||
notifs_by_room, state_by_room, notif_events, user_id, reason
|
# Only one room has new stuff
|
||||||
)
|
room_id = list(notifs_by_room.keys())[0]
|
||||||
|
|
||||||
|
summary_text = await self.make_summary_text_single_room(
|
||||||
|
room_id,
|
||||||
|
notifs_by_room[room_id],
|
||||||
|
state_by_room[room_id],
|
||||||
|
notif_events,
|
||||||
|
user_id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
summary_text = await self.make_summary_text(
|
||||||
|
notifs_by_room, state_by_room, notif_events, reason
|
||||||
|
)
|
||||||
|
|
||||||
template_vars = {
|
template_vars = {
|
||||||
"user_display_name": user_display_name,
|
"user_display_name": user_display_name,
|
||||||
|
@ -492,138 +504,177 @@ class Mailer:
|
||||||
if "url" in event.content:
|
if "url" in event.content:
|
||||||
messagevars["image_url"] = event.content["url"]
|
messagevars["image_url"] = event.content["url"]
|
||||||
|
|
||||||
|
async def make_summary_text_single_room(
|
||||||
|
self,
|
||||||
|
room_id: str,
|
||||||
|
notifs: List[Dict[str, Any]],
|
||||||
|
room_state_ids: StateMap[str],
|
||||||
|
notif_events: Dict[str, EventBase],
|
||||||
|
user_id: str,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Make a summary text for the email when only a single room has notifications.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id: The ID of the room.
|
||||||
|
notifs: The notifications for this room.
|
||||||
|
room_state_ids: The state map for the room.
|
||||||
|
notif_events: A map of event ID -> notification event.
|
||||||
|
user_id: The user receiving the notification.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The summary text.
|
||||||
|
"""
|
||||||
|
# If the room has some kind of name, use it, but we don't
|
||||||
|
# want the generated-from-names one here otherwise we'll
|
||||||
|
# end up with, "new message from Bob in the Bob room"
|
||||||
|
room_name = await calculate_room_name(
|
||||||
|
self.store, room_state_ids, user_id, fallback_to_members=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# See if one of the notifs is an invite event for the user
|
||||||
|
invite_event = None
|
||||||
|
for n in notifs:
|
||||||
|
ev = notif_events[n["event_id"]]
|
||||||
|
if ev.type == EventTypes.Member and ev.state_key == user_id:
|
||||||
|
if ev.content.get("membership") == Membership.INVITE:
|
||||||
|
invite_event = ev
|
||||||
|
break
|
||||||
|
|
||||||
|
if invite_event:
|
||||||
|
inviter_member_event_id = room_state_ids.get(
|
||||||
|
("m.room.member", invite_event.sender)
|
||||||
|
)
|
||||||
|
inviter_name = invite_event.sender
|
||||||
|
if inviter_member_event_id:
|
||||||
|
inviter_member_event = await self.store.get_event(
|
||||||
|
inviter_member_event_id, allow_none=True
|
||||||
|
)
|
||||||
|
if inviter_member_event:
|
||||||
|
inviter_name = name_from_member_event(inviter_member_event)
|
||||||
|
|
||||||
|
if room_name is None:
|
||||||
|
return self.email_subjects.invite_from_person % {
|
||||||
|
"person": inviter_name,
|
||||||
|
"app": self.app_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.email_subjects.invite_from_person_to_room % {
|
||||||
|
"person": inviter_name,
|
||||||
|
"room": room_name,
|
||||||
|
"app": self.app_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(notifs) == 1:
|
||||||
|
# There is just the one notification, so give some detail
|
||||||
|
sender_name = None
|
||||||
|
event = notif_events[notifs[0]["event_id"]]
|
||||||
|
if ("m.room.member", event.sender) in room_state_ids:
|
||||||
|
state_event_id = room_state_ids[("m.room.member", event.sender)]
|
||||||
|
state_event = await self.store.get_event(state_event_id)
|
||||||
|
sender_name = name_from_member_event(state_event)
|
||||||
|
|
||||||
|
if sender_name is not None and room_name is not None:
|
||||||
|
return self.email_subjects.message_from_person_in_room % {
|
||||||
|
"person": sender_name,
|
||||||
|
"room": room_name,
|
||||||
|
"app": self.app_name,
|
||||||
|
}
|
||||||
|
elif sender_name is not None:
|
||||||
|
return self.email_subjects.message_from_person % {
|
||||||
|
"person": sender_name,
|
||||||
|
"app": self.app_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
# The sender is unknown, just use the room name (or ID).
|
||||||
|
return self.email_subjects.messages_in_room % {
|
||||||
|
"room": room_name or room_id,
|
||||||
|
"app": self.app_name,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# There's more than one notification for this room, so just
|
||||||
|
# say there are several
|
||||||
|
if room_name is not None:
|
||||||
|
return self.email_subjects.messages_in_room % {
|
||||||
|
"room": room_name,
|
||||||
|
"app": self.app_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self.make_summary_text_from_member_events(
|
||||||
|
room_id, notifs, room_state_ids, notif_events
|
||||||
|
)
|
||||||
|
|
||||||
async def make_summary_text(
|
async def make_summary_text(
|
||||||
self,
|
self,
|
||||||
notifs_by_room: Dict[str, List[Dict[str, Any]]],
|
notifs_by_room: Dict[str, List[Dict[str, Any]]],
|
||||||
room_state_ids: Dict[str, StateMap[str]],
|
room_state_ids: Dict[str, StateMap[str]],
|
||||||
notif_events: Dict[str, EventBase],
|
notif_events: Dict[str, EventBase],
|
||||||
user_id: str,
|
|
||||||
reason: Dict[str, Any],
|
reason: Dict[str, Any],
|
||||||
):
|
) -> str:
|
||||||
if len(notifs_by_room) == 1:
|
"""
|
||||||
# Only one room has new stuff
|
Make a summary text for the email when multiple rooms have notifications.
|
||||||
room_id = list(notifs_by_room.keys())[0]
|
|
||||||
|
|
||||||
# If the room has some kind of name, use it, but we don't
|
Args:
|
||||||
# want the generated-from-names one here otherwise we'll
|
notifs_by_room: A map of room ID to the notifications for that room.
|
||||||
# end up with, "new message from Bob in the Bob room"
|
room_state_ids: A map of room ID to the state map for that room.
|
||||||
room_name = await calculate_room_name(
|
notif_events: A map of event ID -> notification event.
|
||||||
self.store, room_state_ids[room_id], user_id, fallback_to_members=False
|
reason: The reason this notification is being sent.
|
||||||
)
|
|
||||||
|
|
||||||
# See if one of the notifs is an invite event for the user
|
Returns:
|
||||||
invite_event = None
|
The summary text.
|
||||||
for n in notifs_by_room[room_id]:
|
"""
|
||||||
ev = notif_events[n["event_id"]]
|
# Stuff's happened in multiple different rooms
|
||||||
if ev.type == EventTypes.Member and ev.state_key == user_id:
|
# ...but we still refer to the 'reason' room which triggered the mail
|
||||||
if ev.content.get("membership") == Membership.INVITE:
|
if reason["room_name"] is not None:
|
||||||
invite_event = ev
|
return self.email_subjects.messages_in_room_and_others % {
|
||||||
break
|
"room": reason["room_name"],
|
||||||
|
"app": self.app_name,
|
||||||
|
}
|
||||||
|
|
||||||
if invite_event:
|
room_id = reason["room_id"]
|
||||||
inviter_member_event_id = room_state_ids[room_id].get(
|
return await self.make_summary_text_from_member_events(
|
||||||
("m.room.member", invite_event.sender)
|
room_id, notifs_by_room[room_id], room_state_ids[room_id], notif_events
|
||||||
)
|
)
|
||||||
inviter_name = invite_event.sender
|
|
||||||
if inviter_member_event_id:
|
|
||||||
inviter_member_event = await self.store.get_event(
|
|
||||||
inviter_member_event_id, allow_none=True
|
|
||||||
)
|
|
||||||
if inviter_member_event:
|
|
||||||
inviter_name = name_from_member_event(inviter_member_event)
|
|
||||||
|
|
||||||
if room_name is None:
|
async def make_summary_text_from_member_events(
|
||||||
return self.email_subjects.invite_from_person % {
|
self,
|
||||||
"person": inviter_name,
|
room_id: str,
|
||||||
"app": self.app_name,
|
notifs: List[Dict[str, Any]],
|
||||||
}
|
room_state_ids: StateMap[str],
|
||||||
else:
|
notif_events: Dict[str, EventBase],
|
||||||
return self.email_subjects.invite_from_person_to_room % {
|
) -> str:
|
||||||
"person": inviter_name,
|
"""
|
||||||
"room": room_name,
|
Make a summary text for the email when only a single room has notifications.
|
||||||
"app": self.app_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
sender_name = None
|
Args:
|
||||||
if len(notifs_by_room[room_id]) == 1:
|
room_id: The ID of the room.
|
||||||
# There is just the one notification, so give some detail
|
notifs: The notifications for this room.
|
||||||
event = notif_events[notifs_by_room[room_id][0]["event_id"]]
|
room_state_ids: The state map for the room.
|
||||||
if ("m.room.member", event.sender) in room_state_ids[room_id]:
|
notif_events: A map of event ID -> notification event.
|
||||||
state_event_id = room_state_ids[room_id][
|
|
||||||
("m.room.member", event.sender)
|
|
||||||
]
|
|
||||||
state_event = await self.store.get_event(state_event_id)
|
|
||||||
sender_name = name_from_member_event(state_event)
|
|
||||||
|
|
||||||
if sender_name is not None and room_name is not None:
|
Returns:
|
||||||
return self.email_subjects.message_from_person_in_room % {
|
The summary text.
|
||||||
"person": sender_name,
|
"""
|
||||||
"room": room_name,
|
# If the room doesn't have a name, say who the messages
|
||||||
"app": self.app_name,
|
# are from explicitly to avoid, "messages in the Bob room"
|
||||||
}
|
sender_ids = {notif_events[n["event_id"]].sender for n in notifs}
|
||||||
elif sender_name is not None:
|
|
||||||
return self.email_subjects.message_from_person % {
|
|
||||||
"person": sender_name,
|
|
||||||
"app": self.app_name,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# There's more than one notification for this room, so just
|
|
||||||
# say there are several
|
|
||||||
if room_name is not None:
|
|
||||||
return self.email_subjects.messages_in_room % {
|
|
||||||
"room": room_name,
|
|
||||||
"app": self.app_name,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# If the room doesn't have a name, say who the messages
|
|
||||||
# are from explicitly to avoid, "messages in the Bob room"
|
|
||||||
sender_ids = list(
|
|
||||||
{
|
|
||||||
notif_events[n["event_id"]].sender
|
|
||||||
for n in notifs_by_room[room_id]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
member_events = await self.store.get_events(
|
member_events = await self.store.get_events(
|
||||||
[
|
[room_state_ids[("m.room.member", s)] for s in sender_ids]
|
||||||
room_state_ids[room_id][("m.room.member", s)]
|
)
|
||||||
for s in sender_ids
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.email_subjects.messages_from_person % {
|
# There was a single sender.
|
||||||
"person": descriptor_from_member_events(member_events.values()),
|
if len(sender_ids) == 1:
|
||||||
"app": self.app_name,
|
return self.email_subjects.messages_from_person % {
|
||||||
}
|
"person": descriptor_from_member_events(member_events.values()),
|
||||||
else:
|
"app": self.app_name,
|
||||||
# Stuff's happened in multiple different rooms
|
}
|
||||||
|
|
||||||
# ...but we still refer to the 'reason' room which triggered the mail
|
# There was more than one sender, use the first one and a tweaked template.
|
||||||
if reason["room_name"] is not None:
|
return self.email_subjects.messages_from_person_and_others % {
|
||||||
return self.email_subjects.messages_in_room_and_others % {
|
"person": descriptor_from_member_events(list(member_events.values())[:1]),
|
||||||
"room": reason["room_name"],
|
"app": self.app_name,
|
||||||
"app": self.app_name,
|
}
|
||||||
}
|
|
||||||
else:
|
|
||||||
# If the reason room doesn't have a name, say who the messages
|
|
||||||
# are from explicitly to avoid, "messages in the Bob room"
|
|
||||||
room_id = reason["room_id"]
|
|
||||||
|
|
||||||
sender_ids = list(
|
|
||||||
{
|
|
||||||
notif_events[n["event_id"]].sender
|
|
||||||
for n in notifs_by_room[room_id]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
member_events = await self.store.get_events(
|
|
||||||
[room_state_ids[room_id][("m.room.member", s)] for s in sender_ids]
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.email_subjects.messages_from_person_and_others % {
|
|
||||||
"person": descriptor_from_member_events(member_events.values()),
|
|
||||||
"app": self.app_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
def make_room_link(self, room_id: str) -> str:
|
def make_room_link(self, room_id: str) -> str:
|
||||||
if self.hs.config.email_riot_base_url:
|
if self.hs.config.email_riot_base_url:
|
||||||
|
|
|
@ -187,6 +187,36 @@ class EmailPusherTests(HomeserverTestCase):
|
||||||
# We should get emailed about those messages
|
# We should get emailed about those messages
|
||||||
self._check_for_mail()
|
self._check_for_mail()
|
||||||
|
|
||||||
|
def test_multiple_rooms(self):
|
||||||
|
# We want to test multiple notifications from multiple rooms, so we pause
|
||||||
|
# processing of push while we send messages.
|
||||||
|
self.pusher._pause_processing()
|
||||||
|
|
||||||
|
# Create a simple room with multiple other users
|
||||||
|
rooms = [
|
||||||
|
self.helper.create_room_as(self.user_id, tok=self.access_token),
|
||||||
|
self.helper.create_room_as(self.user_id, tok=self.access_token),
|
||||||
|
]
|
||||||
|
|
||||||
|
for r, other in zip(rooms, self.others):
|
||||||
|
self.helper.invite(
|
||||||
|
room=r, src=self.user_id, tok=self.access_token, targ=other.id
|
||||||
|
)
|
||||||
|
self.helper.join(room=r, user=other.id, tok=other.token)
|
||||||
|
|
||||||
|
# The other users send some messages
|
||||||
|
self.helper.send(rooms[0], body="Hi!", tok=self.others[0].token)
|
||||||
|
self.helper.send(rooms[1], body="There!", tok=self.others[1].token)
|
||||||
|
self.helper.send(rooms[1], body="There!", tok=self.others[1].token)
|
||||||
|
|
||||||
|
# Nothing should have happened yet, as we're paused.
|
||||||
|
assert not self.email_attempts
|
||||||
|
|
||||||
|
self.pusher._resume_processing()
|
||||||
|
|
||||||
|
# We should get emailed about those messages
|
||||||
|
self._check_for_mail()
|
||||||
|
|
||||||
def test_encrypted_message(self):
|
def test_encrypted_message(self):
|
||||||
room = self.helper.create_room_as(self.user_id, tok=self.access_token)
|
room = self.helper.create_room_as(self.user_id, tok=self.access_token)
|
||||||
self.helper.invite(
|
self.helper.invite(
|
||||||
|
|
Loading…
Reference in New Issue