mirror of https://github.com/zulip/zulip.git
read_receipts: Exclude muted users from read receipts.
Removes IDs of users who have muted or been muted by the current user from the list of user IDs returned by the read receipts endpoint. Fixes #22909.
This commit is contained in:
parent
5e58f86aa7
commit
eb377a8872
|
@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 6.0
|
## Changes in Zulip 6.0
|
||||||
|
|
||||||
|
**Feature level 144**
|
||||||
|
|
||||||
|
* [`GET /messages/{message_id}/read_receipts`](/api/get-read-receipts):
|
||||||
|
The `user_ids` array returned by the server no longer includes IDs
|
||||||
|
of users who have been muted by or have muted the current user.
|
||||||
|
|
||||||
**Feature level 143**
|
**Feature level 143**
|
||||||
|
|
||||||
* `PATCH /realm`: The `disallow_disposable_email_addresses`,
|
* `PATCH /realm`: The `disallow_disposable_email_addresses`,
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||||
# 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, as well as
|
# new level means in templates/zerver/api/changelog.md, as well as
|
||||||
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 143
|
API_FEATURE_LEVEL = 144
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -5903,8 +5903,16 @@ paths:
|
||||||
|
|
||||||
The IDs of users who have disabled sending read receipts
|
The IDs of users who have disabled sending read receipts
|
||||||
(`send_read_receipts=false`) will never appear in the response,
|
(`send_read_receipts=false`) will never appear in the response,
|
||||||
nor will the message's sender. The current user's ID will, if
|
nor will the message's sender. Additionally, the IDs of any users
|
||||||
they have marked the read target message as read.
|
who have been muted by the current user or who have muted the
|
||||||
|
current user will not be included in the response.
|
||||||
|
|
||||||
|
The current user's ID will appear if they have marked the target
|
||||||
|
message as read.
|
||||||
|
|
||||||
|
**Changes**: Prior to Zulip 6.0 (feature level 143), the IDs of
|
||||||
|
users who have been muted by or have muted the current user were
|
||||||
|
included in the response.
|
||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
example:
|
example:
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import orjson
|
import orjson
|
||||||
|
from django.utils.timezone import now as timezone_now
|
||||||
|
|
||||||
from zerver.actions.create_user import do_reactivate_user
|
from zerver.actions.create_user import do_reactivate_user
|
||||||
from zerver.actions.realm_settings import do_set_realm_property
|
from zerver.actions.realm_settings import do_set_realm_property
|
||||||
from zerver.actions.user_settings import do_change_user_setting
|
from zerver.actions.user_settings import do_change_user_setting
|
||||||
from zerver.actions.users import do_deactivate_user
|
from zerver.actions.users import do_deactivate_user
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
|
from zerver.lib.user_mutes import add_user_mute, get_mute_object
|
||||||
from zerver.models import UserMessage, UserProfile
|
from zerver.models import UserMessage, UserProfile
|
||||||
|
|
||||||
|
|
||||||
|
@ -206,3 +208,80 @@ class TestReadReceipts(ZulipTestCase):
|
||||||
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
self.assertIn(hamlet.id, result.json()["user_ids"])
|
self.assertIn(hamlet.id, result.json()["user_ids"])
|
||||||
|
|
||||||
|
def test_filter_muted_users(self) -> None:
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
othello = self.example_user("othello")
|
||||||
|
iago = self.example_user("iago")
|
||||||
|
|
||||||
|
# Hamlet mutes Cordelia
|
||||||
|
add_user_mute(hamlet, cordelia, date_muted=timezone_now())
|
||||||
|
# Cordelia mutes Othello
|
||||||
|
add_user_mute(cordelia, othello, date_muted=timezone_now())
|
||||||
|
|
||||||
|
# Iago sends a message
|
||||||
|
message_id = self.send_stream_message(iago, "Verona", "read receipts")
|
||||||
|
|
||||||
|
# Mark message as read for users.
|
||||||
|
self.mark_message_read(hamlet, message_id)
|
||||||
|
self.mark_message_read(cordelia, message_id)
|
||||||
|
self.mark_message_read(othello, message_id)
|
||||||
|
|
||||||
|
# Login as Iago and make sure all three users are in read receipts.
|
||||||
|
self.login("iago")
|
||||||
|
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
||||||
|
response_dict = self.assert_json_success(result)
|
||||||
|
self.assert_length(response_dict["user_ids"], 3)
|
||||||
|
self.assertTrue(hamlet.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(cordelia.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(othello.id in response_dict["user_ids"])
|
||||||
|
|
||||||
|
# Login as Hamlet and make sure Cordelia is not in read receipts.
|
||||||
|
self.login("hamlet")
|
||||||
|
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
||||||
|
response_dict = self.assert_json_success(result)
|
||||||
|
self.assert_length(response_dict["user_ids"], 2)
|
||||||
|
self.assertTrue(hamlet.id in response_dict["user_ids"])
|
||||||
|
self.assertFalse(cordelia.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(othello.id in response_dict["user_ids"])
|
||||||
|
|
||||||
|
# Login as Othello and make sure Cordelia is not in in read receipts.
|
||||||
|
self.login("othello")
|
||||||
|
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
||||||
|
response_dict = self.assert_json_success(result)
|
||||||
|
self.assert_length(response_dict["user_ids"], 2)
|
||||||
|
self.assertTrue(hamlet.id in response_dict["user_ids"])
|
||||||
|
self.assertFalse(cordelia.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(othello.id in response_dict["user_ids"])
|
||||||
|
|
||||||
|
# Login as Cordelia and make sure Hamlet and Othello are not in read receipts.
|
||||||
|
self.login("cordelia")
|
||||||
|
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
||||||
|
response_dict = self.assert_json_success(result)
|
||||||
|
self.assert_length(response_dict["user_ids"], 1)
|
||||||
|
self.assertFalse(hamlet.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(cordelia.id in response_dict["user_ids"])
|
||||||
|
self.assertFalse(othello.id in response_dict["user_ids"])
|
||||||
|
|
||||||
|
# Cordelia unmutes Othello
|
||||||
|
mute_object = get_mute_object(cordelia, othello)
|
||||||
|
assert mute_object is not None
|
||||||
|
mute_object.delete()
|
||||||
|
|
||||||
|
# Now Othello should appear in her read receipts, but not Hamlet.
|
||||||
|
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
||||||
|
response_dict = self.assert_json_success(result)
|
||||||
|
self.assert_length(response_dict["user_ids"], 2)
|
||||||
|
self.assertFalse(hamlet.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(cordelia.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(othello.id in response_dict["user_ids"])
|
||||||
|
|
||||||
|
# Login as Othello and make sure all three users are in read receipts.
|
||||||
|
self.login("othello")
|
||||||
|
result = self.client_get(f"/json/messages/{message_id}/read_receipts")
|
||||||
|
response_dict = self.assert_json_success(result)
|
||||||
|
self.assert_length(response_dict["user_ids"], 3)
|
||||||
|
self.assertTrue(hamlet.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(cordelia.id in response_dict["user_ids"])
|
||||||
|
self.assertTrue(othello.id in response_dict["user_ids"])
|
||||||
|
|
|
@ -29,6 +29,15 @@ def read_receipts(
|
||||||
# read the message before sending it, and showing the sender as
|
# read the message before sending it, and showing the sender as
|
||||||
# having read their own message is likely to be confusing.
|
# having read their own message is likely to be confusing.
|
||||||
#
|
#
|
||||||
|
# * Users who have muted the current user are not included, since
|
||||||
|
# the current user could infer that they have been muted by
|
||||||
|
# said users by noting that the muters immediately read every
|
||||||
|
# message that the current user sends to mutually subscribed
|
||||||
|
# streams.
|
||||||
|
#
|
||||||
|
# * Users muted by the current user are also not included, as this
|
||||||
|
# is consistent with other aspects of how muting works.
|
||||||
|
#
|
||||||
# * Deactivated users are excluded. While in theory someone
|
# * Deactivated users are excluded. While in theory someone
|
||||||
# could be interested in the information, not including them
|
# could be interested in the information, not including them
|
||||||
# is a cleaner policy, and usually read receipts are only of
|
# is a cleaner policy, and usually read receipts are only of
|
||||||
|
@ -66,6 +75,8 @@ def read_receipts(
|
||||||
user_profile__send_read_receipts=True,
|
user_profile__send_read_receipts=True,
|
||||||
)
|
)
|
||||||
.exclude(user_profile_id=message.sender_id)
|
.exclude(user_profile_id=message.sender_id)
|
||||||
|
.exclude(user_profile__muter__muted_user_id=user_profile.id)
|
||||||
|
.exclude(user_profile__muted__user_profile_id=user_profile.id)
|
||||||
.extra(
|
.extra(
|
||||||
where=[UserMessage.where_read()],
|
where=[UserMessage.where_read()],
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue