mirror of https://github.com/zulip/zulip.git
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.
This commit is contained in:
parent
3eaa71cef8
commit
55a8e7dff2
|
@ -30,6 +30,7 @@ function setup_settings_label() {
|
||||||
enable_login_emails: i18n.t("Send email notifications for new logins to my account"),
|
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"),
|
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"),
|
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
|
// display settings
|
||||||
dense_mode: i18n.t("Dense mode"),
|
dense_mode: i18n.t("Dense mode"),
|
||||||
|
|
|
@ -216,10 +216,15 @@ const email_notification_settings = [
|
||||||
"realm_name_in_notifications",
|
"realm_name_in_notifications",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const presence_notification_settings = [
|
||||||
|
"presence_enabled",
|
||||||
|
];
|
||||||
|
|
||||||
const other_notification_settings = desktop_notification_settings.concat(
|
const other_notification_settings = desktop_notification_settings.concat(
|
||||||
["desktop_icon_count_display"],
|
["desktop_icon_count_display"],
|
||||||
mobile_notification_settings,
|
mobile_notification_settings,
|
||||||
email_notification_settings,
|
email_notification_settings,
|
||||||
|
presence_notification_settings,
|
||||||
["notification_sound"]
|
["notification_sound"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -245,6 +250,7 @@ exports.all_notifications = () => ({
|
||||||
desktop_notification_settings: desktop_notification_settings,
|
desktop_notification_settings: desktop_notification_settings,
|
||||||
mobile_notification_settings: mobile_notification_settings,
|
mobile_notification_settings: mobile_notification_settings,
|
||||||
email_notification_settings: email_notification_settings,
|
email_notification_settings: email_notification_settings,
|
||||||
|
presence_notification_settings: presence_notification_settings,
|
||||||
},
|
},
|
||||||
show_push_notifications_tooltip: {
|
show_push_notifications_tooltip: {
|
||||||
push_notifications: !page_params.realm_push_notifications_enabled,
|
push_notifications: !page_params.realm_push_notifications_enabled,
|
||||||
|
|
|
@ -107,6 +107,15 @@
|
||||||
label=(lookup ../settings_label this)}}
|
label=(lookup ../settings_label this)}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
|
<h5>{{t "Presence" }}</h5>
|
||||||
|
|
||||||
|
{{#each notification_settings.presence_notification_settings}}
|
||||||
|
{{> settings_checkbox
|
||||||
|
setting_name=this
|
||||||
|
is_checked=(lookup ../page_params this)
|
||||||
|
label=(lookup ../settings_label this)}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,6 +15,8 @@ below features are supported.
|
||||||
* `zulip_version` and `zulip_feature_level` are always returned
|
* `zulip_version` and `zulip_feature_level` are always returned
|
||||||
in `POST /register`; previously they were only returned if `event_types`
|
in `POST /register`; previously they were only returned if `event_types`
|
||||||
included `zulip_version`.
|
included `zulip_version`.
|
||||||
|
* Added new `presence_enabled` user notification setting; previously
|
||||||
|
[presence](/help/status-and-availability) was always enabled.
|
||||||
|
|
||||||
**Feature level 2**:
|
**Feature level 2**:
|
||||||
|
|
||||||
|
|
|
@ -33,17 +33,18 @@ they haven't set a status, no status will appear.
|
||||||
|
|
||||||
There are four possible availabilities:
|
There are four possible availabilities:
|
||||||
|
|
||||||
* **Active** (<span class="indicator green solid"></span>): Zulip is open and in
|
* **Active** (<span class="indicator green solid"></span>): Zulip is
|
||||||
focus on web, desktop or mobile, or was in the last 140 seconds.
|
open and in focus on web, desktop or mobile, or was in the last 140
|
||||||
|
seconds.
|
||||||
|
|
||||||
* **Idle** (<span class="indicator orange"></span>): Zulip is open on your
|
* **Idle** (<span class="indicator orange"></span>): Zulip is open on
|
||||||
computer (either desktop or web), but you are not active.
|
your computer (either desktop or web), but you are not active.
|
||||||
|
|
||||||
* **Offline** (<span class="indicator grey"></span>): Zulip is not open on
|
* **Offline** (<span class="indicator grey"></span>): Zulip is not
|
||||||
your computer.
|
open on your computer.
|
||||||
|
|
||||||
* **Unavailable** (<span class="indicator grey-line"></span>): You can always
|
* **Unavailable** (<span class="indicator grey-line"></span>): You can
|
||||||
manually set your availability to unavailable.
|
always manually set your availability to unavailable.
|
||||||
|
|
||||||
For [Group PMs](/help/private-messages), a green circle
|
For [Group PMs](/help/private-messages), a green circle
|
||||||
(<span class="indicator green solid"></span>)
|
(<span class="indicator green solid"></span>)
|
||||||
|
@ -52,8 +53,9 @@ class="indicator green"></span>) means that some are active and some are
|
||||||
not. A white circle (<span class="indicator grey"></span>) means that none
|
not. A white circle (<span class="indicator grey"></span>) means that none
|
||||||
are active.
|
are active.
|
||||||
|
|
||||||
You can see when someone was last active by hovering over their name in the
|
You can see when someone was last recorded as active by hovering over
|
||||||
left or right sidebar.
|
their name in the left or right sidebar (even if the user is marked as
|
||||||
|
unavailable).
|
||||||
|
|
||||||
## Set yourself as unavailable
|
## Set yourself as unavailable
|
||||||
|
|
||||||
|
@ -67,4 +69,25 @@ left or right sidebar.
|
||||||
|
|
||||||
{end_tabs}
|
{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.
|
||||||
|
|
|
@ -29,7 +29,7 @@ DESKTOP_WARNING_VERSION = "5.0.0"
|
||||||
#
|
#
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in templates/zerver/api/changelog.md.
|
# 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
|
# 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
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -916,6 +916,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
|
||||||
enable_digest_emails: bool = models.BooleanField(default=True)
|
enable_digest_emails: bool = models.BooleanField(default=True)
|
||||||
enable_login_emails: bool = models.BooleanField(default=True)
|
enable_login_emails: bool = models.BooleanField(default=True)
|
||||||
realm_name_in_notifications: bool = models.BooleanField(default=False)
|
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
|
# Used for rate-limiting certain automated messages generated by bots
|
||||||
last_reminder: Optional[datetime.datetime] = models.DateTimeField(default=None, null=True)
|
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,
|
pm_content_in_desktop_notifications=bool,
|
||||||
desktop_icon_count_display=int,
|
desktop_icon_count_display=int,
|
||||||
realm_name_in_notifications=bool,
|
realm_name_in_notifications=bool,
|
||||||
|
presence_enabled=bool,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -2897,6 +2897,13 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
|
- name: presence_enabled
|
||||||
|
in: query
|
||||||
|
description: |
|
||||||
|
Display the presence status to other users when online.
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Success.
|
description: Success.
|
||||||
|
|
|
@ -119,6 +119,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"pm_content_in_desktop_notifications",
|
"pm_content_in_desktop_notifications",
|
||||||
"pointer",
|
"pointer",
|
||||||
"poll_timeout",
|
"poll_timeout",
|
||||||
|
"presence_enabled",
|
||||||
"presences",
|
"presences",
|
||||||
"prompt_for_invites",
|
"prompt_for_invites",
|
||||||
"queue_id",
|
"queue_id",
|
||||||
|
|
|
@ -605,3 +605,33 @@ class GetRealmStatusesTest(ZulipTestCase):
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
json = result.json()
|
json = result.json()
|
||||||
self.assertEqual(set(json['presences'].keys()), {hamlet.email, othello.email})
|
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)})
|
||||||
|
|
|
@ -85,7 +85,7 @@ def update_active_status_backend(request: HttpRequest, user_profile: UserProfile
|
||||||
status_val = UserPresence.status_from_string(status)
|
status_val = UserPresence.status_from_string(status)
|
||||||
if status_val is None:
|
if status_val is None:
|
||||||
raise JsonableError(_("Invalid status: %s") % (status,))
|
raise JsonableError(_("Invalid status: %s") % (status,))
|
||||||
else:
|
elif user_profile.presence_enabled:
|
||||||
update_user_presence(user_profile, request.client, timezone_now(),
|
update_user_presence(user_profile, request.client, timezone_now(),
|
||||||
status_val, new_user_input)
|
status_val, new_user_input)
|
||||||
|
|
||||||
|
|
|
@ -200,7 +200,8 @@ def json_change_notify_settings(
|
||||||
message_content_in_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
|
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),
|
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),
|
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:
|
) -> HttpResponse:
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue