From ec96fc965985d11347bc09ee260c5f03e10d70a2 Mon Sep 17 00:00:00 2001 From: Sahil Batra Date: Tue, 13 May 2025 16:38:26 +0530 Subject: [PATCH] streams: Allow adding newly created channels to folders. Fixes part of #31972. --- api_docs/unmerged.d/ZF-919531.md | 2 ++ zerver/lib/streams.py | 5 ++++ zerver/openapi/zulip.yaml | 13 ++++++++ zerver/tests/test_subs.py | 51 ++++++++++++++++++++++++++++++++ zerver/views/streams.py | 9 +++++- 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 api_docs/unmerged.d/ZF-919531.md diff --git a/api_docs/unmerged.d/ZF-919531.md b/api_docs/unmerged.d/ZF-919531.md new file mode 100644 index 0000000000..17b7c4302a --- /dev/null +++ b/api_docs/unmerged.d/ZF-919531.md @@ -0,0 +1,2 @@ +* [`POST /users/me/subscriptions`](/api/subscribe): Added support to add + newly created channels to folder using `folder_id` parameter. diff --git a/zerver/lib/streams.py b/zerver/lib/streams.py index 725b224f59..60ab2304de 100644 --- a/zerver/lib/streams.py +++ b/zerver/lib/streams.py @@ -35,6 +35,7 @@ from zerver.lib.user_groups import ( user_has_permission_for_group_setting, ) from zerver.models import ( + ChannelFolder, DefaultStreamGroup, Message, NamedUserGroup, @@ -88,6 +89,7 @@ class StreamDict(TypedDict, total=False): can_send_message_group: UserGroup | None can_remove_subscribers_group: UserGroup | None can_subscribe_group: UserGroup | None + folder: ChannelFolder | None def get_stream_permission_policy_key( @@ -265,6 +267,7 @@ def create_stream_if_needed( can_send_message_group: UserGroup | None = None, can_remove_subscribers_group: UserGroup | None = None, can_subscribe_group: UserGroup | None = None, + folder: ChannelFolder | None = None, acting_user: UserProfile | None = None, anonymous_group_membership: dict[int, UserGroupMembersData] | None = None, ) -> tuple[Stream, bool]: @@ -304,6 +307,7 @@ def create_stream_if_needed( history_public_to_subscribers=history_public_to_subscribers, is_in_zephyr_realm=realm.is_zephyr_mirror_realm, message_retention_days=message_retention_days, + folder=folder, **group_setting_values, ), ) @@ -383,6 +387,7 @@ def create_streams_if_needed( can_send_message_group=stream_dict.get("can_send_message_group", None), can_remove_subscribers_group=stream_dict.get("can_remove_subscribers_group", None), can_subscribe_group=stream_dict.get("can_subscribe_group", None), + folder=stream_dict.get("folder", None), acting_user=acting_user, anonymous_group_membership=anonymous_group_membership, ) diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index a55d1ff960..52a7429262 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -11219,6 +11219,17 @@ paths: $ref: "#/components/schemas/CanSendMessageGroup" can_subscribe_group: $ref: "#/components/schemas/CanSubscribeGroup" + folder_id: + description: | + This parameter determines the folder to which the newly + created channel will be added. + + If the value is `None`, the channel will not be added to + any folder. + + **Changes**: New in Zulip 11.0 (feature level ZF-919531). + type: integer + example: 1 required: - subscriptions encoding: @@ -11248,6 +11259,8 @@ paths: contentType: application/json can_subscribe_group: contentType: application/json + folder_id: + contentType: application/json responses: "200": description: Success. diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index 624593b2fd..20c74c8f95 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -15,6 +15,7 @@ from django.utils.timezone import now as timezone_now from typing_extensions import override from zerver.actions.bots import do_change_bot_owner +from zerver.actions.channel_folders import check_add_channel_folder from zerver.actions.create_realm import do_create_realm from zerver.actions.default_streams import ( do_add_default_stream, @@ -952,6 +953,56 @@ class TestCreateStreams(ZulipTestCase): self.assert_length(channel_events_messages, 1) self.assertIn(policy_key_map[policy_key], channel_events_messages[0].content) + def test_adding_channels_to_folder_during_creation(self) -> None: + realm = get_realm("zulip") + iago = self.example_user("iago") + hamlet = self.example_user("hamlet") + channel_folder = check_add_channel_folder("Backend", "", acting_user=iago) + + subscriptions = [ + {"name": "new_stream", "description": "New stream"}, + {"name": "new_stream_2", "description": "New stream 2"}, + ] + extra_post_data = {} + + extra_post_data["folder_id"] = orjson.dumps(99).decode() + result = self.subscribe_via_post( + hamlet, + subscriptions, + extra_post_data, + allow_fail=True, + subdomain="zulip", + ) + self.assert_json_error(result, "Invalid channel folder ID") + + extra_post_data["folder_id"] = orjson.dumps(channel_folder.id).decode() + result = self.subscribe_via_post( + hamlet, + subscriptions, + extra_post_data, + subdomain="zulip", + ) + stream = get_stream("new_stream", realm) + self.assertEqual(stream.folder, channel_folder) + stream = get_stream("new_stream_2", realm) + self.assertEqual(stream.folder, channel_folder) + + subscriptions = [ + {"name": "new_stream_3", "description": "New stream 3"}, + {"name": "new_stream_4", "description": "New stream 4"}, + ] + extra_post_data = {} + result = self.subscribe_via_post( + hamlet, + subscriptions, + extra_post_data, + subdomain="zulip", + ) + stream = get_stream("new_stream_3", realm) + self.assertIsNone(stream.folder) + stream = get_stream("new_stream_4", realm) + self.assertIsNone(stream.folder) + class RecipientTest(ZulipTestCase): def test_recipient(self) -> None: diff --git a/zerver/views/streams.py b/zerver/views/streams.py index 867c7925ad..67cd9082ca 100644 --- a/zerver/views/streams.py +++ b/zerver/views/streams.py @@ -48,6 +48,7 @@ from zerver.decorator import ( require_non_guest_user, require_realm_admin, ) +from zerver.lib.channel_folders import get_channel_folder_by_id from zerver.lib.default_streams import get_default_stream_ids_for_realm from zerver.lib.email_mirror_helpers import encode_email_address, get_channel_email_token from zerver.lib.exceptions import ( @@ -103,7 +104,7 @@ from zerver.lib.user_groups import ( from zerver.lib.user_topics import get_users_with_user_topic_visibility_policy from zerver.lib.users import access_bot_by_id, bulk_access_users_by_email, bulk_access_users_by_id from zerver.lib.utils import assert_is_not_none -from zerver.models import Realm, Stream, UserMessage, UserProfile, UserTopic +from zerver.models import ChannelFolder, Realm, Stream, UserMessage, UserProfile, UserTopic from zerver.models.groups import SystemGroups from zerver.models.users import get_system_bot @@ -624,6 +625,7 @@ def add_subscriptions_backend( announce: Json[bool] = False, principals: Json[list[str] | list[int]] | None = None, authorization_errors_fatal: Json[bool] = True, + folder_id: Json[int] | None = None, ) -> HttpResponse: realm = user_profile.realm stream_dicts = [] @@ -668,6 +670,10 @@ def add_subscriptions_backend( UserGroupMembersData(direct_subgroups=[], direct_members=[user_profile.id]) ) + folder: ChannelFolder | None = None + if folder_id is not None: + folder = get_channel_folder_by_id(folder_id, realm) + for stream_obj in streams_raw: # 'color' field is optional # check for its presence in the streams_raw first @@ -698,6 +704,7 @@ def add_subscriptions_backend( "can_remove_subscribers_group" ] stream_dict_copy["can_subscribe_group"] = group_settings_map["can_subscribe_group"] + stream_dict_copy["folder"] = folder stream_dicts.append(stream_dict_copy)