typing: Limit typing notifications in large streams.

This commit is contained in:
Tim Abbott 2023-11-14 13:57:16 -08:00
parent f8a0035215
commit 2e2997bd7d
4 changed files with 50 additions and 8 deletions

View File

@ -2,8 +2,10 @@
The Zulip web app displays typing notifications in [conversation The Zulip web app displays typing notifications in [conversation
views](/help/reading-conversations) and [**All direct views](/help/reading-conversations) and [**All direct
messages**](/help/direct-messages#access-all-dms). The mobile app displays messages**](/help/direct-messages#access-all-dms). Typing
typing notifications in direct message conversations. notifications are not shown in streams with more than 100
subscribers. The mobile app displays typing notifications in direct
message conversations.
Typing notifications are only sent while one is actively editing text in Typing notifications are only sent while one is actively editing text in
the compose box. They disappear if typing is paused for several seconds, the compose box. They disappear if typing is paused for several seconds,

View File

@ -1,5 +1,6 @@
from typing import List from typing import List
from django.conf import settings
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
@ -78,9 +79,20 @@ def do_send_stream_typing_notification(
) )
# We don't notify long_term_idle subscribers. # We don't notify long_term_idle subscribers.
subscriptions = get_active_subscriptions_for_stream_id( subscriptions_query = get_active_subscriptions_for_stream_id(
stream.id, include_deactivated_users=False 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))
total_subscriptions = subscriptions_query.count()
if total_subscriptions > settings.MAX_STREAM_SIZE_FOR_TYPING_NOTIFICATIONS:
# TODO: Stream typing notifications are disabled in streams
# with too many subscribers for performance reasons.
return
user_ids_to_notify = set(
subscriptions_query.exclude(user_profile__long_term_idle=True).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

@ -381,7 +381,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
topic=topic, topic=topic,
) )
with self.assert_database_query_count(5): with self.assert_database_query_count(6):
with self.capture_send_event_calls(expected_num_events=1) as events: with self.capture_send_event_calls(expected_num_events=1) as events:
result = self.api_post(sender, "/api/v1/typing", params) result = self.api_post(sender, "/api/v1/typing", params)
self.assert_json_success(result) self.assert_json_success(result)
@ -412,7 +412,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
topic=topic, topic=topic,
) )
with self.assert_database_query_count(5): with self.assert_database_query_count(6):
with self.capture_send_event_calls(expected_num_events=1) as events: with self.capture_send_event_calls(expected_num_events=1) as events:
result = self.api_post(sender, "/api/v1/typing", params) result = self.api_post(sender, "/api/v1/typing", params)
self.assert_json_success(result) self.assert_json_success(result)
@ -428,6 +428,29 @@ 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_max_stream_size_for_typing_notifications_setting(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"
for name in ["aaron", "iago", "cordelia", "prospero", "othello", "polonius"]:
user = self.example_user(name)
self.subscribe(user, stream_name)
params = dict(
type="stream",
op="start",
stream_id=str(stream_id),
topic=topic,
)
with self.settings(MAX_STREAM_SIZE_FOR_TYPING_NOTIFICATIONS=5):
with self.assert_database_query_count(5):
with self.capture_send_event_calls(expected_num_events=0) as events:
result = self.api_post(sender, "/api/v1/typing", params)
self.assert_json_success(result)
self.assert_length(events, 0)
def test_notify_not_long_term_idle_subscribers_only(self) -> None: def test_notify_not_long_term_idle_subscribers_only(self) -> None:
sender = self.example_user("hamlet") sender = self.example_user("hamlet")
stream_name = self.get_streams(sender)[0] stream_name = self.get_streams(sender)[0]
@ -453,7 +476,7 @@ class TypingHappyPathTestStreams(ZulipTestCase):
topic=topic, topic=topic,
) )
with self.assert_database_query_count(5): with self.assert_database_query_count(6):
with self.capture_send_event_calls(expected_num_events=1) as events: with self.capture_send_event_calls(expected_num_events=1) as events:
result = self.api_post(sender, "/api/v1/typing", params) result = self.api_post(sender, "/api/v1/typing", params)
self.assert_json_success(result) self.assert_json_success(result)

View File

@ -587,3 +587,8 @@ TYPING_STOPPED_WAIT_PERIOD_MILLISECONDS = 5000
# How often a client should send start notifications to the server to # How often a client should send start notifications to the server to
# indicate that the user is still interacting with the compose UI. # indicate that the user is still interacting with the compose UI.
TYPING_STARTED_WAIT_PERIOD_MILLISECONDS = 10000 TYPING_STARTED_WAIT_PERIOD_MILLISECONDS = 10000
# The maximum number of subscribers for a stream to have typing
# notifications enabled. Default is set to avoid excessive Tornado
# load in large organizations.
MAX_STREAM_SIZE_FOR_TYPING_NOTIFICATIONS = 100