diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 20b9a70b51..88b97adb0d 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -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** diff --git a/zerver/actions/message_edit.py b/zerver/actions/message_edit.py index fc74427a25..23f568ec2d 100644 --- a/zerver/actions/message_edit.py +++ b/zerver/actions/message_edit.py @@ -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) diff --git a/zerver/actions/message_send.py b/zerver/actions/message_send.py index 01fbcd3b0a..649d7a7a0a 100644 --- a/zerver/actions/message_send.py +++ b/zerver/actions/message_send.py @@ -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), diff --git a/zerver/lib/message.py b/zerver/lib/message.py index e53d1c8303..55649ce364 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -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] diff --git a/zerver/lib/notification_data.py b/zerver/lib/notification_data.py index 901d2e0447..cc9c934a70 100644 --- a/zerver/lib/notification_data.py +++ b/zerver/lib/notification_data.py @@ -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: diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index e43b8a21c9..735faffe69 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -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), ) diff --git a/zerver/models.py b/zerver/models.py index 12e6a1aa8e..6bb178f5cd 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -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" diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 31c8777f30..ae64869ecb 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -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: | diff --git a/zerver/tests/test_event_queue.py b/zerver/tests/test_event_queue.py index 24a4aed921..1d58cdfee0 100644 --- a/zerver/tests/test_event_queue.py +++ b/zerver/tests/test_event_queue.py @@ -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: diff --git a/zerver/tests/test_notification_data.py b/zerver/tests/test_notification_data.py index 4d52326bd0..e90728baff 100644 --- a/zerver/tests/test_notification_data.py +++ b/zerver/tests/test_notification_data.py @@ -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) diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 6acc335fb1..0ff7e9925b 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -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") diff --git a/zerver/tornado/event_queue.py b/zerver/tornado/event_queue.py index cfbefd08c6..16916c1c5e 100644 --- a/zerver/tornado/event_queue.py +++ b/zerver/tornado/event_queue.py @@ -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 diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 619b172a3c..20327d8f1a 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -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), diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index 2c96603f39..80eceeaa94 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -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),