Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API support to set can_mention_group setting to anonymous user group. #29937

Merged
merged 4 commits into from
May 14, 2024

Conversation

sahil839
Copy link
Collaborator

@sahil839 sahil839 commented May 3, 2024

Fixes:

Screenshots and screen captures:

Self-review checklist
  • Self-reviewed the changes for clarity and maintainability
    (variable names, code reuse, readability, etc.).

Communicate decisions, questions, and potential concerns.

  • Explains differences from previous plans (e.g., issue description).
  • Highlights technical choices and bugs encountered.
  • Calls out remaining decisions and concerns.
  • Automated tests verify logic where appropriate.

Individual commits are ready for review (see commit discipline).

  • Each commit is a coherent idea.
  • Commit message(s) explain reasoning and motivation for changes.

Completed manual review and testing of the following:

  • Visual appearance of the changes.
  • Responsiveness and internationalization.
  • Strings and tooltips.
  • End-to-end functionality of buttons, interactions and flows.
  • Corner cases, error conditions, and easily imagined bugs.

direct_subgroups: List[int],
current_setting_value: Optional[UserGroup],
) -> UserGroup: # nocoverage
if current_setting_value is not None and not hasattr(current_setting_value, "named_user_group"):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably avoid one query here by updating the UserGroup query to have select_related(can_mention_group__named_user_group).

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be worth doing -- but seems reasonable to leave for a follow-up.

zerver/lib/user_groups.py Outdated Show resolved Hide resolved
zerver/lib/user_groups.py Outdated Show resolved Hide resolved
The list of user group IDs that are direct subgroups of the user group.
type: array
items:
type: integer
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be taking another pass at the API documentation.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think we want to define it as a type at the bottom of the file, so the markup here can just be an include, plus I guess the Changes entry.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I have kept description separate here for now since the description is not exactly same at all places like we use "permission to mention the new group" in a couple of places like the endpoint to create groups.

"can_mention_group", "can_mention_group__named_user_group"
)
.prefetch_related(
"can_mention_group__direct_members", "can_mention_group__direct_subgroups"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds two queries - one to fetch direct_members and one for direct_subgroups. And this count will keep increasing for each setting that we add.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

37d6b40#r1589802705 might help the prefetching work properly? Not sure.

A single bump of two queries is OK, but we'll need to figure out a way to make it just do two queries period -- one to fetch realm_groups and the second to fetch all of the groups they point to, and Python can glue it together.

It should be possible to make that happen, but it's likely very messy, so I think we should just leave a TODO comment here and fix it in a follow-up before adding 4 more group-valued settings here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can do that. Will do it as a follow-up.

parsed_current_setting_value = get_parsed_group_setting_value(current_value)

return not is_api_value_same_as_current_setting_value(
parsed_current_setting_value, new_setting_value
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure of the function and variable name here, just added them in a way so that we can avoid duplicate logic and also need not have queries like user_group.direct_members.all() twice. Any suggestions would be helpful here.

@staticmethod
@override
def msg_format() -> str:
return _("Someone else modified this setting at the same time.")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what should be the message and the class name here. Used one of the error code suggested in CZO for now, but we can change after more discussion.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah post in "api design" asking for suggests text, I think we can do better but seems not a blocker.

@@ -138,12 +138,20 @@ def is_api_value_same_as_current_setting_value(
) == set(current_value["direct_subgroups"])


def check_setting_value_changed(
def can_edit_group_setting(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here again, this can be named better.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe validate_group_setting_value_change

zerver/openapi/zulip.yaml Outdated Show resolved Hide resolved
current_value: Union[int, AnonymousSettingGroupDict],
api_value: Union[int, AnonymousSettingGroupDict],
) -> bool:
if isinstance(current_value, int):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not confident of this function here, since it seems somewhat hard to read. Would try to think of something so that we can get a better readable function but we also duplicate a lot of code and queries. Maybe we can add some comments for explaination.

Copy link
Sponsor Member

@timabbott timabbott May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think probably the right approach involves having a bit of code that rewrites the {direct_members: [], direct_subgroups: [group_id]} format into the integer format at the API boundary, before we try to look up any objects; possibly even in a typed_endpoint parsing function layer, or as basically the first thing we do in the views code -- and then we don't need to consider that type of possibility here, since the only way to have equality is if they're both a pure integer or identical dicts.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to do it in typed_endpoint but added the code to use a function to parse this in the views at top itself.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, seems fine to do that as a follow-up refactor later.

@sahil839 sahil839 marked this pull request as ready for review May 3, 2024 18:31
@sahil839 sahil839 changed the title [WIP]Add API support to set can_mention_group setting to anonymous user group. Add API support to set can_mention_group setting to anonymous user group. May 3, 2024
return named_user_group.usergroup_ptr

assert "direct_members" in setting_user_group
assert "direct_subgroups" in setting_user_group
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably be removed by making AnonymousSettingGroupDict better defined, but we might also want to change it to a dataclass anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had added these statements when I was not using TypedDict. Anyways changed it to a dataclass.

check_setting_configuration_for_system_groups(
named_user_group, setting_name, permission_configuration
)
return named_user_group.usergroup_ptr
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to deduplicate this with the block at the top of the file; I think it might feel easier to do that after resolving 7df7a3e#r1589801457 though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link would not point to any comment, but this was on the code which checked the case of len(direct_members) == 0 and len(direct_subgroups) == 1), which is now removed and we convert such cases to an integer in views code itself.

"direct_members": [member.id for member in setting_value_group.direct_members.all()],
"direct_subgroups": [
subgroup.id for subgroup in setting_value_group.direct_subgroups.all()
],
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These queries are inefficient, use .values_list(flat=True) to only ask the database for the list of IDs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use values_list(flat=True), then there would be no advantage of using prefetch_related and there would be a new query for each group, since prefetch_related caches the result for group.direct_members.all() call only.

Instead updated the prefetch_related call to set the queryset with .only("id").

@@ -175,7 +176,7 @@ def do_send_create_user_group_event(
id=user_group.id,
is_system_group=user_group.is_system_group,
direct_subgroup_ids=[direct_subgroup.id for direct_subgroup in direct_subgroups],
can_mention_group=user_group.can_mention_group_id,
can_mention_group=get_group_setting_value_for_api(user_group.can_mention_group),
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hunk might belong in the previous commit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this commit, one could only set the setting to named user groups while creating groups, so passing ID would have been fine then. But this commit adds support to set the setting to anonymous groups as well, and thus added this change here.

int,
DictType(
required_keys=[("direct_members", ListType(int)), ("direct_subgroups", ListType(int))]
),
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably line-wrap this so each field gets its own line; you can tell ruff-format that's what you want by adding trailing commas.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

zerver/lib/user_groups.py Outdated Show resolved Hide resolved
),
),
]
)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess next time you're looking at writing a validator function like this, it's probably worth just converting the file to the typed_endpoint / Pydantic system first, since it'll save a migration later.

anonymous user group.

The request will be rejected if this value does not match
the actual setting value.
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should explain the purpose of this parameter a bit more clearly -- the fact that it's optional, but also that the purpose is to prevent races by having the client send what value it expects the server to already have.

I guess that does present a question of whether we want to call this _expected_current instead of _previous; probably worth discussing that detail a bit.

):
# We do not allow changing the setting if the value sent in
# "_previous" field is not same as the current setting value.
raise PreviousSettingValueMismatchedError
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like with the API documentation, this is a good place for a comment, but the should hint concisely what's going on -- e.g., that this check is here to help prevent races, by refusing to change a setting where the client (and thus UI it was presenting to the user) showed a different existing state.

@timabbott
Copy link
Sponsor Member

Awesome, this is looking great! I think the slowest part of finishing it might be finalizing some of the naming questions in the "api design" stream; I gave this a full read and the above are my only implementation notes.

RealmAuditLog.OLD_VALUE: get_group_setting_value_for_audit_log_data(
old_setting_api_value
),
RealmAuditLog.NEW_VALUE: get_group_setting_value_for_audit_log_data(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because the extra_data field needs a JSON serializable object and thus we cannot set these to a dataclass. This worked previously because we used TypedDict.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! Probably worth having get_group_setting_value_for_audit_log_data include a comment explaining that detail.

@@ -121,6 +122,34 @@ def get_user_group(request: HttpRequest, user_profile: UserProfile) -> HttpRespo
return json_success(request, data={"user_groups": user_groups})


def are_both_setting_values_equal(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a better name for this function. We can also move this function to zerver.lib.user_groups probably or some file like zerver.lib.utils.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zerver.lib.user_groups seems probably a better home.

@sahil839
Copy link
Collaborator Author

sahil839 commented May 8, 2024

Added a commit to migrate the endpoints to use @typed_endpoint. Tried to migrate them before making the new changes but that resulted in some failure in tests due to using Optional[Json[int]] inside Annotated which would probably have needed some changes in our test code in test_openapi.

So, instead added the commit at the end when we no longer require Annotated as we do not need to set whence value to something different than the parameter name.

@sahil839
Copy link
Collaborator Author

sahil839 commented May 8, 2024

Have not made any changes to the commit for adding the _previous parameter except some documentation changes as dicussed on CZO, since the discussion is still not over.

to mention the group.
- $ref: "#/components/schemas/CanMentionGroup"
required:
- new
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how the allowed types are depicted currently. Think we would have to explain the types in description properly.
Screenshot from 2024-05-10 23-48-22

@sahil839
Copy link
Collaborator Author

Updated the PR -

  • To use the recently discussed approach for passing the previous setting value, i.e. allowing both {old: ..., new: ...} and the value directly.

Have not updated the error code and message yet when the previous value is not correct. Also, finding it hard to explain the allowed value in API docs, so would be good to get some help on that.

@@ -0,0 +1,32 @@
# Updating group settings

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add an introductory line here like "Client should pass a group setting update object", but am not sure how to phrase it.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'll do an introduction revision pass on this before merging, looks like a great start though.

Copy link
Sponsor Member

@timabbott timabbott May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up doing quite a bit of reworking to make this nice:

diff --git a/api_docs/changelog.md b/api_docs/changelog.md
index 6c7401c8b0..a54dd829cf 100644
--- a/api_docs/changelog.md
+++ b/api_docs/changelog.md
@@ -23,8 +23,8 @@ format used by the Zulip server that they are interacting with.
 **Feature level 260**:
 
 * [`PATCH /user_groups/{user_group_id}`](/api/update-user-group):
-  `can_mention_group` parameter now also accepts object value
-  containing `old` and `new` fields.
+  Updating `can_mention_group` now uses a race-resistant format where
+  the client sends the expected `old` value and desired `new` value.
 
 **Feature level 259**:
 
diff --git a/api_docs/group-setting-values.md b/api_docs/group-setting-values.md
new file mode 100644
index 0000000000..aceaf4f7c2
--- /dev/null
+++ b/api_docs/group-setting-values.md
@@ -0,0 +1,83 @@
+# Group-setting values
+
+Settings defining permissions in Zulip are increasingly represented
+using [user groups](/help/user-groups), which offer much more flexible
+configuration than the older [roles](/api/roles-and-permissions) system.
+
+In the API, these settings are represented using a **group-setting
+value**, which can take two forms:
+
+- An integer user group ID, which can be either a named user group
+  visible in the UI or a [role-based system group](#system-groups).
+- An object with fields `direct_member_ids` containing a list of
+  integer user IDs and `direct_subgroup_ids` containing a list of
+  integer group IDs. The setting's value is the union of the
+  identified collection of users and groups.
+
+Group-setting values in the object form function very much like a
+formal user group object, without requiring the naming and UI clutter
+overhead involved with creating a visible user group just to store the
+value of a single setting.
+
+The server will canonicalize an object with empty `direct_member_ids`
+and with `direct_subgroup_ids` containing just the given group ID to
+the integer format.
+
+## System groups
+
+The Zulip server maintains a collection of system groups that
+correspond to the users with a given role; this makes it convenient to
+store concepts like "all administrators" in a group-setting
+value. These use a special naming convention and can be recognized by
+the `is_system_group` property on their group object.
+
+The following system groups are maintained by the Zulip server:
+
+- `role:internet`: Everyone on the Internet has this permission; this
+  is used to configure the [public access
+  option](/help/public-access-option).
+- `role:everyone`: All users, including guests.
+- `role:members`: All users, excluding guests.
+- `role:fullmembers`: All [full
+  members](https://zulip.com/api/roles-and-permissions#determining-if-a-user-is-a-full-member)
+  of the organization.
+- `role:moderators`: All users with at least the moderator role.
+- `role:administrators`: All users with at least the administrator
+  role.
+- `role:owners`: All users with the owner role.
+- `role:nobody`: The formal empty group. Used in the API to represent
+  disabling a feature.
+
+Client UI for setting a permission is encouraged to display system
+groups using their description, rather than using their names, which
+are chosen to be unique and clear in the API.
+
+System groups should generally not be displayed in UI for
+administering an organization's user groups, since they are not
+directly mutable.
+
+## Updating group-setting values
+
+The Zulip API uses a special format for modifying an existing setting
+using a group-setting value.
+
+A **group-setting update** is an object with a `new` field and an
+optional `old` field, each containing a group-setting value. The
+setting's value will be set to the membership expressed by the `new`
+field.
+
+The `old` field expresses the client's understanding of the current
+value of the setting. If the `old` field is present and does not match
+the actual current value of the setting, then the request will fail
+with error code `EXPECTATION_MISMATCH` and no changes will be applied.
+
+When a user edits the setting in a UI, the resulting API request
+should generally always include the `old` field, giving the value
+the list had when the user started editing. This accurately expresses
+the user's intent, and if two users edit the same list around the
+same time, it prevents a situation where the second change
+accidentally reverts the first one without either user noticing.
+
+Omitting `old` is appropriate where the intent really is a new complete
+list rather than an edit, for example in an integration that syncs the
+list from an external source of truth.
diff --git a/api_docs/roles-and-permissions.md b/api_docs/roles-and-permissions.md
index 662ae6513d..db3c4acba9 100644
--- a/api_docs/roles-and-permissions.md
+++ b/api_docs/roles-and-permissions.md
@@ -102,6 +102,11 @@ and owners.
 Note that specific settings and policies in the Zulip API that use these
 permission levels will likely support a subset of those listed above.
 
+## Group-based permissions
+
+Some settings have been migrated to a more flexible system based on
+[user groups](/api/group-setting-values).
+
 ## Determining if a user is a full member
 
 When a Zulip organization has set up a [waiting period before new members
diff --git a/api_docs/sidebar_index.md b/api_docs/sidebar_index.md
index 099164b9c5..10540e7946 100644
--- a/api_docs/sidebar_index.md
+++ b/api_docs/sidebar_index.md
@@ -21,7 +21,7 @@
 * [HTTP headers](/api/http-headers)
 * [Error handling](/api/rest-error-handling)
 * [Roles and permissions](/api/roles-and-permissions)
-* [Updating group settings](/api/updating-group-settings)
+* [Group-setting values](/api/group-setting-values)
 * [Message formatting](/api/message-formatting)
 * [Client libraries](/api/client-libraries)
 * [API changelog](/api/changelog)
diff --git a/api_docs/updating-group-settings.md b/api_docs/updating-group-settings.md
deleted file mode 100644
index c819756650..0000000000
--- a/api_docs/updating-group-settings.md
+++ /dev/null
@@ -1,32 +0,0 @@
-# Updating group settings
-
-A **group-setting update** is an object with a `new` field and
-an optional `old` field, each containing a group-setting value
-(as defined below). The setting's value will be set to the
-membership expressed by the `new` field.
-
-The `old` field expresses the expected current value of the setting.
-If the `old` field is present and does not match the actual current
-value of the setting, then the request will fail with error code
-`EXPECTATION_MISMATCH` and no changes will be applied.
-
-When a user edits the setting in a UI, the resulting API request
-should generally always include the `old` field, giving the value
-the list had when the user started editing. This accurately expresses
-the user's intent, and if two users edit the same list around the
-same time, it prevents a situation where the second change
-accidentally reverts the first one without either user noticing.
-
-Omitting `old` is appropriate where the intent really is a new complete
-list rather than an edit, for example in an integration that syncs the
-list from an external source of truth.
-
-A **group-setting value** can take two forms: either an integer group ID,
-or an object with fields `direct_member_ids` containing a list of integer
-user IDs and `direct_subgroup_ids` containing a list of integer group IDs.
-
-In the object form, the setting's value consists of the identified users
-and groups.
-
-The integer form is equivalent to an object with empty `direct_member_ids`
-and with `direct_subgroup_ids` containing just the given group ID.
diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml
index cb0fe8e75c..c82c797451 100644
--- a/zerver/openapi/zulip.yaml
+++ b/zerver/openapi/zulip.yaml
@@ -3167,7 +3167,7 @@ paths:
                                         changed.
                                     can_mention_group:
                                       allOf:
-                                        - $ref: "#/components/schemas/CanMentionGroup"
+                                        - $ref: "#/components/schemas/GroupSettingValue"
                                         - description: |
                                             Either the ID of a named user group that has permission
                                             to mention the group, or an object describing the set of
@@ -18589,9 +18589,8 @@ paths:
                 can_mention_group:
                   allOf:
                     - description: |
-                        Either the ID of a named user group that has permission to
-                        mention the group, or an object describing the set of users
-                        and groups who have permission mention the new group.
+                        A [group-setting value](/api/group-setting-values) defining the set
+                        of users who have permission to mention the new group.
 
                         This setting cannot be set to `"role:internet"` and
                         `"role:owners"` system groups.
@@ -18604,7 +18603,7 @@ paths:
 
                         New in Zulip 8.0 (feature level 191). Previously, groups
                         could be mentioned if and only if they were not system groups.
-                    - $ref: "#/components/schemas/CanMentionGroup"
+                    - $ref: "#/components/schemas/GroupSettingValue"
                   example: 11
               required:
                 - name
@@ -18743,10 +18742,9 @@ paths:
                   example: The marketing team.
                 can_mention_group:
                   description: |
-                    The set of users who have permission to mention this group.
-
-                    This parameter is expressed as a [group-setting update](/api/updating-group-settings).
-                    See [details](/api/updating-group-settings) on that format.
+                    The set of users who have permission to mention
+                    this group, expressed as an [update to a
+                    group-setting value](/api/group-valued-settings#updating-group-setting-values).
 
                     This setting cannot be set to `"role:internet"` and `"role:owners"`
                     system groups.
@@ -18768,15 +18766,15 @@ paths:
                     new:
                       allOf:
                         - description: |
-                            The new value for who would have the permission to
-                            mention the group.
-                        - $ref: "#/components/schemas/CanMentionGroup"
+                            The new [group-setting value](/api/group-setting-values) for who would
+                            have the permission to mention the group.
+                        - $ref: "#/components/schemas/GroupSettingValue"
                     old:
                       allOf:
                         - description: |
-                            The expected current value for who has the permission
-                            to mention the group.
-                        - $ref: "#/components/schemas/CanMentionGroup"
+                            The expected current [group-setting value](/api/group-setting-values)
+                            for who has the permission to mention the group.
+                        - $ref: "#/components/schemas/GroupSettingValue"
                   required:
                     - new
                   example:
@@ -18904,11 +18902,10 @@ paths:
                                 **Changes**: New in Zulip 5.0 (feature level 93).
                             can_mention_group:
                               allOf:
-                                - $ref: "#/components/schemas/CanMentionGroup"
+                                - $ref: "#/components/schemas/GroupSettingValue"
                                 - description: |
-                                    Either the ID of a named user group that has permission to
-                                    mention the group, or an object describing the set of users
-                                    and groups who have permission to mention the group.
+                                    A [group-setting value](/api/group-setting-values) defining the set
+                                    of users who have permission to mention the new group.
 
                                     **Changes**: Before Zulip 9.0 (feature level 258), the
                                     `can_mention_group` field was always an integer.
@@ -20088,11 +20085,10 @@ components:
             **Changes**: New in Zulip 5.0 (feature level 93).
         can_mention_group:
           allOf:
-            - $ref: "#/components/schemas/CanMentionGroup"
+            - $ref: "#/components/schemas/GroupSettingValue"
             - description: |
-                Either the ID of a named user group that has permission to
-                mention the group, or an object describing the set of users
-                and groups who have permission mention the group.
+                A [group-setting value](/api/group-setting-values) defining the set
+                of users who have permission to mention the new group.
 
                 **Changes**: Before Zulip 9.0 (feature level 258), the
                 `can_mention_group` field was always an integer.
@@ -20102,26 +20098,26 @@ components:
 
                 New in Zulip 8.0 (feature level 191). Previously, groups
                 could be mentioned if and only if they were not system groups.
-    CanMentionGroup:
+    GroupSettingValue:
       oneOf:
         - type: object
           additionalProperties: false
           properties:
             direct_members:
               description: |
-                The list of user IDs that have permission to
-                mention the group.
+                The list of IDs of individual users in the collection of users with this permission.
               type: array
               items:
                 type: integer
             direct_subgroups:
               description: |
-                The list of user group IDs that have permission
-                to mention the group.
+                The list of IDs of the groups in the collection of users with this permission.
               type: array
               items:
                 type: integer
         - type: integer
+          description: |
+            The ID of the [user group](/help/user-groups) with this permission.
     Invite:
       type: object
       description: |

- description: |
The expected current value for who has the permission
to mention the group.
- $ref: "#/components/schemas/CanMentionGroup"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot for reference -
image

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@staticmethod
@override
def msg_format() -> str:
return _("'old' value does not match the expected value.")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this error fine for here considering we were discussing about showing different message in the client as we pass a unique error code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have not changed the error message to be different in client.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this seems fine; I think it should be more like "Submitted old value does not match current value of this setting."...

if check_setting_value_changed(current_value, new_setting_value):
if validate_group_setting_value_change(
current_value, new_setting_value, previous_setting_value
):
setting_value_group = access_user_group_for_setting(
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied this change for better readability.

diff --git a/zerver/views/user_groups.py b/zerver/views/user_groups.py
index 7eeaec5ca3..90abb9c526 100644
--- a/zerver/views/user_groups.py
+++ b/zerver/views/user_groups.py
@@ -129,12 +129,12 @@ def are_both_setting_values_equal(
 def validate_group_setting_value_change(
     current_value: UserGroup,
     new_setting_value: Union[int, AnonymousSettingGroupDict],
-    previous_setting_value: Optional[Union[int, AnonymousSettingGroupDict]],
+    expected_current_setting_value: Optional[Union[int, AnonymousSettingGroupDict]],
 ) -> bool:
     current_setting_api_value = get_group_setting_value_for_api(current_value)
 
-    if previous_setting_value is not None and not are_both_setting_values_equal(
-        current_setting_api_value, previous_setting_value
+    if expected_current_setting_value is not None and not are_both_setting_values_equal(
+        expected_current_setting_value, current_setting_api_value,
     ):
         # This check is here to help prevent races, by refusing to
         # change a setting where the client (and thus the UI presented
@@ -185,13 +185,13 @@ def edit_user_group(
         setting_value = request_settings_dict[setting_name]
         new_setting_value = parse_group_setting_value(setting_value.new)
 
-        previous_setting_value = None
+        expected_current_setting_value = None
         if setting_value.old is not None:
-            previous_setting_value = parse_group_setting_value(setting_value.old)
+            expected_current_setting_value = parse_group_setting_value(setting_value.old)
 
         current_value = getattr(user_group, setting_name)
         if validate_group_setting_value_change(
-            current_value, new_setting_value, previous_setting_value
+            current_value, new_setting_value, expected_current_setting_value
         ):
             setting_value_group = access_user_group_for_setting(
                 new_setting_value,

@timabbott
Copy link
Sponsor Member

This is great! I did a bunch of reworking to define a reusable #/components/schemas/GroupSettingValue component, document the system groups system as a side detail, and overall just get the content into a reasonable shape.

There is one thing that's broken, which is that if you look at, say http://localhost:9991/api/create-user-group, the full content of the API documentation doesn't actually display:

image

This is probably something that I messed up in my changes; I think probably the right plan is to let this merge as a content update, as the API docs are usable in this state, but fix that as a priority follow-up. @Vector73 would you be up for debugging why the extra details aren't being displayed? I think there's some version on it on every page using can_mention_group or the includable GroupSettingValue.

@timabbott timabbott enabled auto-merge (rebase) May 14, 2024 18:02
sahil839 and others added 2 commits May 14, 2024 11:52
This commit adds support to pass object containing both old and new
values of the can_mention_group setting, as well as detailed API
documentation for this part of the API system.

Co-authored-by: Tim Abbott <tabbott@zulip.com>
Co-authored-by: Greg PRice <greg@zulip.com>
This commit updates the webapp code to use new format to update
the can_mention_group setting.
@timabbott timabbott merged commit 9d87131 into zulip:main May 14, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants