settings: Add push notifications for the followed topics.

This commit makes it possible for users to control
the push notifications for messages sent to followed topics
via a global notification setting.

There is no support for configuring this setting
through the UI yet.
This commit is contained in:
Prakhar Pratyush 2023-05-28 20:33:04 +05:30 committed by Tim Abbott
parent 5e5538886f
commit d73c715dc2
14 changed files with 133 additions and 8 deletions

View File

@ -24,8 +24,9 @@ format used by the Zulip server that they are interacting with.
* [`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.
Added `enable_followed_topic_email_notifications` and `enable_followed_topic_push_notifications`
boolean fields to control email and push notifications, respectively, for messages
sent to followed topics.
**Feature level 188**

View File

@ -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_push_user_ids"] = list(info.followed_topic_push_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)

View File

@ -167,6 +167,7 @@ class RecipientInfoResult:
stream_push_user_ids: Set[int]
wildcard_mention_user_ids: Set[int]
followed_topic_email_user_ids: Set[int]
followed_topic_push_user_ids: Set[int]
muted_sender_user_ids: Set[int]
um_eligible_user_ids: Set[int]
long_term_idle_user_ids: Set[int]
@ -197,6 +198,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_push_user_ids: Set[int] = set()
followed_topic_email_user_ids: Set[int] = set()
muted_sender_user_ids: Set[int] = get_muting_users(sender_id)
@ -229,12 +231,16 @@ def get_recipient_info(
followed_topic_email_notifications=F(
"user_profile__enable_followed_topic_email_notifications"
),
followed_topic_push_notifications=F(
"user_profile__enable_followed_topic_push_notifications"
),
)
.values(
"user_profile_id",
"push_notifications",
"email_notifications",
"wildcard_mentions_notify",
"followed_topic_push_notifications",
"followed_topic_email_notifications",
"user_profile_email_notifications",
"user_profile_push_notifications",
@ -278,6 +284,7 @@ def get_recipient_info(
followed_topic_email_user_ids = followed_topic_notification_recipients(
"email_notifications"
)
followed_topic_push_user_ids = followed_topic_notification_recipients("push_notifications")
if possible_wildcard_mention:
# We calculate `wildcard_mention_user_ids` only if there's a possible
@ -403,6 +410,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_push_user_ids=followed_topic_push_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,
@ -587,6 +595,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_push_user_ids=info.followed_topic_push_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,
@ -612,6 +621,7 @@ def create_user_messages(
stream_push_user_ids: AbstractSet[int],
stream_email_user_ids: AbstractSet[int],
mentioned_user_ids: AbstractSet[int],
followed_topic_push_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]],
@ -673,6 +683,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_push_user_ids
and user_profile_id not in followed_topic_email_user_ids
and is_stream_message
and int(flags) == 0
@ -787,6 +798,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_push_user_ids=send_request.followed_topic_push_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,
@ -888,6 +900,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_push_user_ids=send_request.followed_topic_push_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,
@ -914,6 +927,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_push_user_ids=list(send_request.followed_topic_push_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),

View File

@ -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 push notifications setting ON.
followed_topic_push_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]

View File

@ -19,6 +19,7 @@ class UserMessageNotificationsData:
wildcard_mention_push_notify: bool
stream_push_notify: bool
stream_email_notify: bool
followed_topic_push_notify: bool
followed_topic_email_notify: bool
sender_is_muted: bool
disable_external_notifications: bool
@ -30,9 +31,15 @@ class UserMessageNotificationsData:
self.stream_email_notify
or self.stream_push_notify
or self.followed_topic_email_notify
or self.followed_topic_push_notify
)
if 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
or self.followed_topic_email_notify
or self.followed_topic_push_notify
):
assert not (self.pm_email_notify or self.pm_push_notify)
@classmethod
@ -49,6 +56,7 @@ class UserMessageNotificationsData:
stream_push_user_ids: Set[int],
stream_email_user_ids: Set[int],
wildcard_mention_user_ids: Set[int],
followed_topic_push_user_ids: Set[int],
followed_topic_email_user_ids: Set[int],
muted_sender_user_ids: Set[int],
all_bot_user_ids: Set[int],
@ -66,6 +74,7 @@ class UserMessageNotificationsData:
online_push_enabled=False,
stream_push_notify=False,
stream_email_notify=False,
followed_topic_push_notify=False,
followed_topic_email_notify=False,
sender_is_muted=False,
disable_external_notifications=False,
@ -104,6 +113,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_push_notify=(user_id in followed_topic_push_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,
@ -152,6 +162,8 @@ class UserMessageNotificationsData:
return NotificationTriggers.MENTION
elif self.wildcard_mention_push_notify:
return NotificationTriggers.WILDCARD_MENTION
elif self.followed_topic_push_notify:
return NotificationTriggers.FOLLOWED_TOPIC_PUSH
elif self.stream_push_notify:
return NotificationTriggers.STREAM_PUSH
else:

View File

@ -1730,6 +1730,7 @@ Output:
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),
followed_topic_push_notify=kwargs.get("followed_topic_push_notify", False),
sender_is_muted=kwargs.get("sender_is_muted", False),
disable_external_notifications=kwargs.get("disable_external_notifications", False),
)

View File

@ -1724,6 +1724,7 @@ class UserBaseSettings(models.Model):
modern_notification_settings: Dict[str, Any] = dict(
# Add new notification settings here.
enable_followed_topic_email_notifications=bool,
enable_followed_topic_push_notifications=bool,
)
notification_setting_types = {
@ -4284,6 +4285,7 @@ class NotificationTriggers:
WILDCARD_MENTION = "wildcard_mentioned"
STREAM_PUSH = "stream_push_notify"
STREAM_EMAIL = "stream_email_notify"
FOLLOWED_TOPIC_PUSH = "followed_topic_push_notify"
FOLLOWED_TOPIC_EMAIL = "followed_topic_email_notify"

View File

@ -9757,6 +9757,15 @@ paths:
schema:
type: boolean
example: true
- name: enable_followed_topic_push_notifications
in: query
description: |
Enable push notifications for messages sent to followed topics.
**Changes**: New in Zulip 8.0 (feature level 189).
schema:
type: boolean
example: false
- name: email_notifications_batching_period_seconds
in: query
description: |
@ -11854,6 +11863,12 @@ paths:
description: |
Enable email notifications for messages sent to followed topics.
**Changes**: New in Zulip 8.0 (feature level 189).
enable_followed_topic_push_notifications:
type: boolean
description: |
Enable push notifications for messages sent to followed topics.
**Changes**: New in Zulip 8.0 (feature level 189).
email_notifications_batching_period_seconds:
type: integer
@ -13920,6 +13935,12 @@ paths:
description: |
Enable email notifications for messages sent to followed topics.
**Changes**: New in Zulip 8.0 (feature level 189).
enable_followed_topic_push_notifications:
type: boolean
description: |
Enable push notifications for messages sent to followed topics.
**Changes**: New in Zulip 8.0 (feature level 189).
enable_digest_emails:
type: boolean
@ -15063,6 +15084,15 @@ paths:
schema:
type: boolean
example: true
- name: enable_followed_topic_push_notifications
in: query
description: |
Enable push notifications for messages sent to followed topics.
**Changes**: New in Zulip 8.0 (feature level 189).
schema:
type: boolean
example: false
- name: enable_digest_emails
in: query
description: |

View File

@ -760,8 +760,8 @@ 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.
def test_followed_topic_email_and_push_notify(self) -> None:
# By default, messages sent in followed topics should send notifications.
do_set_user_topic_visibility_policy(
self.user_profile,
get_stream("Denmark", self.user_profile.realm),
@ -781,7 +781,8 @@ class MissedMessageHookTest(ZulipTestCase):
message_id=msg_id,
user_id=self.user_profile.id,
followed_topic_email_notify=True,
already_notified={"email_notified": True, "push_notified": False},
followed_topic_push_notify=True,
already_notified={"email_notified": True, "push_notified": True},
)
def test_followed_topic_email_notify_global_setting(self) -> None:
@ -807,7 +808,35 @@ class MissedMessageHookTest(ZulipTestCase):
message_id=msg_id,
user_id=self.user_profile.id,
followed_topic_email_notify=False,
already_notified={"email_notified": False, "push_notified": False},
followed_topic_push_notify=True,
already_notified={"email_notified": False, "push_notified": True},
)
def test_followed_topic_push_notify_global_setting(self) -> None:
do_change_user_setting(
self.user_profile, "enable_followed_topic_push_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=True,
followed_topic_push_notify=False,
already_notified={"email_notified": True, "push_notified": False},
)
def test_muted_sender(self) -> None:

View File

@ -60,6 +60,16 @@ class TestNotificationData(ZulipTestCase):
)
self.assertTrue(user_data.is_push_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_push_notify=True
)
self.assertEqual(
user_data.get_push_notification_trigger(acting_user_id=acting_user_id, idle=True),
"followed_topic_push_notify",
)
self.assertTrue(user_data.is_push_notifiable(acting_user_id=acting_user_id, idle=True))
# Now, test the `online_push_enabled` property
# Test no notifications when not idle
user_data = self.create_user_notifications_data_object(user_id=user_id, pm_push_notify=True)
@ -278,6 +288,7 @@ class TestNotificationData(ZulipTestCase):
stream_push_user_ids=set(),
wildcard_mention_user_ids=set(),
followed_topic_email_user_ids=set(),
followed_topic_push_user_ids=set(),
)
self.assertEqual(user_data.is_notifiable(acting_user_id=1000, idle=True), notifiable)

View File

@ -1763,6 +1763,7 @@ class RecipientInfoTest(ZulipTestCase):
stream_push_user_ids=set(),
stream_email_user_ids=set(),
wildcard_mention_user_ids=set(),
followed_topic_push_user_ids=set(),
followed_topic_email_user_ids=set(),
muted_sender_user_ids=set(),
um_eligible_user_ids=all_user_ids,
@ -1981,7 +1982,7 @@ class RecipientInfoTest(ZulipTestCase):
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.
# and 'followed_topic_push_notifications' global settings enabled by default.
do_set_user_topic_visibility_policy(
hamlet,
stream,
@ -1996,6 +1997,7 @@ class RecipientInfoTest(ZulipTestCase):
stream_topic=stream_topic,
)
self.assertEqual(info.followed_topic_email_user_ids, {hamlet.id})
self.assertEqual(info.followed_topic_push_user_ids, {hamlet.id})
# Omit Hamlet from followed_topic_email_user_ids
do_change_user_setting(
@ -2004,6 +2006,13 @@ class RecipientInfoTest(ZulipTestCase):
False,
acting_user=None,
)
# Omit Hamlet from followed_topic_push_user_ids
do_change_user_setting(
hamlet,
"enable_followed_topic_push_notifications",
False,
acting_user=None,
)
info = get_recipient_info(
realm_id=realm.id,
@ -2012,6 +2021,7 @@ class RecipientInfoTest(ZulipTestCase):
stream_topic=stream_topic,
)
self.assertEqual(info.followed_topic_email_user_ids, set())
self.assertEqual(info.followed_topic_push_user_ids, set())
def test_get_recipient_info_invalid_recipient_type(self) -> None:
hamlet = self.example_user("hamlet")

View File

@ -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_push_notify=internal_data.get("followed_topic_push_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,
@ -934,6 +935,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_push_user_ids = set(event_template.get("followed_topic_push_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", []))
@ -985,6 +987,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_push_user_ids=followed_topic_push_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,
@ -1137,6 +1140,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_push_user_ids = set(event_template.pop("followed_topic_push_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", []))
@ -1198,6 +1202,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_push_user_ids=followed_topic_push_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,
@ -1274,6 +1279,7 @@ def maybe_enqueue_notifications_for_message_update(
if (
user_notifications_data.stream_push_notify
or user_notifications_data.stream_email_notify
or user_notifications_data.followed_topic_push_notify
or user_notifications_data.followed_topic_email_notify
):
# Currently we assume that if this flag is set to True, then

View File

@ -429,6 +429,9 @@ def update_realm_user_settings_defaults(
enable_followed_topic_email_notifications: Optional[bool] = REQ(
json_validator=check_bool, default=None
),
enable_followed_topic_push_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),

View File

@ -196,6 +196,9 @@ def json_change_settings(
enable_followed_topic_email_notifications: Optional[bool] = REQ(
json_validator=check_bool, default=None
),
enable_followed_topic_push_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),