From 55a8e7dff22715297d319e006261fc44f18e0f1a Mon Sep 17 00:00:00 2001 From: Hashir Sarwar Date: Fri, 1 May 2020 23:39:26 +0500 Subject: [PATCH] settings: Offer hiding presence info from other users. For privacy-minded folks who don't want to leak the information of whether they're online, this adds an option to disable sending presence updates to other users. The new settings lies in the "Other notification settings" section of the "Notification settings" page, under a "Presence" subheading. Closes #14798. --- static/js/settings.js | 1 + static/js/settings_config.js | 6 +++ .../settings/notification_settings.hbs | 9 ++++ templates/zerver/api/changelog.md | 2 + .../zerver/help/status-and-availability.md | 45 ++++++++++++++----- version.py | 2 +- .../0280_userprofile_presence_enabled.py | 18 ++++++++ zerver/models.py | 2 + zerver/openapi/zulip.yaml | 7 +++ zerver/tests/test_home.py | 1 + zerver/tests/test_presence.py | 30 +++++++++++++ zerver/views/presence.py | 2 +- zerver/views/user_settings.py | 3 +- 13 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 zerver/migrations/0280_userprofile_presence_enabled.py diff --git a/static/js/settings.js b/static/js/settings.js index 6c2133a362..fbbf5c9b0e 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -30,6 +30,7 @@ function setup_settings_label() { enable_login_emails: i18n.t("Send email notifications for new logins to my account"), message_content_in_email_notifications: i18n.t("Include message content in missed message emails"), realm_name_in_notifications: i18n.t("Include organization name in subject of missed message emails"), + presence_enabled: i18n.t("Display my availability to other users when online"), // display settings dense_mode: i18n.t("Dense mode"), diff --git a/static/js/settings_config.js b/static/js/settings_config.js index aa81d7d9c3..0bd9c4c6b4 100644 --- a/static/js/settings_config.js +++ b/static/js/settings_config.js @@ -216,10 +216,15 @@ const email_notification_settings = [ "realm_name_in_notifications", ]; +const presence_notification_settings = [ + "presence_enabled", +]; + const other_notification_settings = desktop_notification_settings.concat( ["desktop_icon_count_display"], mobile_notification_settings, email_notification_settings, + presence_notification_settings, ["notification_sound"] ); @@ -245,6 +250,7 @@ exports.all_notifications = () => ({ desktop_notification_settings: desktop_notification_settings, mobile_notification_settings: mobile_notification_settings, email_notification_settings: email_notification_settings, + presence_notification_settings: presence_notification_settings, }, show_push_notifications_tooltip: { push_notifications: !page_params.realm_push_notifications_enabled, diff --git a/static/templates/settings/notification_settings.hbs b/static/templates/settings/notification_settings.hbs index e8837a98e2..910e094cf3 100644 --- a/static/templates/settings/notification_settings.hbs +++ b/static/templates/settings/notification_settings.hbs @@ -107,6 +107,15 @@ label=(lookup ../settings_label this)}} {{/each}} +
{{t "Presence" }}
+ + {{#each notification_settings.presence_notification_settings}} + {{> settings_checkbox + setting_name=this + is_checked=(lookup ../page_params this) + label=(lookup ../settings_label this)}} + {{/each}} + diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index 69d0393370..fd3a989d61 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -15,6 +15,8 @@ below features are supported. * `zulip_version` and `zulip_feature_level` are always returned in `POST /register`; previously they were only returned if `event_types` included `zulip_version`. +* Added new `presence_enabled` user notification setting; previously + [presence](/help/status-and-availability) was always enabled. **Feature level 2**: diff --git a/templates/zerver/help/status-and-availability.md b/templates/zerver/help/status-and-availability.md index b9f6b9ae6b..5fd561273d 100644 --- a/templates/zerver/help/status-and-availability.md +++ b/templates/zerver/help/status-and-availability.md @@ -33,17 +33,18 @@ they haven't set a status, no status will appear. There are four possible availabilities: -* **Active** (): Zulip is open and in - focus on web, desktop or mobile, or was in the last 140 seconds. +* **Active** (): Zulip is + open and in focus on web, desktop or mobile, or was in the last 140 + seconds. -* **Idle** (): Zulip is open on your - computer (either desktop or web), but you are not active. +* **Idle** (): Zulip is open on + your computer (either desktop or web), but you are not active. -* **Offline** (): Zulip is not open on - your computer. +* **Offline** (): Zulip is not + open on your computer. -* **Unavailable** (): You can always - manually set your availability to unavailable. +* **Unavailable** (): You can + always manually set your availability to unavailable. For [Group PMs](/help/private-messages), a green circle () @@ -52,8 +53,9 @@ class="indicator green">) means that some are active and some are not. A white circle () means that none are active. -You can see when someone was last active by hovering over their name in the -left or right sidebar. +You can see when someone was last recorded as active by hovering over +their name in the left or right sidebar (even if the user is marked as +unavailable). ## Set yourself as unavailable @@ -67,4 +69,25 @@ left or right sidebar. {end_tabs} -This will also obscure whether you were recently active. +## Disable updating availability + +Zulip supports the privacy option of never updating the availability +information for your account. The result is that you will always +appear to other users as **Offline** (or **Unavailable**, if you've +set an appropriate status), regardless of your activity in Zulip. + +With this setting, your "Last active" time displayed to other users in +the UI will be frozen as the time you enabled this setting. + +{start_tabs} + +{settings_tab|notifications} + +1. Under **Other notification settings**, in the **Presence** + subsection, toggle **Display my availability to other users**. + +{end_tabs} + +Note that because this setting works by making your availability to +updating, you'll still appear to other users as active for a few +minutes after disabling updates to your availability. diff --git a/version.py b/version.py index d785789755..fca77a4e64 100644 --- a/version.py +++ b/version.py @@ -29,7 +29,7 @@ DESKTOP_WARNING_VERSION = "5.0.0" # # Changes should be accompanied by documentation explaining what the # new level means in templates/zerver/api/changelog.md. -API_FEATURE_LEVEL = 2 +API_FEATURE_LEVEL = 3 # 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 diff --git a/zerver/migrations/0280_userprofile_presence_enabled.py b/zerver/migrations/0280_userprofile_presence_enabled.py new file mode 100644 index 0000000000..2943ef785c --- /dev/null +++ b/zerver/migrations/0280_userprofile_presence_enabled.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.12 on 2020-05-01 16:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0279_message_recipient_subject_indexes'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='presence_enabled', + field=models.BooleanField(default=True), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 24376f00c8..bb6caf7c76 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -916,6 +916,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin): enable_digest_emails: bool = models.BooleanField(default=True) enable_login_emails: bool = models.BooleanField(default=True) realm_name_in_notifications: bool = models.BooleanField(default=False) + presence_enabled: bool = models.BooleanField(default=True) # Used for rate-limiting certain automated messages generated by bots last_reminder: Optional[datetime.datetime] = models.DateTimeField(default=None, null=True) @@ -1048,6 +1049,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin): pm_content_in_desktop_notifications=bool, desktop_icon_count_display=int, realm_name_in_notifications=bool, + presence_enabled=bool, ) class Meta: diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 495a87fb6a..142a77ee95 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -2897,6 +2897,13 @@ paths: schema: type: boolean example: true + - name: presence_enabled + in: query + description: | + Display the presence status to other users when online. + schema: + type: boolean + example: true responses: '200': description: Success. diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index be34f86ea0..d385da18bc 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -119,6 +119,7 @@ class HomeTest(ZulipTestCase): "pm_content_in_desktop_notifications", "pointer", "poll_timeout", + "presence_enabled", "presences", "prompt_for_invites", "queue_id", diff --git a/zerver/tests/test_presence.py b/zerver/tests/test_presence.py index 911570b5cd..ab31c6c2bd 100644 --- a/zerver/tests/test_presence.py +++ b/zerver/tests/test_presence.py @@ -605,3 +605,33 @@ class GetRealmStatusesTest(ZulipTestCase): self.assert_json_success(result) json = result.json() self.assertEqual(set(json['presences'].keys()), {hamlet.email, othello.email}) + + def test_presence_disabled(self) -> None: + # Disable presence status and test whether the presence + # is reported or not. + othello = self.example_user("othello") + hamlet = self.example_user("hamlet") + othello.presence_enabled = False + hamlet.presence_enabled = True + othello.save(update_fields=['presence_enabled']) + hamlet.save(update_fields=['presence_enabled']) + + result = self.api_post(othello, "/api/v1/users/me/presence", + dict(status='active'), + HTTP_USER_AGENT="ZulipAndroid/1.0") + + result = self.api_post(hamlet, "/api/v1/users/me/presence", + dict(status='idle'), + HTTP_USER_AGENT="ZulipDesktop/1.0") + self.assert_json_success(result) + json = result.json() + + # Othello's presence status is disabled so it won't be reported. + self.assertEqual(set(json['presences'].keys()), {hamlet.email}) + + result = self.api_post(hamlet, "/api/v1/users/me/presence", + dict(status='active', slim_presence='true'), + HTTP_USER_AGENT="ZulipDesktop/1.0") + self.assert_json_success(result) + json = result.json() + self.assertEqual(set(json['presences'].keys()), {str(hamlet.id)}) diff --git a/zerver/views/presence.py b/zerver/views/presence.py index 16b423fd3a..d841e75d9e 100644 --- a/zerver/views/presence.py +++ b/zerver/views/presence.py @@ -85,7 +85,7 @@ def update_active_status_backend(request: HttpRequest, user_profile: UserProfile status_val = UserPresence.status_from_string(status) if status_val is None: raise JsonableError(_("Invalid status: %s") % (status,)) - else: + elif user_profile.presence_enabled: update_user_presence(user_profile, request.client, timezone_now(), status_val, new_user_input) diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index a24967ed44..60ceed8010 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -200,7 +200,8 @@ def json_change_notify_settings( message_content_in_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None), pm_content_in_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None), desktop_icon_count_display: Optional[int]=REQ(validator=check_int, default=None), - realm_name_in_notifications: Optional[bool]=REQ(validator=check_bool, default=None) + realm_name_in_notifications: Optional[bool]=REQ(validator=check_bool, default=None), + presence_enabled: Optional[bool]=REQ(validator=check_bool, default=None), ) -> HttpResponse: result = {}