-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
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
settings: Allow "resolve topic" permissions to be managed independently. #29965
base: main
Are you sure you want to change the base?
Conversation
Hello @zulip/server-settings members, this pull request was labeled with the "area: settings (admin/org)" label, so you may want to check it out! |
So I guess I should have made sure folks were aware of this previously, but all future permissions settings need to be done using groups (see #19525), and likely building on the approach in #29937. Maybe you can chat with @sahil839, but I think you might be able to build this on top of #29937, which should merge in the next couple days. |
8cb9fd4
to
e7c6889
Compare
@@ -302,7 +303,7 @@ export function dispatch_normal_event(event) { | |||
gear_menu.rerender(); | |||
} | |||
|
|||
if (key === "edit_topic_policy") { | |||
if (key === "edit_topic_policy" || key === "resolve_topic_group") { | |||
message_live_update.rerender_messages_view(); | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so as to update the resolve topic icon in the message list.
if (setting_name === "resolve_topic_group") { | ||
group_setting_config = {...group_setting_config, allow_nobody_group: false}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is done to remove the "role:nobody" from dropdown options in case of resolve_topic_group
since it is represented by checkbox.
if ( | ||
realm.realm_resolve_topic_group === user_groups.get_user_group_from_name("role:nobody").id | ||
) { | ||
update_resolve_topic_sub_settings(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When initial group of the resolve_topic_group
is "role:nobody", we don't want to show it in dropdown option. Hence, we rather show the checkbox as unchecked and dropdown disabled with "role:members" (default).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if there is need to show the option selected as members
when the option is disabled. We can just show the option which was selected before the setting was disabled and I guess this is what we do for other similar settings. Maybe we can have a tooltip on hovering over it that "Resolving topics is not allowed in this organization".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you can just call the code to disable the dropdown initially in build_page
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can just show the option which was selected before the setting was disabled and I guess this is what we do for other similar settings.
The problem here is that we are using just one database field for both allow_resolve_topic
and can_resolve_topics_group
settings. So technically, if you uncheck the checkbox, the value of this field is role:nobody
-- which removes the possibility of storing the last option of dropdown. Hence, I thought I would just simply show the default value (members).
const is_checked = $(e.target).prop("checked"); | ||
update_resolve_topic_sub_settings(is_checked); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for disabling/enabling the dropdown depending on the allow_resolve_topic
checkbox.
property_name, | ||
property_value, | ||
); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the permission is set to role:nobody
using checkbox, when resolve_topic_group
is updated, we set the dropdown value with updated one only if the value !== role:nobody
.id. Else, we just disable dropdown.
web/src/settings_org.js
Outdated
if (input_value === false) { | ||
user_group = user_groups.get_user_group_from_name("role:nobody"); | ||
data.resolve_topic_group = user_group.id; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When submitting request, if allow_resolve_topic
checkbox is checked, we send a request of resolve_topic_group = dropdown value
. Else, we send request of resolve_topic_group
= role:nobody
.id.
def is_resolve_topic_request( | ||
user_profile: UserProfile, | ||
message_id: PathOnly[NonNegativeInt], | ||
new_topic_name: OptionalTopic, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used for determining if the message edit request is a resolve topic request, and calling the corresponding resolve topic method in actions. Not sure if is_resolve_topic_request
is a good name.
) and not new_topic_name.startswith(RESOLVED_TOPIC_PREFIX) | ||
|
||
if topic_resolved or topic_unresolved: | ||
return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the conditions are enough for determining if it is a resolve topic request?
addressed review:
Renamed |
1cabfda
to
3c7449e
Compare
Updated it to use the groups object rather than group ids. |
zerver/views/realm.py
Outdated
return setting_value["direct_subgroups"][0] | ||
|
||
return AnonymousSettingGroupDict( # nocoverage | ||
direct_members=setting_value["direct_members"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No setting that returns AnonymousSettingGroupDict
currently in views/realm
. Hence, no coverage
for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Posted some initial comments. Only reviewed backend changes for now, will review the remaining tommorrow.
zerver/views/realm.py
Outdated
@@ -101,6 +132,9 @@ def update_realm( | |||
edit_topic_policy: Optional[int] = REQ( | |||
json_validator=check_int_in(Realm.EDIT_TOPIC_POLICY_TYPES), default=None | |||
), | |||
can_resolve_topics_group_id: Optional[Union[Dict[str, List[int]], int]] = REQ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to just name this as can_resolve_topics_group
now since it can also be a dict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the docs mention that can_resolve_topics_group
can only be set to a system group, so there is no advantage of this conversion and this should just be kept as int. I guess we would support setting it to any group(s) in future, so would be good to just confirm it once whether we want to support only system groups.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, I just realised that there is an assertion that is preventing me to access the group setting if permission_configuration.require_system_group
is True (if the setting value is not int), which in this case would hence raise error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would still be good to confirm if we just want to allow any group(s) in the API and we can just design the API according to that now instead of reworking it later. We can allow only system groups in the UI for now though or just allow all groups in the UI as well but only single group.
zerver/models/realms.py
Outdated
allow_everyone_group=False, | ||
default_group_name=SystemGroups.MEMBERS, | ||
id_field_name="can_resolve_topics_group_id", | ||
allowed_system_groups=[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally use this only if we want to allow a couple of groups and we cannot do that using allow_...
parameters, which I think is not the case here and you can just keep this list empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya makes sense! updated
raise JsonableError(_("Direct messages cannot be moved to channels.")) | ||
|
||
if not user_profile.can_resolve_topic(): | ||
raise JsonableError(_("You don't have permission to resolve topics.")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This duplicates some code from check_update_message. Maybe we can do some refactor to still have separate functions but reduce the duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only the message.is_stream_message()
is duplicated. user_profile.can_resolve_topic()
is a new method. So I guess it is fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this also needs changes like below to chck permission to move between streams. Might be better to just have a function named check_resolve_topic
which is called from check_update_message
if the user is trying to resolve the topic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the problem with calling it from check_update_message
is that there are also lots of checks for the time limit for editing, but according to this issue - There should not be an option to time-limit topic resolution.
.
Another problem is it only checks the stream post policy if stream_id
is None. For resolving the topics, we also need to check it here.
zerver/actions/message_edit.py
Outdated
and raises a JsonableError if otherwise. | ||
It returns the number changed. | ||
""" | ||
message = access_message(user_profile, message_id, lock_message=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also called in is_resolve_topic_request
, I guess we should try removing one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated!
zerver/views/realm.py
Outdated
] | ||
), | ||
] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was working on a PR for converting this to typed_endpoint
. I will try to open the PR tommorrow, so that we do not need these changes.
@sahil839 addressed your reviews. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Posted some comments.
Much of the frontend code would have been simpler if could just avoid the checkbox and keep the dropdown with "Nobody" option, but yeah having a checkbox to disable resolving seems good from UX perspective so it is fine for now.
} | ||
const sub = stream_data.get_sub(stream_name); | ||
group.user_can_resolve_topic = | ||
stream_data.can_post_messages_in_stream(sub) && settings_data.user_can_resolve_topic(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has this been discussed to allow only users who can post messages in stream to allow resolving topics?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, it is related to this issue - #21281
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it is related, but #21811 mentions that the behavior should be discussed.
web/src/settings_components.ts
Outdated
@@ -891,6 +909,9 @@ export function check_property_changed( | |||
case "field_data": | |||
proposed_val = get_input_element_value(elem, "field-data-setting"); | |||
break; | |||
case "realm_allow_resolve_topic": | |||
proposed_val = get_input_element_value(elem, "checkbox"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is required as we do not have this for other checkbox settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh ya right, thanks for catching it. Must have left it when I updated the setting. updated it.
unique_id_type: dropdown_widget.DataTypes.NUMBER, | ||
}); | ||
settings_components.set_can_resolve_topics_group_widget(can_resolve_topics_group_widget); | ||
can_resolve_topics_group_widget.setup(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should look to somehow deduplicate this as a follow-up, else there will be lot of these when we would convert existing settings to be group based settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure! do you want me to update in this rather?
raise JsonableError(_("Direct messages cannot be moved to channels.")) | ||
|
||
if not user_profile.can_resolve_topic(): | ||
raise JsonableError(_("You don't have permission to resolve topics.")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this also needs changes like below to chck permission to move between streams. Might be better to just have a function named check_resolve_topic
which is called from check_update_message
if the user is trying to resolve the topic.
if not user_profile.can_move_messages_between_streams(): | ||
raise JsonableError(_("You don't have permission to move this message")) | ||
new_stream = access_stream_by_id(user_profile, stream_id, require_active=True)[0] | ||
check_stream_access_based_on_stream_post_policy(user_profile, new_stream) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure but should we check the stream posting permission in original stream as well as resolving topics is only allowed if you are allowed to post in the original stream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya it checks the permission in the original stream in the else statement below.
web/src/settings_components.ts
Outdated
const group_id = can_resolve_topics_group_widget.value(); | ||
assert(typeof group_id === "number"); | ||
data.can_resolve_topics_group = group_id; | ||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just move this code to a function which will be called only if property_name === "allow_resolve_topic" || property_name === "can_resolve_topics_group"
and the function should just return the value of setting by checking two values - it should return "nobody" group if the checkbox is unchecked and the group the ID from the dropdown widget if it is checked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya you're right. it makes it a lot cleaner. updated!
if ( | ||
realm.realm_resolve_topic_group === user_groups.get_user_group_from_name("role:nobody").id | ||
) { | ||
update_resolve_topic_sub_settings(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if there is need to show the option selected as members
when the option is disabled. We can just show the option which was selected before the setting was disabled and I guess this is what we do for other similar settings. Maybe we can have a tooltip on hovering over it that "Resolving topics is not allowed in this organization".
if ( | ||
realm.realm_resolve_topic_group === user_groups.get_user_group_from_name("role:nobody").id | ||
) { | ||
update_resolve_topic_sub_settings(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you can just call the code to disable the dropdown initially in build_page
.
web/src/settings_org.js
Outdated
@@ -567,6 +585,10 @@ export function discard_property_element_changes(elem, for_realm_default_setting | |||
case "message_retention_days": | |||
set_message_retention_setting_dropdown(sub); | |||
break; | |||
case "allow_resolve_topic": | |||
settings_components.set_input_element_value($elem, property_value); | |||
update_resolve_topic_sub_settings(property_value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we do not need this. When this function will be called for "allow_resolve_topic" it will also be called for "realm_can_resolve_topics_group", so the update_resolve_topic...
part will be handled. And the set_input_element_value
will be called in "default" case. Can you check this once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya you're right. updated!
zerver/views/realm.py
Outdated
@@ -101,6 +132,9 @@ def update_realm( | |||
edit_topic_policy: Optional[int] = REQ( | |||
json_validator=check_int_in(Realm.EDIT_TOPIC_POLICY_TYPES), default=None | |||
), | |||
can_resolve_topics_group_id: Optional[Union[Dict[str, List[int]], int]] = REQ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would still be good to confirm if we just want to allow any group(s) in the API and we can just design the API according to that now instead of reworking it later. We can allow only system groups in the UI for now though or just allow all groups in the UI as well but only single group.
This commit separates the "resolve topic" permissions from the topic editing permissions, through the introduction of setting - "can_resolve_topics_group" which is the groups id of the user group whose members can resolve topics. In addition, it also restricts resolve topic permissions to streams where users have posting permissions. Fixes zulip#21811
This commit separates the "resolve topic" permissions from the topic editing permissions, through the introduction of two settings - "allow_resolve_topic" to control whether to allow resolve topics in organization, and "resolve_topic_policy" to restrict the resolve topics permissions.
In addition, it also restricts resolve topic permissions to streams where users have posting permissions.
Fixes #21811, #21281
#api desing thread
Screenshots and screen captures:
Self-review checklist
(variable names, code reuse, readability, etc.).
Communicate decisions, questions, and potential concerns.
Individual commits are ready for review (see commit discipline).
Completed manual review and testing of the following: