mirror of https://github.com/zulip/zulip.git
settings: Add option to disable seeing typing notifications.
This commit adds an option to the advanced section of Preferences settings, that would allow users to choose whether to receive typing notifications from other users. Fixes #29642
This commit is contained in:
parent
c7e2e048cb
commit
68b4298d8e
|
@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 9.0
|
||||
|
||||
**Feature level 253**
|
||||
|
||||
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
|
||||
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
|
||||
Added new `receives_typing_notifications` option to allow users to decide whether
|
||||
to receive typing notification events from other users.
|
||||
|
||||
**Feature level 252**
|
||||
|
||||
* `PATCH /realm/profile_fields/{field_id}`: `name`, `hint`, `display_in_profile_summary`,
|
||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||
# Changes should be accompanied by documentation explaining what the
|
||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
API_FEATURE_LEVEL = 252
|
||||
API_FEATURE_LEVEL = 253
|
||||
|
||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||
# only when going from an old version of the code to a newer version. Bump
|
||||
|
|
|
@ -36,6 +36,7 @@ export type RealmDefaultSettings = {
|
|||
pm_content_in_desktop_notifications: boolean;
|
||||
presence_enabled: boolean;
|
||||
realm_name_in_email_notifications_policy: number;
|
||||
receives_typing_notifications: boolean;
|
||||
send_private_typing_notifications: boolean;
|
||||
send_stream_typing_notifications: boolean;
|
||||
starred_message_counts: boolean;
|
||||
|
|
|
@ -706,6 +706,7 @@ export function dispatch_normal_event(event) {
|
|||
"web_escape_navigates_to_home_view",
|
||||
"fluid_layout_width",
|
||||
"high_contrast_mode",
|
||||
"receives_typing_notifications",
|
||||
"timezone",
|
||||
"twenty_four_hour_time",
|
||||
"translate_emoticons",
|
||||
|
@ -801,6 +802,12 @@ export function dispatch_normal_event(event) {
|
|||
if (event.property === "starred_message_counts") {
|
||||
starred_messages_ui.rerender_ui();
|
||||
}
|
||||
if (
|
||||
event.property === "receives_typing_notifications" &&
|
||||
!user_settings.receives_typing_notifications
|
||||
) {
|
||||
typing_events.disable_typing_notification();
|
||||
}
|
||||
if (event.property === "fluid_layout_width") {
|
||||
scroll_bar.set_layout_width();
|
||||
}
|
||||
|
|
|
@ -149,6 +149,7 @@ export const get_all_preferences = (): DisplaySettings => ({
|
|||
"dense_mode",
|
||||
"high_contrast_mode",
|
||||
"starred_message_counts",
|
||||
"receives_typing_notifications",
|
||||
"fluid_layout_width",
|
||||
],
|
||||
},
|
||||
|
@ -563,6 +564,7 @@ export const preferences_settings_labels = {
|
|||
),
|
||||
fluid_layout_width: $t({defaultMessage: "Use full width on wide screens"}),
|
||||
high_contrast_mode: $t({defaultMessage: "High contrast mode"}),
|
||||
receives_typing_notifications: $t({defaultMessage: "Show when other users are typing"}),
|
||||
starred_message_counts: $t({defaultMessage: "Show counts for starred messages"}),
|
||||
twenty_four_hour_time: $t({defaultMessage: "Time format"}),
|
||||
translate_emoticons: new Handlebars.SafeString(
|
||||
|
|
|
@ -64,6 +64,14 @@ export function get_topic_typists(stream_id: number, topic: string): number[] {
|
|||
return muted_users.filter_muted_user_ids(typists);
|
||||
}
|
||||
|
||||
export function clear_typing_data(): void {
|
||||
for (const [, timer] of inbound_timer_dict.entries()) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
inbound_timer_dict.clear();
|
||||
typists_dict.clear();
|
||||
}
|
||||
|
||||
// The next functions aren't pure data, but it is easy
|
||||
// enough to mock the setTimeout/clearTimeout functions.
|
||||
export function clear_inbound_timer(key: string): void {
|
||||
|
|
|
@ -146,3 +146,8 @@ export function display_notification(event: TypingEvent): void {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function disable_typing_notification(): void {
|
||||
typing_data.clear_typing_data();
|
||||
render_notifications_for_narrow();
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ export type UserSettings = (StreamNotificationSettings &
|
|||
pm_content_in_desktop_notifications: boolean;
|
||||
presence_enabled: boolean;
|
||||
realm_name_in_email_notifications_policy: number;
|
||||
receives_typing_notifications: boolean;
|
||||
send_private_typing_notifications: boolean;
|
||||
send_read_receipts: boolean;
|
||||
send_stream_typing_notifications: boolean;
|
||||
|
|
|
@ -1016,6 +1016,17 @@ run_test("user_settings", ({override}) => {
|
|||
dispatch(event);
|
||||
assert_same(user_settings.starred_message_counts, true);
|
||||
|
||||
event = event_fixtures.user_settings__receives_typing_notifications;
|
||||
user_settings.receives_typing_notifications = false;
|
||||
dispatch(event);
|
||||
assert_same(user_settings.receives_typing_notifications, true);
|
||||
|
||||
event = event_fixtures.user_settings__receives_typing_notifications_disabled;
|
||||
override(typing_events, "disable_typing_notification", noop);
|
||||
user_settings.receives_typing_notifications = true;
|
||||
dispatch(event);
|
||||
assert_same(user_settings.receives_typing_notifications, false);
|
||||
|
||||
override(scroll_bar, "set_layout_width", noop);
|
||||
event = event_fixtures.user_settings__fluid_layout_width;
|
||||
user_settings.fluid_layout_width = false;
|
||||
|
|
|
@ -999,6 +999,20 @@ exports.fixtures = {
|
|||
value: true,
|
||||
},
|
||||
|
||||
user_settings__receives_typing_notifications: {
|
||||
type: "user_settings",
|
||||
op: "update",
|
||||
property: "receives_typing_notifications",
|
||||
value: true,
|
||||
},
|
||||
|
||||
user_settings__receives_typing_notifications_disabled: {
|
||||
type: "user_settings",
|
||||
op: "update",
|
||||
property: "receives_typing_notifications",
|
||||
value: false,
|
||||
},
|
||||
|
||||
user_settings__starred_message_counts: {
|
||||
type: "user_settings",
|
||||
op: "update",
|
||||
|
|
|
@ -82,6 +82,12 @@ test("basics", () => {
|
|||
// test duplicate ids in a groups
|
||||
typing_data.add_typist(typing_data.get_direct_message_conversation_key([20, 40, 20]), 20);
|
||||
assert.deepEqual(typing_data.get_group_typists([20, 40]), [20]);
|
||||
|
||||
// test clearing out typing data
|
||||
typing_data.clear_typing_data();
|
||||
assert.deepEqual(typing_data.get_group_typists(), []);
|
||||
assert.deepEqual(typing_data.get_all_direct_message_typists(), []);
|
||||
assert.deepEqual(typing_data.get_topic_typists(stream_id, topic), []);
|
||||
});
|
||||
|
||||
test("muted_typists_excluded", () => {
|
||||
|
@ -181,6 +187,16 @@ test("timers", () => {
|
|||
timer_set: true,
|
||||
});
|
||||
|
||||
// clearing out typing data
|
||||
kickstart();
|
||||
typing_data.clear_typing_data();
|
||||
assert.deepEqual(events, {
|
||||
f: stub_f,
|
||||
timer_cleared: true,
|
||||
timer_set: true,
|
||||
});
|
||||
|
||||
kickstart();
|
||||
// first time clearing, we clear
|
||||
clear();
|
||||
assert.deepEqual(events, {
|
||||
|
|
|
@ -28,7 +28,11 @@ def do_send_typing_notification(
|
|||
)
|
||||
|
||||
# Only deliver the notification to active user recipients
|
||||
user_ids_to_notify = [user.id for user in recipient_user_profiles if user.is_active]
|
||||
user_ids_to_notify = [
|
||||
user.id
|
||||
for user in recipient_user_profiles
|
||||
if user.is_active and user.receives_typing_notifications
|
||||
]
|
||||
|
||||
send_event(realm, event, user_ids_to_notify)
|
||||
|
||||
|
@ -91,9 +95,9 @@ def do_send_stream_typing_notification(
|
|||
return
|
||||
|
||||
user_ids_to_notify = set(
|
||||
subscriptions_query.exclude(user_profile__long_term_idle=True).values_list(
|
||||
"user_profile_id", flat=True
|
||||
)
|
||||
subscriptions_query.exclude(user_profile__long_term_idle=True)
|
||||
.exclude(user_profile__receives_typing_notifications=False)
|
||||
.values_list("user_profile_id", flat=True)
|
||||
)
|
||||
|
||||
send_event(sender.realm, event, user_ids_to_notify)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.2.12 on 2024-04-16 14:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0507_rework_realm_upload_quota_gb"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="realmuserdefault",
|
||||
name="receives_typing_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="userprofile",
|
||||
name="receives_typing_notifications",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
|
@ -245,6 +245,9 @@ class UserBaseSettings(models.Model):
|
|||
send_private_typing_notifications = models.BooleanField(default=True)
|
||||
send_read_receipts = models.BooleanField(default=True)
|
||||
|
||||
# Whether the user wants to see typing notifications.
|
||||
receives_typing_notifications = models.BooleanField(default=True)
|
||||
|
||||
# Who in the organization has access to users' actual email
|
||||
# addresses. Controls whether the UserProfile.email field is
|
||||
# the same as UserProfile.delivery_email, or is instead a fake
|
||||
|
@ -317,6 +320,7 @@ class UserBaseSettings(models.Model):
|
|||
display_emoji_reaction_users=bool,
|
||||
email_address_visibility=int,
|
||||
web_escape_navigates_to_home_view=bool,
|
||||
receives_typing_notifications=bool,
|
||||
send_private_typing_notifications=bool,
|
||||
send_read_receipts=bool,
|
||||
send_stream_typing_notifications=bool,
|
||||
|
|
|
@ -10570,6 +10570,16 @@ paths:
|
|||
messages](/help/star-a-message#display-the-number-of-starred-messages).
|
||||
type: boolean
|
||||
example: true
|
||||
receives_typing_notifications:
|
||||
description: |
|
||||
Whether the user is configured to receive typing notifications from other users.
|
||||
The server will only deliver typing notifications events to users who for whom this
|
||||
is enabled.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 253). Previously, there were
|
||||
only options to disable sending typing notifications.
|
||||
type: boolean
|
||||
example: true
|
||||
fluid_layout_width:
|
||||
description: |
|
||||
Whether to use the [maximum available screen width](/help/enable-full-width-display)
|
||||
|
@ -11017,6 +11027,8 @@ paths:
|
|||
contentType: application/json
|
||||
starred_message_counts:
|
||||
contentType: application/json
|
||||
receives_typing_notifications:
|
||||
contentType: application/json
|
||||
fluid_layout_width:
|
||||
contentType: application/json
|
||||
high_contrast_mode:
|
||||
|
@ -13368,6 +13380,15 @@ paths:
|
|||
description: |
|
||||
Whether clients should display the [number of starred
|
||||
messages](/help/star-a-message#display-the-number-of-starred-messages).
|
||||
receives_typing_notifications:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the user is configured to receive typing notifications from
|
||||
other users. The server will only deliver typing notifications events
|
||||
to users who for whom this is enabled.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 253). Previously, there were
|
||||
only options to disable sending typing notifications.
|
||||
fluid_layout_width:
|
||||
type: boolean
|
||||
description: |
|
||||
|
@ -14442,6 +14463,15 @@ paths:
|
|||
client capability and access the `user_settings` object instead.
|
||||
|
||||
[capabilities]: /api/register-queue#parameter-client_capabilities
|
||||
receives_typing_notifications:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the user is configured to receive typing notifications from other
|
||||
users. The server will only deliver typing notifications events to users who
|
||||
for whom this is enabled.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 253). Previously, there were
|
||||
only options to disable sending typing notifications.
|
||||
enter_sends:
|
||||
deprecated: true
|
||||
type: boolean
|
||||
|
@ -15673,6 +15703,15 @@ paths:
|
|||
description: |
|
||||
Whether clients should display the [number of starred
|
||||
messages](/help/star-a-message#display-the-number-of-starred-messages).
|
||||
receives_typing_notifications:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether the user is configured to receive typing notifications from
|
||||
other users. The server will only deliver typing notifications events
|
||||
to users who for whom this is enabled.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 253). Previously, there were
|
||||
only options to disable sending typing notifications.
|
||||
fluid_layout_width:
|
||||
type: boolean
|
||||
description: |
|
||||
|
@ -16758,6 +16797,19 @@ paths:
|
|||
the `PATCH /settings/display` endpoint.
|
||||
type: boolean
|
||||
example: true
|
||||
receives_typing_notifications:
|
||||
description: |
|
||||
Whether the user is configured to receive typing notifications from other users.
|
||||
The server will only deliver typing notifications events to users who for whom this
|
||||
is enabled.
|
||||
|
||||
By default, this is set to true, enabling user to receive typing
|
||||
notifications from other users.
|
||||
|
||||
**Changes**: New in Zulip 9.0 (feature level 253). Previously, there were only
|
||||
options to disable sending typing notifications.
|
||||
type: boolean
|
||||
example: true
|
||||
fluid_layout_width:
|
||||
description: |
|
||||
Whether to use the [maximum available screen width](/help/enable-full-width-display)
|
||||
|
@ -17313,6 +17365,8 @@ paths:
|
|||
contentType: application/json
|
||||
starred_message_counts:
|
||||
contentType: application/json
|
||||
receives_typing_notifications:
|
||||
contentType: application/json
|
||||
fluid_layout_width:
|
||||
contentType: application/json
|
||||
high_contrast_mode:
|
||||
|
|
|
@ -585,3 +585,36 @@ class TestSendTypingNotificationsSettings(ZulipTestCase):
|
|||
result = self.api_post(sender, "/api/v1/typing", params)
|
||||
self.assert_json_error(result, "User has disabled typing notifications for stream messages")
|
||||
self.assertEqual(events, [])
|
||||
|
||||
def test_typing_notifications_disabled(self) -> None:
|
||||
sender = self.example_user("hamlet")
|
||||
stream_name = self.get_streams(sender)[0]
|
||||
stream_id = self.get_stream_id(stream_name)
|
||||
topic_name = "Some topic"
|
||||
|
||||
aaron = self.example_user("aaron")
|
||||
iago = self.example_user("iago")
|
||||
for user in [aaron, iago]:
|
||||
self.subscribe(user, stream_name)
|
||||
|
||||
aaron.receives_typing_notifications = False
|
||||
aaron.save()
|
||||
|
||||
params = dict(
|
||||
type="stream",
|
||||
op="start",
|
||||
stream_id=str(stream_id),
|
||||
topic=topic_name,
|
||||
)
|
||||
|
||||
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_user_ids = set(events[0]["users"])
|
||||
|
||||
# Only users who have typing notifications enabled would receive
|
||||
# notifications.
|
||||
self.assertNotIn(aaron.id, event_user_ids)
|
||||
self.assertIn(iago.id, event_user_ids)
|
||||
|
|
|
@ -521,6 +521,7 @@ def update_realm_user_settings_defaults(
|
|||
default=None,
|
||||
),
|
||||
starred_message_counts: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
receives_typing_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
web_stream_unreads_count_display_policy: Optional[int] = REQ(
|
||||
json_validator=check_int_in(UserProfile.WEB_STREAM_UNREADS_COUNT_DISPLAY_POLICY_CHOICES),
|
||||
default=None,
|
||||
|
|
|
@ -208,6 +208,7 @@ def json_change_settings(
|
|||
default=None,
|
||||
),
|
||||
starred_message_counts: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
receives_typing_notifications: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
fluid_layout_width: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
high_contrast_mode: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
color_scheme: Optional[int] = REQ(
|
||||
|
|
Loading…
Reference in New Issue