settings: Add `can_move_messages_between_topics_group` realm setting.

Added `can_move_messages_between_topics_group` realm setting to replace
`edit_topic_policy`.
This commit is contained in:
Vector73 2024-10-27 21:31:01 +05:30 committed by Tim Abbott
parent 1edf507be9
commit ed5638ec3c
29 changed files with 331 additions and 238 deletions

View File

@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 10.0 ## Changes in Zulip 10.0
**Feature level 316**
* `PATCH /realm`, [`GET /events`](/api/get-events),
[`POST /register`](/api/register-queue):
Added `can_move_messages_between_topics_group` realm setting which is a
[group-setting value](/api/group-setting-values) describing the set of users
with permission to move messages from one topic to another within a channel
in the organization.
**Feature level 315** **Feature level 315**
* [POST /register](/api/register-queue), [`GET * [POST /register](/api/register-queue), [`GET

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 = 315 # Last bumped for `is_archived` API_FEATURE_LEVEL = 316 # Last bumped for `can_move_messages_between_topics_group`
# 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

@ -111,8 +111,8 @@ export function is_topic_editable(message: Message, edit_limit_seconds_buffer =
} }
// Organization admins and moderators can edit message topics indefinitely, // Organization admins and moderators can edit message topics indefinitely,
// irrespective of the topic editing deadline, if edit_topic_policy allows // irrespective of the topic editing deadline, if they are in the
// them to do so. // can_move_messages_between_topics_group.
if (current_user.is_admin || current_user.is_moderator) { if (current_user.is_admin || current_user.is_moderator) {
return true; return true;
} }

View File

@ -208,7 +208,6 @@ export function dispatch_normal_event(event) {
const realm_settings = { const realm_settings = {
allow_edit_history: noop, allow_edit_history: noop,
allow_message_editing: noop, allow_message_editing: noop,
edit_topic_policy: noop,
avatar_changes_disabled: settings_account.update_avatar_change_display, avatar_changes_disabled: settings_account.update_avatar_change_display,
bot_creation_policy: settings_bots.update_bot_permissions_ui, bot_creation_policy: settings_bots.update_bot_permissions_ui,
can_delete_any_message_group: noop, can_delete_any_message_group: noop,
@ -323,7 +322,7 @@ export function dispatch_normal_event(event) {
compose_recipient.check_posting_policy_for_compose_box(); compose_recipient.check_posting_policy_for_compose_box();
} }
if (key === "edit_topic_policy") { if (key === "can_move_messages_between_topics_group") {
message_live_update.rerender_messages_view(); message_live_update.rerender_messages_view();
} }

View File

@ -238,7 +238,6 @@ export const simple_dropdown_realm_settings_schema = realm_schema.pick({
realm_invite_to_stream_policy: true, realm_invite_to_stream_policy: true,
realm_invite_to_realm_policy: true, realm_invite_to_realm_policy: true,
realm_wildcard_mention_policy: true, realm_wildcard_mention_policy: true,
realm_edit_topic_policy: true,
realm_org_type: true, realm_org_type: true,
}); });
export type SimpleDropdownRealmSettings = z.infer<typeof simple_dropdown_realm_settings_schema>; export type SimpleDropdownRealmSettings = z.infer<typeof simple_dropdown_realm_settings_schema>;
@ -484,6 +483,7 @@ const dropdown_widget_map = new Map<string, DropdownWidget | null>([
["realm_can_delete_any_message_group", null], ["realm_can_delete_any_message_group", null],
["realm_can_delete_own_message_group", null], ["realm_can_delete_own_message_group", null],
["realm_can_move_messages_between_channels_group", null], ["realm_can_move_messages_between_channels_group", null],
["realm_can_move_messages_between_topics_group", null],
["realm_direct_message_initiator_group", null], ["realm_direct_message_initiator_group", null],
["realm_direct_message_permission_group", null], ["realm_direct_message_permission_group", null],
]); ]);
@ -802,6 +802,7 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea
case "realm_can_delete_any_message_group": case "realm_can_delete_any_message_group":
case "realm_can_delete_own_message_group": case "realm_can_delete_own_message_group":
case "realm_can_move_messages_between_channels_group": case "realm_can_move_messages_between_channels_group":
case "realm_can_move_messages_between_topics_group":
case "realm_direct_message_initiator_group": case "realm_direct_message_initiator_group":
case "realm_direct_message_permission_group": case "realm_direct_message_permission_group":
proposed_val = get_dropdown_list_widget_setting_value($elem); proposed_val = get_dropdown_list_widget_setting_value($elem);
@ -1054,6 +1055,7 @@ export function populate_data_for_realm_settings_request(
"can_delete_any_message_group", "can_delete_any_message_group",
"can_delete_own_message_group", "can_delete_own_message_group",
"can_move_messages_between_channels_group", "can_move_messages_between_channels_group",
"can_move_messages_between_topics_group",
"create_multiuse_invite_group", "create_multiuse_invite_group",
"direct_message_initiator_group", "direct_message_initiator_group",
"direct_message_permission_group", "direct_message_permission_group",

View File

@ -311,43 +311,6 @@ export const wildcard_mention_policy_values = {
}, },
}; };
export const common_message_policy_values = {
by_everyone: {
order: 1,
code: 5,
description: $t({defaultMessage: "Admins, moderators, members and guests"}),
},
by_members: {
order: 2,
code: 1,
description: $t({defaultMessage: "Admins, moderators and members"}),
},
by_full_members: {
order: 3,
code: 3,
description: $t({defaultMessage: "Admins, moderators and full members"}),
},
by_moderators_only: {
order: 4,
code: 4,
description: $t({defaultMessage: "Admins and moderators"}),
},
by_admins_only: {
order: 5,
code: 2,
description: $t({defaultMessage: "Admins only"}),
},
};
export const edit_topic_policy_values = {
...common_message_policy_values,
nobody: {
order: 6,
code: 6,
description: $t({defaultMessage: "Nobody"}),
},
};
export const time_limit_dropdown_values = [ export const time_limit_dropdown_values = [
{ {
text: $t({defaultMessage: "Any time"}), text: $t({defaultMessage: "Any time"}),

View File

@ -71,18 +71,7 @@ function user_has_permission(policy_value: number): boolean {
return true; return true;
} }
if (page_params.is_spectator) { if (page_params.is_spectator || current_user.is_guest) {
return false;
}
/* At present, by_everyone is not present in common_policy_values,
* but we include a check for it here, so that code using
* common_message_policy_values or other supersets can use this function. */
if (policy_value === settings_config.common_message_policy_values.by_everyone.code) {
return true;
}
if (current_user.is_guest) {
return false; return false;
} }
@ -262,7 +251,11 @@ export function user_can_add_custom_emoji(): boolean {
} }
export function user_can_move_messages_to_another_topic(): boolean { export function user_can_move_messages_to_another_topic(): boolean {
return user_has_permission(realm.realm_edit_topic_policy); return user_has_permission_for_group_setting(
realm.realm_can_move_messages_between_topics_group,
"can_move_messages_between_topics_group",
"realm",
);
} }
export function user_can_delete_any_message(): boolean { export function user_can_delete_any_message(): boolean {

View File

@ -161,9 +161,7 @@ export function enable_or_disable_group_permission_settings(): void {
type OrganizationSettingsOptions = { type OrganizationSettingsOptions = {
common_policy_values: SettingOptionValueWithKey[]; common_policy_values: SettingOptionValueWithKey[];
wildcard_mention_policy_values: SettingOptionValueWithKey[]; wildcard_mention_policy_values: SettingOptionValueWithKey[];
common_message_policy_values: SettingOptionValueWithKey[];
invite_to_realm_policy_values: SettingOptionValueWithKey[]; invite_to_realm_policy_values: SettingOptionValueWithKey[];
edit_topic_policy_values: SettingOptionValueWithKey[];
}; };
export function get_organization_settings_options(): OrganizationSettingsOptions { export function get_organization_settings_options(): OrganizationSettingsOptions {
@ -174,15 +172,9 @@ export function get_organization_settings_options(): OrganizationSettingsOptions
wildcard_mention_policy_values: settings_components.get_sorted_options_list( wildcard_mention_policy_values: settings_components.get_sorted_options_list(
settings_config.wildcard_mention_policy_values, settings_config.wildcard_mention_policy_values,
), ),
common_message_policy_values: settings_components.get_sorted_options_list(
settings_config.common_message_policy_values,
),
invite_to_realm_policy_values: settings_components.get_sorted_options_list( invite_to_realm_policy_values: settings_components.get_sorted_options_list(
settings_config.email_invite_to_realm_policy_values, settings_config.email_invite_to_realm_policy_values,
), ),
edit_topic_policy_values: settings_components.get_sorted_options_list(
settings_config.edit_topic_policy_values,
),
}; };
} }
@ -281,27 +273,9 @@ function set_msg_edit_limit_dropdown(): void {
function message_move_limit_setting_enabled( function message_move_limit_setting_enabled(
related_setting_name: related_setting_name:
| "realm_edit_topic_policy" | "realm_can_move_messages_between_topics_group"
| "realm_can_move_messages_between_channels_group", | "realm_can_move_messages_between_channels_group",
): boolean { ): boolean {
if (related_setting_name === "realm_edit_topic_policy") {
const setting_value_string = $<HTMLSelectOneElement>(
`select:not(multiple)#id_${CSS.escape(related_setting_name)}`,
).val();
assert(setting_value_string !== undefined);
const setting_value = Number.parseInt(setting_value_string, 10);
const settings_options = settings_config.edit_topic_policy_values;
if (
setting_value === settings_options.by_admins_only.code ||
setting_value === settings_options.by_moderators_only.code ||
setting_value === settings_options.nobody.code
) {
return false;
}
return true;
}
const user_group_id = settings_components.get_dropdown_list_widget_setting_value( const user_group_id = settings_components.get_dropdown_list_widget_setting_value(
$(`#id_${related_setting_name}`), $(`#id_${related_setting_name}`),
); );
@ -334,7 +308,9 @@ function set_msg_move_limit_setting(property_name: MessageMoveTimeLimitSetting):
let disable_setting; let disable_setting;
if (property_name === "realm_move_messages_within_stream_limit_seconds") { if (property_name === "realm_move_messages_within_stream_limit_seconds") {
disable_setting = message_move_limit_setting_enabled("realm_edit_topic_policy"); disable_setting = message_move_limit_setting_enabled(
"realm_can_move_messages_between_topics_group",
);
} else { } else {
disable_setting = message_move_limit_setting_enabled( disable_setting = message_move_limit_setting_enabled(
"realm_can_move_messages_between_channels_group", "realm_can_move_messages_between_channels_group",
@ -559,6 +535,9 @@ function update_dependent_subsettings(property_name: string): void {
case "realm_can_move_messages_between_channels_group": case "realm_can_move_messages_between_channels_group":
set_msg_move_limit_setting("realm_move_messages_between_streams_limit_seconds"); set_msg_move_limit_setting("realm_move_messages_between_streams_limit_seconds");
break; break;
case "realm_can_move_messages_between_topics_group":
set_msg_move_limit_setting("realm_move_messages_within_stream_limit_seconds");
break;
case "realm_org_join_restrictions": case "realm_org_join_restrictions":
set_org_join_restrictions_dropdown(); set_org_join_restrictions_dropdown();
break; break;
@ -612,6 +591,7 @@ export function discard_realm_property_element_changes(elem: HTMLElement): void
case "realm_can_delete_any_message_group": case "realm_can_delete_any_message_group":
case "realm_can_delete_own_message_group": case "realm_can_delete_own_message_group":
case "realm_can_move_messages_between_channels_group": case "realm_can_move_messages_between_channels_group":
case "realm_can_move_messages_between_topics_group":
assert(typeof property_value === "string" || typeof property_value === "number"); assert(typeof property_value === "string" || typeof property_value === "number");
settings_components.set_dropdown_list_widget_setting_value( settings_components.set_dropdown_list_widget_setting_value(
property_name, property_name,
@ -1018,6 +998,13 @@ export function set_up_dropdown_widget_for_realm_group_settings(): void {
break; break;
} }
case "can_move_messages_between_topics_group": {
dropdown_list_item_click_callback = () => {
set_msg_move_limit_setting("realm_move_messages_within_stream_limit_seconds");
};
break;
}
// No default // No default
} }
@ -1338,30 +1325,6 @@ export function build_page(): void {
update_message_edit_sub_settings(this.checked); update_message_edit_sub_settings(this.checked);
}); });
$("#org-moving-msgs").on(
"change",
".move-message-policy-setting",
function (this: HTMLElement) {
const $policy_dropdown_elem = $(this);
const property_name = z
.enum(["realm_edit_topic_policy", "realm_can_move_messages_between_channels_group"])
.parse(settings_components.extract_property_name($policy_dropdown_elem));
const disable_time_limit_setting = message_move_limit_setting_enabled(property_name);
let time_limit_setting_name: MessageMoveTimeLimitSetting;
if (property_name === "realm_edit_topic_policy") {
time_limit_setting_name = "realm_move_messages_within_stream_limit_seconds";
} else {
time_limit_setting_name = "realm_move_messages_between_streams_limit_seconds";
}
enable_or_disable_related_message_move_time_limit_setting(
time_limit_setting_name,
disable_time_limit_setting,
);
},
);
$("#id_realm_org_join_restrictions").on("click", (e) => { $("#id_realm_org_join_restrictions").on("click", (e) => {
// This prevents the disappearance of modal when there are // This prevents the disappearance of modal when there are
// no allowed domains otherwise it gets closed due to // no allowed domains otherwise it gets closed due to

View File

@ -296,6 +296,7 @@ export const realm_schema = z.object({
realm_can_delete_own_message_group: z.number(), realm_can_delete_own_message_group: z.number(),
realm_can_manage_all_groups: group_setting_value_schema, realm_can_manage_all_groups: group_setting_value_schema,
realm_can_move_messages_between_channels_group: z.number(), realm_can_move_messages_between_channels_group: z.number(),
realm_can_move_messages_between_topics_group: z.number(),
realm_create_multiuse_invite_group: group_setting_value_schema, realm_create_multiuse_invite_group: group_setting_value_schema,
realm_date_created: z.number(), realm_date_created: z.number(),
realm_default_code_block_language: z.string(), realm_default_code_block_language: z.string(),
@ -321,7 +322,6 @@ export const realm_schema = z.object({
allow_subdomains: z.boolean(), allow_subdomains: z.boolean(),
}), }),
), ),
realm_edit_topic_policy: z.number(),
realm_email_auth_enabled: z.boolean(), realm_email_auth_enabled: z.boolean(),
realm_email_changes_disabled: z.boolean(), realm_email_changes_disabled: z.boolean(),
realm_emails_restricted_to_domains: z.boolean(), realm_emails_restricted_to_domains: z.boolean(),

View File

@ -355,7 +355,7 @@ export async function build_move_topic_to_stream_popover(
// When the modal is opened for moving the whole topic from left sidebar, // When the modal is opened for moving the whole topic from left sidebar,
// we do not have any message object and so we disable the stream input // we do not have any message object and so we disable the stream input
// based on the can_move_messages_between_channels_group setting and topic // based on the can_move_messages_between_channels_group setting and topic
// input based on edit_topic_policy. In other cases, message object is // input based on can_move_messages_between_topics_group. In other cases, message object is
// available and thus we check the time-based permissions as well in the // available and thus we check the time-based permissions as well in the
// below if block to enable or disable the stream and topic input. // below if block to enable or disable the stream and topic input.
let disable_stream_input = !settings_data.user_can_move_messages_between_streams(); let disable_stream_input = !settings_data.user_can_move_messages_between_streams();

View File

@ -191,12 +191,11 @@
</h3> </h3>
{{> settings_save_discard_widget section_name="moving-msgs" }} {{> settings_save_discard_widget section_name="moving-msgs" }}
</div> </div>
<div class="input-group">
<label for="realm_edit_topic_policy" class="settings-field-label">{{t "Who can move messages to another topic" }}</label> {{> ../dropdown_widget_with_label
<select name="realm_edit_topic_policy" id="id_realm_edit_topic_policy" class="prop-element move-message-policy-setting settings_select bootstrap-focus-style" data-setting-widget-type="number"> widget_name="realm_can_move_messages_between_topics_group"
{{> dropdown_options_widget option_values=edit_topic_policy_values}} label=(t 'Who can move messages to another topic')
</select> value_type="number" }}
</div>
<div class="input-group time-limit-setting"> <div class="input-group time-limit-setting">
<label for="realm_move_messages_within_stream_limit_seconds" class="settings-field-label">{{t "Time limit for editing topics" }} <i>({{t "does not apply to moderators and administrators" }})</i></label> <label for="realm_move_messages_within_stream_limit_seconds" class="settings-field-label">{{t "Time limit for editing topics" }} <i>({{t "does not apply to moderators and administrators" }})</i></label>

View File

@ -588,10 +588,10 @@ run_test("realm settings", ({override}) => {
override(realm, "realm_create_multiuse_invite_group", 1); override(realm, "realm_create_multiuse_invite_group", 1);
override(realm, "realm_allow_message_editing", false); override(realm, "realm_allow_message_editing", false);
override(realm, "realm_message_content_edit_limit_seconds", 0); override(realm, "realm_message_content_edit_limit_seconds", 0);
override(realm, "realm_edit_topic_policy", 3);
override(realm, "realm_authentication_methods", {Google: {enabled: false, available: true}}); override(realm, "realm_authentication_methods", {Google: {enabled: false, available: true}});
override(realm, "realm_can_add_custom_emoji_group", 1); override(realm, "realm_can_add_custom_emoji_group", 1);
override(realm, "realm_can_create_public_channel_group", 1); override(realm, "realm_can_create_public_channel_group", 1);
override(realm, "realm_can_move_messages_between_topics_group", 1);
override(realm, "realm_direct_message_permission_group", 1); override(realm, "realm_direct_message_permission_group", 1);
override(realm, "realm_plan_type", 2); override(realm, "realm_plan_type", 2);
override(realm, "realm_upload_quota_mib", 5000); override(realm, "realm_upload_quota_mib", 5000);
@ -601,12 +601,12 @@ run_test("realm settings", ({override}) => {
assert_same(realm.realm_create_multiuse_invite_group, 3); assert_same(realm.realm_create_multiuse_invite_group, 3);
assert_same(realm.realm_allow_message_editing, true); assert_same(realm.realm_allow_message_editing, true);
assert_same(realm.realm_message_content_edit_limit_seconds, 5); assert_same(realm.realm_message_content_edit_limit_seconds, 5);
assert_same(realm.realm_edit_topic_policy, 4);
assert_same(realm.realm_authentication_methods, { assert_same(realm.realm_authentication_methods, {
Google: {enabled: true, available: true}, Google: {enabled: true, available: true},
}); });
assert_same(realm.realm_can_add_custom_emoji_group, 3); assert_same(realm.realm_can_add_custom_emoji_group, 3);
assert_same(realm.realm_can_create_public_channel_group, 3); assert_same(realm.realm_can_create_public_channel_group, 3);
assert_same(realm.realm_can_move_messages_between_topics_group, 3);
assert_same(realm.realm_direct_message_permission_group, 3); assert_same(realm.realm_direct_message_permission_group, 3);
assert_same(realm.realm_plan_type, 3); assert_same(realm.realm_plan_type, 3);
assert_same(realm.realm_upload_quota_mib, 50000); assert_same(realm.realm_upload_quota_mib, 50000);

View File

@ -365,13 +365,13 @@ exports.fixtures = {
data: { data: {
allow_message_editing: true, allow_message_editing: true,
message_content_edit_limit_seconds: 5, message_content_edit_limit_seconds: 5,
edit_topic_policy: 4,
create_multiuse_invite_group: 3, create_multiuse_invite_group: 3,
authentication_methods: { authentication_methods: {
Google: {enabled: true, available: true}, Google: {enabled: true, available: true},
}, },
can_add_custom_emoji_group: 3, can_add_custom_emoji_group: 3,
can_create_public_channel_group: 3, can_create_public_channel_group: 3,
can_move_messages_between_topics_group: 3,
direct_message_permission_group: 3, direct_message_permission_group: 3,
plan_type: 3, plan_type: 3,
upload_quota_mib: 50000, upload_quota_mib: 50000,

View File

@ -142,7 +142,6 @@ function set_page_params_no_edit_restrictions({override}) {
override(realm, "realm_allow_edit_history", true); override(realm, "realm_allow_edit_history", true);
override(realm, "realm_message_content_delete_limit_seconds", null); override(realm, "realm_message_content_delete_limit_seconds", null);
override(realm, "realm_enable_read_receipts", true); override(realm, "realm_enable_read_receipts", true);
override(realm, "realm_edit_topic_policy", 5);
override(realm, "realm_move_messages_within_stream_limit_seconds", null); override(realm, "realm_move_messages_within_stream_limit_seconds", null);
} }
@ -168,6 +167,7 @@ test("my_message_all_actions", ({override}) => {
set_page_params_no_edit_restrictions({override}); set_page_params_no_edit_restrictions({override});
override(realm, "realm_can_delete_any_message_group", everyone.id); override(realm, "realm_can_delete_any_message_group", everyone.id);
override(realm, "realm_can_delete_own_message_group", everyone.id); override(realm, "realm_can_delete_own_message_group", everyone.id);
override(realm, "realm_can_move_messages_between_topics_group", everyone.id);
override(current_user, "user_id", me.user_id); override(current_user, "user_id", me.user_id);
// Get message with maximum permissions available // Get message with maximum permissions available
// Initialize message list // Initialize message list
@ -259,6 +259,8 @@ test("not_my_message_view_actions", ({override}) => {
test("not_my_message_view_source_and_move", ({override}) => { test("not_my_message_view_source_and_move", ({override}) => {
set_page_params_no_edit_restrictions({override}); set_page_params_no_edit_restrictions({override});
override(realm, "realm_can_delete_any_message_group", everyone.id); override(realm, "realm_can_delete_any_message_group", everyone.id);
override(realm, "realm_can_move_messages_between_topics_group", everyone.id);
override(current_user, "user_id", me.user_id);
// Get message that is movable with viewable source // Get message that is movable with viewable source
const list = init_message_list(); const list = init_message_list();

View File

@ -163,66 +163,6 @@ test_policy(
settings_data.user_can_invite_users_by_email, settings_data.user_can_invite_users_by_email,
); );
function test_message_policy(label, policy, validation_func) {
run_test(label, ({override}) => {
override(current_user, "is_admin", true);
override(realm, policy, settings_config.common_message_policy_values.by_admins_only.code);
assert.equal(validation_func(), true);
override(current_user, "is_admin", false);
override(current_user, "is_moderator", true);
assert.equal(validation_func(), false);
override(
realm,
policy,
settings_config.common_message_policy_values.by_moderators_only.code,
);
assert.equal(validation_func(), true);
override(current_user, "is_moderator", false);
assert.equal(validation_func(), false);
override(current_user, "is_guest", true);
override(realm, policy, settings_config.common_message_policy_values.by_everyone.code);
assert.equal(validation_func(), true);
override(realm, policy, settings_config.common_message_policy_values.by_members.code);
assert.equal(validation_func(), false);
override(current_user, "is_guest", false);
assert.equal(validation_func(), true);
override(realm, policy, settings_config.common_message_policy_values.by_full_members.code);
override(current_user, "user_id", 30);
isaac.date_joined = new Date(Date.now());
override(realm, "realm_waiting_period_threshold", 10);
settings_data.initialize(isaac.date_joined);
assert.equal(validation_func(), false);
isaac.date_joined = new Date(Date.now() - 20 * 86400000);
settings_data.initialize(isaac.date_joined);
assert.equal(validation_func(), true);
});
}
test_message_policy(
"user_can_move_messages_to_another_topic",
"realm_edit_topic_policy",
settings_data.user_can_move_messages_to_another_topic,
);
run_test("user_can_move_messages_to_another_topic_nobody_case", ({override}) => {
override(current_user, "is_admin", true);
override(current_user, "is_guest", false);
override(
realm,
"realm_edit_topic_policy",
settings_config.edit_topic_policy_values.nobody.code,
);
assert.equal(settings_data.user_can_move_messages_to_another_topic(), false);
});
test_realm_group_settings( test_realm_group_settings(
"realm_can_add_custom_emoji_group", "realm_can_add_custom_emoji_group",
settings_data.user_can_add_custom_emoji, settings_data.user_can_add_custom_emoji,
@ -243,6 +183,11 @@ test_realm_group_settings(
settings_data.user_can_move_messages_between_streams, settings_data.user_can_move_messages_between_streams,
); );
test_realm_group_settings(
"realm_can_move_messages_between_topics_group",
settings_data.user_can_move_messages_to_another_topic,
);
run_test("using_dark_theme", ({override}) => { run_test("using_dark_theme", ({override}) => {
override(user_settings, "color_scheme", settings_config.color_scheme_values.dark.code); override(user_settings, "color_scheme", settings_config.color_scheme_values.dark.code);
assert.equal(settings_data.using_dark_theme(), true); assert.equal(settings_data.using_dark_theme(), true);

View File

@ -439,19 +439,11 @@ function test_discard_changes_button({override}, discard_changes) {
}; };
override(realm, "realm_allow_edit_history", true); override(realm, "realm_allow_edit_history", true);
override(
realm,
"realm_edit_topic_policy",
settings_config.common_message_policy_values.by_everyone.code,
);
override(realm, "realm_allow_message_editing", true); override(realm, "realm_allow_message_editing", true);
override(realm, "realm_message_content_edit_limit_seconds", 3600); override(realm, "realm_message_content_edit_limit_seconds", 3600);
override(realm, "realm_message_content_delete_limit_seconds", 120); override(realm, "realm_message_content_delete_limit_seconds", 120);
const $allow_edit_history = $("#id_realm_allow_edit_history").prop("checked", false); const $allow_edit_history = $("#id_realm_allow_edit_history").prop("checked", false);
const $edit_topic_policy = $("#id_realm_edit_topic_policy").val(
settings_config.common_message_policy_values.by_admins_only.code,
);
const $msg_edit_limit_setting = $("#id_realm_message_content_edit_limit_seconds").val( const $msg_edit_limit_setting = $("#id_realm_message_content_edit_limit_seconds").val(
"custom_period", "custom_period",
); );
@ -468,7 +460,6 @@ function test_discard_changes_button({override}, discard_changes) {
$allow_edit_history.attr("id", "id_realm_allow_edit_history"); $allow_edit_history.attr("id", "id_realm_allow_edit_history");
$msg_edit_limit_setting.attr("id", "id_realm_message_content_edit_limit_seconds"); $msg_edit_limit_setting.attr("id", "id_realm_message_content_edit_limit_seconds");
$msg_delete_limit_setting.attr("id", "id_realm_message_content_delete_limit_seconds"); $msg_delete_limit_setting.attr("id", "id_realm_message_content_delete_limit_seconds");
$edit_topic_policy.attr("id", "id_realm_edit_topic_policy");
$message_content_edit_limit_minutes.attr("id", "id_realm_message_content_edit_limit_minutes"); $message_content_edit_limit_minutes.attr("id", "id_realm_message_content_edit_limit_minutes");
$message_content_delete_limit_minutes.attr( $message_content_delete_limit_minutes.attr(
"id", "id",
@ -480,7 +471,6 @@ function test_discard_changes_button({override}, discard_changes) {
$allow_edit_history, $allow_edit_history,
$msg_edit_limit_setting, $msg_edit_limit_setting,
$msg_delete_limit_setting, $msg_delete_limit_setting,
$edit_topic_policy,
]); ]);
const {$discard_button, $save_button_controls, props} = createSaveButtons("msg-editing"); const {$discard_button, $save_button_controls, props} = createSaveButtons("msg-editing");
@ -494,10 +484,6 @@ function test_discard_changes_button({override}, discard_changes) {
discard_changes.call({to_$: () => $(".save-discard-widget-button.discard-button")}, ev); discard_changes.call({to_$: () => $(".save-discard-widget-button.discard-button")}, ev);
assert.equal($allow_edit_history.prop("checked"), true); assert.equal($allow_edit_history.prop("checked"), true);
assert.equal(
$edit_topic_policy.val(),
settings_config.common_message_policy_values.by_everyone.code,
);
assert.equal($msg_edit_limit_setting.val(), "3600"); assert.equal($msg_edit_limit_setting.val(), "3600");
assert.equal($message_content_edit_limit_minutes.val(), "60"); assert.equal($message_content_edit_limit_minutes.val(), "60");
assert.equal($msg_delete_limit_setting.val(), "120"); assert.equal($msg_delete_limit_setting.val(), "120");

View File

@ -1078,6 +1078,7 @@ group_setting_update_data_type = DictType(
("can_delete_own_message_group", group_setting_type), ("can_delete_own_message_group", group_setting_type),
("can_manage_all_groups", group_setting_type), ("can_manage_all_groups", group_setting_type),
("can_move_messages_between_channels_group", group_setting_type), ("can_move_messages_between_channels_group", group_setting_type),
("can_move_messages_between_topics_group", group_setting_type),
("direct_message_initiator_group", group_setting_type), ("direct_message_initiator_group", group_setting_type),
("direct_message_permission_group", group_setting_type), ("direct_message_permission_group", group_setting_type),
], ],

View File

@ -0,0 +1,23 @@
# Generated by Django 5.0.9 on 2024-10-25 14:21
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0617_remove_prefix_from_archived_streams"),
]
operations = [
migrations.AddField(
model_name="realm",
name="can_move_messages_between_topics_group",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.RESTRICT,
related_name="+",
to="zerver.usergroup",
),
),
]

View File

@ -0,0 +1,47 @@
# Generated by Django 4.2.1 on 2023-06-12 10:47
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
from django.db.models import OuterRef
def set_default_value_for_can_move_messages_between_topics_group(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
Realm = apps.get_model("zerver", "Realm")
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
edit_topic_policy_to_group_name = {
1: "role:members",
2: "role:administrators",
3: "role:fullmembers",
4: "role:moderators",
5: "role:everyone",
6: "role:nobody",
}
for id, group_name in edit_topic_policy_to_group_name.items():
Realm.objects.filter(
can_move_messages_between_topics_group=None, edit_topic_policy=id
).update(
can_move_messages_between_topics_group=NamedUserGroup.objects.filter(
name=group_name, realm=OuterRef("id"), is_system_group=True
).values("pk")
)
class Migration(migrations.Migration):
atomic = False
dependencies = [
("zerver", "0618_realm_can_move_messages_between_topics_group"),
]
operations = [
migrations.RunPython(
set_default_value_for_can_move_messages_between_topics_group,
elidable=True,
reverse_code=migrations.RunPython.noop,
)
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.0.9 on 2024-10-25 14:25
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0619_set_default_value_for_can_move_messages_between_topics_group"),
]
operations = [
migrations.AlterField(
model_name="realm",
name="can_move_messages_between_topics_group",
field=models.ForeignKey(
on_delete=django.db.models.deletion.RESTRICT,
related_name="+",
to="zerver.usergroup",
),
),
]

View File

@ -306,6 +306,11 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
"UserGroup", on_delete=models.RESTRICT, related_name="+" "UserGroup", on_delete=models.RESTRICT, related_name="+"
) )
# UserGroup which is allowed to move messages between topics.
can_move_messages_between_topics_group = models.ForeignKey(
"UserGroup", on_delete=models.RESTRICT, related_name="+"
)
# Who in the organization is allowed to edit topics of any message. # Who in the organization is allowed to edit topics of any message.
edit_topic_policy = models.PositiveSmallIntegerField(default=EditTopicPolicyEnum.EVERYONE) edit_topic_policy = models.PositiveSmallIntegerField(default=EditTopicPolicyEnum.EVERYONE)
@ -787,6 +792,15 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
default_group_name=SystemGroups.MEMBERS, default_group_name=SystemGroups.MEMBERS,
id_field_name="can_move_messages_between_channels_group_id", id_field_name="can_move_messages_between_channels_group_id",
), ),
can_move_messages_between_topics_group=GroupPermissionSetting(
require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS,
allow_internet_group=False,
allow_owners_group=False,
allow_nobody_group=True,
allow_everyone_group=True,
default_group_name=SystemGroups.EVERYONE,
id_field_name="can_move_messages_between_topics_group_id",
),
direct_message_initiator_group=GroupPermissionSetting( direct_message_initiator_group=GroupPermissionSetting(
require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS,
allow_internet_group=False, allow_internet_group=False,
@ -1203,6 +1217,8 @@ def get_realm_with_settings(realm_id: int) -> Realm:
"can_manage_all_groups__named_user_group", "can_manage_all_groups__named_user_group",
"can_move_messages_between_channels_group", "can_move_messages_between_channels_group",
"can_move_messages_between_channels_group__named_user_group", "can_move_messages_between_channels_group__named_user_group",
"can_move_messages_between_topics_group",
"can_move_messages_between_topics_group__named_user_group",
"direct_message_initiator_group", "direct_message_initiator_group",
"direct_message_initiator_group__named_user_group", "direct_message_initiator_group__named_user_group",
"direct_message_permission_group", "direct_message_permission_group",

View File

@ -897,7 +897,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
return self.has_permission("can_manage_all_groups") return self.has_permission("can_manage_all_groups")
def can_move_messages_to_another_topic(self) -> bool: def can_move_messages_to_another_topic(self) -> bool:
return self.has_permission("edit_topic_policy") return self.has_permission("can_move_messages_between_topics_group")
def can_add_custom_emoji(self) -> bool: def can_add_custom_emoji(self) -> bool:
return self.has_permission("can_add_custom_emoji_group") return self.has_permission("can_add_custom_emoji_group")

View File

@ -4515,6 +4515,20 @@ paths:
In Zulip 7.0 (feature level 159), `Nobody` was added as an option to In Zulip 7.0 (feature level 159), `Nobody` was added as an option to
`move_messages_between_streams_policy` enum. `move_messages_between_streams_policy` enum.
- $ref: "#/components/schemas/GroupSettingValue" - $ref: "#/components/schemas/GroupSettingValue"
can_move_messages_between_topics_group:
allOf:
- description: |
A [group-setting value](/api/group-setting-values) defining the set of
users who have permission to move messages from one topic to another
within a channel in the organization.
**Changes**: New in Zulip 10.0 (feature level 316). Previously, this
permission was controlled by the enum `edit_topic_policy`. Values were
1=Members, 2=Admins, 3=Full members, 4=Moderators, 5=Everyone, 6=Nobody.
In Zulip 7.0 (feature level 159), `Nobody` was added as an option to
`edit_topic_policy` enum.
- $ref: "#/components/schemas/GroupSettingValue"
can_manage_all_groups: can_manage_all_groups:
allOf: allOf:
- $ref: "#/components/schemas/GroupSettingValue" - $ref: "#/components/schemas/GroupSettingValue"
@ -8303,7 +8317,7 @@ paths:
- `allow_message_editing` - `allow_message_editing`
- `can_move_messages_between_channels_group` - `can_move_messages_between_channels_group`
- `edit_topic_policy` - `can_move_messages_between_topics_group`
- `message_content_edit_limit_seconds` - `message_content_edit_limit_seconds`
- `move_messages_within_stream_limit_seconds` - `move_messages_within_stream_limit_seconds`
- `move_messages_between_streams_limit_seconds` - `move_messages_between_streams_limit_seconds`
@ -8313,6 +8327,10 @@ paths:
of the [`realm op: update_dict`](/api/get-events#realm-update_dict) of the [`realm op: update_dict`](/api/get-events#realm-update_dict)
event in [`GET /events`](/api/get-events). event in [`GET /events`](/api/get-events).
**Changes**: In Zulip 10.0 (feature level 316), `edit_topic_policy`
was removed and replaced by `can_move_messages_between_topics_group`
realm setting.
**Changes**: In Zulip 10.0 (feature level 310), `move_messages_between_streams_policy` **Changes**: In Zulip 10.0 (feature level 310), `move_messages_between_streams_policy`
was removed and replaced by `can_move_messages_between_channels_group` was removed and replaced by `can_move_messages_between_channels_group`
realm setting. realm setting.
@ -16351,6 +16369,22 @@ paths:
In Zulip 7.0 (feature level 159), `Nobody` was added as an option to In Zulip 7.0 (feature level 159), `Nobody` was added as an option to
`move_messages_between_streams_policy` enum. `move_messages_between_streams_policy` enum.
- $ref: "#/components/schemas/GroupSettingValue" - $ref: "#/components/schemas/GroupSettingValue"
realm_can_move_messages_between_topics_group:
allOf:
- description: |
Present if `realm` is present in `fetch_event_types`.
A [group-setting value](/api/group-setting-values) defining the set of
users who have permission to move messages from one topic to another
within a channel in the organization.
**Changes**: New in Zulip 10.0 (feature level 316). Previously, this
permission was controlled by the enum `edit_topic_policy`. Values were
1=Members, 2=Admins, 3=Full members, 4=Moderators, 5=Everyone, 6=Nobody.
In Zulip 7.0 (feature level 159), `Nobody` was added as an option to
`edit_topic_policy` enum.
- $ref: "#/components/schemas/GroupSettingValue"
realm_bot_creation_policy: realm_bot_creation_policy:
type: integer type: integer
description: | description: |

View File

@ -31,6 +31,7 @@ from zerver.actions.realm_linkifiers import (
) )
from zerver.actions.realm_playgrounds import check_add_realm_playground, do_remove_realm_playground from zerver.actions.realm_playgrounds import check_add_realm_playground, do_remove_realm_playground
from zerver.actions.realm_settings import ( from zerver.actions.realm_settings import (
do_change_realm_permission_group_setting,
do_deactivate_realm, do_deactivate_realm,
do_reactivate_realm, do_reactivate_realm,
do_set_realm_authentication_methods, do_set_realm_authentication_methods,
@ -89,7 +90,7 @@ from zerver.models.linkifiers import linkifiers_for_realm
from zerver.models.realm_audit_logs import AuditLogEventType from zerver.models.realm_audit_logs import AuditLogEventType
from zerver.models.realm_emoji import EmojiInfo, get_all_custom_emoji_for_realm from zerver.models.realm_emoji import EmojiInfo, get_all_custom_emoji_for_realm
from zerver.models.realm_playgrounds import get_realm_playgrounds from zerver.models.realm_playgrounds import get_realm_playgrounds
from zerver.models.realms import EditTopicPolicyEnum, RealmDomainDict, get_realm, get_realm_domains from zerver.models.realms import RealmDomainDict, get_realm, get_realm_domains
from zerver.models.streams import get_stream from zerver.models.streams import get_stream
@ -546,14 +547,24 @@ class TestRealmAuditLog(ZulipTestCase):
1, 1,
) )
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
everyone_system_group = NamedUserGroup.objects.get(
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
)
value_expected = { value_expected = {
RealmAuditLog.OLD_VALUE: EditTopicPolicyEnum.EVERYONE, RealmAuditLog.OLD_VALUE: everyone_system_group.id,
RealmAuditLog.NEW_VALUE: EditTopicPolicyEnum.ADMINS_ONLY, RealmAuditLog.NEW_VALUE: administrators_system_group.id,
"property": "edit_topic_policy", "property": "can_move_messages_between_topics_group",
} }
do_set_realm_property( do_change_realm_permission_group_setting(
realm, "edit_topic_policy", EditTopicPolicyEnum.ADMINS_ONLY, acting_user=user realm,
"can_move_messages_between_topics_group",
administrators_system_group,
acting_user=user,
) )
self.assertEqual( self.assertEqual(
RealmAuditLog.objects.filter( RealmAuditLog.objects.filter(

View File

@ -138,6 +138,7 @@ class HomeTest(ZulipTestCase):
"realm_can_delete_own_message_group", "realm_can_delete_own_message_group",
"realm_can_manage_all_groups", "realm_can_manage_all_groups",
"realm_can_move_messages_between_channels_group", "realm_can_move_messages_between_channels_group",
"realm_can_move_messages_between_topics_group",
"realm_create_multiuse_invite_group", "realm_create_multiuse_invite_group",
"realm_create_private_stream_policy", "realm_create_private_stream_policy",
"realm_create_public_stream_policy", "realm_create_public_stream_policy",

View File

@ -6,7 +6,11 @@ import orjson
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from zerver.actions.message_edit import get_mentions_for_message_updates from zerver.actions.message_edit import get_mentions_for_message_updates
from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property from zerver.actions.realm_settings import (
do_change_realm_permission_group_setting,
do_change_realm_plan_type,
do_set_realm_property,
)
from zerver.actions.streams import do_deactivate_stream from zerver.actions.streams import do_deactivate_stream
from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group
from zerver.actions.user_topics import do_set_user_topic_visibility_policy from zerver.actions.user_topics import do_set_user_topic_visibility_policy
@ -18,7 +22,7 @@ from zerver.lib.topic import TOPIC_NAME
from zerver.lib.utils import assert_is_not_none from zerver.lib.utils import assert_is_not_none
from zerver.models import Attachment, Message, NamedUserGroup, Realm, UserProfile, UserTopic from zerver.models import Attachment, Message, NamedUserGroup, Realm, UserProfile, UserTopic
from zerver.models.groups import SystemGroups from zerver.models.groups import SystemGroups
from zerver.models.realms import EditTopicPolicyEnum, WildcardMentionPolicyEnum, get_realm from zerver.models.realms import WildcardMentionPolicyEnum, get_realm
from zerver.models.streams import get_stream from zerver.models.streams import get_stream
@ -892,7 +896,7 @@ class EditMessageTest(ZulipTestCase):
def set_message_editing_params( def set_message_editing_params(
allow_message_editing: bool, allow_message_editing: bool,
message_content_edit_limit_seconds: int | str, message_content_edit_limit_seconds: int | str,
edit_topic_policy: int, can_move_messages_between_topics_group: NamedUserGroup,
) -> None: ) -> None:
result = self.client_patch( result = self.client_patch(
"/json/realm", "/json/realm",
@ -901,7 +905,11 @@ class EditMessageTest(ZulipTestCase):
"message_content_edit_limit_seconds": orjson.dumps( "message_content_edit_limit_seconds": orjson.dumps(
message_content_edit_limit_seconds message_content_edit_limit_seconds
).decode(), ).decode(),
"edit_topic_policy": orjson.dumps(edit_topic_policy).decode(), "can_move_messages_between_topics_group": orjson.dumps(
{
"new": can_move_messages_between_topics_group.id,
}
).decode(),
}, },
) )
self.assert_json_success(result) self.assert_json_success(result)
@ -949,31 +957,35 @@ class EditMessageTest(ZulipTestCase):
message.date_sent -= timedelta(seconds=180) message.date_sent -= timedelta(seconds=180)
message.save() message.save()
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=get_realm("zulip"), is_system_group=True
)
# test the various possible message editing settings # test the various possible message editing settings
# high enough time limit, all edits allowed # high enough time limit, all edits allowed
set_message_editing_params(True, 240, EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(True, 240, administrators_system_group)
do_edit_message_assert_success(id_, "A") do_edit_message_assert_success(id_, "A")
# out of time, only topic editing allowed # out of time, only topic editing allowed
set_message_editing_params(True, 120, EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(True, 120, administrators_system_group)
do_edit_message_assert_success(id_, "B", True) do_edit_message_assert_success(id_, "B", True)
do_edit_message_assert_error(id_, "C", "The time limit for editing this message has passed") do_edit_message_assert_error(id_, "C", "The time limit for editing this message has passed")
# infinite time, all edits allowed # infinite time, all edits allowed
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(True, "unlimited", administrators_system_group)
do_edit_message_assert_success(id_, "D") do_edit_message_assert_success(id_, "D")
# without allow_message_editing, editing content is not allowed but # without allow_message_editing, editing content is not allowed but
# editing topic is allowed if topic-edit time limit has not passed # editing topic is allowed if topic-edit time limit has not passed
# irrespective of content-edit time limit. # irrespective of content-edit time limit.
set_message_editing_params(False, 240, EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(False, 240, administrators_system_group)
do_edit_message_assert_success(id_, "B", True) do_edit_message_assert_success(id_, "B", True)
set_message_editing_params(False, 240, EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(False, 240, administrators_system_group)
do_edit_message_assert_success(id_, "E", True) do_edit_message_assert_success(id_, "E", True)
set_message_editing_params(False, 120, EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(False, 120, administrators_system_group)
do_edit_message_assert_success(id_, "F", True) do_edit_message_assert_success(id_, "F", True)
set_message_editing_params(False, "unlimited", EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(False, "unlimited", administrators_system_group)
do_edit_message_assert_success(id_, "G", True) do_edit_message_assert_success(id_, "G", True)
def test_edit_message_in_archived_stream(self) -> None: def test_edit_message_in_archived_stream(self) -> None:
@ -1003,11 +1015,11 @@ class EditMessageTest(ZulipTestCase):
) )
self.assert_json_error(result, "Invalid message(s)") self.assert_json_error(result, "Invalid message(s)")
def test_edit_topic_policy(self) -> None: def test_can_move_messages_between_topics_group(self) -> None:
def set_message_editing_params( def set_message_editing_params(
allow_message_editing: bool, allow_message_editing: bool,
message_content_edit_limit_seconds: int | str, message_content_edit_limit_seconds: int | str,
edit_topic_policy: int, can_move_messages_between_topics_group: NamedUserGroup,
) -> None: ) -> None:
self.login("iago") self.login("iago")
result = self.client_patch( result = self.client_patch(
@ -1017,7 +1029,11 @@ class EditMessageTest(ZulipTestCase):
"message_content_edit_limit_seconds": orjson.dumps( "message_content_edit_limit_seconds": orjson.dumps(
message_content_edit_limit_seconds message_content_edit_limit_seconds
).decode(), ).decode(),
"edit_topic_policy": orjson.dumps(edit_topic_policy).decode(), "can_move_messages_between_topics_group": orjson.dumps(
{
"new": can_move_messages_between_topics_group.id,
}
).decode(),
}, },
) )
self.assert_json_success(result) self.assert_json_success(result)
@ -1056,30 +1072,51 @@ class EditMessageTest(ZulipTestCase):
# Guest user must be subscribed to the stream to access the message. # Guest user must be subscribed to the stream to access the message.
polonius = self.example_user("polonius") polonius = self.example_user("polonius")
realm = polonius.realm
self.subscribe(polonius, "Denmark") self.subscribe(polonius, "Denmark")
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
full_members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.FULL_MEMBERS, realm=realm, is_system_group=True
)
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
moderators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
)
everyone_system_group = NamedUserGroup.objects.get(
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
)
nobody_system_group = NamedUserGroup.objects.get(
name=SystemGroups.NOBODY, realm=realm, is_system_group=True
)
# any user can edit the topic of a message # any user can edit the topic of a message
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.EVERYONE) set_message_editing_params(True, "unlimited", everyone_system_group)
do_edit_message_assert_success(id_, "A", "polonius") do_edit_message_assert_success(id_, "A", "polonius")
# only members can edit topic of a message # only members can edit topic of a message
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.MEMBERS_ONLY) set_message_editing_params(True, "unlimited", members_system_group)
do_edit_message_assert_error( do_edit_message_assert_error(
id_, "B", "You don't have permission to edit this message", "polonius" id_, "B", "You don't have permission to edit this message", "polonius"
) )
do_edit_message_assert_success(id_, "B", "cordelia") do_edit_message_assert_success(id_, "B", "cordelia")
# only full members can edit topic of a message # only full members can edit topic of a message
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.FULL_MEMBERS_ONLY) set_message_editing_params(True, "unlimited", full_members_system_group)
cordelia = self.example_user("cordelia") cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
do_set_realm_property(cordelia.realm, "waiting_period_threshold", 10, acting_user=None)
cordelia.date_joined = timezone_now() - timedelta(days=9) cordelia.date_joined = timezone_now() - timedelta(days=9)
cordelia.save() cordelia.save()
hamlet.date_joined = timezone_now() - timedelta(days=9) hamlet.date_joined = timezone_now() - timedelta(days=9)
hamlet.save() hamlet.save()
do_set_realm_property(cordelia.realm, "waiting_period_threshold", 10, acting_user=None)
do_edit_message_assert_error( do_edit_message_assert_error(
id_, "C", "You don't have permission to edit this message", "cordelia" id_, "C", "You don't have permission to edit this message", "cordelia"
) )
@ -1089,15 +1126,12 @@ class EditMessageTest(ZulipTestCase):
id_, "C", "You don't have permission to edit this message", "hamlet" id_, "C", "You don't have permission to edit this message", "hamlet"
) )
cordelia.date_joined = timezone_now() - timedelta(days=11) do_set_realm_property(cordelia.realm, "waiting_period_threshold", 8, acting_user=None)
cordelia.save()
hamlet.date_joined = timezone_now() - timedelta(days=11)
hamlet.save()
do_edit_message_assert_success(id_, "C", "cordelia") do_edit_message_assert_success(id_, "C", "cordelia")
do_edit_message_assert_success(id_, "CD", "hamlet") do_edit_message_assert_success(id_, "CD", "hamlet")
# only moderators can edit topic of a message # only moderators can edit topic of a message
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.MODERATORS_ONLY) set_message_editing_params(True, "unlimited", moderators_system_group)
do_edit_message_assert_error( do_edit_message_assert_error(
id_, "D", "You don't have permission to edit this message", "cordelia" id_, "D", "You don't have permission to edit this message", "cordelia"
) )
@ -1108,14 +1142,14 @@ class EditMessageTest(ZulipTestCase):
do_edit_message_assert_success(id_, "D", "shiva") do_edit_message_assert_success(id_, "D", "shiva")
# only admins can edit the topics of messages # only admins can edit the topics of messages
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.ADMINS_ONLY) set_message_editing_params(True, "unlimited", administrators_system_group)
do_edit_message_assert_error( do_edit_message_assert_error(
id_, "E", "You don't have permission to edit this message", "shiva" id_, "E", "You don't have permission to edit this message", "shiva"
) )
do_edit_message_assert_success(id_, "E", "iago") do_edit_message_assert_success(id_, "E", "iago")
# even owners and admins cannot edit the topics of messages # even owners and admins cannot edit the topics of messages
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.NOBODY) set_message_editing_params(True, "unlimited", nobody_system_group)
do_edit_message_assert_error( do_edit_message_assert_error(
id_, "H", "You don't have permission to edit this message", "desdemona" id_, "H", "You don't have permission to edit this message", "desdemona"
) )
@ -1124,14 +1158,14 @@ class EditMessageTest(ZulipTestCase):
) )
# users can edit topics even if allow_message_editing is False # users can edit topics even if allow_message_editing is False
set_message_editing_params(False, "unlimited", EditTopicPolicyEnum.EVERYONE) set_message_editing_params(False, "unlimited", everyone_system_group)
do_edit_message_assert_success(id_, "D", "cordelia") do_edit_message_assert_success(id_, "D", "cordelia")
# non-admin users cannot edit topics sent > 1 week ago including # non-admin users cannot edit topics sent > 1 week ago including
# sender of the message. # sender of the message.
message.date_sent -= timedelta(seconds=604900) message.date_sent -= timedelta(seconds=604900)
message.save() message.save()
set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.EVERYONE) set_message_editing_params(True, "unlimited", everyone_system_group)
do_edit_message_assert_success(id_, "E", "iago") do_edit_message_assert_success(id_, "E", "iago")
do_edit_message_assert_success(id_, "F", "shiva") do_edit_message_assert_success(id_, "F", "shiva")
do_edit_message_assert_error( do_edit_message_assert_error(
@ -1158,6 +1192,39 @@ class EditMessageTest(ZulipTestCase):
do_edit_message_assert_success(id_, "G", "cordelia") do_edit_message_assert_success(id_, "G", "cordelia")
do_edit_message_assert_success(id_, "H", "hamlet") do_edit_message_assert_success(id_, "H", "hamlet")
# Test for checking setting for non-system user group.
user_group = check_add_user_group(
realm, "new_group", [polonius, cordelia], acting_user=cordelia
)
set_message_editing_params(True, "unlimited", user_group)
# Polonius and Cordelia are in the allowed user group, so can move messages.
do_edit_message_assert_success(id_, "I", "polonius")
do_edit_message_assert_success(id_, "J", "cordelia")
# Iago is not in the allowed user group, so cannot move messages.
do_edit_message_assert_error(
id_, "K", "You don't have permission to edit this message", "iago"
)
# Test for checking the setting for anonymous user group.
anonymous_user_group = self.create_or_update_anonymous_group_for_setting(
[cordelia],
[administrators_system_group],
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_topics_group",
anonymous_user_group,
acting_user=None,
)
# Cordelia is the direct member of the anonymous user group, so can move messages.
do_edit_message_assert_success(id_, "K", "cordelia")
# Iago is in the `administrators_system_group` subgroup, so can move messages.
do_edit_message_assert_success(id_, "L", "iago")
# Shiva is not in the anonymous user group, so cannot move messages.
do_edit_message_assert_error(
id_, "M", "You don't have permission to edit this message", "shiva"
)
@mock.patch("zerver.actions.message_edit.send_event_on_commit") @mock.patch("zerver.actions.message_edit.send_event_on_commit")
def test_topic_wildcard_mention_in_followed_topic( def test_topic_wildcard_mention_in_followed_topic(
self, mock_send_event: mock.MagicMock self, mock_send_event: mock.MagicMock

View File

@ -16,7 +16,7 @@ from zerver.lib.test_helpers import queries_captured
from zerver.lib.url_encoding import near_stream_message_url from zerver.lib.url_encoding import near_stream_message_url
from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProfile from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProfile
from zerver.models.groups import SystemGroups from zerver.models.groups import SystemGroups
from zerver.models.realms import EditTopicPolicyEnum, get_realm from zerver.models.realms import get_realm
from zerver.models.streams import get_stream from zerver.models.streams import get_stream
@ -1130,10 +1130,19 @@ class MessageMoveStreamTest(ZulipTestCase):
(user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics( (user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics(
"othello", "old_stream_1", "new_stream_1", "test" "othello", "old_stream_1", "new_stream_1", "test"
) )
realm = user_profile.realm realm = user_profile.realm
realm.edit_topic_policy = EditTopicPolicyEnum.ADMINS_ONLY
realm.save() administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_move_messages_between_topics_group",
administrators_system_group,
acting_user=None,
)
self.login("cordelia") self.login("cordelia")
members_system_group = NamedUserGroup.objects.get( members_system_group = NamedUserGroup.objects.get(
@ -1175,7 +1184,7 @@ class MessageMoveStreamTest(ZulipTestCase):
"iago", "test move stream", "new stream", "test" "iago", "test move stream", "new stream", "test"
) )
with self.assert_database_query_count(53), self.assert_memcached_count(14): with self.assert_database_query_count(55), self.assert_memcached_count(14):
result = self.client_patch( result = self.client_patch(
f"/json/messages/{msg_id}", f"/json/messages/{msg_id}",
{ {

View File

@ -266,7 +266,7 @@ class MessageMoveTopicTest(ZulipTestCase):
# state + 1/user with a UserTopic row for the events data) # state + 1/user with a UserTopic row for the events data)
# beyond what is typical were there not UserTopic records to # beyond what is typical were there not UserTopic records to
# update. Ideally, we'd eliminate the per-user component. # update. Ideally, we'd eliminate the per-user component.
with self.assert_database_query_count(25): with self.assert_database_query_count(27):
check_update_message( check_update_message(
user_profile=hamlet, user_profile=hamlet,
message_id=message_id, message_id=message_id,
@ -426,7 +426,7 @@ class MessageMoveTopicTest(ZulipTestCase):
set_topic_visibility_policy(desdemona, muted_topics, UserTopic.VisibilityPolicy.MUTED) set_topic_visibility_policy(desdemona, muted_topics, UserTopic.VisibilityPolicy.MUTED)
set_topic_visibility_policy(cordelia, muted_topics, UserTopic.VisibilityPolicy.MUTED) set_topic_visibility_policy(cordelia, muted_topics, UserTopic.VisibilityPolicy.MUTED)
with self.assert_database_query_count(29): with self.assert_database_query_count(31):
check_update_message( check_update_message(
user_profile=desdemona, user_profile=desdemona,
message_id=message_id, message_id=message_id,
@ -449,7 +449,7 @@ class MessageMoveTopicTest(ZulipTestCase):
second_message_id = self.send_stream_message( second_message_id = self.send_stream_message(
hamlet, stream_name, topic_name="changed topic name", content="Second message" hamlet, stream_name, topic_name="changed topic name", content="Second message"
) )
with self.assert_database_query_count(23): with self.assert_database_query_count(24):
check_update_message( check_update_message(
user_profile=desdemona, user_profile=desdemona,
message_id=second_message_id, message_id=second_message_id,
@ -528,7 +528,7 @@ class MessageMoveTopicTest(ZulipTestCase):
users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id) users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id)
change_all_topic_name = "Topic 1 edited" change_all_topic_name = "Topic 1 edited"
with self.assert_database_query_count(30): with self.assert_database_query_count(32):
check_update_message( check_update_message(
user_profile=hamlet, user_profile=hamlet,
message_id=message_id, message_id=message_id,

View File

@ -143,6 +143,7 @@ def update_realm(
can_create_web_public_channel_group: Json[GroupSettingChangeRequest] | None = None, can_create_web_public_channel_group: Json[GroupSettingChangeRequest] | None = None,
can_manage_all_groups: Json[GroupSettingChangeRequest] | None = None, can_manage_all_groups: Json[GroupSettingChangeRequest] | None = None,
can_move_messages_between_channels_group: Json[GroupSettingChangeRequest] | None = None, can_move_messages_between_channels_group: Json[GroupSettingChangeRequest] | None = None,
can_move_messages_between_topics_group: Json[GroupSettingChangeRequest] | None = None,
direct_message_initiator_group: Json[GroupSettingChangeRequest] | None = None, direct_message_initiator_group: Json[GroupSettingChangeRequest] | None = None,
direct_message_permission_group: Json[GroupSettingChangeRequest] | None = None, direct_message_permission_group: Json[GroupSettingChangeRequest] | None = None,
invite_to_stream_policy: Json[CommonPolicyEnum] | None = None, invite_to_stream_policy: Json[CommonPolicyEnum] | None = None,