typing_notifications: Don't notify long_term_idle subscribers.

The event for stream typing notifications is no longer sent
to the long_term_idle subscribers of the stream.

This helps to reduce the tornado's work of parsing super-long
JSON-encoded lists of user IDs in large streams. Now the lists
are shorter.
This commit is contained in:
Prakhar Pratyush 2023-11-07 20:19:13 +05:30 committed by Tim Abbott
parent 6e9da8ab2a
commit e6e156709a
3 changed files with 64 additions and 14 deletions

View File

@ -3,7 +3,7 @@ from typing import List
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError
from zerver.lib.stream_subscription import get_user_ids_for_streams from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id
from zerver.models import Realm, Stream, UserProfile, get_user_by_id_in_realm_including_cross_realm from zerver.models import Realm, Stream, UserProfile, get_user_by_id_in_realm_including_cross_realm
from zerver.tornado.django_api import send_event from zerver.tornado.django_api import send_event
@ -77,6 +77,10 @@ def do_send_stream_typing_notification(
topic=topic, topic=topic,
) )
user_ids_to_notify = get_user_ids_for_streams({stream.id})[stream.id] # We don't notify long_term_idle subscribers.
subscriptions = get_active_subscriptions_for_stream_id(
stream.id, include_deactivated_users=False
).exclude(user_profile__long_term_idle=True)
user_ids_to_notify = set(subscriptions.values_list("user_profile_id", flat=True))
send_event(sender.realm, event, user_ids_to_notify) send_event(sender.realm, event, user_ids_to_notify)

View File

@ -1186,6 +1186,17 @@ Output:
return [subscription.user_profile for subscription in subscriptions] return [subscription.user_profile for subscription in subscriptions]
def not_long_term_idle_subscriber_ids(self, stream_name: str, realm: Realm) -> Set[int]:
stream = Stream.objects.get(name=stream_name, realm=realm)
recipient = Recipient.objects.get(type_id=stream.id, type=Recipient.STREAM)
subscriptions = Subscription.objects.filter(
recipient=recipient, active=True, is_user_active=True
).exclude(user_profile__long_term_idle=True)
user_profile_ids = set(subscriptions.values_list("user_profile_id", flat=True))
return user_profile_ids
def assert_json_success( def assert_json_success(
self, self,
result: Union["TestHttpResponse", HttpResponse], result: Union["TestHttpResponse", HttpResponse],

View File

@ -372,10 +372,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
stream_id = self.get_stream_id(stream_name) stream_id = self.get_stream_id(stream_name)
topic = "Some topic" topic = "Some topic"
expected_user_ids = { expected_user_ids = self.not_long_term_idle_subscriber_ids(stream_name, sender.realm)
user_profile.id
for user_profile in self.users_subscribed_to_stream(stream_name, sender.realm)
}
params = dict( params = dict(
type="stream", type="stream",
@ -406,10 +403,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
stream_id = self.get_stream_id(stream_name) stream_id = self.get_stream_id(stream_name)
topic = "Some topic" topic = "Some topic"
expected_user_ids = { expected_user_ids = self.not_long_term_idle_subscriber_ids(stream_name, sender.realm)
user_profile.id
for user_profile in self.users_subscribed_to_stream(stream_name, sender.realm)
}
params = dict( params = dict(
type="stream", type="stream",
@ -434,6 +428,50 @@ class TypingHappyPathTestStreams(ZulipTestCase):
self.assertEqual("typing", event["type"]) self.assertEqual("typing", event["type"])
self.assertEqual("stop", event["op"]) self.assertEqual("stop", event["op"])
def test_notify_not_long_term_idle_subscribers_only(self) -> None:
sender = self.example_user("hamlet")
stream_name = self.get_streams(sender)[0]
stream_id = self.get_stream_id(stream_name)
topic = "Some topic"
aaron = self.example_user("aaron")
iago = self.example_user("iago")
for user in [aaron, iago]:
self.subscribe(user, stream_name)
self.soft_deactivate_user(user)
subscriber_ids = {
user_profile.id
for user_profile in self.users_subscribed_to_stream(stream_name, sender.realm)
}
not_long_term_idle_subscriber_ids = subscriber_ids - {aaron.id, iago.id}
params = dict(
type="stream",
op="start",
stream_id=str(stream_id),
topic=topic,
)
with self.assert_database_query_count(5):
with self.capture_send_event_calls(expected_num_events=1) as events:
result = self.api_post(sender, "/api/v1/typing", params)
self.assert_json_success(result)
self.assert_length(events, 1)
event = events[0]["event"]
event_user_ids = set(events[0]["users"])
# Only subscribers who are not long_term_idle are notified for typing notifications.
self.assertNotEqual(subscriber_ids, event_user_ids)
self.assertEqual(not_long_term_idle_subscriber_ids, event_user_ids)
self.assertEqual(sender.email, event["sender"]["email"])
self.assertEqual(stream_id, event["stream_id"])
self.assertEqual(topic, event["topic"])
self.assertEqual("typing", event["type"])
self.assertEqual("start", event["op"])
class TestSendTypingNotificationsSettings(ZulipTestCase): class TestSendTypingNotificationsSettings(ZulipTestCase):
def test_send_private_typing_notifications_setting(self) -> None: def test_send_private_typing_notifications_setting(self) -> None:
@ -475,10 +513,7 @@ class TestSendTypingNotificationsSettings(ZulipTestCase):
stream_id = self.get_stream_id(stream_name) stream_id = self.get_stream_id(stream_name)
topic = "Some topic" topic = "Some topic"
expected_user_ids = { expected_user_ids = self.not_long_term_idle_subscriber_ids(stream_name, sender.realm)
user_profile.id
for user_profile in self.users_subscribed_to_stream(stream_name, sender.realm)
}
params = dict( params = dict(
type="stream", type="stream",