user_topic: 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, toggling topic visibility policy operation supports
empty topic name.

Adds backward compatibility for:
- `topic_name` field in the `user_topic` event type
This commit is contained in:
Prakhar Pratyush
2024-11-14 18:45:31 +05:30
committed by Tim Abbott
parent 2dea392d9e
commit 27e95f7d33
5 changed files with 108 additions and 2 deletions

View File

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

View File

@@ -5,6 +5,7 @@ from django.db import transaction
from django.utils.timezone import now as timezone_now
from zerver.lib.timestamp import datetime_to_timestamp
from zerver.lib.topic import maybe_rename_general_chat_to_empty_topic
from zerver.lib.user_topics import (
bulk_set_user_topic_visibility_policy_in_database,
get_topic_mutes,
@@ -26,6 +27,8 @@ def bulk_do_set_user_topic_visibility_policy(
if last_updated is None:
last_updated = timezone_now()
topic_name = maybe_rename_general_chat_to_empty_topic(topic_name)
user_profiles_with_changed_user_topic_rows = bulk_set_user_topic_visibility_policy_in_database(
user_profiles,
stream.id,

View File

@@ -2207,6 +2207,17 @@ paths:
type: string
description: |
The name of the topic.
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
last_updated:
type: integer
description: |
@@ -10948,6 +10959,13 @@ paths:
Clients should use the `max_topic_length` returned by the
[`POST /register`](/api/register-queue) endpoint to determine
the maximum topic length.
Note: When the value of `realm_empty_topic_display_name` found in
the [POST /register](/api/register-queue) response is used for this
parameter, it is interpreted as an empty string.
**Changes**: Before Zulip 10.0 (feature level 334), empty string
was not a valid topic name for channel messages.
type: string
example: dinner
visibility_policy:

View File

@@ -5,6 +5,7 @@ import orjson
from django.utils.timezone import now as timezone_now
from zerver.actions.streams import do_change_stream_permission
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.user_topics import set_topic_visibility_policy, topic_has_visibility_policy
from zerver.models import Message, UserMessage, UserTopic
@@ -376,7 +377,7 @@ class EmptyTopicNameTest(ZulipTestCase):
apply_markdown=True,
client_type_name="website",
empty_topic_name=True,
event_types=["message", "update_message", "delete_message"],
event_types=["message", "update_message", "delete_message", "user_topic"],
last_connection_time=time.time(),
queue_timeout=600,
realm_id=hamlet.realm.id,
@@ -422,6 +423,37 @@ class EmptyTopicNameTest(ZulipTestCase):
self.assertEqual(events[4]["topic"], "")
self.assertEqual(events[5]["topic"], "")
# reset
self.send_stream_message(
iago, "Denmark", topic_name="", skip_capture_on_commit_callbacks=True
)
self.send_stream_message(
iago,
"Verona",
topic_name=Message.EMPTY_TOPIC_FALLBACK_NAME,
skip_capture_on_commit_callbacks=True,
)
self.login_user(hamlet)
denmark = get_stream("Denmark", hamlet.realm)
verona = get_stream("Verona", hamlet.realm)
with self.captureOnCommitCallbacks(execute=True):
do_set_user_topic_visibility_policy(
hamlet,
denmark,
"",
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
)
do_set_user_topic_visibility_policy(
hamlet,
verona,
Message.EMPTY_TOPIC_FALLBACK_NAME,
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
)
events = client.event_queue.contents()
self.assertEqual(events[6]["topic_name"], "")
self.assertEqual(events[7]["topic_name"], "")
def test_client_not_supports_empty_topic_name(self) -> None:
iago = self.example_user("iago")
hamlet = self.example_user("hamlet")
@@ -430,7 +462,7 @@ class EmptyTopicNameTest(ZulipTestCase):
apply_markdown=True,
client_type_name="zulip-mobile",
empty_topic_name=False,
event_types=["message", "update_message", "delete_message"],
event_types=["message", "update_message", "delete_message", "user_topic"],
last_connection_time=time.time(),
queue_timeout=600,
realm_id=hamlet.realm.id,
@@ -476,6 +508,37 @@ class EmptyTopicNameTest(ZulipTestCase):
self.assertEqual(events[4]["topic"], Message.EMPTY_TOPIC_FALLBACK_NAME)
self.assertEqual(events[5]["topic"], Message.EMPTY_TOPIC_FALLBACK_NAME)
# reset
self.send_stream_message(
iago, "Denmark", topic_name="", skip_capture_on_commit_callbacks=True
)
self.send_stream_message(
iago,
"Verona",
topic_name=Message.EMPTY_TOPIC_FALLBACK_NAME,
skip_capture_on_commit_callbacks=True,
)
self.login_user(hamlet)
denmark = get_stream("Denmark", hamlet.realm)
verona = get_stream("Verona", hamlet.realm)
with self.captureOnCommitCallbacks(execute=True):
do_set_user_topic_visibility_policy(
hamlet,
denmark,
"",
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
)
do_set_user_topic_visibility_policy(
hamlet,
verona,
Message.EMPTY_TOPIC_FALLBACK_NAME,
visibility_policy=UserTopic.VisibilityPolicy.FOLLOWED,
)
events = client.event_queue.contents()
self.assertEqual(events[6]["topic_name"], Message.EMPTY_TOPIC_FALLBACK_NAME)
self.assertEqual(events[7]["topic_name"], Message.EMPTY_TOPIC_FALLBACK_NAME)
def test_fetch_messages(self) -> None:
hamlet = self.example_user("hamlet")
self.login_user(hamlet)

View File

@@ -1623,6 +1623,25 @@ def process_user_group_name_update_event(event: Mapping[str, Any], users: Iterab
client.add_event(user_group_event)
def process_user_topic_event(event: Mapping[str, Any], users: Iterable[int]) -> None:
empty_topic_name_fallback_event: Mapping[str, Any] | dict[str, Any]
if event.get("topic_name") == "":
empty_topic_name_fallback_event = dict(event)
empty_topic_name_fallback_event["topic_name"] = Message.EMPTY_TOPIC_FALLBACK_NAME
else:
empty_topic_name_fallback_event = event
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"]
@@ -1651,6 +1670,8 @@ def process_notification(notice: Mapping[str, Any]) -> None:
# event sent for updating name separately for clients with different
# capabilities.
process_user_group_name_update_event(event, cast(list[int], users))
elif event["type"] == "user_topic":
process_user_topic_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.