From 4c9d26ce174e91db8c431d61c8b4e9a428a30368 Mon Sep 17 00:00:00 2001 From: Prakhar Pratyush Date: Wed, 7 Jun 2023 22:49:33 +0530 Subject: [PATCH] mention: Send notifications for @topic wildcard mentions. This commit completes the notifications part of the @topic wildcard mention feature. Notifications are sent to the topic participants for the @topic wildcard mention. --- templates/zerver/emails/missed_message.html | 4 + templates/zerver/emails/missed_message.txt | 4 + web/src/markdown.js | 1 + web/tests/markdown.test.js | 27 ++- zerver/actions/message_edit.py | 15 +- zerver/actions/message_send.py | 36 +++- zerver/lib/email_notifications.py | 27 ++- zerver/lib/markdown/__init__.py | 3 + zerver/lib/message.py | 26 ++- zerver/lib/notification_data.py | 49 ++++- zerver/lib/push_notifications.py | 13 ++ zerver/lib/test_classes.py | 12 ++ .../0331_scheduledmessagenotificationemail.py | 5 + zerver/models.py | 7 + zerver/tests/test_email_notifications.py | 199 ++++++++++++++++-- zerver/tests/test_event_queue.py | 135 ++++++++++++ zerver/tests/test_events.py | 7 + zerver/tests/test_message_edit.py | 141 +++++++++++++ .../tests/test_message_edit_notifications.py | 86 ++++++++ zerver/tests/test_message_send.py | 99 +++++++++ zerver/tests/test_notification_data.py | 54 +++++ zerver/tests/test_push_notifications.py | 79 +++++++ zerver/tests/test_soft_deactivation.py | 13 ++ zerver/tornado/event_queue.py | 24 +++ 24 files changed, 1033 insertions(+), 33 deletions(-) diff --git a/templates/zerver/emails/missed_message.html b/templates/zerver/emails/missed_message.html index 322507251b..7eaa6f0605 100644 --- a/templates/zerver/emails/missed_message.html +++ b/templates/zerver/emails/missed_message.html @@ -29,8 +29,12 @@ {% trans %}You are receiving this because you were personally mentioned.{% endtrans %}
{% elif mentioned_user_group_name %} {% trans %}You are receiving this because @{{ mentioned_user_group_name }} was mentioned.{% endtrans %}
+ {% elif topic_wildcard_mentioned_in_followed_topic %} + {% trans %}You are receiving this because all topic participants were mentioned in #{{ stream_name }} > {{ topic_name }}.{% endtrans %}
{% elif stream_wildcard_mentioned_in_followed_topic %} {% trans %}You are receiving this because you have wildcard mention notifications enabled for topics you follow.{% endtrans %}
+ {% elif topic_wildcard_mentioned %} + {% trans %}You are receiving this because all topic participants were mentioned in #{{ stream_name }} > {{ topic_name }}.{% endtrans %}
{% elif stream_wildcard_mentioned %} {% trans %}You are receiving this because everyone was mentioned in #{{ stream_name }}.{% endtrans %}
{% elif followed_topic_email_notify %} diff --git a/templates/zerver/emails/missed_message.txt b/templates/zerver/emails/missed_message.txt index bcc899f82a..da4cd74f94 100644 --- a/templates/zerver/emails/missed_message.txt +++ b/templates/zerver/emails/missed_message.txt @@ -25,8 +25,12 @@ See {{ alert_notif_url }} for more details. {% trans %}You are receiving this because you were personally mentioned.{% endtrans %} {% elif mentioned_user_group_name %} {% trans %}You are receiving this because @{{ mentioned_user_group_name }} was mentioned.{% endtrans %} +{% elif topic_wildcard_mentioned_in_followed_topic %} +{% trans %}You are receiving this because all topic participants were mentioned in #{{ stream_name }} > {{ topic_name }}.{% endtrans %} {% elif stream_wildcard_mentioned_in_followed_topic %} {% trans %}You are receiving this because you have wildcard mention notifications enabled for topics you follow.{% endtrans %} +{% elif topic_wildcard_mentioned %} +{% trans %}You are receiving this because all topic participants were mentioned in #{{ stream_name }} > {{ topic_name }}.{% endtrans %} {% elif stream_wildcard_mentioned %} {% trans %}You are receiving this because everyone was mentioned in #{{ stream_name }}.{% endtrans %} {% elif followed_topic_email_notify %} diff --git a/web/src/markdown.js b/web/src/markdown.js index 4cf56fa76d..89e3602765 100644 --- a/web/src/markdown.js +++ b/web/src/markdown.js @@ -133,6 +133,7 @@ function parse_with_options({raw_content, helper_config, options}) { display_text = mention; } else { // Topic Wildcard mention + mentioned_wildcard = true; display_text = "@" + mention; classes = "topic-mention"; } diff --git a/web/tests/markdown.test.js b/web/tests/markdown.test.js index 460b60abd2..573b5c3eff 100644 --- a/web/tests/markdown.test.js +++ b/web/tests/markdown.test.js @@ -711,10 +711,10 @@ test("message_flags", () => { input = "testing this @**all** @**Cordelia, Lear's daughter**"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); - assert.equal(message.is_me_message, false); assert.equal(message.flags.includes("mentioned"), true); assert.equal(message.flags.includes("wildcard_mentioned"), true); + input = "test @**everyone**"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); @@ -729,6 +729,13 @@ test("message_flags", () => { assert.equal(message.flags.includes("wildcard_mentioned"), true); assert.equal(message.flags.includes("mentioned"), false); + input = "test @**topic**"; + message = {topic: "No links here", raw_content: input}; + markdown.apply_markdown(message); + assert.equal(message.is_me_message, false); + assert.equal(message.flags.includes("wildcard_mentioned"), true); + assert.equal(message.flags.includes("mentioned"), false); + input = "test @all"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); @@ -741,6 +748,12 @@ test("message_flags", () => { assert.equal(message.flags.includes("wildcard_mentioned"), false); assert.equal(message.flags.includes("mentioned"), false); + input = "test @topic"; + message = {topic: "No links here", raw_content: input}; + markdown.apply_markdown(message); + assert.equal(message.flags.includes("wildcard_mentioned"), false); + assert.equal(message.flags.includes("mentioned"), false); + input = "test @any"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); @@ -783,6 +796,18 @@ test("message_flags", () => { assert.equal(message.flags.includes("wildcard_mentioned"), false); assert.equal(message.flags.includes("mentioned"), false); + input = "test @_**topic**"; + message = {topic: "No links here", raw_content: input}; + markdown.apply_markdown(message); + assert.equal(message.flags.includes("wildcard_mentioned"), false); + assert.equal(message.flags.includes("mentioned"), false); + + input = "> test @**topic**"; + message = {topic: "No links here", raw_content: input}; + markdown.apply_markdown(message); + assert.equal(message.flags.includes("wildcard_mentioned"), false); + assert.equal(message.flags.includes("mentioned"), false); + input = "test @_*hamletcharacters*"; message = {topic: "No links here", raw_content: input}; markdown.apply_markdown(message); diff --git a/zerver/actions/message_edit.py b/zerver/actions/message_edit.py index 19f725e02a..6ee9d80356 100644 --- a/zerver/actions/message_edit.py +++ b/zerver/actions/message_edit.py @@ -258,7 +258,7 @@ def get_mentions_for_message_updates(message_id: int) -> Set[int]: def update_user_message_flags( rendering_result: MessageRenderingResult, ums: Iterable[UserMessage] ) -> None: - wildcard = rendering_result.mentions_stream_wildcard + wildcard_mentioned = rendering_result.has_wildcard_mention() mentioned_ids = rendering_result.mentions_user_ids ids_with_alert_words = rendering_result.user_ids_with_alert_words changed_ums: Set[UserMessage] = set() @@ -280,7 +280,7 @@ def update_user_message_flags( mentioned = um.user_profile_id in mentioned_ids update_flag(um, mentioned, UserMessage.flags.mentioned) - update_flag(um, wildcard, UserMessage.flags.wildcard_mentioned) + update_flag(um, wildcard_mentioned, UserMessage.flags.wildcard_mentioned) for um in changed_ums: um.save(update_fields=["flags"]) @@ -490,6 +490,15 @@ def do_update_message( event["stream_wildcard_mention_user_ids"] = [] event["stream_wildcard_mention_in_followed_topic_user_ids"] = [] + if rendering_result.mentions_topic_wildcard: + event["topic_wildcard_mention_user_ids"] = list(info.topic_wildcard_mention_user_ids) + event["topic_wildcard_mention_in_followed_topic_user_ids"] = list( + info.topic_wildcard_mention_in_followed_topic_user_ids + ) + else: + event["topic_wildcard_mention_user_ids"] = [] + event["topic_wildcard_mention_in_followed_topic_user_ids"] = [] + do_update_mobile_push_notification( target_message, prior_mention_user_ids, @@ -1249,7 +1258,7 @@ def check_update_message( ) links_for_embed |= rendering_result.links_for_preview - if message.is_stream_message() and rendering_result.mentions_stream_wildcard: + if message.is_stream_message() and rendering_result.has_wildcard_mention(): stream = access_stream_by_id(user_profile, message.recipient.type_id)[0] if not wildcard_mention_allowed(message.sender, stream): raise JsonableError( diff --git a/zerver/actions/message_send.py b/zerver/actions/message_send.py index f9c996458d..814b44ebff 100644 --- a/zerver/actions/message_send.py +++ b/zerver/actions/message_send.py @@ -609,9 +609,9 @@ def build_message_send_dict( members = mention_data.get_group_members(group_id) rendering_result.mentions_user_ids.update(members) - # Only send data to Tornado about stream wildcard mentions if message - # rendering determined the message had an actual stream wildcard - # mention in it (and not e.g. stream wildcard mention syntax inside a + # Only send data to Tornado about stream or topic wildcard mentions if message + # rendering determined the message had an actual stream or topic wildcard + # mention in it (and not e.g. stream or topic wildcard mention syntax inside a # code block). if rendering_result.mentions_stream_wildcard: stream_wildcard_mention_user_ids = info.stream_wildcard_mention_user_ids @@ -622,6 +622,14 @@ def build_message_send_dict( stream_wildcard_mention_user_ids = set() stream_wildcard_mention_in_followed_topic_user_ids = set() + if rendering_result.mentions_topic_wildcard: + topic_wildcard_mention_user_ids = info.topic_wildcard_mention_user_ids + topic_wildcard_mention_in_followed_topic_user_ids = ( + info.topic_wildcard_mention_in_followed_topic_user_ids + ) + else: + topic_wildcard_mention_user_ids = set() + topic_wildcard_mention_in_followed_topic_user_ids = set() """ Once we have the actual list of mentioned ids from message rendering, we can patch in "default bots" (aka normal bots) @@ -656,7 +664,9 @@ def build_message_send_dict( default_bot_user_ids=info.default_bot_user_ids, service_bot_tuples=info.service_bot_tuples, all_bot_user_ids=info.all_bot_user_ids, + topic_wildcard_mention_user_ids=topic_wildcard_mention_user_ids, stream_wildcard_mention_user_ids=stream_wildcard_mention_user_ids, + topic_wildcard_mention_in_followed_topic_user_ids=topic_wildcard_mention_in_followed_topic_user_ids, stream_wildcard_mention_in_followed_topic_user_ids=stream_wildcard_mention_in_followed_topic_user_ids, links_for_embed=links_for_embed, widget_content=widget_content_dict, @@ -680,12 +690,17 @@ def create_user_messages( mark_as_read_user_ids: Set[int], limit_unread_user_ids: Optional[Set[int]], scheduled_message_to_self: bool, + topic_wildcard_mention_user_ids: Set[int], + topic_wildcard_mention_in_followed_topic_user_ids: Set[int], ) -> List[UserMessageLite]: # These properties on the Message are set via # render_markdown by code in the Markdown inline patterns ids_with_alert_words = rendering_result.user_ids_with_alert_words sender_id = message.sender.id is_stream_message = message.is_stream_message() + all_topic_wildcard_mention_user_ids = topic_wildcard_mention_user_ids.union( + topic_wildcard_mention_in_followed_topic_user_ids + ) base_flags = 0 if rendering_result.mentions_stream_wildcard: @@ -732,6 +747,11 @@ def create_user_messages( flags |= UserMessage.flags.mentioned if user_profile_id in ids_with_alert_words: flags |= UserMessage.flags.has_alert_word + if ( + rendering_result.mentions_topic_wildcard + and user_profile_id in all_topic_wildcard_mention_user_ids + ): + flags |= UserMessage.flags.wildcard_mentioned if ( user_profile_id in long_term_idle_user_ids @@ -857,6 +877,8 @@ def do_send_messages( 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, + topic_wildcard_mention_user_ids=send_request.topic_wildcard_mention_user_ids, + topic_wildcard_mention_in_followed_topic_user_ids=send_request.topic_wildcard_mention_in_followed_topic_user_ids, ) for um in user_messages: @@ -953,9 +975,11 @@ def do_send_messages( pm_mention_email_disabled_user_ids=send_request.pm_mention_email_disabled_user_ids, stream_push_user_ids=send_request.stream_push_user_ids, stream_email_user_ids=send_request.stream_email_user_ids, + topic_wildcard_mention_user_ids=send_request.topic_wildcard_mention_user_ids, stream_wildcard_mention_user_ids=send_request.stream_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, + topic_wildcard_mention_in_followed_topic_user_ids=send_request.topic_wildcard_mention_in_followed_topic_user_ids, stream_wildcard_mention_in_followed_topic_user_ids=send_request.stream_wildcard_mention_in_followed_topic_user_ids, muted_sender_user_ids=send_request.muted_sender_user_ids, all_bot_user_ids=send_request.all_bot_user_ids, @@ -981,9 +1005,13 @@ 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), + topic_wildcard_mention_user_ids=list(send_request.topic_wildcard_mention_user_ids), stream_wildcard_mention_user_ids=list(send_request.stream_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), + topic_wildcard_mention_in_followed_topic_user_ids=list( + send_request.topic_wildcard_mention_in_followed_topic_user_ids + ), stream_wildcard_mention_in_followed_topic_user_ids=list( send_request.stream_wildcard_mention_in_followed_topic_user_ids ), @@ -1522,7 +1550,7 @@ def check_message( if ( stream is not None - and message_send_dict.rendering_result.mentions_stream_wildcard + and message_send_dict.rendering_result.has_wildcard_mention() and not wildcard_mention_allowed(sender, stream) ): raise JsonableError( diff --git a/zerver/lib/email_notifications.py b/zerver/lib/email_notifications.py index ec2744771f..21cea434d2 100644 --- a/zerver/lib/email_notifications.py +++ b/zerver/lib/email_notifications.py @@ -455,14 +455,23 @@ def do_send_missedmessage_events_reply_in_zulip( for message in missed_messages ) - context.update( - mention="mentioned" in unique_triggers + mention = ( + "mentioned" in unique_triggers + or "topic_wildcard_mentioned" in unique_triggers or "stream_wildcard_mentioned" in unique_triggers - or "stream_wildcard_mentioned_in_followed_topic" in unique_triggers, + or "topic_wildcard_mentioned_in_followed_topic" in unique_triggers + or "stream_wildcard_mentioned_in_followed_topic" in unique_triggers + ) + + context.update( + mention=mention, personal_mentioned=personal_mentioned, + topic_wildcard_mentioned="topic_wildcard_mentioned" in unique_triggers, stream_wildcard_mentioned="stream_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, + topic_wildcard_mentioned_in_followed_topic="topic_wildcard_mentioned_in_followed_topic" + in unique_triggers, stream_wildcard_mentioned_in_followed_topic="stream_wildcard_mentioned_in_followed_topic" in unique_triggers, mentioned_user_group_name=mentioned_user_group_name, @@ -526,10 +535,14 @@ def do_send_missedmessage_events_reply_in_zulip( { m["message"].sender for m in missed_messages - if m["trigger"] == NotificationTriggers.MENTION - or m["trigger"] == NotificationTriggers.STREAM_WILDCARD_MENTION - or m["trigger"] - == NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC + if m["trigger"] + in [ + NotificationTriggers.MENTION, + NotificationTriggers.TOPIC_WILDCARD_MENTION, + NotificationTriggers.STREAM_WILDCARD_MENTION, + NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC, + NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC, + ] } ) message = missed_messages[0]["message"] diff --git a/zerver/lib/markdown/__init__.py b/zerver/lib/markdown/__init__.py index 9e93f8b9bd..c41afa8d62 100644 --- a/zerver/lib/markdown/__init__.py +++ b/zerver/lib/markdown/__init__.py @@ -128,6 +128,9 @@ class MessageRenderingResult: user_ids_with_alert_words: Set[int] potential_attachment_path_ids: List[str] + def has_wildcard_mention(self) -> bool: + return self.mentions_stream_wildcard or self.mentions_topic_wildcard + @dataclass class DbData: diff --git a/zerver/lib/message.py b/zerver/lib/message.py index b5ddc81788..2e0a3bfe28 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -158,9 +158,11 @@ 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. + # 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. + # 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] @@ -168,8 +170,26 @@ class SendMessageRequest: default_bot_user_ids: Set[int] service_bot_tuples: List[Tuple[int, int]] all_bot_user_ids: Set[int] + # IDs of topic participants who should be notified of topic wildcard mention. + # The 'user_allows_notifications_in_StreamTopic' with 'wildcard_mentions_notify' + # setting ON should return True. + # A user_id can exist in either or both of the 'topic_wildcard_mention_user_ids' + # and 'topic_wildcard_mention_in_followed_topic_user_ids' sets. + topic_wildcard_mention_user_ids: Set[int] + # IDs of users subscribed to the stream who should be notified of + # stream wildcard mention. + # The 'user_allows_notifications_in_StreamTopic' with 'wildcard_mentions_notify' + # setting ON should return True. + # A user_id can exist in either or both of the 'stream_wildcard_mention_user_ids' + # and 'stream_wildcard_mention_in_followed_topic_user_ids' sets. stream_wildcard_mention_user_ids: Set[int] - # IDs of users who have followed the topic the message (having stream wildcard) is being sent to, and have the followed topic wildcard mentions notify setting ON. + # IDs of topic participants who have followed the topic the message + # (having topic wildcard) is being sent to, and have the + # 'followed_topic_wildcard_mentions_notify' setting ON. + topic_wildcard_mention_in_followed_topic_user_ids: Set[int] + # IDs of users who have followed the topic the message + # (having stream wildcard) is being sent to, and have the + # 'followed_topic_wildcard_mentions_notify' setting ON. stream_wildcard_mention_in_followed_topic_user_ids: Set[int] links_for_embed: Set[str] widget_content: Optional[Dict[str, Any]] diff --git a/zerver/lib/notification_data.py b/zerver/lib/notification_data.py index 3d60fd5405..600da2bb6c 100644 --- a/zerver/lib/notification_data.py +++ b/zerver/lib/notification_data.py @@ -15,12 +15,16 @@ class UserMessageNotificationsData: pm_push_notify: bool mention_email_notify: bool mention_push_notify: bool + topic_wildcard_mention_email_notify: bool + topic_wildcard_mention_push_notify: bool stream_wildcard_mention_email_notify: bool stream_wildcard_mention_push_notify: bool stream_push_notify: bool stream_email_notify: bool followed_topic_push_notify: bool followed_topic_email_notify: bool + topic_wildcard_mention_in_followed_topic_push_notify: bool + topic_wildcard_mention_in_followed_topic_email_notify: bool stream_wildcard_mention_in_followed_topic_push_notify: bool stream_wildcard_mention_in_followed_topic_email_notify: bool sender_is_muted: bool @@ -57,9 +61,11 @@ class UserMessageNotificationsData: pm_mention_email_disabled_user_ids: Set[int], stream_push_user_ids: Set[int], stream_email_user_ids: Set[int], + topic_wildcard_mention_user_ids: Set[int], stream_wildcard_mention_user_ids: Set[int], followed_topic_push_user_ids: Set[int], followed_topic_email_user_ids: Set[int], + topic_wildcard_mention_in_followed_topic_user_ids: Set[int], stream_wildcard_mention_in_followed_topic_user_ids: Set[int], muted_sender_user_ids: Set[int], all_bot_user_ids: Set[int], @@ -70,33 +76,48 @@ class UserMessageNotificationsData: user_id=user_id, pm_email_notify=False, mention_email_notify=False, + topic_wildcard_mention_email_notify=False, stream_wildcard_mention_email_notify=False, pm_push_notify=False, mention_push_notify=False, + topic_wildcard_mention_push_notify=False, stream_wildcard_mention_push_notify=False, online_push_enabled=False, stream_push_notify=False, stream_email_notify=False, followed_topic_push_notify=False, followed_topic_email_notify=False, + topic_wildcard_mention_in_followed_topic_push_notify=False, + topic_wildcard_mention_in_followed_topic_email_notify=False, stream_wildcard_mention_in_followed_topic_push_notify=False, stream_wildcard_mention_in_followed_topic_email_notify=False, sender_is_muted=False, disable_external_notifications=False, ) - # `stream_wildcard_mention_user_ids` are those user IDs for whom stream wildcard - # mentions should obey notification settings of personal mentions. Hence, it isn't an - # independent notification setting and acts as a wrapper. + # `stream_wildcard_mention_user_ids`, `topic_wildcard_mention_user_ids`, + # `stream_wildcard_mention_in_followed_topic_user_ids` and `topic_wildcard_mention_in_followed_topic_user_ids` + # are those user IDs for whom stream or topic wildcard mentions should obey notification + # settings for personal mentions. Hence, it isn't an independent notification setting and acts as a wrapper. pm_email_notify = user_id not in pm_mention_email_disabled_user_ids and private_message mention_email_notify = ( user_id not in pm_mention_email_disabled_user_ids and "mentioned" in flags ) + topic_wildcard_mention_email_notify = ( + user_id in topic_wildcard_mention_user_ids + and user_id not in pm_mention_email_disabled_user_ids + and "wildcard_mentioned" in flags + ) stream_wildcard_mention_email_notify = ( user_id in stream_wildcard_mention_user_ids and user_id not in pm_mention_email_disabled_user_ids and "wildcard_mentioned" in flags ) + topic_wildcard_mention_in_followed_topic_email_notify = ( + user_id in topic_wildcard_mention_in_followed_topic_user_ids + and user_id not in pm_mention_email_disabled_user_ids + and "wildcard_mentioned" in flags + ) stream_wildcard_mention_in_followed_topic_email_notify = ( user_id in stream_wildcard_mention_in_followed_topic_user_ids and user_id not in pm_mention_email_disabled_user_ids @@ -107,11 +128,21 @@ class UserMessageNotificationsData: mention_push_notify = ( user_id not in pm_mention_push_disabled_user_ids and "mentioned" in flags ) + topic_wildcard_mention_push_notify = ( + user_id in topic_wildcard_mention_user_ids + and user_id not in pm_mention_push_disabled_user_ids + and "wildcard_mentioned" in flags + ) stream_wildcard_mention_push_notify = ( user_id in stream_wildcard_mention_user_ids and user_id not in pm_mention_push_disabled_user_ids and "wildcard_mentioned" in flags ) + topic_wildcard_mention_in_followed_topic_push_notify = ( + user_id in topic_wildcard_mention_in_followed_topic_user_ids + and user_id not in pm_mention_push_disabled_user_ids + and "wildcard_mentioned" in flags + ) stream_wildcard_mention_in_followed_topic_push_notify = ( user_id in stream_wildcard_mention_in_followed_topic_user_ids and user_id not in pm_mention_push_disabled_user_ids @@ -121,15 +152,19 @@ class UserMessageNotificationsData: user_id=user_id, pm_email_notify=pm_email_notify, mention_email_notify=mention_email_notify, + topic_wildcard_mention_email_notify=topic_wildcard_mention_email_notify, stream_wildcard_mention_email_notify=stream_wildcard_mention_email_notify, pm_push_notify=pm_push_notify, mention_push_notify=mention_push_notify, + topic_wildcard_mention_push_notify=topic_wildcard_mention_push_notify, stream_wildcard_mention_push_notify=stream_wildcard_mention_push_notify, 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), + topic_wildcard_mention_in_followed_topic_push_notify=topic_wildcard_mention_in_followed_topic_push_notify, + topic_wildcard_mention_in_followed_topic_email_notify=topic_wildcard_mention_in_followed_topic_email_notify, stream_wildcard_mention_in_followed_topic_push_notify=stream_wildcard_mention_in_followed_topic_push_notify, stream_wildcard_mention_in_followed_topic_email_notify=stream_wildcard_mention_in_followed_topic_email_notify, sender_is_muted=(user_id in muted_sender_user_ids), @@ -177,8 +212,12 @@ class UserMessageNotificationsData: return NotificationTriggers.PRIVATE_MESSAGE elif self.mention_push_notify: return NotificationTriggers.MENTION + elif self.topic_wildcard_mention_in_followed_topic_push_notify: + return NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC elif self.stream_wildcard_mention_in_followed_topic_push_notify: return NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC + elif self.topic_wildcard_mention_push_notify: + return NotificationTriggers.TOPIC_WILDCARD_MENTION elif self.stream_wildcard_mention_push_notify: return NotificationTriggers.STREAM_WILDCARD_MENTION elif self.followed_topic_push_notify: @@ -205,8 +244,12 @@ class UserMessageNotificationsData: return NotificationTriggers.PRIVATE_MESSAGE elif self.mention_email_notify: return NotificationTriggers.MENTION + elif self.topic_wildcard_mention_in_followed_topic_email_notify: + return NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC elif self.stream_wildcard_mention_in_followed_topic_email_notify: return NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC + elif self.topic_wildcard_mention_email_notify: + return NotificationTriggers.TOPIC_WILDCARD_MENTION elif self.stream_wildcard_mention_email_notify: return NotificationTriggers.STREAM_WILDCARD_MENTION elif self.followed_topic_email_notify: diff --git a/zerver/lib/push_notifications.py b/zerver/lib/push_notifications.py index 11022728ff..46aeeee38b 100644 --- a/zerver/lib/push_notifications.py +++ b/zerver/lib/push_notifications.py @@ -672,11 +672,18 @@ def get_gcm_alert( return f"{sender_str} mentioned you in #{display_recipient}" else: return f"{sender_str} mentioned @{mentioned_user_group_name} in #{display_recipient}" + elif ( + message.is_stream_message() + and trigger == NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC + ): + return "TODO - 2" elif ( message.is_stream_message() and trigger == NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC ): return "TODO" + elif message.is_stream_message() and trigger == NotificationTriggers.TOPIC_WILDCARD_MENTION: + return f"{sender_str} mentioned all topic participants in #{display_recipient} > {message.topic_name()}" elif message.is_stream_message() and trigger == NotificationTriggers.STREAM_WILDCARD_MENTION: return f"{sender_str} mentioned everyone in #{display_recipient}" else: @@ -842,8 +849,14 @@ def get_apns_alert_subtitle( ) else: return _("{full_name} mentioned you:").format(full_name=message.sender.full_name) + elif trigger == NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC: + return _("TODO - 2") elif trigger == NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC: return _("TODO") + elif trigger == NotificationTriggers.TOPIC_WILDCARD_MENTION: + return _("{full_name} mentioned all topic participants:").format( + full_name=message.sender.full_name + ) elif trigger == NotificationTriggers.STREAM_WILDCARD_MENTION: return _("{full_name} mentioned everyone:").format(full_name=message.sender.full_name) elif message.recipient.type == Recipient.PERSONAL: diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index bdc38f2d0f..832f701ae9 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -1704,6 +1704,12 @@ Output: pm_push_notify=kwargs.get("pm_push_notify", False), mention_email_notify=kwargs.get("mention_email_notify", False), mention_push_notify=kwargs.get("mention_push_notify", False), + topic_wildcard_mention_email_notify=kwargs.get( + "topic_wildcard_mention_email_notify", False + ), + topic_wildcard_mention_push_notify=kwargs.get( + "topic_wildcard_mention_push_notify", False + ), stream_wildcard_mention_email_notify=kwargs.get( "stream_wildcard_mention_email_notify", False ), @@ -1714,6 +1720,12 @@ Output: 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), + topic_wildcard_mention_in_followed_topic_email_notify=kwargs.get( + "topic_wildcard_mention_in_followed_topic_email_notify", False + ), + topic_wildcard_mention_in_followed_topic_push_notify=kwargs.get( + "topic_wildcard_mention_in_followed_topic_push_notify", False + ), stream_wildcard_mention_in_followed_topic_email_notify=kwargs.get( "stream_wildcard_mention_in_followed_topic_email_notify", False ), diff --git a/zerver/migrations/0331_scheduledmessagenotificationemail.py b/zerver/migrations/0331_scheduledmessagenotificationemail.py index d24e415d0b..66d08d9d3d 100644 --- a/zerver/migrations/0331_scheduledmessagenotificationemail.py +++ b/zerver/migrations/0331_scheduledmessagenotificationemail.py @@ -26,9 +26,14 @@ class Migration(migrations.Migration): choices=[ ("private_message", "Private message"), ("mentioned", "Mention"), + ("topic_wildcard_mentioned", "Topic wildcard mention"), ("stream_wildcard_mentioned", "Stream wildcard mention"), ("stream_email_notify", "Stream notifications enabled"), ("followed_topic_email_notify", "Followed topic notifications enabled"), + ( + "topic_wildcard_mentioned_in_followed_topic", + "Topic wildcard mention in followed topic", + ), ( "stream_wildcard_mentioned_in_followed_topic", "Stream wildcard mention in followed topic", diff --git a/zerver/models.py b/zerver/models.py index 2af0a1960b..edaad57610 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -4268,11 +4268,13 @@ class NotificationTriggers: # "private_message" is for 1:1 direct messages as well as huddles PRIVATE_MESSAGE = "private_message" MENTION = "mentioned" + TOPIC_WILDCARD_MENTION = "topic_wildcard_mentioned" STREAM_WILDCARD_MENTION = "stream_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" + TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC = "topic_wildcard_mentioned_in_followed_topic" STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC = "stream_wildcard_mentioned_in_followed_topic" @@ -4289,9 +4291,14 @@ class ScheduledMessageNotificationEmail(models.Model): EMAIL_NOTIFICATION_TRIGGER_CHOICES = [ (NotificationTriggers.PRIVATE_MESSAGE, "Private message"), (NotificationTriggers.MENTION, "Mention"), + (NotificationTriggers.TOPIC_WILDCARD_MENTION, "Topic wildcard mention"), (NotificationTriggers.STREAM_WILDCARD_MENTION, "Stream wildcard mention"), (NotificationTriggers.STREAM_EMAIL, "Stream notifications enabled"), (NotificationTriggers.FOLLOWED_TOPIC_EMAIL, "Followed topic notifications enabled"), + ( + NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC, + "Topic wildcard mention in followed topic", + ), ( NotificationTriggers.STREAM_WILDCARD_MENTION_IN_FOLLOWED_TOPIC, "Stream wildcard mention in followed topic", diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py index 78f1c9240f..3bd3e4e367 100644 --- a/zerver/tests/test_email_notifications.py +++ b/zerver/tests/test_email_notifications.py @@ -705,6 +705,47 @@ class TestMissedMessages(ZulipTestCase): trigger="mentioned", ) + def _extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic( + self, send_as_user: bool, show_message_content: bool = True + ) -> None: + for i in range(1, 6): + self.send_stream_message(self.example_user("othello"), "Denmark", content=str(i)) + self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2") + msg_id = self.send_stream_message(self.example_user("othello"), "Denmark", "@**topic**") + + if show_message_content: + verify_body_include = [ + "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**topic** -- ", + "You are receiving this because all topic participants were mentioned in #Denmark > test.", + ] + email_subject = "#Denmark > test" + verify_body_does_not_include: List[str] = [] + else: + # Test in case if message content in missed email message are disabled. + verify_body_include = [ + "This email does not include message content because you have disabled message ", + "http://zulip.testserver/help/pm-mention-alert-notifications ", + "View or reply in Zulip Dev Zulip", + " Manage email preferences: http://zulip.testserver/#settings/notifications", + ] + email_subject = "New messages" + verify_body_does_not_include = [ + "Othello, the Moor of Venice", + "1 2 3 4 5 @**topic**", + "private", + "group", + "Reply to this email directly, or view it in Zulip Dev Zulip", + ] + self._test_cases( + msg_id, + verify_body_include, + email_subject, + send_as_user, + show_message_content=show_message_content, + verify_body_does_not_include=verify_body_does_not_include, + trigger="topic_wildcard_mentioned_in_followed_topic", + ) + def _extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic( self, send_as_user: bool, show_message_content: bool = True ) -> None: @@ -730,6 +771,7 @@ class TestMissedMessages(ZulipTestCase): ] email_subject = "New messages" verify_body_does_not_include = [ + "Denmark > test", "Othello, the Moor of Venice", "1 2 3 4 5 @**all**", "private", @@ -746,6 +788,47 @@ class TestMissedMessages(ZulipTestCase): trigger="stream_wildcard_mentioned_in_followed_topic", ) + def _extra_context_in_missed_stream_messages_topic_wildcard_mention( + self, send_as_user: bool, show_message_content: bool = True + ) -> None: + for i in range(1, 6): + self.send_stream_message(self.example_user("othello"), "Denmark", content=str(i)) + self.send_stream_message(self.example_user("othello"), "Denmark", "11", topic_name="test2") + msg_id = self.send_stream_message(self.example_user("othello"), "denmark", "@**topic**") + + if show_message_content: + verify_body_include = [ + "Othello, the Moor of Venice: > 1 > 2 > 3 > 4 > 5 > @**topic** -- ", + "You are receiving this because all topic participants were mentioned in #Denmark > test.", + ] + email_subject = "#Denmark > test" + verify_body_does_not_include: List[str] = [] + else: + # Test in case if message content in missed email message are disabled. + verify_body_include = [ + "This email does not include message content because you have disabled message ", + "http://zulip.testserver/help/pm-mention-alert-notifications ", + "View or reply in Zulip Dev Zulip", + " Manage email preferences: http://zulip.testserver/#settings/notifications", + ] + email_subject = "New messages" + verify_body_does_not_include = [ + "Othello, the Moor of Venice", + "1 2 3 4 5 @**topic**", + "private", + "group", + "Reply to this email directly, or view it in Zulip Dev Zulip", + ] + self._test_cases( + msg_id, + verify_body_include, + email_subject, + send_as_user, + show_message_content=show_message_content, + verify_body_does_not_include=verify_body_does_not_include, + trigger="topic_wildcard_mentioned", + ) + def _extra_context_in_missed_stream_messages_stream_wildcard_mention( self, send_as_user: bool, show_message_content: bool = True ) -> None: @@ -1110,7 +1193,7 @@ class TestMissedMessages(ZulipTestCase): for text in expected_email_include: self.assertIn(text, self.normalize_string(mail.outbox[0].body)) - def test_user_group_over_stream_wildcard_mention_in_followed_topic_priority(self) -> None: + def test_user_group_over_topic_wildcard_mention_in_followed_topic_priority(self) -> None: hamlet = self.example_user("hamlet") cordelia = self.example_user("cordelia") othello = self.example_user("othello") @@ -1119,8 +1202,8 @@ class TestMissedMessages(ZulipTestCase): get_realm("zulip"), "hamlet_and_cordelia", [hamlet, cordelia], acting_user=None ) - stream_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message( - othello, "Denmark", "@**all**" + topic_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message( + othello, "Denmark", "@**topic**" ) user_group_mentioned_message_id = self.send_stream_message( othello, "Denmark", "@*hamlet_and_cordelia*" @@ -1129,8 +1212,8 @@ class TestMissedMessages(ZulipTestCase): handle_missedmessage_emails( hamlet.id, { - stream_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData( - trigger="stream_wildcard_mentioned_in_followed_topic" + topic_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData( + trigger="topic_wildcard_mentioned_in_followed_topic" ), user_group_mentioned_message_id: MissedMessageData( trigger="mentioned", mentioned_user_group_id=hamlet_and_cordelia.id @@ -1139,19 +1222,52 @@ class TestMissedMessages(ZulipTestCase): ) expected_email_include = [ - "Othello, the Moor of Venice: > @**all** > @*hamlet_and_cordelia* -- ", + "Othello, the Moor of Venice: > @**topic** > @*hamlet_and_cordelia* -- ", "You are receiving this because @hamlet_and_cordelia was mentioned.", ] for text in expected_email_include: self.assertIn(text, self.normalize_string(mail.outbox[0].body)) - def test_stream_wildcard_in_followed_topic_over_stream_wildcard_mention_priority(self) -> None: + def test_topic_wildcard_in_followed_topic_over_stream_wildcard_mention_in_followed_topic_priority( + self, + ) -> None: hamlet = self.example_user("hamlet") othello = self.example_user("othello") - stream_wildcard_mentioned_message_id = self.send_stream_message( - othello, "Denmark", "@**all**" + stream_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message( + othello, "Denmark", "@**stream**" + ) + topic_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message( + othello, "Denmark", "@**topic**" + ) + + handle_missedmessage_emails( + hamlet.id, + { + stream_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData( + trigger="stream_wildcard_mentioned_in_followed_topic" + ), + topic_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData( + trigger="topic_wildcard_mentioned_in_followed_topic" + ), + }, + ) + + expected_email_include = [ + "Othello, the Moor of Venice: > @**stream** > @**topic** -- ", + "You are receiving this because all topic participants were mentioned in #Denmark > test.", + ] + + for text in expected_email_include: + self.assertIn(text, self.normalize_string(mail.outbox[0].body)) + + def test_stream_wildcard_in_followed_topic_over_topic_wildcard_mention_priority(self) -> None: + hamlet = self.example_user("hamlet") + othello = self.example_user("othello") + + topic_wildcard_mentioned_message_id = self.send_stream_message( + othello, "Denmark", "@**topic**" ) stream_wildcard_mentioned_in_followed_topic_message_id = self.send_stream_message( othello, "Denmark", "@**all**" @@ -1160,8 +1276,8 @@ class TestMissedMessages(ZulipTestCase): handle_missedmessage_emails( hamlet.id, { - stream_wildcard_mentioned_message_id: MissedMessageData( - trigger="stream_wildcard_mentioned" + topic_wildcard_mentioned_message_id: MissedMessageData( + trigger="topic_wildcard_mentioned" ), stream_wildcard_mentioned_in_followed_topic_message_id: MissedMessageData( trigger="stream_wildcard_mentioned_in_followed_topic" @@ -1170,13 +1286,44 @@ class TestMissedMessages(ZulipTestCase): ) expected_email_include = [ - "Othello, the Moor of Venice: > @**all** > @**all** -- ", + "Othello, the Moor of Venice: > @**topic** > @**all** -- ", "You are receiving this because you have wildcard mention notifications enabled for topics you follow.", ] for text in expected_email_include: self.assertIn(text, self.normalize_string(mail.outbox[0].body)) + def test_topic_wildcard_over_stream_wildcard_mention_priority(self) -> None: + hamlet = self.example_user("hamlet") + othello = self.example_user("othello") + + stream_wildcard_mentioned_message_id = self.send_stream_message( + othello, "Denmark", "@**all**" + ) + topic_wildcard_mentioned_message_id = self.send_stream_message( + othello, "Denmark", "@**topic**" + ) + + handle_missedmessage_emails( + hamlet.id, + { + stream_wildcard_mentioned_message_id: MissedMessageData( + trigger="stream_wildcard_mentioned" + ), + topic_wildcard_mentioned_message_id: MissedMessageData( + trigger="topic_wildcard_mentioned" + ), + }, + ) + + expected_email_include = [ + "Othello, the Moor of Venice: > @**all** > @**topic** -- ", + "You are receiving this because all topic participants were mentioned in #Denmark > test.", + ] + + for text in expected_email_include: + self.assertIn(text, self.normalize_string(mail.outbox[0].body)) + def test_stream_wildcard_mention_over_followed_topic_notify_priority(self) -> None: hamlet = self.example_user("hamlet") othello = self.example_user("othello") @@ -1316,10 +1463,18 @@ class TestMissedMessages(ZulipTestCase): ) self._extra_context_in_missed_stream_messages_mention(False, show_message_content=False) mail.outbox = [] + self._extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic( + False, show_message_content=False + ) + mail.outbox = [] self._extra_context_in_missed_stream_messages_stream_wildcard_mention_in_followed_topic( False, show_message_content=False ) mail.outbox = [] + self._extra_context_in_missed_stream_messages_topic_wildcard_mention( + False, show_message_content=False + ) + mail.outbox = [] self._extra_context_in_missed_stream_messages_stream_wildcard_mention( False, show_message_content=False ) @@ -1337,6 +1492,19 @@ class TestMissedMessages(ZulipTestCase): def test_extra_context_in_missed_stream_messages(self) -> None: self._extra_context_in_missed_stream_messages_mention(False) + @override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True) + def test_extra_context_in_missed_stream_messages_as_user_topic_wildcard_in_followed_topic( + self, + ) -> None: + self._extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic(True) + + def test_extra_context_in_missed_stream_messages_topic_wildcard_in_followed_topic( + self, + ) -> None: + self._extra_context_in_missed_stream_messages_topic_wildcard_mention_in_followed_topic( + False + ) + @override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True) def test_extra_context_in_missed_stream_messages_as_user_stream_wildcard_in_followed_topic( self, @@ -1352,6 +1520,13 @@ class TestMissedMessages(ZulipTestCase): False ) + @override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True) + def test_extra_context_in_missed_stream_messages_as_user_topic_wildcard(self) -> None: + self._extra_context_in_missed_stream_messages_topic_wildcard_mention(True) + + def test_extra_context_in_missed_stream_messages_topic_wildcard(self) -> None: + self._extra_context_in_missed_stream_messages_topic_wildcard_mention(False) + @override_settings(SEND_MISSED_MESSAGE_EMAILS_AS_USER=True) def test_extra_context_in_missed_stream_messages_as_user_stream_wildcard(self) -> None: self._extra_context_in_missed_stream_messages_stream_wildcard_mention(True) diff --git a/zerver/tests/test_event_queue.py b/zerver/tests/test_event_queue.py index 2cded97d19..4633a6a7ff 100644 --- a/zerver/tests/test_event_queue.py +++ b/zerver/tests/test_event_queue.py @@ -298,6 +298,69 @@ class MissedMessageHookTest(ZulipTestCase): already_notified={"email_notified": True, "push_notified": False}, ) + def test_topic_wildcard_mention(self) -> None: + # By default, topic wildcard mentions should send notifications, just like regular mentions + self.send_stream_message(self.user_profile, "Denmark") + msg_id = self.send_stream_message(self.iago, "Denmark", content="@**topic** what's up?") + 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() + args_dict = mock_enqueue.call_args_list[1][1] + + self.assert_maybe_enqueue_notifications_call_args( + args_dict=args_dict, + message_id=msg_id, + user_id=self.user_profile.id, + topic_wildcard_mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + already_notified={"email_notified": True, "push_notified": True}, + ) + + def test_topic_wildcard_mention_in_muted_stream(self) -> None: + # Topic wildcard mentions in muted streams don't notify. + self.change_subscription_properties({"is_muted": True}) + self.send_stream_message(self.user_profile, "Denmark") + msg_id = self.send_stream_message(self.iago, "Denmark", content="@**topic** what's up?") + 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() + args_dict = mock_enqueue.call_args_list[1][1] + + self.assert_maybe_enqueue_notifications_call_args( + args_dict=args_dict, + message_id=msg_id, + user_id=self.user_profile.id, + topic_wildcard_mention_email_notify=False, + topic_wildcard_mention_push_notify=False, + already_notified={"email_notified": False, "push_notified": False}, + ) + + def test_topic_wildcard_mention_in_muted_topic(self) -> None: + # Topic wildcard mentions in muted topics don't notify. + do_set_user_topic_visibility_policy( + self.user_profile, + get_stream("Denmark", self.user_profile.realm), + "mutingtest", + visibility_policy=UserTopic.VisibilityPolicy.MUTED, + ) + self.send_stream_message(self.user_profile, "Denmark") + msg_id = self.send_stream_message( + self.iago, "Denmark", topic_name="mutingtest", content="@**topic** what's up?" + ) + 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() + args_dict = mock_enqueue.call_args_list[1][1] + + self.assert_maybe_enqueue_notifications_call_args( + args_dict=args_dict, + message_id=msg_id, + user_id=self.user_profile.id, + topic_wildcard_mention_email_notify=False, + topic_wildcard_mention_push_notify=False, + already_notified={"email_notified": False, "push_notified": False}, + ) + def test_stream_wildcard_mention(self) -> None: # By default, stream wildcard mentions should send notifications, just like regular mentions msg_id = self.send_stream_message(self.iago, "Denmark", content="@**all** what's up?") @@ -842,6 +905,42 @@ class MissedMessageHookTest(ZulipTestCase): already_notified={"email_notified": True, "push_notified": False}, ) + def test_topic_wildcard_mention_in_followed_topic_notify(self) -> None: + do_change_user_setting( + self.user_profile, "wildcard_mentions_notify", False, acting_user=None + ) + do_change_user_setting( + self.user_profile, "enable_followed_topic_email_notifications", False, acting_user=None + ) + do_change_user_setting( + self.user_profile, "enable_followed_topic_push_notifications", False, acting_user=None + ) + + # By default, wildcard mentions in followed topics should send notifications, just like regular mentions. + do_set_user_topic_visibility_policy( + self.user_profile, + get_stream("Denmark", self.user_profile.realm), + "followed_topic_test", + visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED, + ) + self.send_stream_message(self.user_profile, "Denmark", topic_name="followed_topic_test") + msg_id = self.send_stream_message( + self.iago, "Denmark", content="@**topic** what's up?", 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() + args_dict = mock_enqueue.call_args_list[1][1] + + self.assert_maybe_enqueue_notifications_call_args( + args_dict=args_dict, + message_id=msg_id, + user_id=self.user_profile.id, + topic_wildcard_mention_in_followed_topic_email_notify=True, + topic_wildcard_mention_in_followed_topic_push_notify=True, + already_notified={"email_notified": True, "push_notified": True}, + ) + def test_stream_wildcard_mention_in_followed_topic_notify(self) -> None: do_change_user_setting( self.user_profile, "wildcard_mentions_notify", False, acting_user=None @@ -877,6 +976,42 @@ class MissedMessageHookTest(ZulipTestCase): already_notified={"email_notified": True, "push_notified": True}, ) + def test_topic_wildcard_mention_in_followed_topic_muted_stream(self) -> None: + # By default, topic wildcard mentions in a followed topic with muted stream DO notify. + do_change_user_setting( + self.user_profile, "wildcard_mentions_notify", False, acting_user=None + ) + do_change_user_setting( + self.user_profile, "enable_followed_topic_email_notifications", False, acting_user=None + ) + do_change_user_setting( + self.user_profile, "enable_followed_topic_push_notifications", False, acting_user=None + ) + + self.change_subscription_properties({"is_muted": True}) + do_set_user_topic_visibility_policy( + self.user_profile, + get_stream("Denmark", self.user_profile.realm), + "test", + visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED, + ) + self.send_stream_message(self.user_profile, "Denmark") + + msg_id = self.send_stream_message(self.iago, "Denmark", content="@**topic** what's up?") + 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() + args_dict = mock_enqueue.call_args_list[1][1] + + self.assert_maybe_enqueue_notifications_call_args( + args_dict=args_dict, + message_id=msg_id, + user_id=self.user_profile.id, + topic_wildcard_mention_in_followed_topic_email_notify=True, + topic_wildcard_mention_in_followed_topic_push_notify=True, + already_notified={"email_notified": True, "push_notified": True}, + ) + def test_stream_wildcard_mention_in_followed_topic_muted_stream(self) -> None: # By default, stream wildcard mentions in a followed topic with muted stream DO notify. do_change_user_setting( diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 149af269e7..43ec086b0c 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -456,6 +456,13 @@ class NormalActionsTest(BaseAction): lambda: self.send_stream_message(self.example_user("cordelia"), "Verona", content), ) + def test_topic_wildcard_mentioned_send_message_events(self) -> None: + for i in range(3): + content = "mentioning... @**topic** hello " + str(i) + self.verify_action( + lambda: self.send_stream_message(self.example_user("cordelia"), "Verona", content), + ) + def test_stream_wildcard_mentioned_send_message_events(self) -> None: for i in range(3): content = "mentioning... @**all** hello " + str(i) diff --git a/zerver/tests/test_message_edit.py b/zerver/tests/test_message_edit.py index 83fc160d00..5e30765413 100644 --- a/zerver/tests/test_message_edit.py +++ b/zerver/tests/test_message_edit.py @@ -2038,6 +2038,58 @@ class EditMessageTest(EditMessageTestCase): original_topic_state=UserTopic.VisibilityPolicy.UNMUTED, ) + @mock.patch("zerver.actions.message_edit.send_event") + def test_topic_wildcard_mention_in_followed_topic( + self, mock_send_event: mock.MagicMock + ) -> None: + stream_name = "Macbeth" + hamlet = self.example_user("hamlet") + cordelia = self.example_user("cordelia") + self.make_stream(stream_name, history_public_to_subscribers=True) + self.subscribe(hamlet, stream_name) + self.subscribe(cordelia, stream_name) + self.login_user(hamlet) + + do_set_user_topic_visibility_policy( + user_profile=hamlet, + stream=get_stream(stream_name, cordelia.realm), + topic="test", + visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED, + ) + message_id = self.send_stream_message(hamlet, stream_name, "Hello everyone") + + def notify(user_id: int) -> Dict[str, Any]: + return { + "id": user_id, + "flags": ["wildcard_mentioned"], + } + + users_to_be_notified = sorted(map(notify, [cordelia.id, hamlet.id]), key=itemgetter("id")) + result = self.client_patch( + f"/json/messages/{message_id}", + { + "content": "Hello @**topic**", + }, + ) + self.assert_json_success(result) + + # Extract the send_event call where event type is 'update_message'. + # Here we assert 'topic_wildcard_mention_in_followed_topic_user_ids' + # has been set properly. + called = False + for call_args in mock_send_event.call_args_list: + (arg_realm, arg_event, arg_notified_users) = call_args[0] + if arg_event["type"] == "update_message": + self.assertEqual(arg_event["type"], "update_message") + self.assertEqual( + arg_event["topic_wildcard_mention_in_followed_topic_user_ids"], [hamlet.id] + ) + self.assertEqual( + sorted(arg_notified_users, key=itemgetter("id")), users_to_be_notified + ) + called = True + self.assertTrue(called) + @mock.patch("zerver.actions.message_edit.send_event") def test_stream_wildcard_mention_in_followed_topic( self, mock_send_event: mock.MagicMock @@ -2090,6 +2142,95 @@ class EditMessageTest(EditMessageTestCase): called = True self.assertTrue(called) + @mock.patch("zerver.actions.message_edit.send_event") + def test_topic_wildcard_mention(self, mock_send_event: mock.MagicMock) -> None: + stream_name = "Macbeth" + hamlet = self.example_user("hamlet") + cordelia = self.example_user("cordelia") + self.make_stream(stream_name, history_public_to_subscribers=True) + self.subscribe(hamlet, stream_name) + self.subscribe(cordelia, stream_name) + self.login_user(hamlet) + message_id = self.send_stream_message(hamlet, stream_name, "Hello everyone") + + def notify(user_id: int) -> Dict[str, Any]: + return { + "id": user_id, + "flags": ["wildcard_mentioned"], + } + + users_to_be_notified = sorted(map(notify, [cordelia.id, hamlet.id]), key=itemgetter("id")) + result = self.client_patch( + f"/json/messages/{message_id}", + { + "content": "Hello @**topic**", + }, + ) + self.assert_json_success(result) + + # Extract the send_event call where event type is 'update_message'. + # Here we assert topic_wildcard_mention_user_ids has been set properly. + called = False + for call_args in mock_send_event.call_args_list: + (arg_realm, arg_event, arg_notified_users) = call_args[0] + if arg_event["type"] == "update_message": + self.assertEqual(arg_event["type"], "update_message") + self.assertEqual(arg_event["topic_wildcard_mention_user_ids"], [hamlet.id]) + self.assertEqual( + sorted(arg_notified_users, key=itemgetter("id")), users_to_be_notified + ) + called = True + self.assertTrue(called) + + def test_topic_wildcard_mention_restrictions_when_editing(self) -> None: + cordelia = self.example_user("cordelia") + shiva = self.example_user("shiva") + self.login("cordelia") + stream_name = "Macbeth" + self.make_stream(stream_name, history_public_to_subscribers=True) + self.subscribe(cordelia, stream_name) + self.subscribe(shiva, stream_name) + message_id = self.send_stream_message(cordelia, stream_name, "Hello everyone") + + realm = cordelia.realm + do_set_realm_property( + realm, + "wildcard_mention_policy", + Realm.WILDCARD_MENTION_POLICY_MODERATORS, + acting_user=None, + ) + + with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=17): + result = self.client_patch( + "/json/messages/" + str(message_id), + { + "content": "Hello @**topic**", + }, + ) + self.assert_json_error( + result, "You do not have permission to use wildcard mentions in this stream." + ) + + with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=14): + result = self.client_patch( + "/json/messages/" + str(message_id), + { + "content": "Hello @**topic**", + }, + ) + self.assert_json_success(result) + + self.login("shiva") + message_id = self.send_stream_message(shiva, stream_name, "Hi everyone") + with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=17): + result = self.client_patch( + "/json/messages/" + str(message_id), + { + "content": "Hello @**topic**", + }, + ) + self.assert_json_success(result) + @mock.patch("zerver.actions.message_edit.send_event") def test_stream_wildcard_mention(self, mock_send_event: mock.MagicMock) -> None: stream_name = "Macbeth" diff --git a/zerver/tests/test_message_edit_notifications.py b/zerver/tests/test_message_edit_notifications.py index 7fc95d3906..29f36a7f86 100644 --- a/zerver/tests/test_message_edit_notifications.py +++ b/zerver/tests/test_message_edit_notifications.py @@ -398,6 +398,56 @@ class EditMessageSideEffectsTest(ZulipTestCase): # actual content of these messages.) self.assert_length(info["queue_messages"], 2) + def test_updates_with_topic_wildcard_mention_in_followed_topic(self) -> None: + cordelia = self.example_user("cordelia") + hamlet = self.example_user("hamlet") + self.subscribe(cordelia, "Scotland") + + do_change_user_setting( + cordelia, "enable_followed_topic_email_notifications", False, acting_user=None + ) + do_change_user_setting( + cordelia, "enable_followed_topic_push_notifications", False, acting_user=None + ) + do_change_user_setting(cordelia, "wildcard_mentions_notify", False, acting_user=None) + do_set_user_topic_visibility_policy( + user_profile=cordelia, + stream=get_stream("Scotland", cordelia.realm), + topic="test", + visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED, + ) + + # Only users who either sent or reacted to messages in the topic + # are considered for @topic mention notifications. + self.send_stream_message(cordelia, "Scotland") + + # We will simulate that the user still has an active client, + # but they don't have UserPresence rows, so we will still + # send offline notifications. + original_content = "no mention" + updated_content = "now we mention @**topic**" + notification_message_data = self._send_and_update_message( + original_content, + updated_content, + connected_to_zulip=True, + ) + + message_id = notification_message_data["message_id"] + info = notification_message_data["info"] + + expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters( + user_id=cordelia.id, + acting_user_id=hamlet.id, + message_id=message_id, + topic_wildcard_mention_in_followed_topic_email_notify=True, + topic_wildcard_mention_in_followed_topic_push_notify=True, + already_notified={}, + ) + self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs) + + # messages will get enqueued. + self.assert_length(info["queue_messages"], 2) + def test_updates_with_stream_wildcard_mention_in_followed_topic(self) -> None: cordelia = self.example_user("cordelia") hamlet = self.example_user("hamlet") @@ -444,6 +494,42 @@ class EditMessageSideEffectsTest(ZulipTestCase): # messages will get enqueued. self.assert_length(info["queue_messages"], 2) + def test_updates_with_topic_wildcard_mention(self) -> None: + cordelia = self.example_user("cordelia") + hamlet = self.example_user("hamlet") + + # Only users who either sent or reacted to messages in the topic + # are considered for @topic mention notifications. + self.subscribe(cordelia, "Scotland") + self.send_stream_message(cordelia, "Scotland") + + # We will simulate that the user still has an active client, + # but they don't have UserPresence rows, so we will still + # send offline notifications. + original_content = "no mention" + updated_content = "now we mention @**topic**" + notification_message_data = self._send_and_update_message( + original_content, + updated_content, + connected_to_zulip=True, + ) + + message_id = notification_message_data["message_id"] + info = notification_message_data["info"] + + expected_enqueue_kwargs = self.get_maybe_enqueue_notifications_parameters( + user_id=cordelia.id, + acting_user_id=hamlet.id, + message_id=message_id, + topic_wildcard_mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + already_notified={}, + ) + self.assertEqual(info["enqueue_kwargs"], expected_enqueue_kwargs) + + # messages will get enqueued. + self.assert_length(info["queue_messages"], 2) + def test_updates_with_stream_wildcard_mention(self) -> None: cordelia = self.example_user("cordelia") hamlet = self.example_user("hamlet") diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index b267cc4a0f..dfd25d4fac 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -1621,6 +1621,105 @@ class StreamMessagesTest(ZulipTestCase): self.assertEqual(user_message.message.content, content) self.assertTrue(user_message.flags.mentioned) + def send_and_verify_topic_wildcard_mention_message( + self, sender_name: str, test_fails: bool = False, sub_count: int = 16 + ) -> None: + sender = self.example_user(sender_name) + content = "@**topic** test topic wildcard mention" + with mock.patch("zerver.lib.message.num_subscribers_for_stream_id", return_value=sub_count): + if not test_fails: + msg_id = self.send_stream_message(sender, "test_stream", content) + result = self.api_get(sender, "/api/v1/messages/" + str(msg_id)) + self.assert_json_success(result) + + else: + with self.assertRaisesRegex( + JsonableError, + "You do not have permission to use wildcard mentions in this stream.", + ): + self.send_stream_message(sender, "test_stream", content) + + def test_topic_wildcard_mention_restrictions(self) -> None: + cordelia = self.example_user("cordelia") + iago = self.example_user("iago") + polonius = self.example_user("polonius") + shiva = self.example_user("shiva") + realm = cordelia.realm + + stream_name = "test_stream" + self.subscribe(cordelia, stream_name) + self.subscribe(iago, stream_name) + self.subscribe(polonius, stream_name) + self.subscribe(shiva, stream_name) + + do_set_realm_property( + realm, + "wildcard_mention_policy", + Realm.WILDCARD_MENTION_POLICY_EVERYONE, + acting_user=None, + ) + self.send_and_verify_topic_wildcard_mention_message("polonius") + + do_set_realm_property( + realm, + "wildcard_mention_policy", + Realm.WILDCARD_MENTION_POLICY_MEMBERS, + acting_user=None, + ) + self.send_and_verify_topic_wildcard_mention_message("polonius", test_fails=True) + # There is no restriction on small streams. + self.send_and_verify_topic_wildcard_mention_message("polonius", sub_count=10) + self.send_and_verify_topic_wildcard_mention_message("cordelia") + + do_set_realm_property( + realm, + "wildcard_mention_policy", + Realm.WILDCARD_MENTION_POLICY_FULL_MEMBERS, + acting_user=None, + ) + do_set_realm_property(realm, "waiting_period_threshold", 10, acting_user=None) + iago.date_joined = timezone_now() + iago.save() + shiva.date_joined = timezone_now() + shiva.save() + cordelia.date_joined = timezone_now() + cordelia.save() + self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True) + self.send_and_verify_topic_wildcard_mention_message("cordelia", sub_count=10) + # Administrators and moderators can use wildcard mentions even if they are new. + self.send_and_verify_topic_wildcard_mention_message("iago") + self.send_and_verify_topic_wildcard_mention_message("shiva") + + cordelia.date_joined = timezone_now() - datetime.timedelta(days=11) + cordelia.save() + self.send_and_verify_topic_wildcard_mention_message("cordelia") + + do_set_realm_property( + realm, + "wildcard_mention_policy", + Realm.WILDCARD_MENTION_POLICY_MODERATORS, + acting_user=None, + ) + self.send_and_verify_topic_wildcard_mention_message("cordelia", test_fails=True) + self.send_and_verify_topic_wildcard_mention_message("cordelia", sub_count=10) + self.send_and_verify_topic_wildcard_mention_message("shiva") + + cordelia.date_joined = timezone_now() + cordelia.save() + do_set_realm_property( + realm, "wildcard_mention_policy", Realm.WILDCARD_MENTION_POLICY_ADMINS, acting_user=None + ) + self.send_and_verify_topic_wildcard_mention_message("shiva", test_fails=True) + # There is no restriction on small streams. + self.send_and_verify_topic_wildcard_mention_message("shiva", sub_count=10) + self.send_and_verify_topic_wildcard_mention_message("iago") + + do_set_realm_property( + realm, "wildcard_mention_policy", Realm.WILDCARD_MENTION_POLICY_NOBODY, acting_user=None + ) + self.send_and_verify_topic_wildcard_mention_message("iago", test_fails=True) + self.send_and_verify_topic_wildcard_mention_message("iago", sub_count=10) + def send_and_verify_stream_wildcard_mention_message( self, sender_name: str, test_fails: bool = False, sub_count: int = 16 ) -> None: diff --git a/zerver/tests/test_notification_data.py b/zerver/tests/test_notification_data.py index f2946dbd60..c1afa5cf89 100644 --- a/zerver/tests/test_notification_data.py +++ b/zerver/tests/test_notification_data.py @@ -40,6 +40,16 @@ class TestNotificationData(ZulipTestCase): ) self.assertTrue(user_data.is_push_notifiable(acting_user_id=acting_user_id, idle=True)) + # Topic Wildcard mention + user_data = self.create_user_notifications_data_object( + user_id=user_id, topic_wildcard_mention_push_notify=True + ) + self.assertEqual( + user_data.get_push_notification_trigger(acting_user_id=acting_user_id, idle=True), + "topic_wildcard_mentioned", + ) + self.assertTrue(user_data.is_push_notifiable(acting_user_id=acting_user_id, idle=True)) + # Stream Wildcard mention user_data = self.create_user_notifications_data_object( user_id=user_id, stream_wildcard_mention_push_notify=True @@ -70,6 +80,16 @@ class TestNotificationData(ZulipTestCase): ) self.assertTrue(user_data.is_push_notifiable(acting_user_id=acting_user_id, idle=True)) + # Topic wildcard mention in followed topic + user_data = self.create_user_notifications_data_object( + user_id=user_id, topic_wildcard_mention_in_followed_topic_push_notify=True + ) + self.assertEqual( + user_data.get_push_notification_trigger(acting_user_id=acting_user_id, idle=True), + "topic_wildcard_mentioned_in_followed_topic", + ) + self.assertTrue(user_data.is_push_notifiable(acting_user_id=acting_user_id, idle=True)) + # Stream wildcard mention in followed topic user_data = self.create_user_notifications_data_object( user_id=user_id, stream_wildcard_mention_in_followed_topic_push_notify=True @@ -108,6 +128,8 @@ class TestNotificationData(ZulipTestCase): pm_email_notify=True, mention_push_notify=True, mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + topic_wildcard_mention_email_notify=True, stream_wildcard_mention_push_notify=True, stream_wildcard_mention_email_notify=True, ) @@ -124,6 +146,8 @@ class TestNotificationData(ZulipTestCase): pm_email_notify=True, mention_push_notify=True, mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + topic_wildcard_mention_email_notify=True, stream_wildcard_mention_push_notify=True, stream_wildcard_mention_email_notify=True, ) @@ -140,6 +164,8 @@ class TestNotificationData(ZulipTestCase): pm_email_notify=True, mention_push_notify=True, mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + topic_wildcard_mention_email_notify=True, stream_wildcard_mention_push_notify=True, stream_wildcard_mention_email_notify=True, disable_external_notifications=True, @@ -182,6 +208,16 @@ class TestNotificationData(ZulipTestCase): ) self.assertTrue(user_data.is_email_notifiable(acting_user_id=acting_user_id, idle=True)) + # Topic Wildcard mention + user_data = self.create_user_notifications_data_object( + user_id=user_id, topic_wildcard_mention_email_notify=True + ) + self.assertEqual( + user_data.get_email_notification_trigger(acting_user_id=acting_user_id, idle=True), + "topic_wildcard_mentioned", + ) + self.assertTrue(user_data.is_email_notifiable(acting_user_id=acting_user_id, idle=True)) + # Stream Wildcard mention user_data = self.create_user_notifications_data_object( user_id=user_id, stream_wildcard_mention_email_notify=True @@ -212,6 +248,16 @@ class TestNotificationData(ZulipTestCase): ) self.assertTrue(user_data.is_email_notifiable(acting_user_id=acting_user_id, idle=True)) + # Topic wildcard mention in followed topic + user_data = self.create_user_notifications_data_object( + user_id=user_id, topic_wildcard_mention_in_followed_topic_email_notify=True + ) + self.assertEqual( + user_data.get_email_notification_trigger(acting_user_id=acting_user_id, idle=True), + "topic_wildcard_mentioned_in_followed_topic", + ) + self.assertTrue(user_data.is_email_notifiable(acting_user_id=acting_user_id, idle=True)) + # Stream wildcard mention in followed topic user_data = self.create_user_notifications_data_object( user_id=user_id, stream_wildcard_mention_in_followed_topic_email_notify=True @@ -241,6 +287,8 @@ class TestNotificationData(ZulipTestCase): pm_email_notify=True, mention_push_notify=True, mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + topic_wildcard_mention_email_notify=True, stream_wildcard_mention_push_notify=True, stream_wildcard_mention_email_notify=True, ) @@ -257,6 +305,8 @@ class TestNotificationData(ZulipTestCase): pm_email_notify=True, mention_push_notify=True, mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + topic_wildcard_mention_email_notify=True, stream_wildcard_mention_push_notify=True, stream_wildcard_mention_email_notify=True, ) @@ -273,6 +323,8 @@ class TestNotificationData(ZulipTestCase): pm_email_notify=True, mention_push_notify=True, mention_email_notify=True, + topic_wildcard_mention_push_notify=True, + topic_wildcard_mention_email_notify=True, stream_wildcard_mention_push_notify=True, stream_wildcard_mention_email_notify=True, disable_external_notifications=True, @@ -306,9 +358,11 @@ class TestNotificationData(ZulipTestCase): muted_sender_user_ids=set(), stream_email_user_ids=set(), stream_push_user_ids=set(), + topic_wildcard_mention_user_ids=set(), stream_wildcard_mention_user_ids=set(), followed_topic_email_user_ids=set(), followed_topic_push_user_ids=set(), + topic_wildcard_mention_in_followed_topic_user_ids=set(), stream_wildcard_mention_in_followed_topic_user_ids=set(), ) self.assertEqual(user_data.is_notifiable(acting_user_id=1000, idle=True), notifiable) diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py index 346f88cf61..9a32f54c48 100644 --- a/zerver/tests/test_push_notifications.py +++ b/zerver/tests/test_push_notifications.py @@ -2094,6 +2094,41 @@ class TestGetAPNsPayload(PushNotificationTest): } self.assertDictEqual(payload, expected) + def test_get_message_payload_apns_topic_wildcard_mention_in_followed_topic(self) -> None: + user_profile = self.example_user("othello") + stream = Stream.objects.filter(name="Verona").get() + message = self.get_message(Recipient.STREAM, stream.id, stream.realm_id) + payload = get_message_payload_apns( + user_profile, + message, + NotificationTriggers.TOPIC_WILDCARD_MENTION_IN_FOLLOWED_TOPIC, + ) + expected = { + "alert": { + "title": "#Verona > Test topic", + "subtitle": "TODO - 2", + "body": message.content, + }, + "sound": "default", + "badge": 0, + "custom": { + "zulip": { + "message_ids": [message.id], + "recipient_type": "stream", + "sender_email": self.sender.email, + "sender_id": self.sender.id, + "stream": get_display_recipient(message.recipient), + "stream_id": stream.id, + "topic": message.topic_name(), + "server": settings.EXTERNAL_HOST, + "realm_id": self.sender.realm.id, + "realm_uri": self.sender.realm.uri, + "user_id": user_profile.id, + }, + }, + } + self.assertDictEqual(payload, expected) + def test_get_message_payload_apns_stream_wildcard_mention_in_followed_topic(self) -> None: user_profile = self.example_user("othello") stream = Stream.objects.filter(name="Verona").get() @@ -2127,6 +2162,39 @@ class TestGetAPNsPayload(PushNotificationTest): } self.assertDictEqual(payload, expected) + def test_get_message_payload_apns_topic_wildcard_mention(self) -> None: + user_profile = self.example_user("othello") + stream = Stream.objects.filter(name="Verona").get() + message = self.get_message(Recipient.STREAM, stream.id, stream.realm_id) + payload = get_message_payload_apns( + user_profile, message, NotificationTriggers.TOPIC_WILDCARD_MENTION + ) + expected = { + "alert": { + "title": "#Verona > Test topic", + "subtitle": "King Hamlet mentioned all topic participants:", + "body": message.content, + }, + "sound": "default", + "badge": 0, + "custom": { + "zulip": { + "message_ids": [message.id], + "recipient_type": "stream", + "sender_email": self.sender.email, + "sender_id": self.sender.id, + "stream": get_display_recipient(message.recipient), + "stream_id": stream.id, + "topic": message.topic_name(), + "server": settings.EXTERNAL_HOST, + "realm_id": self.sender.realm.id, + "realm_uri": self.sender.realm.uri, + "user_id": user_profile.id, + }, + }, + } + self.assertDictEqual(payload, expected) + def test_get_message_payload_apns_stream_wildcard_mention(self) -> None: user_profile = self.example_user("othello") stream = Stream.objects.filter(name="Verona").get() @@ -2267,11 +2335,22 @@ class TestGetGCMPayload(PushNotificationTest): mentioned_user_group_name="mobile_team", ) + def test_get_message_payload_gcm_topic_wildcard_mention_in_followed_topic(self) -> None: + self._test_get_message_payload_gcm_mentions( + "topic_wildcard_mentioned_in_followed_topic", "TODO - 2" + ) + def test_get_message_payload_gcm_stream_wildcard_mention_in_followed_topic(self) -> None: self._test_get_message_payload_gcm_mentions( "stream_wildcard_mentioned_in_followed_topic", "TODO" ) + def test_get_message_payload_gcm_topic_wildcard_mention(self) -> None: + self._test_get_message_payload_gcm_mentions( + "topic_wildcard_mentioned", + "King Hamlet mentioned all topic participants in #Verona > Test topic", + ) + def test_get_message_payload_gcm_stream_wildcard_mention(self) -> None: self._test_get_message_payload_gcm_mentions( "stream_wildcard_mentioned", "King Hamlet mentioned everyone in #Verona" diff --git a/zerver/tests/test_soft_deactivation.py b/zerver/tests/test_soft_deactivation.py index 48ca22d22c..a7131e2588 100644 --- a/zerver/tests/test_soft_deactivation.py +++ b/zerver/tests/test_soft_deactivation.py @@ -645,11 +645,13 @@ class SoftDeactivationMessageTest(ZulipTestCase): content: str, *, possible_stream_wildcard_mention: bool = False, + topic_participant_user_ids: AbstractSet[int] = set(), possibly_mentioned_user_ids: AbstractSet[int] = set(), ) -> None: assert_num_possible_users( expected_count=3, possible_stream_wildcard_mention=possible_stream_wildcard_mention, + topic_participant_user_ids=topic_participant_user_ids, possibly_mentioned_user_ids=possibly_mentioned_user_ids, ) general_user_msg_count = len(get_user_messages(cordelia)) @@ -770,6 +772,17 @@ class SoftDeactivationMessageTest(ZulipTestCase): ) assert_stream_message_not_sent_to_idle_user("Test @**bogus** mention") + # Test UserMessage row is created while user is deactivated if + # there is a topic wildcard mention i.e. @topic + do_soft_activate_users([long_term_idle_user]) + self.send_stream_message(long_term_idle_user, stream_name, "Hi", topic_name) + topic_participant_user_ids = {long_term_idle_user.id} + + do_soft_deactivate_users([long_term_idle_user]) + assert_stream_message_sent_to_idle_user( + "Test @**topic** mention", topic_participant_user_ids=topic_participant_user_ids + ) + # Test UserMessage row is created while user is deactivated if there # is a alert word in message. do_add_alert_words(long_term_idle_user, ["test_alert_word"]) diff --git a/zerver/tornado/event_queue.py b/zerver/tornado/event_queue.py index 767d167584..5feb0ac347 100644 --- a/zerver/tornado/event_queue.py +++ b/zerver/tornado/event_queue.py @@ -805,12 +805,24 @@ def missedmessage_hook( pm_email_notify=internal_data.get("pm_email_notify", False), mention_push_notify=internal_data.get("mention_push_notify", False), mention_email_notify=internal_data.get("mention_email_notify", False), + topic_wildcard_mention_push_notify=internal_data.get( + "topic_wildcard_mention_push_notify", False + ), + topic_wildcard_mention_email_notify=internal_data.get( + "topic_wildcard_mention_email_notify", False + ), stream_wildcard_mention_push_notify=stream_wildcard_mention_push_notify, stream_wildcard_mention_email_notify=stream_wildcard_mention_email_notify, 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), + topic_wildcard_mention_in_followed_topic_push_notify=internal_data.get( + "topic_wildcard_mention_in_followed_topic_push_notify", False + ), + topic_wildcard_mention_in_followed_topic_email_notify=internal_data.get( + "topic_wildcard_mention_in_followed_topic_email_notify", False + ), stream_wildcard_mention_in_followed_topic_push_notify=internal_data.get( "stream_wildcard_mention_in_followed_topic_push_notify", False ), @@ -969,6 +981,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", [])) + topic_wildcard_mention_user_ids = set(event_template.get("topic_wildcard_mention_user_ids", [])) # TODO/compatibility: Translation code for the rename of # `wildcard_mention_user_ids` to `stream_wildcard_mention_user_ids`. @@ -983,6 +996,9 @@ def process_message_event( 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", [])) + topic_wildcard_mention_in_followed_topic_user_ids = set( + event_template.get("topic_wildcard_mention_in_followed_topic_user_ids", []) + ) stream_wildcard_mention_in_followed_topic_user_ids = set( event_template.get("stream_wildcard_mention_in_followed_topic_user_ids", []) ) @@ -1035,9 +1051,11 @@ def process_message_event( pm_mention_email_disabled_user_ids=pm_mention_email_disabled_user_ids, stream_push_user_ids=stream_push_user_ids, stream_email_user_ids=stream_email_user_ids, + topic_wildcard_mention_user_ids=topic_wildcard_mention_user_ids, stream_wildcard_mention_user_ids=stream_wildcard_mention_user_ids, followed_topic_push_user_ids=followed_topic_push_user_ids, followed_topic_email_user_ids=followed_topic_email_user_ids, + topic_wildcard_mention_in_followed_topic_user_ids=topic_wildcard_mention_in_followed_topic_user_ids, stream_wildcard_mention_in_followed_topic_user_ids=stream_wildcard_mention_in_followed_topic_user_ids, muted_sender_user_ids=muted_sender_user_ids, all_bot_user_ids=all_bot_user_ids, @@ -1189,6 +1207,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", [])) + topic_wildcard_mention_user_ids = set(event_template.pop("topic_wildcard_mention_user_ids", [])) # TODO/compatibility: Translation code for the rename of # `wildcard_mention_user_ids` to `stream_wildcard_mention_user_ids`. @@ -1203,6 +1222,9 @@ def process_message_update_event( 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", [])) + topic_wildcard_mention_in_followed_topic_user_ids = set( + event_template.pop("topic_wildcard_mention_in_followed_topic_user_ids", []) + ) stream_wildcard_mention_in_followed_topic_user_ids = set( event_template.pop("stream_wildcard_mention_in_followed_topic_user_ids", []) ) @@ -1265,9 +1287,11 @@ def process_message_update_event( pm_mention_email_disabled_user_ids=pm_mention_email_disabled_user_ids, stream_push_user_ids=stream_push_user_ids, stream_email_user_ids=stream_email_user_ids, + topic_wildcard_mention_user_ids=topic_wildcard_mention_user_ids, stream_wildcard_mention_user_ids=stream_wildcard_mention_user_ids, followed_topic_push_user_ids=followed_topic_push_user_ids, followed_topic_email_user_ids=followed_topic_email_user_ids, + topic_wildcard_mention_in_followed_topic_user_ids=topic_wildcard_mention_in_followed_topic_user_ids, stream_wildcard_mention_in_followed_topic_user_ids=stream_wildcard_mention_in_followed_topic_user_ids, muted_sender_user_ids=muted_sender_user_ids, all_bot_user_ids=all_bot_user_ids,