groups: Allow excluding deactivated groups in 'GET /user_groups' response.

This commit is contained in:
Sahil Batra 2024-06-06 15:30:16 +05:30 committed by Tim Abbott
parent fef2925ff0
commit 688c5ad0af
7 changed files with 69 additions and 12 deletions

View File

@ -29,6 +29,8 @@ format used by the Zulip server that they are interacting with.
the user group objects to identify deactivated user groups. the user group objects to identify deactivated user groups.
* [`GET /events`](/api/get-events): When a user group is deactivated, * [`GET /events`](/api/get-events): When a user group is deactivated,
a `user_group` event with `op=update` is sent to clients. a `user_group` event with `op=update` is sent to clients.
* [`GET /user_groups`](/api/get-user-groups): Added support for
excluding deactivated user groups from the response.
**Feature level 289** **Feature level 289**

View File

@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# new level means in api_docs/changelog.md, as well as "**Changes**" # new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`. # entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 289 # return ID as key while subscribing to a stream. API_FEATURE_LEVEL = 290 # Last bumped for UserGroup.deactivated
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump # only when going from an old version of the code to a newer version. Bump

View File

@ -513,7 +513,7 @@ def fetch_initial_state_data(
state["realm_playgrounds"] = get_realm_playgrounds(realm) state["realm_playgrounds"] = get_realm_playgrounds(realm)
if want("realm_user_groups"): if want("realm_user_groups"):
state["realm_user_groups"] = user_groups_in_realm_serialized(realm) state["realm_user_groups"] = user_groups_in_realm_serialized(realm, allow_deactivated=True)
if user_profile is not None: if user_profile is not None:
settings_user = user_profile settings_user = user_profile

View File

@ -472,7 +472,9 @@ def get_setting_value_for_user_group_object(
) )
def user_groups_in_realm_serialized(realm: Realm) -> list[UserGroupDict]: def user_groups_in_realm_serialized(
realm: Realm, *, allow_deactivated: bool
) -> list[UserGroupDict]:
"""This function is used in do_events_register code path so this code """This function is used in do_events_register code path so this code
should be performant. We need to do 2 database queries because should be performant. We need to do 2 database queries because
Django's ORM doesn't properly support the left join between Django's ORM doesn't properly support the left join between
@ -485,6 +487,9 @@ def user_groups_in_realm_serialized(realm: Realm) -> list[UserGroupDict]:
"can_mention_group__named_user_group", "can_mention_group__named_user_group",
).filter(realm=realm) ).filter(realm=realm)
if not allow_deactivated:
realm_groups = realm_groups.filter(deactivated=False)
membership = UserGroupMembership.objects.filter(user_group__realm=realm).values_list( membership = UserGroupMembership.objects.filter(user_group__realm=realm).values_list(
"user_group_id", "user_profile_id" "user_group_id", "user_profile_id"
) )

View File

@ -19967,6 +19967,26 @@ paths:
**Note**: This endpoint is only available to [members and **Note**: This endpoint is only available to [members and
administrators](/help/roles-and-permissions); bots and guests administrators](/help/roles-and-permissions); bots and guests
cannot use this endpoint. cannot use this endpoint.
requestBody:
required: false
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
allow_deactivated:
description: |
Whether to include deactivated user groups in the response.
**Changes**: New in Zulip 10.0 (feature level 290). Previously,
deactivated user groups did not exist and thus would never be
included in the response.
type: boolean
example: true
default: false
encoding:
allow_deactivated:
contentType: application/json
responses: responses:
"200": "200":
description: Success. description: Success.

View File

@ -84,7 +84,7 @@ class UserGroupTestCase(ZulipTestCase):
assert user_group is not None assert user_group is not None
empty_user_group = check_add_user_group(realm, "newgroup", [], acting_user=None) empty_user_group = check_add_user_group(realm, "newgroup", [], acting_user=None)
user_groups = user_groups_in_realm_serialized(realm) user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=False)
self.assert_length(user_groups, 10) self.assert_length(user_groups, 10)
self.assertEqual(user_groups[0]["id"], user_group.id) self.assertEqual(user_groups[0]["id"], user_group.id)
self.assertEqual(user_groups[0]["name"], SystemGroups.NOBODY) self.assertEqual(user_groups[0]["name"], SystemGroups.NOBODY)
@ -141,7 +141,7 @@ class UserGroupTestCase(ZulipTestCase):
}, },
acting_user=None, acting_user=None,
) )
user_groups = user_groups_in_realm_serialized(realm) user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=False)
self.assertEqual(user_groups[10]["id"], new_user_group.id) self.assertEqual(user_groups[10]["id"], new_user_group.id)
self.assertEqual(user_groups[10]["name"], "newgroup2") self.assertEqual(user_groups[10]["name"], "newgroup2")
self.assertEqual(user_groups[10]["description"], "") self.assertEqual(user_groups[10]["description"], "")
@ -162,10 +162,33 @@ class UserGroupTestCase(ZulipTestCase):
) )
self.assertFalse(user_groups[0]["deactivated"]) self.assertFalse(user_groups[0]["deactivated"])
do_deactivate_user_group(new_user_group, acting_user=None) another_new_group = check_add_user_group(
user_groups = user_groups_in_realm_serialized(realm) realm, "newgroup3", [self.example_user("hamlet")], acting_user=None
)
add_subgroups_to_user_group(
new_user_group, [another_new_group, owners_system_group], acting_user=None
)
do_deactivate_user_group(another_new_group, acting_user=None)
user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=True)
self.assert_length(user_groups, 12)
self.assertEqual(user_groups[10]["id"], new_user_group.id) self.assertEqual(user_groups[10]["id"], new_user_group.id)
self.assertTrue(user_groups[10]["deactivated"]) self.assertEqual(user_groups[10]["name"], "newgroup2")
self.assertFalse(user_groups[10]["deactivated"])
self.assertCountEqual(
user_groups[10]["direct_subgroup_ids"], [another_new_group.id, owners_system_group.id]
)
self.assertEqual(user_groups[11]["id"], another_new_group.id)
self.assertEqual(user_groups[11]["name"], "newgroup3")
self.assertTrue(user_groups[11]["deactivated"])
user_groups = user_groups_in_realm_serialized(realm, allow_deactivated=False)
self.assert_length(user_groups, 11)
self.assertEqual(user_groups[10]["id"], new_user_group.id)
self.assertEqual(user_groups[10]["name"], "newgroup2")
self.assertFalse(user_groups[10]["deactivated"])
self.assertCountEqual(
user_groups[10]["direct_subgroup_ids"], [another_new_group.id, owners_system_group.id]
)
def test_get_direct_user_groups(self) -> None: def test_get_direct_user_groups(self) -> None:
othello = self.example_user("othello") othello = self.example_user("othello")

View File

@ -22,7 +22,7 @@ from zerver.decorator import require_member_or_admin, require_user_group_create_
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError
from zerver.lib.mention import MentionBackend, silent_mention_syntax_for_user from zerver.lib.mention import MentionBackend, silent_mention_syntax_for_user
from zerver.lib.response import json_success from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint, typed_endpoint_without_parameters from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
from zerver.lib.user_groups import ( from zerver.lib.user_groups import (
AnonymousSettingGroupDict, AnonymousSettingGroupDict,
GroupSettingChangeRequest, GroupSettingChangeRequest,
@ -93,9 +93,16 @@ def add_user_group(
@require_member_or_admin @require_member_or_admin
@typed_endpoint_without_parameters @typed_endpoint
def get_user_group(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: def get_user_group(
user_groups = user_groups_in_realm_serialized(user_profile.realm) request: HttpRequest,
user_profile: UserProfile,
*,
allow_deactivated: Json[bool] = False,
) -> HttpResponse:
user_groups = user_groups_in_realm_serialized(
user_profile.realm, allow_deactivated=allow_deactivated
)
return json_success(request, data={"user_groups": user_groups}) return json_success(request, data={"user_groups": user_groups})