mirror of https://github.com/zulip/zulip.git
unread_msgs: Fix all unreads in muted stream being treated as muted.
Earlier, 'is_row_muted' returned 'true' if the message was in a muted stream or muted topic. If the message is in an unmuted or followed topic in a muted stream, such topics should be treated as not muted topics in an unmuted stream. This commit fixes the incorrect behavior. Now, for wildcard mentions, 'unread_msgs.mentions' exclude the IDs in muted streams only if the message is in default or muted topic. Also, 'unread_msgs.count' takes into account the unreads in unmuted or followed topics in muted streams too. Documents that this bug was fixed in the API changelog.
This commit is contained in:
parent
a18a526427
commit
49092dfa79
|
@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 8.0
|
## Changes in Zulip 8.0
|
||||||
|
|
||||||
|
**Feature level 213**
|
||||||
|
|
||||||
|
* [`POST /register`](/api/register-queue): Fixed incorrect handling of
|
||||||
|
unmuted and followed topics in calculating the `mentions` and
|
||||||
|
`count` fields of the `unread_msgs` object.
|
||||||
|
|
||||||
**Feature level 212**
|
**Feature level 212**
|
||||||
|
|
||||||
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue),
|
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue),
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 212
|
API_FEATURE_LEVEL = 213
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -53,7 +53,7 @@ from zerver.lib.topic import DB_TOPIC_NAME, MESSAGE__TOPIC, TOPIC_LINKS, TOPIC_N
|
||||||
from zerver.lib.types import DisplayRecipientT, EditHistoryEvent, UserDisplayRecipient
|
from zerver.lib.types import DisplayRecipientT, EditHistoryEvent, UserDisplayRecipient
|
||||||
from zerver.lib.url_preview.types import UrlEmbedData
|
from zerver.lib.url_preview.types import UrlEmbedData
|
||||||
from zerver.lib.user_groups import is_user_in_group
|
from zerver.lib.user_groups import is_user_in_group
|
||||||
from zerver.lib.user_topics import build_get_topic_visibility_policy, topic_has_visibility_policy
|
from zerver.lib.user_topics import build_get_topic_visibility_policy, get_topic_visibility_policy
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
MAX_TOPIC_NAME_LENGTH,
|
MAX_TOPIC_NAME_LENGTH,
|
||||||
Message,
|
Message,
|
||||||
|
@ -1114,10 +1114,19 @@ def extract_unread_data_from_um_rows(
|
||||||
get_topic_visibility_policy = build_get_topic_visibility_policy(user_profile)
|
get_topic_visibility_policy = build_get_topic_visibility_policy(user_profile)
|
||||||
|
|
||||||
def is_row_muted(stream_id: int, recipient_id: int, topic: str) -> bool:
|
def is_row_muted(stream_id: int, recipient_id: int, topic: str) -> bool:
|
||||||
if stream_id in muted_stream_ids:
|
stream_muted = stream_id in muted_stream_ids
|
||||||
|
visibility_policy = get_topic_visibility_policy(recipient_id, topic)
|
||||||
|
|
||||||
|
if stream_muted and visibility_policy in [
|
||||||
|
UserTopic.VisibilityPolicy.UNMUTED,
|
||||||
|
UserTopic.VisibilityPolicy.FOLLOWED,
|
||||||
|
]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if stream_muted:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
visibility_policy = get_topic_visibility_policy(recipient_id, topic)
|
# muted topic in unmuted stream
|
||||||
if visibility_policy == UserTopic.VisibilityPolicy.MUTED:
|
if visibility_policy == UserTopic.VisibilityPolicy.MUTED:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -1317,12 +1326,15 @@ def apply_unread_message_event(
|
||||||
topic=topic,
|
topic=topic,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
stream_muted = stream_id in state["muted_stream_ids"]
|
||||||
stream_id not in state["muted_stream_ids"]
|
visibility_policy = get_topic_visibility_policy(user_profile, stream_id, topic)
|
||||||
# This next check hits the database.
|
# A stream message is unmuted if it belongs to:
|
||||||
and not topic_has_visibility_policy(
|
# * a not muted topic in a normal stream
|
||||||
user_profile, stream_id, topic, UserTopic.VisibilityPolicy.MUTED
|
# * an unmuted or followed topic in a muted stream
|
||||||
)
|
if (not stream_muted and visibility_policy != UserTopic.VisibilityPolicy.MUTED) or (
|
||||||
|
stream_muted
|
||||||
|
and visibility_policy
|
||||||
|
in [UserTopic.VisibilityPolicy.UNMUTED, UserTopic.VisibilityPolicy.FOLLOWED]
|
||||||
):
|
):
|
||||||
state["unmuted_stream_msgs"].add(message_id)
|
state["unmuted_stream_msgs"].add(message_id)
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,21 @@ def set_topic_visibility_policy(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_topic_visibility_policy(
|
||||||
|
user_profile: UserProfile,
|
||||||
|
stream_id: int,
|
||||||
|
topic_name: str,
|
||||||
|
) -> int:
|
||||||
|
try:
|
||||||
|
user_topic = UserTopic.objects.get(
|
||||||
|
user_profile=user_profile, stream_id=stream_id, topic_name__iexact=topic_name
|
||||||
|
)
|
||||||
|
visibility_policy = user_topic.visibility_policy
|
||||||
|
except UserTopic.DoesNotExist:
|
||||||
|
visibility_policy = UserTopic.VisibilityPolicy.INHERIT
|
||||||
|
return visibility_policy
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic(savepoint=False)
|
@transaction.atomic(savepoint=False)
|
||||||
def bulk_set_user_topic_visibility_policy_in_database(
|
def bulk_set_user_topic_visibility_policy_in_database(
|
||||||
user_profiles: List[UserProfile],
|
user_profiles: List[UserProfile],
|
||||||
|
|
|
@ -11851,9 +11851,11 @@ paths:
|
||||||
count:
|
count:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
The total number of unread messages to display; this includes one-on-one
|
The total number of unread messages to display. This includes one-on-one and group
|
||||||
and group direct messages, as well as all messages to unmuted topics
|
direct messages, as well as stream messages that are not [muted](/help/mute-a-topic).
|
||||||
on unmuted streams.
|
|
||||||
|
**Changes**: Before Zulip 8.0 (feature level 213), the unmute and follow
|
||||||
|
topic features were not handled correctly in calculating this field.
|
||||||
pms:
|
pms:
|
||||||
type: array
|
type: array
|
||||||
description: |
|
description: |
|
||||||
|
@ -11897,8 +11899,9 @@ paths:
|
||||||
streams:
|
streams:
|
||||||
type: array
|
type: array
|
||||||
description: |
|
description: |
|
||||||
An array of objects where each object contains details of unread
|
An array of dictionaries where each dictionary contains details of all
|
||||||
messages of a single subscribed stream, including muted streams.
|
unread messages of a single subscribed stream. This includes muted streams
|
||||||
|
and muted topics, even though those messages are excluded from count.
|
||||||
|
|
||||||
**Changes**: Prior to Zulip 5.0 (feature level 90), these objects
|
**Changes**: Prior to Zulip 5.0 (feature level 90), these objects
|
||||||
included a `sender_ids` property, which listed the set of IDs of
|
included a `sender_ids` property, which listed the set of IDs of
|
||||||
|
@ -11947,9 +11950,12 @@ paths:
|
||||||
mentions:
|
mentions:
|
||||||
type: array
|
type: array
|
||||||
description: |
|
description: |
|
||||||
Array containing the IDs of all unread messages in which the user has been
|
Array containing the IDs of all unread messages in which the user was
|
||||||
mentioned. For muted streams, wildcard mentions will not be considered for
|
mentioned directly, and unread [non-muted](/help/mute-a-topic) messages
|
||||||
this array.
|
in which the user was mentioned through a wildcard.
|
||||||
|
|
||||||
|
**Changes**: Before Zulip 8.0 (feature level 213), the unmute and follow
|
||||||
|
topic features were not handled correctly in calculating this field.
|
||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
old_unreads_missing:
|
old_unreads_missing:
|
||||||
|
|
|
@ -529,8 +529,9 @@ class NormalActionsTest(BaseAction):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_stream_send_message_events(self) -> None:
|
def test_stream_send_message_events(self) -> None:
|
||||||
|
user_profile = self.example_user("hamlet")
|
||||||
events = self.verify_action(
|
events = self.verify_action(
|
||||||
lambda: self.send_stream_message(self.example_user("hamlet"), "Verona", "hello"),
|
lambda: self.send_stream_message(user_profile, "Verona", "hello"),
|
||||||
client_gravatar=False,
|
client_gravatar=False,
|
||||||
)
|
)
|
||||||
check_message("events[0]", events[0])
|
check_message("events[0]", events[0])
|
||||||
|
@ -544,12 +545,31 @@ class NormalActionsTest(BaseAction):
|
||||||
)
|
)
|
||||||
|
|
||||||
events = self.verify_action(
|
events = self.verify_action(
|
||||||
lambda: self.send_stream_message(self.example_user("hamlet"), "Verona", "hello"),
|
lambda: self.send_stream_message(user_profile, "Verona", "hello"),
|
||||||
client_gravatar=True,
|
client_gravatar=True,
|
||||||
)
|
)
|
||||||
check_message("events[0]", events[0])
|
check_message("events[0]", events[0])
|
||||||
assert events[0]["message"]["avatar_url"] is None
|
assert events[0]["message"]["avatar_url"] is None
|
||||||
|
|
||||||
|
# Here we add coverage for the case where 'apply_unread_message_event'
|
||||||
|
# should be called and unread messages in unmuted or followed topic in
|
||||||
|
# muted stream is treated as unmuted stream message, thus added to 'unmuted_stream_msgs'.
|
||||||
|
stream = get_stream("Verona", user_profile.realm)
|
||||||
|
sub = get_subscription(stream.name, user_profile)
|
||||||
|
do_change_subscription_property(
|
||||||
|
user_profile, sub, stream, "is_muted", True, acting_user=None
|
||||||
|
)
|
||||||
|
do_set_user_topic_visibility_policy(
|
||||||
|
user_profile,
|
||||||
|
stream,
|
||||||
|
"test",
|
||||||
|
visibility_policy=UserTopic.VisibilityPolicy.UNMUTED,
|
||||||
|
)
|
||||||
|
self.verify_action(
|
||||||
|
lambda: self.send_stream_message(self.example_user("aaron"), "Verona", "hello"),
|
||||||
|
state_change_expected=True,
|
||||||
|
)
|
||||||
|
|
||||||
def test_stream_update_message_events(self) -> None:
|
def test_stream_update_message_events(self) -> None:
|
||||||
self.send_stream_message(self.example_user("hamlet"), "Verona", "hello")
|
self.send_stream_message(self.example_user("hamlet"), "Verona", "hello")
|
||||||
|
|
||||||
|
|
|
@ -704,7 +704,9 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
subscription.is_muted = True
|
subscription.is_muted = True
|
||||||
subscription.save()
|
subscription.save()
|
||||||
|
|
||||||
def mute_topic(self, user_profile: UserProfile, stream_name: str, topic_name: str) -> None:
|
def set_topic_visibility_policy(
|
||||||
|
self, user_profile: UserProfile, stream_name: str, topic_name: str, visibility_policy: int
|
||||||
|
) -> None:
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
stream = get_stream(stream_name, realm)
|
stream = get_stream(stream_name, realm)
|
||||||
|
|
||||||
|
@ -712,7 +714,7 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
user_profile,
|
user_profile,
|
||||||
stream,
|
stream,
|
||||||
topic_name,
|
topic_name,
|
||||||
visibility_policy=UserTopic.VisibilityPolicy.MUTED,
|
visibility_policy=visibility_policy,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_raw_unread_stream(self) -> None:
|
def test_raw_unread_stream(self) -> None:
|
||||||
|
@ -752,10 +754,11 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
stream=get_stream("test here", realm),
|
stream=get_stream("test here", realm),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mute_topic(
|
self.set_topic_visibility_policy(
|
||||||
user_profile=hamlet,
|
user_profile=hamlet,
|
||||||
stream_name="devel",
|
stream_name="devel",
|
||||||
topic_name="ruby",
|
topic_name="ruby",
|
||||||
|
visibility_policy=UserTopic.VisibilityPolicy.MUTED,
|
||||||
)
|
)
|
||||||
|
|
||||||
raw_unread_data = get_raw_unread_data(
|
raw_unread_data = get_raw_unread_data(
|
||||||
|
@ -960,7 +963,12 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
muted_stream = self.subscribe(user_profile, "Muted stream")
|
muted_stream = self.subscribe(user_profile, "Muted stream")
|
||||||
self.subscribe(sender, muted_stream.name)
|
self.subscribe(sender, muted_stream.name)
|
||||||
self.mute_stream(user_profile, muted_stream)
|
self.mute_stream(user_profile, muted_stream)
|
||||||
self.mute_topic(user_profile, "Denmark", "muted-topic")
|
self.set_topic_visibility_policy(
|
||||||
|
user_profile, "Denmark", "muted-topic", UserTopic.VisibilityPolicy.MUTED
|
||||||
|
)
|
||||||
|
self.set_topic_visibility_policy(
|
||||||
|
user_profile, "Muted stream", "unmuted-topic", UserTopic.VisibilityPolicy.UNMUTED
|
||||||
|
)
|
||||||
|
|
||||||
stream_message_id = self.send_stream_message(sender, "Denmark", "hello")
|
stream_message_id = self.send_stream_message(sender, "Denmark", "hello")
|
||||||
muted_stream_message_id = self.send_stream_message(sender, "Muted stream", "hello")
|
muted_stream_message_id = self.send_stream_message(sender, "Muted stream", "hello")
|
||||||
|
@ -970,6 +978,12 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
topic_name="muted-topic",
|
topic_name="muted-topic",
|
||||||
content="hello",
|
content="hello",
|
||||||
)
|
)
|
||||||
|
unmuted_topic_muted_stream_message_id = self.send_stream_message(
|
||||||
|
sender,
|
||||||
|
"Muted stream",
|
||||||
|
topic_name="unmuted-topic",
|
||||||
|
content="hello",
|
||||||
|
)
|
||||||
|
|
||||||
huddle_message_id = self.send_huddle_message(
|
huddle_message_id = self.send_huddle_message(
|
||||||
sender,
|
sender,
|
||||||
|
@ -982,17 +996,18 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
aggregated_data = aggregate_unread_data(raw_unread_data)
|
aggregated_data = aggregate_unread_data(raw_unread_data)
|
||||||
return aggregated_data
|
return aggregated_data
|
||||||
|
|
||||||
with mock.patch("zerver.lib.message.MAX_UNREAD_MESSAGES", 4):
|
with mock.patch("zerver.lib.message.MAX_UNREAD_MESSAGES", 5):
|
||||||
result = get_unread_data()
|
result = get_unread_data()
|
||||||
self.assertEqual(result["count"], 2)
|
self.assertEqual(result["count"], 3)
|
||||||
self.assertTrue(result["old_unreads_missing"])
|
self.assertTrue(result["old_unreads_missing"])
|
||||||
|
|
||||||
result = get_unread_data()
|
result = get_unread_data()
|
||||||
|
|
||||||
# The count here reflects the count of unread messages that we will
|
# The count here reflects the count of unread messages that we will report
|
||||||
# report to users in the bankruptcy dialog, and it excludes unread messages
|
# to users in the bankruptcy dialog, and it excludes unread messages from:
|
||||||
# from muted streams and muted topics.
|
# * muted topics in unmuted streams
|
||||||
self.assertEqual(result["count"], 4)
|
# * default or muted topics in muted streams
|
||||||
|
self.assertEqual(result["count"], 5)
|
||||||
self.assertFalse(result["old_unreads_missing"])
|
self.assertFalse(result["old_unreads_missing"])
|
||||||
|
|
||||||
unread_pm = result["pms"][0]
|
unread_pm = result["pms"][0]
|
||||||
|
@ -1016,6 +1031,15 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
self.assertEqual(unread_stream["topic"], "test")
|
self.assertEqual(unread_stream["topic"], "test")
|
||||||
self.assertEqual(unread_stream["unread_message_ids"], [muted_stream_message_id])
|
self.assertEqual(unread_stream["unread_message_ids"], [muted_stream_message_id])
|
||||||
|
|
||||||
|
unread_stream = result["streams"][3]
|
||||||
|
self.assertEqual(
|
||||||
|
unread_stream["stream_id"], get_stream("Muted stream", user_profile.realm).id
|
||||||
|
)
|
||||||
|
self.assertEqual(unread_stream["topic"], "unmuted-topic")
|
||||||
|
self.assertEqual(
|
||||||
|
unread_stream["unread_message_ids"], [unmuted_topic_muted_stream_message_id]
|
||||||
|
)
|
||||||
|
|
||||||
huddle_string = ",".join(
|
huddle_string = ",".join(
|
||||||
str(uid) for uid in sorted([sender_id, user_profile.id, othello.id])
|
str(uid) for uid in sorted([sender_id, user_profile.id, othello.id])
|
||||||
)
|
)
|
||||||
|
@ -1076,6 +1100,21 @@ class GetUnreadMsgsTest(ZulipTestCase):
|
||||||
result = get_unread_data()
|
result = get_unread_data()
|
||||||
self.assertEqual(result["mentions"], [])
|
self.assertEqual(result["mentions"], [])
|
||||||
|
|
||||||
|
# Test with a unmuted topic in muted stream
|
||||||
|
um = UserMessage.objects.get(
|
||||||
|
user_profile_id=user_profile.id,
|
||||||
|
message_id=unmuted_topic_muted_stream_message_id,
|
||||||
|
)
|
||||||
|
um.flags = UserMessage.flags.wildcard_mentioned
|
||||||
|
um.save()
|
||||||
|
result = get_unread_data()
|
||||||
|
self.assertEqual(result["mentions"], [unmuted_topic_muted_stream_message_id])
|
||||||
|
|
||||||
|
um.flags = 0
|
||||||
|
um.save()
|
||||||
|
result = get_unread_data()
|
||||||
|
self.assertEqual(result["mentions"], [])
|
||||||
|
|
||||||
# Test with a muted topic
|
# Test with a muted topic
|
||||||
um = UserMessage.objects.get(
|
um = UserMessage.objects.get(
|
||||||
user_profile_id=user_profile.id,
|
user_profile_id=user_profile.id,
|
||||||
|
|
Loading…
Reference in New Issue