mirror of
https://github.com/zulip/zulip.git
synced 2025-11-01 20:44:04 +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
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.notification_data import get_mentioned_user_group_name
|
||||||
from zerver.lib.queue import queue_json_publish
|
from zerver.lib.queue import queue_json_publish
|
||||||
from zerver.lib.send_email import FromAddress, send_future_email
|
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.types import DisplayRecipientT
|
||||||
from zerver.lib.url_encoding import (
|
from zerver.lib.url_encoding import (
|
||||||
huddle_narrow_url,
|
huddle_narrow_url,
|
||||||
@@ -488,6 +489,11 @@ def do_send_missedmessage_events_reply_in_zulip(
|
|||||||
show_message_content=True,
|
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):
|
with override_language(user_profile.default_language):
|
||||||
from_name: str = _("Zulip notifications")
|
from_name: str = _("Zulip notifications")
|
||||||
from_address = FromAddress.NOREPLY
|
from_address = FromAddress.NOREPLY
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from django.utils.timezone import now as timezone_now
|
|||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
from zerver.lib.logging_util import log_to_file
|
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.lib.utils import assert_is_not_none
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
Message,
|
Message,
|
||||||
@@ -374,3 +375,33 @@ def get_soft_deactivated_users_for_catch_up(filter_kwargs: Any) -> List[UserProf
|
|||||||
**filter_kwargs,
|
**filter_kwargs,
|
||||||
)
|
)
|
||||||
return users_to_catch_up
|
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.notification_data import UserMessageNotificationsData
|
||||||
from zerver.lib.rate_limiter import bounce_redis_key_prefix_for_testing
|
from zerver.lib.rate_limiter import bounce_redis_key_prefix_for_testing
|
||||||
from zerver.lib.sessions import get_session_dict_user
|
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.stream_subscription import get_stream_subscriptions_for_user
|
||||||
from zerver.lib.streams import (
|
from zerver.lib.streams import (
|
||||||
create_stream_if_needed,
|
create_stream_if_needed,
|
||||||
@@ -1524,6 +1525,24 @@ Output:
|
|||||||
UserGroupMembership.objects.filter(user_profile=user, user_group=user_group).exists()
|
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):
|
class WebhookTestCase(ZulipTestCase):
|
||||||
"""Shared test class for all incoming webhooks tests.
|
"""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
|
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):
|
class TestFollowupEmailDelay(ZulipTestCase):
|
||||||
def test_followup_day2_email_delay(self) -> None:
|
def test_followup_day2_email_delay(self) -> None:
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ from zerver.lib.send_email import (
|
|||||||
send_email,
|
send_email,
|
||||||
send_future_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.timestamp import timestamp_to_datetime
|
||||||
from zerver.lib.upload import handle_reupload_emojis_event
|
from zerver.lib.upload import handle_reupload_emojis_event
|
||||||
from zerver.lib.url_preview import preview as url_preview
|
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"])
|
realm = Realm.objects.get(id=event["realm_id"])
|
||||||
logger.info("Processing reupload_realm_emoji event for realm %s", realm.id)
|
logger.info("Processing reupload_realm_emoji event for realm %s", realm.id)
|
||||||
handle_reupload_emojis_event(realm, logger)
|
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()
|
end = time.time()
|
||||||
logger.info("deferred_work processed %s event (%dms)", event["type"], (end - start) * 1000)
|
logger.info("deferred_work processed %s event (%dms)", event["type"], (end - start) * 1000)
|
||||||
|
|||||||
Reference in New Issue
Block a user