mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 12:03:46 +00:00 
			
		
		
		
	email_notifications: Soft reactivate mentioned users.
Signed-off-by: Zixuan James Li <359101898@qq.com>
This commit is contained in:
		
				
					committed by
					
						 Tim Abbott
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							d8101de34d
						
					
				
				
					commit
					a8fd9eb701
				
			| @@ -25,6 +25,7 @@ from zerver.lib.message import bulk_access_messages | ||||
| from zerver.lib.notification_data import get_mentioned_user_group_name | ||||
| from zerver.lib.queue import queue_json_publish | ||||
| from zerver.lib.send_email import FromAddress, send_future_email | ||||
| from zerver.lib.soft_deactivation import soft_reactivate_if_personal_notification | ||||
| from zerver.lib.types import DisplayRecipientT | ||||
| from zerver.lib.url_encoding import ( | ||||
|     huddle_narrow_url, | ||||
| @@ -488,6 +489,11 @@ def do_send_missedmessage_events_reply_in_zulip( | ||||
|             show_message_content=True, | ||||
|         ) | ||||
|  | ||||
|     # Soft reactivate the long_term_idle user personally mentioned | ||||
|     soft_reactivate_if_personal_notification( | ||||
|         user_profile, unique_triggers, mentioned_user_group_name | ||||
|     ) | ||||
|  | ||||
|     with override_language(user_profile.default_language): | ||||
|         from_name: str = _("Zulip notifications") | ||||
|     from_address = FromAddress.NOREPLY | ||||
|   | ||||
| @@ -10,6 +10,7 @@ from django.utils.timezone import now as timezone_now | ||||
| from sentry_sdk import capture_exception | ||||
|  | ||||
| from zerver.lib.logging_util import log_to_file | ||||
| from zerver.lib.queue import queue_json_publish | ||||
| from zerver.lib.utils import assert_is_not_none | ||||
| from zerver.models import ( | ||||
|     Message, | ||||
| @@ -374,3 +375,33 @@ def get_soft_deactivated_users_for_catch_up(filter_kwargs: Any) -> List[UserProf | ||||
|         **filter_kwargs, | ||||
|     ) | ||||
|     return users_to_catch_up | ||||
|  | ||||
|  | ||||
| def soft_reactivate_if_personal_notification( | ||||
|     user_profile: UserProfile, unique_triggers: Set[str], mentioned_user_group_name: Optional[str] | ||||
| ) -> None: | ||||
|     """When we're about to send an email/push notification to a | ||||
|     long_term_idle user, it's very likely that the user will try to | ||||
|     return to Zulip. As a result, it makes sense to optimistically | ||||
|     soft-reactivate that user, to give them a good return experience. | ||||
|  | ||||
|     It's important that we do nothing for wildcard or group mentions, | ||||
|     because soft-reactivating an entire realm would be very expensive | ||||
|     (and we can't easily check the group's size). The caller is | ||||
|     responsible for passing a mentioned_user_group_name that is None | ||||
|     for messages that contain both a personal mention and a group | ||||
|     mention. | ||||
|     """ | ||||
|     if not user_profile.long_term_idle: | ||||
|         return | ||||
|  | ||||
|     private_message = "private_message" in unique_triggers | ||||
|     personal_mention = "mentioned" in unique_triggers and mentioned_user_group_name is None | ||||
|     if not private_message and not personal_mention: | ||||
|         return | ||||
|  | ||||
|     event = { | ||||
|         "type": "soft_reactivate", | ||||
|         "user_profile_id": user_profile.id, | ||||
|     } | ||||
|     queue_json_publish("deferred_work", event) | ||||
|   | ||||
| @@ -56,6 +56,7 @@ from zerver.lib.initial_password import initial_password | ||||
| from zerver.lib.notification_data import UserMessageNotificationsData | ||||
| from zerver.lib.rate_limiter import bounce_redis_key_prefix_for_testing | ||||
| from zerver.lib.sessions import get_session_dict_user | ||||
| from zerver.lib.soft_deactivation import do_soft_deactivate_users | ||||
| from zerver.lib.stream_subscription import get_stream_subscriptions_for_user | ||||
| from zerver.lib.streams import ( | ||||
|     create_stream_if_needed, | ||||
| @@ -1524,6 +1525,24 @@ Output: | ||||
|             UserGroupMembership.objects.filter(user_profile=user, user_group=user_group).exists() | ||||
|         ) | ||||
|  | ||||
|     @contextmanager | ||||
|     def soft_deactivate_and_check_long_term_idle( | ||||
|         self, user: UserProfile, expected: bool | ||||
|     ) -> Iterator[None]: | ||||
|         """ | ||||
|         Ensure that the user is soft deactivated (long term idle), and check if the user | ||||
|         has been reactivated when exiting the context with an assertion | ||||
|         """ | ||||
|         if not user.long_term_idle: | ||||
|             do_soft_deactivate_users([user]) | ||||
|             self.assertTrue(user.long_term_idle) | ||||
|         try: | ||||
|             yield | ||||
|         finally: | ||||
|             # Prevent from using the old user object | ||||
|             user.refresh_from_db() | ||||
|             self.assertEqual(user.long_term_idle, expected) | ||||
|  | ||||
|  | ||||
| class WebhookTestCase(ZulipTestCase): | ||||
|     """Shared test class for all incoming webhooks tests. | ||||
|   | ||||
| @@ -1449,6 +1449,62 @@ class TestMissedMessages(ZulipTestCase): | ||||
|             msg_id, verify_body_include, email_subject, send_as_user=False, verify_html_body=True | ||||
|         ) | ||||
|  | ||||
|     def test_long_term_idle_user_missed_message(self) -> None: | ||||
|         hamlet = self.example_user("hamlet") | ||||
|         othello = self.example_user("othello") | ||||
|         cordelia = self.example_user("cordelia") | ||||
|         large_user_group = create_user_group( | ||||
|             "large_user_group", [hamlet, othello, cordelia], get_realm("zulip") | ||||
|         ) | ||||
|  | ||||
|         # Do note that the event dicts for the missed messages are constructed by hand | ||||
|         # The part of testing the consumption of missed messages by the worker is left to | ||||
|         # test_queue_worker.test_missed_message_worker | ||||
|  | ||||
|         # Personal mention in a stream message should soft reactivate the user | ||||
|         with self.soft_deactivate_and_check_long_term_idle(hamlet, expected=False): | ||||
|             mention = f"@**{hamlet.full_name}**" | ||||
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention) | ||||
|             handle_missedmessage_emails( | ||||
|                 hamlet.id, | ||||
|                 [{"message_id": stream_mentioned_message_id, "trigger": "mentioned"}], | ||||
|             ) | ||||
|  | ||||
|         # Private message should soft reactivate the user | ||||
|         with self.soft_deactivate_and_check_long_term_idle(hamlet, expected=False): | ||||
|             # Soft reactivate the user by sending a personal message | ||||
|             personal_message_id = self.send_personal_message(othello, hamlet, "Message") | ||||
|             handle_missedmessage_emails( | ||||
|                 hamlet.id, | ||||
|                 [{"message_id": personal_message_id, "trigger": "private_message"}], | ||||
|             ) | ||||
|  | ||||
|         # Wild card mention should NOT soft reactivate the user | ||||
|         with self.soft_deactivate_and_check_long_term_idle(hamlet, expected=True): | ||||
|             # Soft reactivate the user by sending a personal message | ||||
|             mention = "@**all**" | ||||
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention) | ||||
|             handle_missedmessage_emails( | ||||
|                 hamlet.id, | ||||
|                 [{"message_id": stream_mentioned_message_id, "trigger": "wildcard_mentioned"}], | ||||
|             ) | ||||
|  | ||||
|         # Group mention should NOT soft reactivate the user | ||||
|         with self.soft_deactivate_and_check_long_term_idle(hamlet, expected=True): | ||||
|             # Soft reactivate the user by sending a personal message | ||||
|             mention = "@*large_user_group*" | ||||
|             stream_mentioned_message_id = self.send_stream_message(othello, "Denmark", mention) | ||||
|             handle_missedmessage_emails( | ||||
|                 hamlet.id, | ||||
|                 [ | ||||
|                     { | ||||
|                         "message_id": stream_mentioned_message_id, | ||||
|                         "trigger": "mentioned", | ||||
|                         "mentioned_user_group_id": large_user_group.id, | ||||
|                     } | ||||
|                 ], | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class TestFollowupEmailDelay(ZulipTestCase): | ||||
|     def test_followup_day2_email_delay(self) -> None: | ||||
|   | ||||
| @@ -83,6 +83,7 @@ from zerver.lib.send_email import ( | ||||
|     send_email, | ||||
|     send_future_email, | ||||
| ) | ||||
| from zerver.lib.soft_deactivation import reactivate_user_if_soft_deactivated | ||||
| from zerver.lib.timestamp import timestamp_to_datetime | ||||
| from zerver.lib.upload import handle_reupload_emojis_event | ||||
| from zerver.lib.url_preview import preview as url_preview | ||||
| @@ -1083,6 +1084,9 @@ class DeferredWorker(QueueProcessingWorker): | ||||
|             realm = Realm.objects.get(id=event["realm_id"]) | ||||
|             logger.info("Processing reupload_realm_emoji event for realm %s", realm.id) | ||||
|             handle_reupload_emojis_event(realm, logger) | ||||
|         elif event["type"] == "soft_reactivate": | ||||
|             user_profile = get_user_profile_by_id(event["user_profile_id"]) | ||||
|             reactivate_user_if_soft_deactivated(user_profile) | ||||
|  | ||||
|         end = time.time() | ||||
|         logger.info("deferred_work processed %s event (%dms)", event["type"], (end - start) * 1000) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user