mirror of https://github.com/zulip/zulip.git
settings: Add email notifications for the followed topics.
This commit makes it possible for users to control the email notifications for messages sent to followed topics via a global notification setting. Although there is no support for configuring this setting through the UI yet. Add five new fields to the UserBaseSettings class for the "followed topic notifications" feature, similar to stream notifications. But this commit consists only of the implementation of email notifications.
This commit is contained in:
parent
e549c416db
commit
5e5538886f
|
@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 8.0
|
||||
|
||||
**Feature level 189**
|
||||
|
||||
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
|
||||
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
|
||||
Added `enable_followed_topic_email_notifications` field to control
|
||||
email notifications for messages sent to followed topics.
|
||||
|
||||
**Feature level 188**
|
||||
|
||||
* [`POST /users/me/muted_users/{muted_user_id}`](/api/mute-user),
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
{% trans %}You are receiving this because everyone was mentioned in #{{ stream_name }}.{% endtrans %}<br />
|
||||
{% elif stream_email_notify %}
|
||||
{% trans %}You are receiving this because you have email notifications enabled for #{{ stream_name }}.{% endtrans %}<br />
|
||||
{% elif followed_topic_email_notify %}
|
||||
{% trans %}You are receiving this because you have email notifications enabled for topics you follow.{% endtrans %}<br />
|
||||
{% endif %}
|
||||
{% if reply_to_zulip %}
|
||||
{% trans notif_url=realm_uri + "/#settings/notifications" %}Reply to this email directly, <a href="{{ narrow_url }}">view it in {{ realm_name }} Zulip</a>, or <a href="{{ notif_url }}">manage email preferences</a>.{% endtrans %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% if show_message_content %}
|
||||
{% if group_pm %} {% trans %}Group DMs with {{ huddle_display_name }}{% endtrans %}
|
||||
{% elif private_message %} {% trans %}DMs with {{ sender_str }}{% endtrans %}
|
||||
{% elif stream_email_notify or mention %}
|
||||
{% elif stream_email_notify or mention or followed_topic_email_notify %}
|
||||
{#
|
||||
Some email clients, like Gmail Web (as of 2022), will auto-thread
|
||||
emails that share a subject and recipients, but will disregard
|
||||
|
|
|
@ -29,6 +29,8 @@ See {{ alert_notif_url }} for more details.
|
|||
{% trans %}You are receiving this because everyone was mentioned in #{{ stream_name }}.{% endtrans %}
|
||||
{% elif stream_email_notify %}
|
||||
{% trans %}You are receiving this because you have email notifications enabled for #{{ stream_name }}.{% endtrans %}
|
||||
{% elif followed_topic_email_notify %}
|
||||
{% trans %}You are receiving this because you have email notifications enabled for topics you follow.{% endtrans %}
|
||||
{% endif %}
|
||||
|
||||
{% if reply_to_zulip %}
|
||||
|
|
|
@ -473,6 +473,7 @@ def do_update_message(
|
|||
event["pm_mention_email_disabled_user_ids"] = list(info.pm_mention_email_disabled_user_ids)
|
||||
event["stream_push_user_ids"] = list(info.stream_push_user_ids)
|
||||
event["stream_email_user_ids"] = list(info.stream_email_user_ids)
|
||||
event["followed_topic_email_user_ids"] = list(info.followed_topic_email_user_ids)
|
||||
event["muted_sender_user_ids"] = list(info.muted_sender_user_ids)
|
||||
event["prior_mention_user_ids"] = list(prior_mention_user_ids)
|
||||
event["presence_idle_user_ids"] = filter_presence_idle_user_ids(info.active_user_ids)
|
||||
|
|
|
@ -166,6 +166,7 @@ class RecipientInfoResult:
|
|||
stream_email_user_ids: Set[int]
|
||||
stream_push_user_ids: Set[int]
|
||||
wildcard_mention_user_ids: Set[int]
|
||||
followed_topic_email_user_ids: Set[int]
|
||||
muted_sender_user_ids: Set[int]
|
||||
um_eligible_user_ids: Set[int]
|
||||
long_term_idle_user_ids: Set[int]
|
||||
|
@ -196,6 +197,7 @@ def get_recipient_info(
|
|||
stream_push_user_ids: Set[int] = set()
|
||||
stream_email_user_ids: Set[int] = set()
|
||||
wildcard_mention_user_ids: Set[int] = set()
|
||||
followed_topic_email_user_ids: Set[int] = set()
|
||||
muted_sender_user_ids: Set[int] = get_muting_users(sender_id)
|
||||
|
||||
if recipient.type == Recipient.PERSONAL:
|
||||
|
@ -214,6 +216,7 @@ def get_recipient_info(
|
|||
get_subscriptions_for_send_message(
|
||||
realm_id=realm_id,
|
||||
stream_id=stream_topic.stream_id,
|
||||
topic_name=stream_topic.topic_name,
|
||||
possible_wildcard_mention=possible_wildcard_mention,
|
||||
possibly_mentioned_user_ids=possibly_mentioned_user_ids,
|
||||
)
|
||||
|
@ -223,12 +226,16 @@ def get_recipient_info(
|
|||
),
|
||||
user_profile_push_notifications=F("user_profile__enable_stream_push_notifications"),
|
||||
user_profile_wildcard_mentions_notify=F("user_profile__wildcard_mentions_notify"),
|
||||
followed_topic_email_notifications=F(
|
||||
"user_profile__enable_followed_topic_email_notifications"
|
||||
),
|
||||
)
|
||||
.values(
|
||||
"user_profile_id",
|
||||
"push_notifications",
|
||||
"email_notifications",
|
||||
"wildcard_mentions_notify",
|
||||
"followed_topic_email_notifications",
|
||||
"user_profile_email_notifications",
|
||||
"user_profile_push_notifications",
|
||||
"user_profile_wildcard_mentions_notify",
|
||||
|
@ -257,6 +264,21 @@ def get_recipient_info(
|
|||
stream_push_user_ids = notification_recipients("push_notifications")
|
||||
stream_email_user_ids = notification_recipients("email_notifications")
|
||||
|
||||
def followed_topic_notification_recipients(setting: str) -> Set[int]:
|
||||
return {
|
||||
row["user_profile_id"]
|
||||
for row in subscription_rows
|
||||
if user_id_to_visibility_policy.get(
|
||||
row["user_profile_id"], UserTopic.VisibilityPolicy.INHERIT
|
||||
)
|
||||
== UserTopic.VisibilityPolicy.FOLLOWED
|
||||
and row["followed_topic_" + setting]
|
||||
}
|
||||
|
||||
followed_topic_email_user_ids = followed_topic_notification_recipients(
|
||||
"email_notifications"
|
||||
)
|
||||
|
||||
if possible_wildcard_mention:
|
||||
# We calculate `wildcard_mention_user_ids` only if there's a possible
|
||||
# wildcard mention in the message. This is important so as to avoid
|
||||
|
@ -381,6 +403,7 @@ def get_recipient_info(
|
|||
stream_push_user_ids=stream_push_user_ids,
|
||||
stream_email_user_ids=stream_email_user_ids,
|
||||
wildcard_mention_user_ids=wildcard_mention_user_ids,
|
||||
followed_topic_email_user_ids=followed_topic_email_user_ids,
|
||||
muted_sender_user_ids=muted_sender_user_ids,
|
||||
um_eligible_user_ids=um_eligible_user_ids,
|
||||
long_term_idle_user_ids=long_term_idle_user_ids,
|
||||
|
@ -564,6 +587,7 @@ def build_message_send_dict(
|
|||
pm_mention_push_disabled_user_ids=info.pm_mention_push_disabled_user_ids,
|
||||
stream_push_user_ids=info.stream_push_user_ids,
|
||||
stream_email_user_ids=info.stream_email_user_ids,
|
||||
followed_topic_email_user_ids=info.followed_topic_email_user_ids,
|
||||
muted_sender_user_ids=info.muted_sender_user_ids,
|
||||
um_eligible_user_ids=info.um_eligible_user_ids,
|
||||
long_term_idle_user_ids=info.long_term_idle_user_ids,
|
||||
|
@ -588,6 +612,7 @@ def create_user_messages(
|
|||
stream_push_user_ids: AbstractSet[int],
|
||||
stream_email_user_ids: AbstractSet[int],
|
||||
mentioned_user_ids: AbstractSet[int],
|
||||
followed_topic_email_user_ids: AbstractSet[int],
|
||||
mark_as_read_user_ids: Set[int],
|
||||
limit_unread_user_ids: Optional[Set[int]],
|
||||
scheduled_message_to_self: bool,
|
||||
|
@ -648,6 +673,7 @@ def create_user_messages(
|
|||
user_profile_id in long_term_idle_user_ids
|
||||
and user_profile_id not in stream_push_user_ids
|
||||
and user_profile_id not in stream_email_user_ids
|
||||
and user_profile_id not in followed_topic_email_user_ids
|
||||
and is_stream_message
|
||||
and int(flags) == 0
|
||||
):
|
||||
|
@ -761,6 +787,7 @@ def do_send_messages(
|
|||
stream_push_user_ids=send_request.stream_push_user_ids,
|
||||
stream_email_user_ids=send_request.stream_email_user_ids,
|
||||
mentioned_user_ids=mentioned_user_ids,
|
||||
followed_topic_email_user_ids=send_request.followed_topic_email_user_ids,
|
||||
mark_as_read_user_ids=mark_as_read_user_ids,
|
||||
limit_unread_user_ids=send_request.limit_unread_user_ids,
|
||||
scheduled_message_to_self=scheduled_message_to_self,
|
||||
|
@ -861,6 +888,7 @@ def do_send_messages(
|
|||
stream_push_user_ids=send_request.stream_push_user_ids,
|
||||
stream_email_user_ids=send_request.stream_email_user_ids,
|
||||
wildcard_mention_user_ids=send_request.wildcard_mention_user_ids,
|
||||
followed_topic_email_user_ids=send_request.followed_topic_email_user_ids,
|
||||
muted_sender_user_ids=send_request.muted_sender_user_ids,
|
||||
all_bot_user_ids=send_request.all_bot_user_ids,
|
||||
)
|
||||
|
@ -886,6 +914,7 @@ def do_send_messages(
|
|||
stream_push_user_ids=list(send_request.stream_push_user_ids),
|
||||
stream_email_user_ids=list(send_request.stream_email_user_ids),
|
||||
wildcard_mention_user_ids=list(send_request.wildcard_mention_user_ids),
|
||||
followed_topic_email_user_ids=list(send_request.followed_topic_email_user_ids),
|
||||
muted_sender_user_ids=list(send_request.muted_sender_user_ids),
|
||||
all_bot_user_ids=list(send_request.all_bot_user_ids),
|
||||
disable_external_notifications=send_request.disable_external_notifications,
|
||||
|
|
|
@ -457,6 +457,7 @@ def do_send_missedmessage_events_reply_in_zulip(
|
|||
personal_mentioned=personal_mentioned,
|
||||
wildcard_mentioned="wildcard_mentioned" in unique_triggers,
|
||||
stream_email_notify="stream_email_notify" in unique_triggers,
|
||||
followed_topic_email_notify="followed_topic_email_notify" in unique_triggers,
|
||||
mention_count=triggers.count("mentioned") + triggers.count("wildcard_mentioned"),
|
||||
mentioned_user_group_name=mentioned_user_group_name,
|
||||
)
|
||||
|
@ -508,7 +509,11 @@ def do_send_missedmessage_events_reply_in_zulip(
|
|||
context.update(huddle_display_name=huddle_display_name)
|
||||
elif missed_messages[0]["message"].recipient.type == Recipient.PERSONAL:
|
||||
context.update(private_message=True)
|
||||
elif context["mention"] or context["stream_email_notify"]:
|
||||
elif (
|
||||
context["mention"]
|
||||
or context["stream_email_notify"]
|
||||
or context["followed_topic_email_notify"]
|
||||
):
|
||||
# Keep only the senders who actually mentioned the user
|
||||
if context["mention"]:
|
||||
senders = list(
|
||||
|
|
|
@ -156,6 +156,8 @@ class SendMessageRequest:
|
|||
pm_mention_email_disabled_user_ids: Set[int]
|
||||
stream_push_user_ids: Set[int]
|
||||
stream_email_user_ids: Set[int]
|
||||
# IDs of users who have followed the topic the message is being sent to, and have the followed topic email notifications setting ON.
|
||||
followed_topic_email_user_ids: Set[int]
|
||||
muted_sender_user_ids: Set[int]
|
||||
um_eligible_user_ids: Set[int]
|
||||
long_term_idle_user_ids: Set[int]
|
||||
|
|
|
@ -19,15 +19,20 @@ class UserMessageNotificationsData:
|
|||
wildcard_mention_push_notify: bool
|
||||
stream_push_notify: bool
|
||||
stream_email_notify: bool
|
||||
followed_topic_email_notify: bool
|
||||
sender_is_muted: bool
|
||||
disable_external_notifications: bool
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# Check that there's no dubious data.
|
||||
if self.pm_email_notify or self.pm_push_notify:
|
||||
assert not (self.stream_email_notify or self.stream_push_notify)
|
||||
assert not (
|
||||
self.stream_email_notify
|
||||
or self.stream_push_notify
|
||||
or self.followed_topic_email_notify
|
||||
)
|
||||
|
||||
if self.stream_email_notify or self.stream_push_notify:
|
||||
if self.stream_email_notify or self.stream_push_notify or self.followed_topic_email_notify:
|
||||
assert not (self.pm_email_notify or self.pm_push_notify)
|
||||
|
||||
@classmethod
|
||||
|
@ -44,6 +49,7 @@ class UserMessageNotificationsData:
|
|||
stream_push_user_ids: Set[int],
|
||||
stream_email_user_ids: Set[int],
|
||||
wildcard_mention_user_ids: Set[int],
|
||||
followed_topic_email_user_ids: Set[int],
|
||||
muted_sender_user_ids: Set[int],
|
||||
all_bot_user_ids: Set[int],
|
||||
) -> "UserMessageNotificationsData":
|
||||
|
@ -60,6 +66,7 @@ class UserMessageNotificationsData:
|
|||
online_push_enabled=False,
|
||||
stream_push_notify=False,
|
||||
stream_email_notify=False,
|
||||
followed_topic_email_notify=False,
|
||||
sender_is_muted=False,
|
||||
disable_external_notifications=False,
|
||||
)
|
||||
|
@ -97,6 +104,7 @@ class UserMessageNotificationsData:
|
|||
online_push_enabled=(user_id in online_push_user_ids),
|
||||
stream_push_notify=(user_id in stream_push_user_ids),
|
||||
stream_email_notify=(user_id in stream_email_user_ids),
|
||||
followed_topic_email_notify=(user_id in followed_topic_email_user_ids),
|
||||
sender_is_muted=(user_id in muted_sender_user_ids),
|
||||
disable_external_notifications=disable_external_notifications,
|
||||
)
|
||||
|
@ -168,6 +176,8 @@ class UserMessageNotificationsData:
|
|||
return NotificationTriggers.MENTION
|
||||
elif self.wildcard_mention_email_notify:
|
||||
return NotificationTriggers.WILDCARD_MENTION
|
||||
elif self.followed_topic_email_notify:
|
||||
return NotificationTriggers.FOLLOWED_TOPIC_EMAIL
|
||||
elif self.stream_email_notify:
|
||||
return NotificationTriggers.STREAM_EMAIL
|
||||
else:
|
||||
|
|
|
@ -7,7 +7,7 @@ from typing import AbstractSet, Any, Collection, Dict, List, Optional, Set
|
|||
from django.db.models import Q, QuerySet
|
||||
from django_stubs_ext import ValuesQuerySet
|
||||
|
||||
from zerver.models import AlertWord, Realm, Recipient, Stream, Subscription, UserProfile
|
||||
from zerver.models import AlertWord, Realm, Recipient, Stream, Subscription, UserProfile, UserTopic
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -305,6 +305,7 @@ def get_subscriptions_for_send_message(
|
|||
*,
|
||||
realm_id: int,
|
||||
stream_id: int,
|
||||
topic_name: str,
|
||||
possible_wildcard_mention: bool,
|
||||
possibly_mentioned_user_ids: AbstractSet[int],
|
||||
) -> QuerySet[Subscription]:
|
||||
|
@ -356,5 +357,12 @@ def get_subscriptions_for_send_message(
|
|||
"user_profile_id"
|
||||
)
|
||||
)
|
||||
| Q(
|
||||
user_profile_id__in=UserTopic.objects.filter(
|
||||
stream_id=stream_id,
|
||||
topic_name__iexact=topic_name,
|
||||
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
).values_list("user_profile_id")
|
||||
)
|
||||
)
|
||||
return query
|
||||
|
|
|
@ -1729,6 +1729,7 @@ Output:
|
|||
wildcard_mention_push_notify=kwargs.get("wildcard_mention_push_notify", False),
|
||||
stream_email_notify=kwargs.get("stream_email_notify", False),
|
||||
stream_push_notify=kwargs.get("stream_push_notify", False),
|
||||
followed_topic_email_notify=kwargs.get("followed_topic_email_notify", False),
|
||||
sender_is_muted=kwargs.get("sender_is_muted", False),
|
||||
disable_external_notifications=kwargs.get("disable_external_notifications", False),
|
||||
)
|
||||
|
|
|
@ -28,6 +28,7 @@ class Migration(migrations.Migration):
|
|||
("mentioned", "Mention"),
|
||||
("wildcard_mentioned", "Wildcard mention"),
|
||||
("stream_email_notify", "Stream notifications enabled"),
|
||||
("followed_topic_email_notify", "Followed topic notifications enabled"),
|
||||
]
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# Generated by Django 4.2.1 on 2023-05-24 11:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0452_realmauditlog_extra_data_json"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="realmuserdefault",
|
||||
name="enable_followed_topic_audible_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="realmuserdefault",
|
||||
name="enable_followed_topic_desktop_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="realmuserdefault",
|
||||
name="enable_followed_topic_email_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="realmuserdefault",
|
||||
name="enable_followed_topic_push_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="realmuserdefault",
|
||||
name="enable_followed_topic_wildcard_mentions_notify",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="enable_followed_topic_audible_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="enable_followed_topic_desktop_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="enable_followed_topic_email_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="enable_followed_topic_push_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="enable_followed_topic_wildcard_mentions_notify",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
|
@ -1589,6 +1589,13 @@ class UserBaseSettings(models.Model):
|
|||
notification_sound = models.CharField(max_length=20, default="zulip")
|
||||
wildcard_mentions_notify = models.BooleanField(default=True)
|
||||
|
||||
# Followed Topics notifications.
|
||||
enable_followed_topic_desktop_notifications = models.BooleanField(default=True)
|
||||
enable_followed_topic_email_notifications = models.BooleanField(default=True)
|
||||
enable_followed_topic_push_notifications = models.BooleanField(default=True)
|
||||
enable_followed_topic_audible_notifications = models.BooleanField(default=True)
|
||||
enable_followed_topic_wildcard_mentions_notify = models.BooleanField(default=True)
|
||||
|
||||
# PM + @-mention notifications.
|
||||
enable_desktop_notifications = models.BooleanField(default=True)
|
||||
pm_content_in_desktop_notifications = models.BooleanField(default=True)
|
||||
|
@ -1716,6 +1723,7 @@ class UserBaseSettings(models.Model):
|
|||
|
||||
modern_notification_settings: Dict[str, Any] = dict(
|
||||
# Add new notification settings here.
|
||||
enable_followed_topic_email_notifications=bool,
|
||||
)
|
||||
|
||||
notification_setting_types = {
|
||||
|
@ -4276,6 +4284,7 @@ class NotificationTriggers:
|
|||
WILDCARD_MENTION = "wildcard_mentioned"
|
||||
STREAM_PUSH = "stream_push_notify"
|
||||
STREAM_EMAIL = "stream_email_notify"
|
||||
FOLLOWED_TOPIC_EMAIL = "followed_topic_email_notify"
|
||||
|
||||
|
||||
class ScheduledMessageNotificationEmail(models.Model):
|
||||
|
@ -4293,6 +4302,7 @@ class ScheduledMessageNotificationEmail(models.Model):
|
|||
(NotificationTriggers.MENTION, "Mention"),
|
||||
(NotificationTriggers.WILDCARD_MENTION, "Wildcard mention"),
|
||||
(NotificationTriggers.STREAM_EMAIL, "Stream notifications enabled"),
|
||||
(NotificationTriggers.FOLLOWED_TOPIC_EMAIL, "Followed topic notifications enabled"),
|
||||
]
|
||||
|
||||
trigger = models.TextField(choices=EMAIL_NOTIFICATION_TRIGGER_CHOICES)
|
||||
|
|
|
@ -9748,6 +9748,15 @@ paths:
|
|||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
- name: enable_followed_topic_email_notifications
|
||||
in: query
|
||||
description: |
|
||||
Enable email notifications for messages sent to followed topics.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 189).
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
- name: email_notifications_batching_period_seconds
|
||||
in: query
|
||||
description: |
|
||||
|
@ -11840,6 +11849,12 @@ paths:
|
|||
description: |
|
||||
Enable audible desktop notifications for private messages and
|
||||
@-mentions.
|
||||
enable_followed_topic_email_notifications:
|
||||
type: boolean
|
||||
description: |
|
||||
Enable email notifications for messages sent to followed topics.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 189).
|
||||
email_notifications_batching_period_seconds:
|
||||
type: integer
|
||||
description: |
|
||||
|
@ -13900,6 +13915,12 @@ paths:
|
|||
description: |
|
||||
Enable mobile notification for private messages and @-mentions received
|
||||
when the user is online.
|
||||
enable_followed_topic_email_notifications:
|
||||
type: boolean
|
||||
description: |
|
||||
Enable email notifications for messages sent to followed topics.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 189).
|
||||
enable_digest_emails:
|
||||
type: boolean
|
||||
description: |
|
||||
|
@ -15033,6 +15054,15 @@ paths:
|
|||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
- name: enable_followed_topic_email_notifications
|
||||
in: query
|
||||
description: |
|
||||
Enable email notifications for messages sent to followed topics.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 189).
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
- name: enable_digest_emails
|
||||
in: query
|
||||
description: |
|
||||
|
|
|
@ -1851,6 +1851,26 @@ class TestMissedMessages(ZulipTestCase):
|
|||
],
|
||||
)
|
||||
|
||||
def test_followed_topic_missed_message(self) -> None:
|
||||
hamlet = self.example_user("hamlet")
|
||||
othello = self.example_user("othello")
|
||||
msg_id = self.send_stream_message(othello, "Denmark")
|
||||
|
||||
handle_missedmessage_emails(
|
||||
hamlet.id,
|
||||
[
|
||||
{"message_id": msg_id, "trigger": "followed_topic_email_notify"},
|
||||
],
|
||||
)
|
||||
self.assert_length(mail.outbox, 1)
|
||||
email_subject = mail.outbox[0].subject
|
||||
email_body = mail.outbox[0].body
|
||||
self.assertEqual("#Denmark > test", email_subject)
|
||||
self.assertIn(
|
||||
"You are receiving this because you have email notifications enabled for topics you follow.",
|
||||
email_body,
|
||||
)
|
||||
|
||||
|
||||
class TestFollowupEmailDelay(ZulipTestCase):
|
||||
def test_get_onboarding_email_schedule(self) -> None:
|
||||
|
|
|
@ -760,6 +760,56 @@ class MissedMessageHookTest(ZulipTestCase):
|
|||
already_notified={"email_notified": False, "push_notified": False},
|
||||
)
|
||||
|
||||
def test_followed_topic_email_notify(self) -> None:
|
||||
# By default, messages sent in followed topics should send email notifications.
|
||||
do_set_user_topic_visibility_policy(
|
||||
self.user_profile,
|
||||
get_stream("Denmark", self.user_profile.realm),
|
||||
"followed_topic_test",
|
||||
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
)
|
||||
msg_id = self.send_stream_message(
|
||||
self.iago, "Denmark", content="what's up everyone?", topic_name="followed_topic_test"
|
||||
)
|
||||
with mock.patch("zerver.tornado.event_queue.maybe_enqueue_notifications") as mock_enqueue:
|
||||
missedmessage_hook(self.user_profile.id, self.client_descriptor, True)
|
||||
mock_enqueue.assert_called_once()
|
||||
args_dict = mock_enqueue.call_args_list[0][1]
|
||||
|
||||
self.assert_maybe_enqueue_notifications_call_args(
|
||||
args_dict=args_dict,
|
||||
message_id=msg_id,
|
||||
user_id=self.user_profile.id,
|
||||
followed_topic_email_notify=True,
|
||||
already_notified={"email_notified": True, "push_notified": False},
|
||||
)
|
||||
|
||||
def test_followed_topic_email_notify_global_setting(self) -> None:
|
||||
do_change_user_setting(
|
||||
self.user_profile, "enable_followed_topic_email_notifications", False, acting_user=None
|
||||
)
|
||||
do_set_user_topic_visibility_policy(
|
||||
self.user_profile,
|
||||
get_stream("Denmark", self.user_profile.realm),
|
||||
"followed_topic_test",
|
||||
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
)
|
||||
msg_id = self.send_stream_message(
|
||||
self.iago, "Denmark", content="what's up everyone?", topic_name="followed_topic_test"
|
||||
)
|
||||
with mock.patch("zerver.tornado.event_queue.maybe_enqueue_notifications") as mock_enqueue:
|
||||
missedmessage_hook(self.user_profile.id, self.client_descriptor, True)
|
||||
mock_enqueue.assert_called_once()
|
||||
args_dict = mock_enqueue.call_args_list[0][1]
|
||||
|
||||
self.assert_maybe_enqueue_notifications_call_args(
|
||||
args_dict=args_dict,
|
||||
message_id=msg_id,
|
||||
user_id=self.user_profile.id,
|
||||
followed_topic_email_notify=False,
|
||||
already_notified={"email_notified": False, "push_notified": False},
|
||||
)
|
||||
|
||||
def test_muted_sender(self) -> None:
|
||||
do_mute_user(self.user_profile, self.iago)
|
||||
msg_id = self.send_personal_message(self.iago, self.user_profile)
|
||||
|
|
|
@ -1946,6 +1946,17 @@ class NormalActionsTest(BaseAction):
|
|||
self.user_profile, notification_setting, False, acting_user=self.user_profile
|
||||
)
|
||||
|
||||
num_events = 2
|
||||
is_modern_notification_setting = (
|
||||
notification_setting in self.user_profile.modern_notification_settings
|
||||
)
|
||||
if is_modern_notification_setting:
|
||||
# The legacy event format is not sent for modern_notification_settings
|
||||
# as it exists only for backwards-compatibility with
|
||||
# clients that don't support the new user_settings event type.
|
||||
# We only send the legacy event for settings added before Feature level 89.
|
||||
num_events = 1
|
||||
|
||||
for setting_value in [True, False]:
|
||||
events = self.verify_action(
|
||||
lambda: do_change_user_setting(
|
||||
|
@ -1954,9 +1965,10 @@ class NormalActionsTest(BaseAction):
|
|||
setting_value,
|
||||
acting_user=self.user_profile,
|
||||
),
|
||||
num_events=2,
|
||||
num_events=num_events,
|
||||
)
|
||||
check_user_settings_update("events[0]", events[0])
|
||||
if not is_modern_notification_setting:
|
||||
check_update_global_notifications("events[1]", events[1], setting_value)
|
||||
|
||||
# Also test with notification_settings_null=True
|
||||
|
@ -1969,9 +1981,10 @@ class NormalActionsTest(BaseAction):
|
|||
),
|
||||
notification_settings_null=True,
|
||||
state_change_expected=False,
|
||||
num_events=2,
|
||||
num_events=num_events,
|
||||
)
|
||||
check_user_settings_update("events[0]", events[0])
|
||||
if not is_modern_notification_setting:
|
||||
check_update_global_notifications("events[1]", events[1], setting_value)
|
||||
|
||||
def test_change_presence_enabled(self) -> None:
|
||||
|
|
|
@ -182,6 +182,16 @@ class TestNotificationData(ZulipTestCase):
|
|||
)
|
||||
self.assertTrue(user_data.is_email_notifiable(acting_user_id=acting_user_id, idle=True))
|
||||
|
||||
# Followed Topic notification
|
||||
user_data = self.create_user_notifications_data_object(
|
||||
user_id=user_id, followed_topic_email_notify=True
|
||||
)
|
||||
self.assertEqual(
|
||||
user_data.get_email_notification_trigger(acting_user_id=acting_user_id, idle=True),
|
||||
"followed_topic_email_notify",
|
||||
)
|
||||
self.assertTrue(user_data.is_email_notifiable(acting_user_id=acting_user_id, idle=True))
|
||||
|
||||
# Test no notifications when not idle
|
||||
user_data = self.create_user_notifications_data_object(
|
||||
user_id=user_id, pm_email_notify=True
|
||||
|
@ -267,6 +277,7 @@ class TestNotificationData(ZulipTestCase):
|
|||
stream_email_user_ids=set(),
|
||||
stream_push_user_ids=set(),
|
||||
wildcard_mention_user_ids=set(),
|
||||
followed_topic_email_user_ids=set(),
|
||||
)
|
||||
self.assertEqual(user_data.is_notifiable(acting_user_id=1000, idle=True), notifiable)
|
||||
|
||||
|
|
|
@ -631,6 +631,7 @@ class SoftDeactivationMessageTest(ZulipTestCase):
|
|||
get_subscriptions_for_send_message(
|
||||
realm_id=realm_id,
|
||||
stream_id=stream_id,
|
||||
topic_name=topic_name,
|
||||
possible_wildcard_mention=possible_wildcard_mention,
|
||||
possibly_mentioned_user_ids=possibly_mentioned_user_ids,
|
||||
)
|
||||
|
|
|
@ -1763,6 +1763,7 @@ class RecipientInfoTest(ZulipTestCase):
|
|||
stream_push_user_ids=set(),
|
||||
stream_email_user_ids=set(),
|
||||
wildcard_mention_user_ids=set(),
|
||||
followed_topic_email_user_ids=set(),
|
||||
muted_sender_user_ids=set(),
|
||||
um_eligible_user_ids=all_user_ids,
|
||||
long_term_idle_user_ids=set(),
|
||||
|
@ -1979,6 +1980,39 @@ class RecipientInfoTest(ZulipTestCase):
|
|||
self.assertEqual(info.default_bot_user_ids, {normal_bot.id})
|
||||
self.assertEqual(info.all_bot_user_ids, {normal_bot.id, service_bot.id})
|
||||
|
||||
# Now Hamlet follows the topic with the 'followed_topic_email_notifications'
|
||||
# global setting enabled by default.
|
||||
do_set_user_topic_visibility_policy(
|
||||
hamlet,
|
||||
stream,
|
||||
topic_name,
|
||||
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
|
||||
)
|
||||
|
||||
info = get_recipient_info(
|
||||
realm_id=realm.id,
|
||||
recipient=recipient,
|
||||
sender_id=hamlet.id,
|
||||
stream_topic=stream_topic,
|
||||
)
|
||||
self.assertEqual(info.followed_topic_email_user_ids, {hamlet.id})
|
||||
|
||||
# Omit Hamlet from followed_topic_email_user_ids
|
||||
do_change_user_setting(
|
||||
hamlet,
|
||||
"enable_followed_topic_email_notifications",
|
||||
False,
|
||||
acting_user=None,
|
||||
)
|
||||
|
||||
info = get_recipient_info(
|
||||
realm_id=realm.id,
|
||||
recipient=recipient,
|
||||
sender_id=hamlet.id,
|
||||
stream_topic=stream_topic,
|
||||
)
|
||||
self.assertEqual(info.followed_topic_email_user_ids, set())
|
||||
|
||||
def test_get_recipient_info_invalid_recipient_type(self) -> None:
|
||||
hamlet = self.example_user("hamlet")
|
||||
realm = hamlet.realm
|
||||
|
|
|
@ -780,6 +780,7 @@ def missedmessage_hook(
|
|||
wildcard_mention_email_notify=internal_data.get("wildcard_mention_email_notify", False),
|
||||
stream_push_notify=internal_data.get("stream_push_notify", False),
|
||||
stream_email_notify=internal_data.get("stream_email_notify", False),
|
||||
followed_topic_email_notify=internal_data.get("followed_topic_email_notify", False),
|
||||
# Since one is by definition idle, we don't need to check online_push_enabled
|
||||
online_push_enabled=False,
|
||||
disable_external_notifications=internal_data.get(
|
||||
|
@ -933,6 +934,7 @@ def process_message_event(
|
|||
stream_push_user_ids = set(event_template.get("stream_push_user_ids", []))
|
||||
stream_email_user_ids = set(event_template.get("stream_email_user_ids", []))
|
||||
wildcard_mention_user_ids = set(event_template.get("wildcard_mention_user_ids", []))
|
||||
followed_topic_email_user_ids = set(event_template.get("followed_topic_email_user_ids", []))
|
||||
muted_sender_user_ids = set(event_template.get("muted_sender_user_ids", []))
|
||||
all_bot_user_ids = set(event_template.get("all_bot_user_ids", []))
|
||||
disable_external_notifications = event_template.get("disable_external_notifications", False)
|
||||
|
@ -983,6 +985,7 @@ def process_message_event(
|
|||
stream_push_user_ids=stream_push_user_ids,
|
||||
stream_email_user_ids=stream_email_user_ids,
|
||||
wildcard_mention_user_ids=wildcard_mention_user_ids,
|
||||
followed_topic_email_user_ids=followed_topic_email_user_ids,
|
||||
muted_sender_user_ids=muted_sender_user_ids,
|
||||
all_bot_user_ids=all_bot_user_ids,
|
||||
)
|
||||
|
@ -1134,6 +1137,7 @@ def process_message_update_event(
|
|||
stream_push_user_ids = set(event_template.pop("stream_push_user_ids", []))
|
||||
stream_email_user_ids = set(event_template.pop("stream_email_user_ids", []))
|
||||
wildcard_mention_user_ids = set(event_template.pop("wildcard_mention_user_ids", []))
|
||||
followed_topic_email_user_ids = set(event_template.pop("followed_topic_email_user_ids", []))
|
||||
muted_sender_user_ids = set(event_template.pop("muted_sender_user_ids", []))
|
||||
all_bot_user_ids = set(event_template.pop("all_bot_user_ids", []))
|
||||
disable_external_notifications = event_template.pop("disable_external_notifications", False)
|
||||
|
@ -1194,6 +1198,7 @@ def process_message_update_event(
|
|||
stream_push_user_ids=stream_push_user_ids,
|
||||
stream_email_user_ids=stream_email_user_ids,
|
||||
wildcard_mention_user_ids=wildcard_mention_user_ids,
|
||||
followed_topic_email_user_ids=followed_topic_email_user_ids,
|
||||
muted_sender_user_ids=muted_sender_user_ids,
|
||||
all_bot_user_ids=all_bot_user_ids,
|
||||
)
|
||||
|
@ -1266,7 +1271,11 @@ def maybe_enqueue_notifications_for_message_update(
|
|||
# without extending the UserMessage data model.
|
||||
return
|
||||
|
||||
if user_notifications_data.stream_push_notify or user_notifications_data.stream_email_notify:
|
||||
if (
|
||||
user_notifications_data.stream_push_notify
|
||||
or user_notifications_data.stream_email_notify
|
||||
or user_notifications_data.followed_topic_email_notify
|
||||
):
|
||||
# Currently we assume that if this flag is set to True, then
|
||||
# the user already was notified about the earlier message,
|
||||
# so we short circuit. We may handle this more rigorously
|
||||
|
|
|
@ -426,6 +426,9 @@ def update_realm_user_settings_defaults(
|
|||
json_validator=check_bool, default=None
|
||||
),
|
||||
wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
enable_followed_topic_email_notifications: Optional[bool] = REQ(
|
||||
json_validator=check_bool, default=None
|
||||
),
|
||||
notification_sound: Optional[str] = REQ(default=None),
|
||||
enable_desktop_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
enable_sounds: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
|
|
|
@ -193,6 +193,9 @@ def json_change_settings(
|
|||
json_validator=check_bool, default=None
|
||||
),
|
||||
wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
enable_followed_topic_email_notifications: Optional[bool] = REQ(
|
||||
json_validator=check_bool, default=None
|
||||
),
|
||||
notification_sound: Optional[str] = REQ(default=None),
|
||||
enable_desktop_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
enable_sounds: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
|
|
Loading…
Reference in New Issue