Skip to content

Commit 55bbf2c

Browse files
committed
get_stream_topics: 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. Adds `allow_empty_topic_name` boolean parameter to `GET /users/me/{stream_id}/topics` endpoint to decide whether the topic names in the fetched `topics` array can be empty strings. If False, the topic names in the fetched response will have the value of `realm_empty_topic_display_name` field in `POST /register` response replacing "". Fixes part of zulip#23291.
1 parent 75b6b1d commit 55bbf2c

File tree

8 files changed

+71
-10
lines changed

8 files changed

+71
-10
lines changed

api_docs/changelog.md

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
2020

2121
## Changes in Zulip 10.0
2222

23+
**Feature level 341**
24+
25+
* [`GET /users/me/{stream_id}/topics`](/api/get-stream-topics):
26+
Added `allow_empty_topic_name` boolean parameter to decide whether the
27+
topic names in the fetched `topics` array can be empty strings.
28+
2329
**Feature level 340**
2430

2531
[`PATCH /user_groups/{user_group_id}`](/api/update-user-group): All

version.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
# new level means in api_docs/changelog.md, as well as "**Changes**"
3535
# entries in the endpoint's documentation in `zulip.yaml`.
3636

37-
API_FEATURE_LEVEL = 340 # Last bumped for updating settings for deactivated groups.
37+
API_FEATURE_LEVEL = (
38+
341 # Last bumped for adding `allow_empty_topic_name` to get-stream-topics endpoint.
39+
)
3840

3941
# Bump the minor PROVISION_VERSION to indicate that folks should provision
4042
# only when going from an old version of the code to a newer version. Bump

web/src/stream_topic_history_util.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function get_server_history(stream_id: number, on_success: () => void): v
2727

2828
void channel.get({
2929
url,
30-
data: {},
30+
data: {allow_empty_topic_name: true},
3131
success(raw_data) {
3232
const data = stream_topic_history_response_schema.parse(raw_data);
3333
const server_history = data.topics;

web/tests/stream_topic_history.test.cjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ test("server_history_end_to_end", () => {
331331

332332
channel.get = (opts) => {
333333
assert.equal(opts.url, "/json/users/me/99/topics");
334-
assert.deepEqual(opts.data, {});
334+
assert.deepEqual(opts.data, {allow_empty_topic_name: true});
335335
assert.ok(stream_topic_history.is_request_pending_for(stream_id));
336336
get_success_callback = opts.success;
337337
get_error_callback = opts.error;

zerver/lib/topic.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ def propagate() -> QuerySet[Message]:
201201
return messages, propagate
202202

203203

204-
def generate_topic_history_from_db_rows(rows: list[tuple[str, int]]) -> list[dict[str, Any]]:
204+
def generate_topic_history_from_db_rows(
205+
rows: list[tuple[str, int]], allow_empty_topic_name: bool
206+
) -> list[dict[str, Any]]:
205207
canonical_topic_names: dict[str, tuple[int, str]] = {}
206208

207209
# Sort rows by max_message_id so that if a topic
@@ -215,13 +217,17 @@ def generate_topic_history_from_db_rows(rows: list[tuple[str, int]]) -> list[dic
215217

216218
history = []
217219
for max_message_id, topic_name in canonical_topic_names.values():
220+
if topic_name == "" and not allow_empty_topic_name:
221+
topic_name = Message.EMPTY_TOPIC_FALLBACK_NAME
218222
history.append(
219223
dict(name=topic_name, max_id=max_message_id),
220224
)
221225
return sorted(history, key=lambda x: -x["max_id"])
222226

223227

224-
def get_topic_history_for_public_stream(realm_id: int, recipient_id: int) -> list[dict[str, Any]]:
228+
def get_topic_history_for_public_stream(
229+
realm_id: int, recipient_id: int, allow_empty_topic_name: bool
230+
) -> list[dict[str, Any]]:
225231
cursor = connection.cursor()
226232
# Uses index: zerver_message_realm_recipient_subject
227233
# Note that this is *case-sensitive*, so that we can display the
@@ -244,14 +250,16 @@ def get_topic_history_for_public_stream(realm_id: int, recipient_id: int) -> lis
244250
rows = cursor.fetchall()
245251
cursor.close()
246252

247-
return generate_topic_history_from_db_rows(rows)
253+
return generate_topic_history_from_db_rows(rows, allow_empty_topic_name)
248254

249255

250256
def get_topic_history_for_stream(
251-
user_profile: UserProfile, recipient_id: int, public_history: bool
257+
user_profile: UserProfile, recipient_id: int, public_history: bool, allow_empty_topic_name: bool
252258
) -> list[dict[str, Any]]:
253259
if public_history:
254-
return get_topic_history_for_public_stream(user_profile.realm_id, recipient_id)
260+
return get_topic_history_for_public_stream(
261+
user_profile.realm_id, recipient_id, allow_empty_topic_name
262+
)
255263

256264
cursor = connection.cursor()
257265
# Uses index: zerver_message_realm_recipient_subject
@@ -279,7 +287,7 @@ def get_topic_history_for_stream(
279287
rows = cursor.fetchall()
280288
cursor.close()
281289

282-
return generate_topic_history_from_db_rows(rows)
290+
return generate_topic_history_from_db_rows(rows, allow_empty_topic_name)
283291

284292

285293
def get_topic_resolution_and_bare_name(stored_name: str) -> tuple[bool, str]:

zerver/openapi/zulip.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -10212,6 +10212,22 @@ paths:
1021210212
user subscribed.
1021310213
parameters:
1021410214
- $ref: "#/components/parameters/ChannelIdInPath"
10215+
- name: allow_empty_topic_name
10216+
in: query
10217+
description: |
10218+
Whether the client supports processing the empty string as
10219+
a topic name in the returned data.
10220+
10221+
If `false`, the value of `realm_empty_topic_display_name`
10222+
found in the [`POST /register`](/api/register-queue) response is
10223+
returned replacing the empty string as the topic name.
10224+
10225+
**Changes**: New in Zulip 10.0 (feature level 341). Previously,
10226+
the empty string was not a valid topic.
10227+
schema:
10228+
type: boolean
10229+
default: false
10230+
example: true
1021510231
responses:
1021610232
"200":
1021710233
description: Success.

zerver/tests/test_message_topics.py

+25
Original file line numberDiff line numberDiff line change
@@ -843,3 +843,28 @@ def test_initial_state_data(self) -> None:
843843
self.assertEqual(
844844
state_data["user_topics"][1]["topic_name"], Message.EMPTY_TOPIC_FALLBACK_NAME
845845
)
846+
847+
def test_get_channel_topics(self) -> None:
848+
hamlet = self.example_user("hamlet")
849+
self.login_user(hamlet)
850+
channel_one = self.make_stream("channel_one")
851+
channel_two = self.make_stream("channel_two")
852+
self.subscribe(hamlet, channel_one.name)
853+
self.subscribe(hamlet, channel_two.name)
854+
855+
self.send_stream_message(hamlet, channel_one.name, topic_name="")
856+
self.send_stream_message(
857+
hamlet, channel_two.name, topic_name=Message.EMPTY_TOPIC_FALLBACK_NAME
858+
)
859+
860+
params = {"allow_empty_topic_name": "false"}
861+
for channel_id in [channel_one.id, channel_two.id]:
862+
result = self.client_get(f"/json/users/me/{channel_id}/topics", params)
863+
data = self.assert_json_success(result)
864+
self.assertEqual(data["topics"][0]["name"], Message.EMPTY_TOPIC_FALLBACK_NAME)
865+
866+
params = {"allow_empty_topic_name": "true"}
867+
for channel_id in [channel_one.id, channel_two.id]:
868+
result = self.client_get(f"/json/users/me/{channel_id}/topics", params)
869+
data = self.assert_json_success(result)
870+
self.assertEqual(data["topics"][0]["name"], "")

zerver/views/streams.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,7 @@ def get_topics_backend(
924924
maybe_user_profile: UserProfile | AnonymousUser,
925925
*,
926926
stream_id: PathOnly[NonNegativeInt],
927+
allow_empty_topic_name: Json[bool] = False,
927928
) -> HttpResponse:
928929
if not maybe_user_profile.is_authenticated:
929930
is_web_public_query = True
@@ -938,7 +939,9 @@ def get_topics_backend(
938939
realm = get_valid_realm_from_request(request)
939940
stream = access_web_public_stream(stream_id, realm)
940941
result = get_topic_history_for_public_stream(
941-
realm_id=realm.id, recipient_id=assert_is_not_none(stream.recipient_id)
942+
realm_id=realm.id,
943+
recipient_id=assert_is_not_none(stream.recipient_id),
944+
allow_empty_topic_name=allow_empty_topic_name,
942945
)
943946

944947
else:
@@ -951,6 +954,7 @@ def get_topics_backend(
951954
user_profile=user_profile,
952955
recipient_id=stream.recipient_id,
953956
public_history=stream.is_history_public_to_subscribers(),
957+
allow_empty_topic_name=allow_empty_topic_name,
954958
)
955959

956960
return json_success(request, data=dict(topics=result))

0 commit comments

Comments
 (0)