mark_unread: Add support for empty topic name.

This commit is a part of the work to support empty string
as a topic name.

Previously, empty string was not a valid topic name.

Now, mark unread operation supports empty topic name.

Adds backward compatibility for:
- `topic` field in the `update_message_flags` event type
   when removing `read` flag
This commit is contained in:
Prakhar Pratyush
2024-11-29 13:06:25 +05:30
committed by Tim Abbott
parent 293db85f67
commit 79e55b1563
4 changed files with 101 additions and 6 deletions

View File

@@ -43,6 +43,7 @@ format used by the Zulip server that they are interacting with.
* `orig_subject` and `subject` fields in the `update_message` event type
* `topic_name` field in the `user_topic` event type
* `topic` field in the `typing` event type
* `topic` field in the `update_message_flags` event type when removing `read` flag
* [`GET /messages`](/api/get-messages),
[`GET /messages/{message_id}`](/api/get-message): Added `allow_empty_topic_name`

View File

@@ -3129,6 +3129,17 @@ paths:
Present only if `type` is `"stream"`.
Name of the topic where the message was sent.
For clients that don't support the `empty_topic_name` [client capability][client-capabilities],
if the actual topic name is empty string, this field's value will instead
be the value of `realm_empty_topic_display_name` found in the
[`POST /register`](/api/register-queue) response.
**Changes**: Before 10.0 (feature level 334), `empty_topic_name`
client capability didn't exist and empty string as the topic name for
channel messages wasn't allowed.
[client-capabilities]: /api/register-queue#parameter-client_capabilities
unmuted_stream_msg:
type: boolean
deprecated: true

View File

@@ -377,7 +377,14 @@ class EmptyTopicNameTest(ZulipTestCase):
apply_markdown=True,
client_type_name="website",
empty_topic_name=True,
event_types=["message", "update_message", "delete_message", "user_topic", "typing"],
event_types=[
"message",
"update_message",
"delete_message",
"user_topic",
"typing",
"update_message_flags",
],
last_connection_time=time.time(),
queue_timeout=600,
realm_id=hamlet.realm.id,
@@ -425,10 +432,10 @@ class EmptyTopicNameTest(ZulipTestCase):
self.assertEqual(events[5]["topic"], "")
# reset
self.send_stream_message(
message_id = self.send_stream_message(
iago, "Denmark", topic_name="", skip_capture_on_commit_callbacks=True
)
self.send_stream_message(
message_id_2 = self.send_stream_message(
iago,
"Verona",
topic_name=Message.EMPTY_TOPIC_FALLBACK_NAME,
@@ -473,6 +480,25 @@ class EmptyTopicNameTest(ZulipTestCase):
self.assertEqual(events[8]["topic"], "")
self.assertEqual(events[9]["topic"], "")
# Prep to mark it as read before marking it as unread.
params = {
"messages": orjson.dumps([message_id, message_id_2]).decode(),
"op": "add",
"flag": "read",
}
self.client_post("/json/messages/flags", params)
with self.captureOnCommitCallbacks(execute=True):
params = {
"messages": orjson.dumps([message_id, message_id_2]).decode(),
"op": "remove",
"flag": "read",
}
self.client_post("/json/messages/flags", params)
events = client.event_queue.contents()
self.assertEqual(events[10]["message_details"][str(message_id)]["topic"], "")
self.assertEqual(events[10]["message_details"][str(message_id_2)]["topic"], "")
def test_client_not_supports_empty_topic_name(self) -> None:
iago = self.example_user("iago")
hamlet = self.example_user("hamlet")
@@ -481,7 +507,14 @@ class EmptyTopicNameTest(ZulipTestCase):
apply_markdown=True,
client_type_name="zulip-mobile",
empty_topic_name=False,
event_types=["message", "update_message", "delete_message", "user_topic", "typing"],
event_types=[
"message",
"update_message",
"delete_message",
"user_topic",
"typing",
"update_message_flags",
],
last_connection_time=time.time(),
queue_timeout=600,
realm_id=hamlet.realm.id,
@@ -529,10 +562,10 @@ class EmptyTopicNameTest(ZulipTestCase):
self.assertEqual(events[5]["topic"], Message.EMPTY_TOPIC_FALLBACK_NAME)
# reset
self.send_stream_message(
message_id = self.send_stream_message(
iago, "Denmark", topic_name="", skip_capture_on_commit_callbacks=True
)
self.send_stream_message(
message_id_2 = self.send_stream_message(
iago,
"Verona",
topic_name=Message.EMPTY_TOPIC_FALLBACK_NAME,
@@ -577,6 +610,31 @@ class EmptyTopicNameTest(ZulipTestCase):
self.assertEqual(events[8]["topic"], Message.EMPTY_TOPIC_FALLBACK_NAME)
self.assertEqual(events[9]["topic"], Message.EMPTY_TOPIC_FALLBACK_NAME)
# Prep to mark it as read before marking it as unread.
params = {
"messages": orjson.dumps([message_id, message_id_2]).decode(),
"op": "add",
"flag": "read",
}
self.client_post("/json/messages/flags", params)
with self.captureOnCommitCallbacks(execute=True):
params = {
"messages": orjson.dumps([message_id, message_id_2]).decode(),
"op": "remove",
"flag": "read",
}
self.client_post("/json/messages/flags", params)
events = client.event_queue.contents()
self.assertEqual(
events[10]["message_details"][str(message_id)]["topic"],
Message.EMPTY_TOPIC_FALLBACK_NAME,
)
self.assertEqual(
events[10]["message_details"][str(message_id_2)]["topic"],
Message.EMPTY_TOPIC_FALLBACK_NAME,
)
def test_fetch_messages(self) -> None:
hamlet = self.example_user("hamlet")
self.login_user(hamlet)

View File

@@ -1663,6 +1663,25 @@ def process_stream_typing_notification_event(
client.add_event(empty_topic_name_fallback_event)
def process_mark_message_unread_event(event: Mapping[str, Any], users: Iterable[int]) -> None:
empty_topic_name_fallback_event = copy.deepcopy(dict(event))
for message_id, message_detail in empty_topic_name_fallback_event["message_details"].items():
if message_detail["type"] == "stream" and message_detail.get("topic") == "":
empty_topic_name_fallback_event["message_details"][message_id]["topic"] = (
Message.EMPTY_TOPIC_FALLBACK_NAME
)
for user_profile_id in users:
for client in get_client_descriptors_for_user(user_profile_id):
if not client.accepts_event(event):
continue
if client.empty_topic_name:
client.add_event(event)
else:
client.add_event(empty_topic_name_fallback_event)
def process_notification(notice: Mapping[str, Any]) -> None:
event: Mapping[str, Any] = notice["event"]
users: list[int] | list[Mapping[str, Any]] = notice["users"]
@@ -1695,6 +1714,12 @@ def process_notification(notice: Mapping[str, Any]) -> None:
process_user_topic_event(event, cast(list[int], users))
elif event["type"] == "typing" and event["message_type"] == "stream":
process_stream_typing_notification_event(event, cast(list[int], users))
elif (
event["type"] == "update_message_flags"
and event["op"] == "remove"
and event["flag"] == "read"
):
process_mark_message_unread_event(event, cast(list[int], users))
elif event["type"] == "cleanup_queue":
# cleanup_event_queue may generate this event to forward cleanup
# requests to the right shard.