diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 83e550074c..3d852599e6 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with. ## 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** * [POST /register](/api/register-queue), [`GET diff --git a/version.py b/version.py index c4dd04a563..a1d505fa9e 100644 --- a/version.py +++ b/version.py @@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 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 # only when going from an old version of the code to a newer version. Bump diff --git a/web/src/message_edit.ts b/web/src/message_edit.ts index 5e4b77e304..ed7e6107af 100644 --- a/web/src/message_edit.ts +++ b/web/src/message_edit.ts @@ -111,8 +111,8 @@ export function is_topic_editable(message: Message, edit_limit_seconds_buffer = } // Organization admins and moderators can edit message topics indefinitely, - // irrespective of the topic editing deadline, if edit_topic_policy allows - // them to do so. + // irrespective of the topic editing deadline, if they are in the + // can_move_messages_between_topics_group. if (current_user.is_admin || current_user.is_moderator) { return true; } diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index 7d15783a77..01f0afe75f 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -208,7 +208,6 @@ export function dispatch_normal_event(event) { const realm_settings = { allow_edit_history: noop, allow_message_editing: noop, - edit_topic_policy: noop, avatar_changes_disabled: settings_account.update_avatar_change_display, bot_creation_policy: settings_bots.update_bot_permissions_ui, can_delete_any_message_group: noop, @@ -323,7 +322,7 @@ export function dispatch_normal_event(event) { 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(); } diff --git a/web/src/settings_components.ts b/web/src/settings_components.ts index ac53b7c897..c4699c2a48 100644 --- a/web/src/settings_components.ts +++ b/web/src/settings_components.ts @@ -238,7 +238,6 @@ export const simple_dropdown_realm_settings_schema = realm_schema.pick({ realm_invite_to_stream_policy: true, realm_invite_to_realm_policy: true, realm_wildcard_mention_policy: true, - realm_edit_topic_policy: true, realm_org_type: true, }); export type SimpleDropdownRealmSettings = z.infer; @@ -484,6 +483,7 @@ const dropdown_widget_map = new Map([ ["realm_can_delete_any_message_group", null], ["realm_can_delete_own_message_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_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_own_message_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_permission_group": 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_own_message_group", "can_move_messages_between_channels_group", + "can_move_messages_between_topics_group", "create_multiuse_invite_group", "direct_message_initiator_group", "direct_message_permission_group", diff --git a/web/src/settings_config.ts b/web/src/settings_config.ts index bdfb6bcd97..609f7dbae7 100644 --- a/web/src/settings_config.ts +++ b/web/src/settings_config.ts @@ -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 = [ { text: $t({defaultMessage: "Any time"}), diff --git a/web/src/settings_data.ts b/web/src/settings_data.ts index 11701e4a1d..bdca2ff94d 100644 --- a/web/src/settings_data.ts +++ b/web/src/settings_data.ts @@ -71,18 +71,7 @@ function user_has_permission(policy_value: number): boolean { return true; } - if (page_params.is_spectator) { - 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) { + if (page_params.is_spectator || current_user.is_guest) { return false; } @@ -262,7 +251,11 @@ export function user_can_add_custom_emoji(): 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 { diff --git a/web/src/settings_org.ts b/web/src/settings_org.ts index 06a61ec1ba..5c8306d2a9 100644 --- a/web/src/settings_org.ts +++ b/web/src/settings_org.ts @@ -161,9 +161,7 @@ export function enable_or_disable_group_permission_settings(): void { type OrganizationSettingsOptions = { common_policy_values: SettingOptionValueWithKey[]; wildcard_mention_policy_values: SettingOptionValueWithKey[]; - common_message_policy_values: SettingOptionValueWithKey[]; invite_to_realm_policy_values: SettingOptionValueWithKey[]; - edit_topic_policy_values: SettingOptionValueWithKey[]; }; 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( 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( 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( related_setting_name: - | "realm_edit_topic_policy" + | "realm_can_move_messages_between_topics_group" | "realm_can_move_messages_between_channels_group", ): boolean { - if (related_setting_name === "realm_edit_topic_policy") { - const setting_value_string = $( - `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( $(`#id_${related_setting_name}`), ); @@ -334,7 +308,9 @@ function set_msg_move_limit_setting(property_name: MessageMoveTimeLimitSetting): let disable_setting; 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 { disable_setting = message_move_limit_setting_enabled( "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": set_msg_move_limit_setting("realm_move_messages_between_streams_limit_seconds"); 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": set_org_join_restrictions_dropdown(); 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_own_message_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"); settings_components.set_dropdown_list_widget_setting_value( property_name, @@ -1018,6 +998,13 @@ export function set_up_dropdown_widget_for_realm_group_settings(): void { 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 } @@ -1338,30 +1325,6 @@ export function build_page(): void { 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) => { // This prevents the disappearance of modal when there are // no allowed domains otherwise it gets closed due to diff --git a/web/src/state_data.ts b/web/src/state_data.ts index d9eb2b66a3..571802fe09 100644 --- a/web/src/state_data.ts +++ b/web/src/state_data.ts @@ -296,6 +296,7 @@ export const realm_schema = z.object({ realm_can_delete_own_message_group: z.number(), realm_can_manage_all_groups: group_setting_value_schema, 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_date_created: z.number(), realm_default_code_block_language: z.string(), @@ -321,7 +322,6 @@ export const realm_schema = z.object({ allow_subdomains: z.boolean(), }), ), - realm_edit_topic_policy: z.number(), realm_email_auth_enabled: z.boolean(), realm_email_changes_disabled: z.boolean(), realm_emails_restricted_to_domains: z.boolean(), diff --git a/web/src/stream_popover.js b/web/src/stream_popover.js index 4157eb6e99..f627063061 100644 --- a/web/src/stream_popover.js +++ b/web/src/stream_popover.js @@ -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, // 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 - // 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 // below if block to enable or disable the stream and topic input. let disable_stream_input = !settings_data.user_can_move_messages_between_streams(); diff --git a/web/templates/settings/organization_permissions_admin.hbs b/web/templates/settings/organization_permissions_admin.hbs index 4ae2f3da11..666eff06e5 100644 --- a/web/templates/settings/organization_permissions_admin.hbs +++ b/web/templates/settings/organization_permissions_admin.hbs @@ -191,12 +191,11 @@ {{> settings_save_discard_widget section_name="moving-msgs" }} -
- - -
+ + {{> ../dropdown_widget_with_label + widget_name="realm_can_move_messages_between_topics_group" + label=(t 'Who can move messages to another topic') + value_type="number" }}
diff --git a/web/tests/dispatch.test.js b/web/tests/dispatch.test.js index 2656f02ea2..a518b15c62 100644 --- a/web/tests/dispatch.test.js +++ b/web/tests/dispatch.test.js @@ -588,10 +588,10 @@ run_test("realm settings", ({override}) => { override(realm, "realm_create_multiuse_invite_group", 1); override(realm, "realm_allow_message_editing", false); 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_can_add_custom_emoji_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_plan_type", 2); 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_allow_message_editing, true); assert_same(realm.realm_message_content_edit_limit_seconds, 5); - assert_same(realm.realm_edit_topic_policy, 4); assert_same(realm.realm_authentication_methods, { Google: {enabled: true, available: true}, }); 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_move_messages_between_topics_group, 3); assert_same(realm.realm_direct_message_permission_group, 3); assert_same(realm.realm_plan_type, 3); assert_same(realm.realm_upload_quota_mib, 50000); diff --git a/web/tests/lib/events.js b/web/tests/lib/events.js index 21940c2662..a395dc89ab 100644 --- a/web/tests/lib/events.js +++ b/web/tests/lib/events.js @@ -365,13 +365,13 @@ exports.fixtures = { data: { allow_message_editing: true, message_content_edit_limit_seconds: 5, - edit_topic_policy: 4, create_multiuse_invite_group: 3, authentication_methods: { Google: {enabled: true, available: true}, }, can_add_custom_emoji_group: 3, can_create_public_channel_group: 3, + can_move_messages_between_topics_group: 3, direct_message_permission_group: 3, plan_type: 3, upload_quota_mib: 50000, diff --git a/web/tests/popover_menus_data.test.js b/web/tests/popover_menus_data.test.js index 61e25a3514..a7be16f87d 100644 --- a/web/tests/popover_menus_data.test.js +++ b/web/tests/popover_menus_data.test.js @@ -142,7 +142,6 @@ function set_page_params_no_edit_restrictions({override}) { override(realm, "realm_allow_edit_history", true); override(realm, "realm_message_content_delete_limit_seconds", null); override(realm, "realm_enable_read_receipts", true); - override(realm, "realm_edit_topic_policy", 5); 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}); override(realm, "realm_can_delete_any_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); // Get message with maximum permissions available // Initialize message list @@ -259,6 +259,8 @@ test("not_my_message_view_actions", ({override}) => { test("not_my_message_view_source_and_move", ({override}) => { set_page_params_no_edit_restrictions({override}); 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 const list = init_message_list(); diff --git a/web/tests/settings_data.test.js b/web/tests/settings_data.test.js index 668feaa0c7..1957b5e021 100644 --- a/web/tests/settings_data.test.js +++ b/web/tests/settings_data.test.js @@ -163,66 +163,6 @@ test_policy( 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( "realm_can_add_custom_emoji_group", settings_data.user_can_add_custom_emoji, @@ -243,6 +183,11 @@ test_realm_group_settings( 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}) => { override(user_settings, "color_scheme", settings_config.color_scheme_values.dark.code); assert.equal(settings_data.using_dark_theme(), true); diff --git a/web/tests/settings_org.test.js b/web/tests/settings_org.test.js index 26c58b235d..66af886a2c 100644 --- a/web/tests/settings_org.test.js +++ b/web/tests/settings_org.test.js @@ -439,19 +439,11 @@ function test_discard_changes_button({override}, discard_changes) { }; 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_message_content_edit_limit_seconds", 3600); override(realm, "realm_message_content_delete_limit_seconds", 120); 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( "custom_period", ); @@ -468,7 +460,6 @@ function test_discard_changes_button({override}, discard_changes) { $allow_edit_history.attr("id", "id_realm_allow_edit_history"); $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"); - $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_delete_limit_minutes.attr( "id", @@ -480,7 +471,6 @@ function test_discard_changes_button({override}, discard_changes) { $allow_edit_history, $msg_edit_limit_setting, $msg_delete_limit_setting, - $edit_topic_policy, ]); 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); 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($message_content_edit_limit_minutes.val(), "60"); assert.equal($msg_delete_limit_setting.val(), "120"); diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index 694355386c..046aea37f8 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -1078,6 +1078,7 @@ group_setting_update_data_type = DictType( ("can_delete_own_message_group", group_setting_type), ("can_manage_all_groups", 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_permission_group", group_setting_type), ], diff --git a/zerver/migrations/0618_realm_can_move_messages_between_topics_group.py b/zerver/migrations/0618_realm_can_move_messages_between_topics_group.py new file mode 100644 index 0000000000..95a082159a --- /dev/null +++ b/zerver/migrations/0618_realm_can_move_messages_between_topics_group.py @@ -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", + ), + ), + ] diff --git a/zerver/migrations/0619_set_default_value_for_can_move_messages_between_topics_group.py b/zerver/migrations/0619_set_default_value_for_can_move_messages_between_topics_group.py new file mode 100644 index 0000000000..f0c5ca9993 --- /dev/null +++ b/zerver/migrations/0619_set_default_value_for_can_move_messages_between_topics_group.py @@ -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, + ) + ] diff --git a/zerver/migrations/0620_alter_realm_can_move_messages_between_topics_group.py b/zerver/migrations/0620_alter_realm_can_move_messages_between_topics_group.py new file mode 100644 index 0000000000..1870e8d00e --- /dev/null +++ b/zerver/migrations/0620_alter_realm_can_move_messages_between_topics_group.py @@ -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", + ), + ), + ] diff --git a/zerver/models/realms.py b/zerver/models/realms.py index 7c0e2d87b1..4d36a452fa 100644 --- a/zerver/models/realms.py +++ b/zerver/models/realms.py @@ -306,6 +306,11 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub "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. 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, 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( require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, 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_move_messages_between_channels_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__named_user_group", "direct_message_permission_group", diff --git a/zerver/models/users.py b/zerver/models/users.py index 406271b38f..a6806dee56 100644 --- a/zerver/models/users.py +++ b/zerver/models/users.py @@ -897,7 +897,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): return self.has_permission("can_manage_all_groups") 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: return self.has_permission("can_add_custom_emoji_group") diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 0f543903e8..db526a7692 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -4515,6 +4515,20 @@ paths: In Zulip 7.0 (feature level 159), `Nobody` was added as an option to `move_messages_between_streams_policy` enum. - $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: allOf: - $ref: "#/components/schemas/GroupSettingValue" @@ -8303,7 +8317,7 @@ paths: - `allow_message_editing` - `can_move_messages_between_channels_group` - - `edit_topic_policy` + - `can_move_messages_between_topics_group` - `message_content_edit_limit_seconds` - `move_messages_within_stream_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) 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` was removed and replaced by `can_move_messages_between_channels_group` realm setting. @@ -16351,6 +16369,22 @@ paths: In Zulip 7.0 (feature level 159), `Nobody` was added as an option to `move_messages_between_streams_policy` enum. - $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: type: integer description: | diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index 9b2e1c40c8..ab846e52eb 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -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_settings import ( + do_change_realm_permission_group_setting, do_deactivate_realm, do_reactivate_realm, 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_emoji import EmojiInfo, get_all_custom_emoji_for_realm 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 @@ -546,14 +547,24 @@ class TestRealmAuditLog(ZulipTestCase): 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 = { - RealmAuditLog.OLD_VALUE: EditTopicPolicyEnum.EVERYONE, - RealmAuditLog.NEW_VALUE: EditTopicPolicyEnum.ADMINS_ONLY, - "property": "edit_topic_policy", + RealmAuditLog.OLD_VALUE: everyone_system_group.id, + RealmAuditLog.NEW_VALUE: administrators_system_group.id, + "property": "can_move_messages_between_topics_group", } - do_set_realm_property( - realm, "edit_topic_policy", EditTopicPolicyEnum.ADMINS_ONLY, acting_user=user + do_change_realm_permission_group_setting( + realm, + "can_move_messages_between_topics_group", + administrators_system_group, + acting_user=user, ) self.assertEqual( RealmAuditLog.objects.filter( diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 031a9b872b..15e91e134f 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -138,6 +138,7 @@ class HomeTest(ZulipTestCase): "realm_can_delete_own_message_group", "realm_can_manage_all_groups", "realm_can_move_messages_between_channels_group", + "realm_can_move_messages_between_topics_group", "realm_create_multiuse_invite_group", "realm_create_private_stream_policy", "realm_create_public_stream_policy", diff --git a/zerver/tests/test_message_edit.py b/zerver/tests/test_message_edit.py index 54ae8639a0..5a74bce349 100644 --- a/zerver/tests/test_message_edit.py +++ b/zerver/tests/test_message_edit.py @@ -6,7 +6,11 @@ import orjson from django.utils.timezone import now as timezone_now 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.user_groups import add_subgroups_to_user_group, check_add_user_group 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.models import Attachment, Message, NamedUserGroup, Realm, UserProfile, UserTopic 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 @@ -892,7 +896,7 @@ class EditMessageTest(ZulipTestCase): def set_message_editing_params( allow_message_editing: bool, message_content_edit_limit_seconds: int | str, - edit_topic_policy: int, + can_move_messages_between_topics_group: NamedUserGroup, ) -> None: result = self.client_patch( "/json/realm", @@ -901,7 +905,11 @@ class EditMessageTest(ZulipTestCase): "message_content_edit_limit_seconds": orjson.dumps( message_content_edit_limit_seconds ).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) @@ -949,31 +957,35 @@ class EditMessageTest(ZulipTestCase): message.date_sent -= timedelta(seconds=180) 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 # 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") # 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_error(id_, "C", "The time limit for editing this message has passed") # 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") # without allow_message_editing, editing content is not allowed but # editing topic is allowed if topic-edit time limit has not passed # 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) - 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) - 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) - 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) def test_edit_message_in_archived_stream(self) -> None: @@ -1003,11 +1015,11 @@ class EditMessageTest(ZulipTestCase): ) 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( allow_message_editing: bool, message_content_edit_limit_seconds: int | str, - edit_topic_policy: int, + can_move_messages_between_topics_group: NamedUserGroup, ) -> None: self.login("iago") result = self.client_patch( @@ -1017,7 +1029,11 @@ class EditMessageTest(ZulipTestCase): "message_content_edit_limit_seconds": orjson.dumps( message_content_edit_limit_seconds ).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) @@ -1056,30 +1072,51 @@ class EditMessageTest(ZulipTestCase): # Guest user must be subscribed to the stream to access the message. polonius = self.example_user("polonius") + realm = polonius.realm 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 - 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") # 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( id_, "B", "You don't have permission to edit this message", "polonius" ) do_edit_message_assert_success(id_, "B", "cordelia") # 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") 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.save() hamlet.date_joined = timezone_now() - timedelta(days=9) hamlet.save() + + do_set_realm_property(cordelia.realm, "waiting_period_threshold", 10, acting_user=None) do_edit_message_assert_error( 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" ) - cordelia.date_joined = timezone_now() - timedelta(days=11) - cordelia.save() - hamlet.date_joined = timezone_now() - timedelta(days=11) - hamlet.save() + do_set_realm_property(cordelia.realm, "waiting_period_threshold", 8, acting_user=None) do_edit_message_assert_success(id_, "C", "cordelia") do_edit_message_assert_success(id_, "CD", "hamlet") # 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( 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") # 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( id_, "E", "You don't have permission to edit this message", "shiva" ) do_edit_message_assert_success(id_, "E", "iago") # 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( 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 - 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") # non-admin users cannot edit topics sent > 1 week ago including # sender of the message. message.date_sent -= timedelta(seconds=604900) 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_, "F", "shiva") 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_, "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") def test_topic_wildcard_mention_in_followed_topic( self, mock_send_event: mock.MagicMock diff --git a/zerver/tests/test_message_move_stream.py b/zerver/tests/test_message_move_stream.py index 3a2fcc425d..6b4fd86797 100644 --- a/zerver/tests/test_message_move_stream.py +++ b/zerver/tests/test_message_move_stream.py @@ -16,7 +16,7 @@ from zerver.lib.test_helpers import queries_captured from zerver.lib.url_encoding import near_stream_message_url from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProfile 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 @@ -1130,10 +1130,19 @@ class MessageMoveStreamTest(ZulipTestCase): (user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics( "othello", "old_stream_1", "new_stream_1", "test" ) - 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") members_system_group = NamedUserGroup.objects.get( @@ -1175,7 +1184,7 @@ class MessageMoveStreamTest(ZulipTestCase): "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( f"/json/messages/{msg_id}", { diff --git a/zerver/tests/test_message_move_topic.py b/zerver/tests/test_message_move_topic.py index 2e1d31e060..8a9f745a19 100644 --- a/zerver/tests/test_message_move_topic.py +++ b/zerver/tests/test_message_move_topic.py @@ -266,7 +266,7 @@ class MessageMoveTopicTest(ZulipTestCase): # state + 1/user with a UserTopic row for the events data) # beyond what is typical were there not UserTopic records to # 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( user_profile=hamlet, 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(cordelia, muted_topics, UserTopic.VisibilityPolicy.MUTED) - with self.assert_database_query_count(29): + with self.assert_database_query_count(31): check_update_message( user_profile=desdemona, message_id=message_id, @@ -449,7 +449,7 @@ class MessageMoveTopicTest(ZulipTestCase): second_message_id = self.send_stream_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( user_profile=desdemona, 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) change_all_topic_name = "Topic 1 edited" - with self.assert_database_query_count(30): + with self.assert_database_query_count(32): check_update_message( user_profile=hamlet, message_id=message_id, diff --git a/zerver/views/realm.py b/zerver/views/realm.py index fc7597975b..397afde2ad 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -143,6 +143,7 @@ def update_realm( can_create_web_public_channel_group: 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_topics_group: Json[GroupSettingChangeRequest] | None = None, direct_message_initiator_group: Json[GroupSettingChangeRequest] | None = None, direct_message_permission_group: Json[GroupSettingChangeRequest] | None = None, invite_to_stream_policy: Json[CommonPolicyEnum] | None = None,