mirror of https://github.com/zulip/zulip.git
user_groups: Include settings and supergroups in error response.
The error response when a user group cannot be deactivated due to it being used as a subgroup or for a setting includes details about the supergroups, streams, user groups as well the settings for which it is used.
This commit is contained in:
parent
19ba94b946
commit
b8a039ee99
|
@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 10.0
|
||||
|
||||
**Feature level 298**
|
||||
|
||||
* [`POST /user_groups/{user_group_id}/deactivate`](/api/deactivate-user-group):
|
||||
Server now returns a specific error response (`"code": CANNOT_DEACTIVATE_GROUP_IN_USE`)
|
||||
when a user group cannot be deactivated because it is in use. The
|
||||
error response contains details about where the user group is being used.
|
||||
|
||||
**Feature level 297**
|
||||
|
||||
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue):
|
||||
|
|
|
@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
|||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||
|
||||
API_FEATURE_LEVEL = 297 # Last bumped for saved_snippets
|
||||
API_FEATURE_LEVEL = 298 # Last bumped for CANNOT_DEACTIVATE_GROUP_IN_USE error.
|
||||
|
||||
# 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
|
||||
|
|
|
@ -54,6 +54,7 @@ class ErrorCode(Enum):
|
|||
PUSH_NOTIFICATIONS_DISALLOWED = auto()
|
||||
EXPECTATION_MISMATCH = auto()
|
||||
SYSTEM_GROUP_REQUIRED = auto()
|
||||
CANNOT_DEACTIVATE_GROUP_IN_USE = auto()
|
||||
|
||||
|
||||
class JsonableError(Exception):
|
||||
|
@ -715,3 +716,19 @@ class IncompatibleParameterValuesError(JsonableError):
|
|||
@override
|
||||
def msg_format() -> str:
|
||||
return _("Incompatible values for '{first_parameter}' and '{second_parameter}'.")
|
||||
|
||||
|
||||
class CannotDeactivateGroupInUseError(JsonableError):
|
||||
code = ErrorCode.CANNOT_DEACTIVATE_GROUP_IN_USE
|
||||
data_fields = ["objections"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
objections: list[dict[str, Any]],
|
||||
) -> None:
|
||||
self.objections = objections
|
||||
|
||||
@staticmethod
|
||||
@override
|
||||
def msg_format() -> str:
|
||||
return _("Cannot deactivate user group in use.")
|
||||
|
|
|
@ -2,7 +2,7 @@ from collections import defaultdict
|
|||
from collections.abc import Collection, Iterable, Iterator, Mapping
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import TypedDict
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import connection, transaction
|
||||
|
@ -13,6 +13,7 @@ from django_cte import With
|
|||
from psycopg2.sql import SQL, Literal
|
||||
|
||||
from zerver.lib.exceptions import (
|
||||
CannotDeactivateGroupInUseError,
|
||||
JsonableError,
|
||||
PreviousSettingValueMismatchedError,
|
||||
SystemGroupRequiredError,
|
||||
|
@ -198,14 +199,14 @@ def access_user_group_for_deactivation(
|
|||
user_group_id, user_profile, permission_setting="can_manage_group"
|
||||
)
|
||||
|
||||
if (
|
||||
objections: list[dict[str, Any]] = []
|
||||
supergroup_ids = (
|
||||
user_group.direct_supergroups.exclude(named_user_group=None)
|
||||
.filter(named_user_group__deactivated=False)
|
||||
.exists()
|
||||
):
|
||||
raise JsonableError(
|
||||
_("You cannot deactivate a user group that is subgroup of any user group.")
|
||||
.values_list("id", flat=True)
|
||||
)
|
||||
if supergroup_ids:
|
||||
objections.append(dict(type="subgroup", supergroup_ids=list(supergroup_ids)))
|
||||
|
||||
anonymous_supergroup_ids = user_group.direct_supergroups.filter(
|
||||
named_user_group=None
|
||||
|
@ -214,10 +215,10 @@ def access_user_group_for_deactivation(
|
|||
# We check both the cases - whether the group is being directly used
|
||||
# as the value of a setting or as a subgroup of an anonymous group
|
||||
# used for a setting.
|
||||
setting_group_ids_using_deactivating_user_group = [
|
||||
*list(anonymous_supergroup_ids),
|
||||
setting_group_ids_using_deactivating_user_group = {
|
||||
*set(anonymous_supergroup_ids),
|
||||
user_group.id,
|
||||
]
|
||||
}
|
||||
|
||||
stream_setting_query = Q()
|
||||
for setting_name in Stream.stream_permission_group_settings:
|
||||
|
@ -225,12 +226,19 @@ def access_user_group_for_deactivation(
|
|||
**{f"{setting_name}__in": setting_group_ids_using_deactivating_user_group}
|
||||
)
|
||||
|
||||
if (
|
||||
Stream.objects.filter(realm_id=user_group.realm_id, deactivated=False)
|
||||
.filter(stream_setting_query)
|
||||
.exists()
|
||||
for stream in Stream.objects.filter(realm_id=user_group.realm_id, deactivated=False).filter(
|
||||
stream_setting_query
|
||||
):
|
||||
raise JsonableError(_("You cannot deactivate a user group which is used for setting."))
|
||||
objection_settings = [
|
||||
setting_name
|
||||
for setting_name in Stream.stream_permission_group_settings
|
||||
if getattr(stream, setting_name + "_id")
|
||||
in setting_group_ids_using_deactivating_user_group
|
||||
]
|
||||
if len(objection_settings) > 0:
|
||||
objections.append(
|
||||
dict(type="channel", channel_id=stream.id, settings=objection_settings)
|
||||
)
|
||||
|
||||
group_setting_query = Q()
|
||||
for setting_name in NamedUserGroup.GROUP_PERMISSION_SETTINGS:
|
||||
|
@ -238,22 +246,33 @@ def access_user_group_for_deactivation(
|
|||
**{f"{setting_name}__in": setting_group_ids_using_deactivating_user_group}
|
||||
)
|
||||
|
||||
for group in NamedUserGroup.objects.filter(
|
||||
realm_id=user_group.realm_id, deactivated=False
|
||||
).filter(group_setting_query):
|
||||
objection_settings = []
|
||||
for setting_name in NamedUserGroup.GROUP_PERMISSION_SETTINGS:
|
||||
if (
|
||||
NamedUserGroup.objects.filter(realm_id=user_group.realm_id, deactivated=False)
|
||||
.filter(group_setting_query)
|
||||
.exists()
|
||||
getattr(group, setting_name + "_id")
|
||||
in setting_group_ids_using_deactivating_user_group
|
||||
):
|
||||
raise JsonableError(_("You cannot deactivate a user group which is used for setting."))
|
||||
objection_settings.append(setting_name)
|
||||
|
||||
realm_setting_query = Q()
|
||||
for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||
realm_setting_query |= Q(
|
||||
**{f"{setting_name}__in": setting_group_ids_using_deactivating_user_group}
|
||||
if len(objection_settings) > 0:
|
||||
objections.append(
|
||||
dict(type="user_group", group_id=group.id, settings=objection_settings)
|
||||
)
|
||||
|
||||
if Realm.objects.filter(id=user_group.realm_id).filter(realm_setting_query).exists():
|
||||
raise JsonableError(_("You cannot deactivate a user group which is used for setting."))
|
||||
objection_settings = []
|
||||
realm = user_group.realm
|
||||
for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||
if getattr(realm, setting_name + "_id") in setting_group_ids_using_deactivating_user_group:
|
||||
objection_settings.append(setting_name)
|
||||
|
||||
if objection_settings:
|
||||
objections.append(dict(type="realm", settings=objection_settings))
|
||||
|
||||
if len(objections) > 0:
|
||||
raise CannotDeactivateGroupInUseError(objections)
|
||||
return user_group
|
||||
|
||||
|
||||
|
|
|
@ -20651,6 +20651,44 @@ paths:
|
|||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/SimpleSuccess"
|
||||
"400":
|
||||
description: Bad request.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- allOf:
|
||||
- $ref: "#/components/schemas/CodedError"
|
||||
- example:
|
||||
{
|
||||
"code": "BAD_REQUEST",
|
||||
"msg": "Invalid user group",
|
||||
"result": "error",
|
||||
}
|
||||
description: |
|
||||
An example JSON response when the user group ID is invalid.
|
||||
- allOf:
|
||||
- $ref: "#/components/schemas/CodedError"
|
||||
- example:
|
||||
{
|
||||
"code": "CANNOT_DEACTIVATE_GROUP_IN_USE",
|
||||
"msg": "Cannot deactivate user group in use.",
|
||||
"objections":
|
||||
[
|
||||
{
|
||||
"type": "realm",
|
||||
"settings":
|
||||
["can_create_public_channel_group"],
|
||||
},
|
||||
],
|
||||
"result": "error",
|
||||
}
|
||||
description: |
|
||||
An example JSON response when the user group being deactivated
|
||||
is used for a setting or as a subgroup.
|
||||
|
||||
**Changes**: New in Zulip 10.0 (feature level 298). Previously,
|
||||
this error returned the `"BAD_REQUEST"` code.
|
||||
/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.
|
||||
|
|
|
@ -1355,8 +1355,10 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
|||
)
|
||||
# Check that group that is subgroup of another group cannot be deactivated.
|
||||
result = self.client_post(f"/json/user_groups/{leadership_group.id}/deactivate")
|
||||
self.assert_json_error(
|
||||
result, "You cannot deactivate a user group that is subgroup of any user group."
|
||||
self.assert_json_error(result, "Cannot deactivate user group in use.")
|
||||
data = orjson.loads(result.content)
|
||||
self.assertEqual(
|
||||
data["objections"], [{"type": "subgroup", "supergroup_ids": [support_group.id]}]
|
||||
)
|
||||
|
||||
# If the supergroup is itself deactivated, then subgroup can be deactivated.
|
||||
|
@ -1393,18 +1395,18 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
|||
)
|
||||
|
||||
result = self.client_post(f"/json/user_groups/{support_group.id}/deactivate")
|
||||
self.assert_json_error(
|
||||
result, "You cannot deactivate a user group which is used for setting."
|
||||
)
|
||||
self.assert_json_error(result, "Cannot deactivate user group in use.")
|
||||
data = orjson.loads(result.content)
|
||||
self.assertEqual(data["objections"], [{"type": "realm", "settings": [setting_name]}])
|
||||
|
||||
do_change_realm_permission_group_setting(
|
||||
realm, setting_name, support_group, acting_user=None
|
||||
)
|
||||
|
||||
result = self.client_post(f"/json/user_groups/{support_group.id}/deactivate")
|
||||
self.assert_json_error(
|
||||
result, "You cannot deactivate a user group which is used for setting."
|
||||
)
|
||||
self.assert_json_error(result, "Cannot deactivate user group in use.")
|
||||
data = orjson.loads(result.content)
|
||||
self.assertEqual(data["objections"], [{"type": "realm", "settings": [setting_name]}])
|
||||
|
||||
# Reset the realm setting to one of the system group so this setting
|
||||
# does not interfere when testing for another setting.
|
||||
|
@ -1419,8 +1421,11 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
|||
)
|
||||
|
||||
result = self.client_post(f"/json/user_groups/{support_group.id}/deactivate")
|
||||
self.assert_json_error(
|
||||
result, "You cannot deactivate a user group which is used for setting."
|
||||
self.assert_json_error(result, "Cannot deactivate user group in use.")
|
||||
data = orjson.loads(result.content)
|
||||
self.assertEqual(
|
||||
data["objections"],
|
||||
[{"type": "channel", "channel_id": stream.id, "settings": [setting_name]}],
|
||||
)
|
||||
|
||||
# Test the group can be deactivated, if the stream which uses
|
||||
|
@ -1444,8 +1449,11 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
|||
)
|
||||
|
||||
result = self.client_post(f"/json/user_groups/{support_group.id}/deactivate")
|
||||
self.assert_json_error(
|
||||
result, "You cannot deactivate a user group which is used for setting."
|
||||
self.assert_json_error(result, "Cannot deactivate user group in use.")
|
||||
data = orjson.loads(result.content)
|
||||
self.assertEqual(
|
||||
data["objections"],
|
||||
[{"type": "channel", "channel_id": stream.id, "settings": [setting_name]}],
|
||||
)
|
||||
|
||||
# Test the group can be deactivated, if the stream which uses
|
||||
|
@ -1473,8 +1481,17 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
|||
)
|
||||
|
||||
result = self.client_post(f"/json/user_groups/{support_group.id}/deactivate")
|
||||
self.assert_json_error(
|
||||
result, "You cannot deactivate a user group which is used for setting."
|
||||
self.assert_json_error(result, "Cannot deactivate user group in use.")
|
||||
data = orjson.loads(result.content)
|
||||
self.assertEqual(
|
||||
data["objections"],
|
||||
[
|
||||
{
|
||||
"type": "user_group",
|
||||
"group_id": leadership_group.id,
|
||||
"settings": [setting_name],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
# Test the group can be deactivated, if the user group which uses
|
||||
|
@ -1499,8 +1516,17 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
|||
)
|
||||
|
||||
result = self.client_post(f"/json/user_groups/{support_group.id}/deactivate")
|
||||
self.assert_json_error(
|
||||
result, "You cannot deactivate a user group which is used for setting."
|
||||
self.assert_json_error(result, "Cannot deactivate user group in use.")
|
||||
data = orjson.loads(result.content)
|
||||
self.assertEqual(
|
||||
data["objections"],
|
||||
[
|
||||
{
|
||||
"type": "user_group",
|
||||
"group_id": leadership_group.id,
|
||||
"settings": [setting_name],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
# Test the group can be deactivated, if the user group which uses
|
||||
|
|
Loading…
Reference in New Issue