diff --git a/help/typing-notifications.md b/help/typing-notifications.md index 39f712a3d2..8368c67c6c 100644 --- a/help/typing-notifications.md +++ b/help/typing-notifications.md @@ -2,8 +2,10 @@ The Zulip web app displays typing notifications in [conversation views](/help/reading-conversations) and [**All direct -messages**](/help/direct-messages#access-all-dms). The mobile app displays -typing notifications in direct message conversations. +messages**](/help/direct-messages#access-all-dms). Typing +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 the compose box. They disappear if typing is paused for several seconds, diff --git a/zerver/actions/typing.py b/zerver/actions/typing.py index 3a25b0c1c1..a2fcbaf5e2 100644 --- a/zerver/actions/typing.py +++ b/zerver/actions/typing.py @@ -1,5 +1,6 @@ from typing import List +from django.conf import settings from django.utils.translation import gettext as _ from zerver.lib.exceptions import JsonableError @@ -78,9 +79,20 @@ def do_send_stream_typing_notification( ) # 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 - ).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) diff --git a/zerver/tests/test_typing.py b/zerver/tests/test_typing.py index 828c5443a5..701b57d22f 100644 --- a/zerver/tests/test_typing.py +++ b/zerver/tests/test_typing.py @@ -381,7 +381,7 @@ class TypingHappyPathTestStreams(ZulipTestCase): 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: result = self.api_post(sender, "/api/v1/typing", params) self.assert_json_success(result) @@ -412,7 +412,7 @@ class TypingHappyPathTestStreams(ZulipTestCase): 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: result = self.api_post(sender, "/api/v1/typing", params) self.assert_json_success(result) @@ -428,6 +428,29 @@ class TypingHappyPathTestStreams(ZulipTestCase): self.assertEqual("typing", event["type"]) 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: sender = self.example_user("hamlet") stream_name = self.get_streams(sender)[0] @@ -453,7 +476,7 @@ class TypingHappyPathTestStreams(ZulipTestCase): 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: result = self.api_post(sender, "/api/v1/typing", params) self.assert_json_success(result) diff --git a/zproject/default_settings.py b/zproject/default_settings.py index 41626011b7..aaad3c05eb 100644 --- a/zproject/default_settings.py +++ b/zproject/default_settings.py @@ -587,3 +587,8 @@ TYPING_STOPPED_WAIT_PERIOD_MILLISECONDS = 5000 # How often a client should send start notifications to the server to # indicate that the user is still interacting with the compose UI. 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