channel_folders: Add API endpoint to get all channel folders.

Fixes part of #31972.
This commit is contained in:
Sahil Batra
2025-05-07 12:14:13 +05:30
committed by Tim Abbott
parent 332abd9e91
commit e93667cc06
8 changed files with 189 additions and 5 deletions

View File

@@ -64,6 +64,7 @@
* [Add a default channel](/api/add-default-stream)
* [Remove a default channel](/api/remove-default-stream)
* [Create a channel folder](/api/create-channel-folder)
* [Get channel folders](/api/get-channel-folders)
#### Users

View File

@@ -0,0 +1,2 @@
* [`GET /channel_folders`](/api/get-channel-folders): Added a new endpoint
to get all channel folders in the realm.

View File

@@ -54,7 +54,12 @@ def get_channel_folder_dict(channel_folder: ChannelFolder) -> ChannelFolderDict:
)
def get_channel_folders_in_realm(realm: Realm) -> list[ChannelFolderDict]:
def get_channel_folders_in_realm(
realm: Realm, include_archived: bool = False
) -> list[ChannelFolderDict]:
folders = ChannelFolder.objects.filter(realm=realm)
if not include_archived:
folders = folders.exclude(is_archived=True)
channel_folders = [get_channel_folder_dict(channel_folder) for channel_folder in folders]
return sorted(channel_folders, key=lambda folder: folder["id"])

View File

@@ -768,7 +768,7 @@ def fetch_initial_state_data(
if want("channel_folders") and user_profile is not None:
# TODO: Spectators should get the channel folders that
# contain atleast one web-public channel.
state["channel_folders"] = get_channel_folders_in_realm(user_profile.realm)
state["channel_folders"] = get_channel_folders_in_realm(user_profile.realm, True)
if want("update_message_flags") and want("message"):
# Keeping unread_msgs updated requires both message flag updates and

View File

@@ -22936,6 +22936,76 @@ paths:
description: |
Error when the user does not have permission
to create a channel folder:
/channel_folders:
get:
operationId: get-channel-folders
summary: Get channel folders
tags: ["channels"]
description: |
Fetches all of the channel folders in the organization.
**Changes**: New in Zulip 11.0 (feature level ZF-2a4adb).
requestBody:
required: false
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
include_archived:
description: |
Whether to include archived channel folders in the response.
type: boolean
example: true
default: false
encoding:
include_archived:
contentType: application/json
responses:
"200":
description: Success.
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/JsonSuccessBase"
- additionalProperties: false
properties:
result: {}
msg: {}
ignored_parameters_unsupported: {}
channel_folders:
type: array
description: |
A list of channel folder objects.
items:
$ref: "#/components/schemas/ChannelFolder"
example:
{
"msg": "",
"result": "success",
"channel_folders":
[
{
"description": "Channels for frontend discussions",
"rendered_description": "<p>Channels for frontend discussions</p>",
"id": 1,
"creator_id": 1,
"date_created": 1691057093,
"name": "Frontend",
"is_archived": false,
},
{
"description": "Channels for **backend** discussions",
"rendered_description": "<p>Channels for <strong>backend</strong> discussions</p>",
"id": 2,
"creator_id": 1,
"date_created": 1791057093,
"name": "Backend",
"is_archived": false,
},
],
}
/real-time:
# This entry is a hack; it exists to give us a place to put the text
# documenting the parameters for call_on_each_event and friends.

View File

@@ -1,5 +1,8 @@
from typing import Any
import orjson
from zerver.actions.channel_folders import check_add_channel_folder
from zerver.lib.test_classes import ZulipTestCase
from zerver.models import ChannelFolder
from zerver.models.realms import get_realm
@@ -70,3 +73,94 @@ class ChannelFolderCreationTest(ZulipTestCase):
params = {"name": invalid_name, "description": "Channels for frontend discussions"}
result = self.client_post("/json/channel_folders/create", params)
self.assert_json_error(result, "Invalid character in channel folder name, at position 4.")
class GetChannelFoldersTest(ZulipTestCase):
def test_get_channel_folders(self) -> None:
iago = self.example_user("iago")
desdemona = self.example_user("desdemona")
frontend_folder = check_add_channel_folder(
"Frontend", "Channels for frontend discussions", acting_user=iago
)
backend_folder = check_add_channel_folder(
"Backend", "Channels for **backend** discussions", acting_user=iago
)
marketing_folder = check_add_channel_folder(
"Marketing", "Channels for marketing discussions", acting_user=desdemona
)
lear_user = self.lear_user("cordelia")
check_add_channel_folder("Devops", "Channels for devops discussions", acting_user=lear_user)
def check_channel_folders_in_zulip_realm(
channel_folders: list[dict[str, Any]], marketing_folder_included: bool = True
) -> None:
if marketing_folder_included:
self.assert_length(channel_folders, 3)
else:
self.assert_length(channel_folders, 2)
self.assertEqual(channel_folders[0]["id"], frontend_folder.id)
self.assertEqual(channel_folders[0]["name"], "Frontend")
self.assertEqual(channel_folders[0]["description"], "Channels for frontend discussions")
self.assertEqual(
channel_folders[0]["rendered_description"],
"<p>Channels for frontend discussions</p>",
)
self.assertEqual(channel_folders[0]["is_archived"], frontend_folder.is_archived)
self.assertEqual(channel_folders[0]["creator_id"], iago.id)
self.assertEqual(channel_folders[1]["id"], backend_folder.id)
self.assertEqual(channel_folders[1]["name"], "Backend")
self.assertEqual(
channel_folders[1]["description"], "Channels for **backend** discussions"
)
self.assertEqual(
channel_folders[1]["rendered_description"],
"<p>Channels for <strong>backend</strong> discussions</p>",
)
self.assertEqual(channel_folders[1]["is_archived"], backend_folder.is_archived)
self.assertEqual(channel_folders[1]["creator_id"], iago.id)
if marketing_folder_included:
self.assertEqual(channel_folders[2]["id"], marketing_folder.id)
self.assertEqual(channel_folders[2]["name"], "Marketing")
self.assertEqual(
channel_folders[2]["description"], "Channels for marketing discussions"
)
self.assertEqual(
channel_folders[2]["rendered_description"],
"<p>Channels for marketing discussions</p>",
)
self.assertEqual(channel_folders[2]["is_archived"], marketing_folder.is_archived)
self.assertEqual(channel_folders[2]["creator_id"], desdemona.id)
self.login("iago")
result = self.client_get("/json/channel_folders")
channel_folders_data = orjson.loads(result.content)["channel_folders"]
check_channel_folders_in_zulip_realm(channel_folders_data)
# Check member user can also see all channel folders.
self.login("hamlet")
result = self.client_get("/json/channel_folders")
channel_folders_data = orjson.loads(result.content)["channel_folders"]
check_channel_folders_in_zulip_realm(channel_folders_data)
# Check guest can also see all channel folders.
self.login("polonius")
result = self.client_get("/json/channel_folders")
channel_folders_data = orjson.loads(result.content)["channel_folders"]
check_channel_folders_in_zulip_realm(channel_folders_data)
marketing_folder.is_archived = True
marketing_folder.save()
result = self.client_get("/json/channel_folders")
channel_folders_data = orjson.loads(result.content)["channel_folders"]
check_channel_folders_in_zulip_realm(channel_folders_data, False)
result = self.client_get(
"/json/channel_folders", {"include_archived": orjson.dumps(True).decode()}
)
channel_folders_data = orjson.loads(result.content)["channel_folders"]
check_channel_folders_in_zulip_realm(channel_folders_data)

View File

@@ -1,11 +1,11 @@
from typing import Annotated
from django.http import HttpRequest, HttpResponse
from pydantic import StringConstraints
from pydantic import Json, StringConstraints
from zerver.actions.channel_folders import check_add_channel_folder
from zerver.decorator import require_realm_admin
from zerver.lib.channel_folders import check_channel_folder_name
from zerver.lib.channel_folders import check_channel_folder_name, get_channel_folders_in_realm
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import typed_endpoint
from zerver.models.channel_folders import ChannelFolder
@@ -25,3 +25,14 @@ def create_channel_folder(
channel_folder = check_add_channel_folder(name, description, acting_user=user_profile)
return json_success(request, data={"channel_folder_id": channel_folder.id})
@typed_endpoint
def get_channel_folders(
request: HttpRequest,
user_profile: UserProfile,
*,
include_archived: Json[bool] = False,
) -> HttpResponse:
channel_folders = get_channel_folders_in_realm(user_profile.realm, include_archived)
return json_success(request, data={"channel_folders": channel_folders})

View File

@@ -45,7 +45,7 @@ from zerver.views.auth import (
start_social_login,
start_social_signup,
)
from zerver.views.channel_folders import create_channel_folder
from zerver.views.channel_folders import create_channel_folder, get_channel_folders
from zerver.views.compatibility import check_global_compatibility
from zerver.views.custom_profile_fields import (
create_realm_custom_profile_field,
@@ -532,6 +532,7 @@ v1_api_and_json_patterns = [
DELETE=remove_subscriptions_backend,
),
rest_path("channel_folders/create", POST=create_channel_folder),
rest_path("channel_folders", GET=get_channel_folders),
# topic-muting -> zerver.views.user_topics
# (deprecated and will be removed once clients are migrated to use '/user_topics')
rest_path("users/me/subscriptions/muted_topics", PATCH=update_muted_topic),