mirror of https://github.com/zulip/zulip.git
stream_settings: Add 'Default stream' option in edit stream UI.
This commit adds a 'Default stream for new users' checkbox in the stream editing UI to allow admins to easily add or remove a stream as the default stream for new users. Previously, this functionality required navigating to separate menu. Fixes a part of #24048.
This commit is contained in:
parent
d346b9bd1c
commit
a81715786c
|
@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 8.0
|
||||
|
||||
**Feature level 200**
|
||||
|
||||
* [`PATCH /streams/{stream_id}`](/api/update-stream): Added
|
||||
`is_default_stream` parameter to add or remove the stream as a default
|
||||
stream for new users.
|
||||
|
||||
**Feature level 199**
|
||||
|
||||
* [`POST /register`](/api/register-queue), [`GET /events`][/api/get-events],
|
||||
|
|
|
@ -102,6 +102,7 @@ export function dispatch_normal_event(event) {
|
|||
case "default_streams":
|
||||
stream_data.set_realm_default_streams(event.default_streams);
|
||||
settings_streams.update_default_streams_table();
|
||||
stream_settings_ui.update_is_default_stream();
|
||||
break;
|
||||
|
||||
case "delete_message": {
|
||||
|
|
|
@ -168,6 +168,9 @@ function get_property_value(property_name, for_realm_default_settings, sub) {
|
|||
if (property_name === "stream_privacy") {
|
||||
return stream_data.get_stream_privacy_policy(sub.stream_id);
|
||||
}
|
||||
if (property_name === "is_default_stream") {
|
||||
return stream_data.is_default_stream_id(sub.stream_id);
|
||||
}
|
||||
|
||||
return sub[property_name];
|
||||
}
|
||||
|
|
|
@ -247,6 +247,7 @@ export function show_settings_for(node) {
|
|||
stream_post_policy_values: stream_data.stream_post_policy_values,
|
||||
stream_privacy_policy_values: stream_data.stream_privacy_policy_values,
|
||||
stream_privacy_policy: stream_data.get_stream_privacy_policy(stream_id),
|
||||
check_default_stream: stream_data.is_default_stream_id(stream_id),
|
||||
zulip_plan_is_not_limited: page_params.zulip_plan_is_not_limited,
|
||||
upgrade_text_for_wide_organization_logo:
|
||||
page_params.upgrade_text_for_wide_organization_logo,
|
||||
|
@ -679,6 +680,9 @@ export function initialize() {
|
|||
const sub = sub_store.get(stream_id);
|
||||
const $subsection = $(e.target).closest(".settings-subsection-parent");
|
||||
settings_org.save_discard_widget_status_handler($subsection, false, sub);
|
||||
if (sub) {
|
||||
stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -714,6 +718,7 @@ export function initialize() {
|
|||
for (const elem of settings_org.get_subsection_property_elements($subsection)) {
|
||||
settings_org.discard_property_element_changes(elem, false, sub);
|
||||
}
|
||||
stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection);
|
||||
const $save_btn_controls = $(e.target).closest(".save-button-controls");
|
||||
settings_org.change_save_button_state($save_btn_controls, "discarded");
|
||||
},
|
||||
|
|
|
@ -250,6 +250,14 @@ export function update_can_remove_subscribers_group_id(sub, new_value) {
|
|||
stream_edit_subscribers.rerender_subscribers_list(sub);
|
||||
}
|
||||
|
||||
export function update_is_default_stream() {
|
||||
const active_stream_id = get_active_data().id;
|
||||
if (active_stream_id) {
|
||||
const sub = sub_store.get(active_stream_id);
|
||||
stream_ui_updates.update_setting_element(sub, "is_default_stream");
|
||||
}
|
||||
}
|
||||
|
||||
export function set_color(stream_id, color) {
|
||||
const sub = sub_store.get(stream_id);
|
||||
stream_edit.set_stream_property(sub, "color", color);
|
||||
|
@ -1156,7 +1164,7 @@ export function update_public_stream_privacy_option_state($container) {
|
|||
$public_stream_elem.prop("disabled", !settings_data.user_can_create_public_streams());
|
||||
}
|
||||
|
||||
export function update_private_stream_privacy_option_state($container) {
|
||||
export function update_private_stream_privacy_option_state($container, is_default_stream = false) {
|
||||
// Disable both "Private, shared history" and "Private, protected history" options.
|
||||
const $private_stream_elem = $container.find(
|
||||
`input[value='${CSS.escape(stream_data.stream_privacy_policy_values.private.code)}']`,
|
||||
|
@ -1167,11 +1175,18 @@ export function update_private_stream_privacy_option_state($container) {
|
|||
)}']`,
|
||||
);
|
||||
|
||||
$private_stream_elem.prop("disabled", !settings_data.user_can_create_private_streams());
|
||||
$private_with_public_history_elem.prop(
|
||||
"disabled",
|
||||
!settings_data.user_can_create_private_streams(),
|
||||
);
|
||||
const disable_private_stream_options =
|
||||
is_default_stream || !settings_data.user_can_create_private_streams();
|
||||
|
||||
$private_stream_elem.prop("disabled", disable_private_stream_options);
|
||||
$private_with_public_history_elem.prop("disabled", disable_private_stream_options);
|
||||
|
||||
$private_stream_elem
|
||||
.closest("div")
|
||||
.toggleClass("default_stream_private_tooltip", is_default_stream);
|
||||
$private_with_public_history_elem
|
||||
.closest("div")
|
||||
.toggleClass("default_stream_private_tooltip", is_default_stream);
|
||||
}
|
||||
|
||||
export function hide_or_disable_stream_privacy_options_if_required($container) {
|
||||
|
|
|
@ -113,6 +113,24 @@ export function update_regular_sub_settings(sub) {
|
|||
}
|
||||
}
|
||||
|
||||
export function update_default_stream_and_stream_privacy_state($container) {
|
||||
const $default_stream = $container.find(".default-stream");
|
||||
const privacy_type = $container.find("input[type=radio][name=privacy]:checked").val();
|
||||
const is_invite_only =
|
||||
privacy_type === "invite-only" || privacy_type === "invite-only-public-history";
|
||||
|
||||
// If a private stream option is selected, the default stream option is disabled.
|
||||
$default_stream.find("input").prop("disabled", is_invite_only);
|
||||
$default_stream.toggleClass(
|
||||
"control-label-disabled default_stream_private_tooltip",
|
||||
is_invite_only,
|
||||
);
|
||||
|
||||
// If the default stream option is checked, the private stream options are disabled.
|
||||
const is_default_stream = $default_stream.find("input").prop("checked");
|
||||
stream_settings_ui.update_private_stream_privacy_option_state($container, is_default_stream);
|
||||
}
|
||||
|
||||
export function enable_or_disable_permission_settings_in_edit_panel(sub) {
|
||||
if (!hash_util.is_editing_stream(sub.stream_id)) {
|
||||
return;
|
||||
|
@ -129,6 +147,8 @@ export function enable_or_disable_permission_settings_in_edit_panel(sub) {
|
|||
return;
|
||||
}
|
||||
|
||||
update_default_stream_and_stream_privacy_state($stream_settings);
|
||||
|
||||
const disable_message_retention_setting =
|
||||
!page_params.zulip_plan_is_not_limited || !page_params.is_owner;
|
||||
$stream_settings
|
||||
|
|
|
@ -292,6 +292,28 @@ export function initialize() {
|
|||
},
|
||||
});
|
||||
|
||||
delegate("body", {
|
||||
target: [".settings-radio-input-parent.default_stream_private_tooltip"],
|
||||
content: $t({
|
||||
defaultMessage: "Default streams for new users cannot be made private.",
|
||||
}),
|
||||
appendTo: () => document.body,
|
||||
onHidden(instance) {
|
||||
instance.destroy();
|
||||
},
|
||||
});
|
||||
|
||||
delegate("body", {
|
||||
target: [".default-stream.default_stream_private_tooltip"],
|
||||
content: $t({
|
||||
defaultMessage: "Private streams cannot be default streams for new users.",
|
||||
}),
|
||||
appendTo: () => document.body,
|
||||
onHidden(instance) {
|
||||
instance.destroy();
|
||||
},
|
||||
});
|
||||
|
||||
delegate("body", {
|
||||
target: ["#generate_multiuse_invite_radio_container.disabled_setting_tooltip"],
|
||||
content: $t({
|
||||
|
|
|
@ -1006,6 +1006,14 @@ div.settings-radio-input-parent {
|
|||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&.default_stream_private_tooltip {
|
||||
cursor: not-allowed;
|
||||
|
||||
& label {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stream-permissions,
|
||||
|
@ -1039,6 +1047,15 @@ div.settings-radio-input-parent {
|
|||
max-width: 100%;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.default-stream {
|
||||
margin: 25px 0;
|
||||
width: fit-content;
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#change_user_group_description,
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
stream_post_policy_values=../stream_post_policy_values
|
||||
stream_privacy_policy_values=../stream_privacy_policy_values
|
||||
stream_privacy_policy=../stream_privacy_policy
|
||||
check_default_stream=../check_default_stream
|
||||
zulip_plan_is_not_limited=../zulip_plan_is_not_limited
|
||||
upgrade_text_for_wide_organization_logo=../upgrade_text_for_wide_organization_logo
|
||||
is_business_type_org=../is_business_type_org
|
||||
|
|
|
@ -16,6 +16,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#if is_stream_edit}}
|
||||
<div class="default-stream">
|
||||
{{> ../settings/settings_checkbox
|
||||
prefix="id_"
|
||||
setting_name="is_default_stream"
|
||||
is_checked=check_default_stream
|
||||
label="Default stream for new users"
|
||||
help_link="/help/set-default-streams-for-new-users"
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="input-group">
|
||||
<label class="dropdown-title">{{t 'Who can post to the stream?'}}
|
||||
{{> ../help_link_widget link="/help/stream-sending-policy" }}
|
||||
|
|
|
@ -293,6 +293,7 @@ run_test("custom profile fields", ({override}) => {
|
|||
run_test("default_streams", ({override}) => {
|
||||
const event = event_fixtures.default_streams;
|
||||
override(settings_streams, "update_default_streams_table", noop);
|
||||
override(stream_settings_ui, "update_is_default_stream", noop);
|
||||
const stub = make_stub();
|
||||
override(stream_data, "set_realm_default_streams", stub.f);
|
||||
dispatch(event);
|
||||
|
|
|
@ -15978,6 +15978,20 @@ paths:
|
|||
type: boolean
|
||||
example: false
|
||||
required: false
|
||||
- name: is_default_stream
|
||||
in: query
|
||||
description: |
|
||||
Add or remove the stream as a [default stream][default-stream]
|
||||
for new users joining the organization.
|
||||
|
||||
[default-stream]: /help/set-default-streams-for-new-users
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 200). Previously, default stream status
|
||||
could only be changed using the [dedicated API endpoint](/api/add-default-stream).
|
||||
schema:
|
||||
type: boolean
|
||||
example: false
|
||||
required: false
|
||||
- $ref: "#/components/parameters/StreamPostPolicy"
|
||||
- $ref: "#/components/parameters/MessageRetentionDays"
|
||||
- $ref: "#/components/parameters/CanRemoveSubscribersGroupId"
|
||||
|
|
|
@ -714,7 +714,7 @@ class StreamAdminTest(ZulipTestCase):
|
|||
"is_private": orjson.dumps(True).decode(),
|
||||
}
|
||||
result = self.client_patch(f"/json/streams/{default_stream.id}", params)
|
||||
self.assert_json_error(result, "Default streams cannot be made private.")
|
||||
self.assert_json_error(result, "A default stream cannot be private.")
|
||||
self.assertFalse(default_stream.invite_only)
|
||||
|
||||
do_change_user_role(user_profile, UserProfile.ROLE_MEMBER, acting_user=None)
|
||||
|
@ -1051,6 +1051,76 @@ class StreamAdminTest(ZulipTestCase):
|
|||
).decode()
|
||||
self.assertEqual(realm_audit_log.extra_data, expected_extra_data)
|
||||
|
||||
def test_add_and_remove_stream_as_default(self) -> None:
|
||||
user_profile = self.example_user("hamlet")
|
||||
self.login_user(user_profile)
|
||||
realm = user_profile.realm
|
||||
stream = self.make_stream("stream", realm=realm)
|
||||
stream_id = self.subscribe(user_profile, "stream").id
|
||||
|
||||
params = {
|
||||
"is_default_stream": orjson.dumps(True).decode(),
|
||||
}
|
||||
result = self.client_patch(f"/json/streams/{stream_id}", params)
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
self.assertFalse(stream_id in get_default_stream_ids_for_realm(realm.id))
|
||||
|
||||
do_change_user_role(user_profile, UserProfile.ROLE_REALM_ADMINISTRATOR, acting_user=None)
|
||||
result = self.client_patch(f"/json/streams/{stream_id}", params)
|
||||
self.assert_json_success(result)
|
||||
self.assertTrue(stream_id in get_default_stream_ids_for_realm(realm.id))
|
||||
|
||||
params = {
|
||||
"is_private": orjson.dumps(True).decode(),
|
||||
}
|
||||
result = self.client_patch(f"/json/streams/{stream_id}", params)
|
||||
self.assert_json_error(result, "A default stream cannot be private.")
|
||||
stream.refresh_from_db()
|
||||
self.assertFalse(stream.invite_only)
|
||||
|
||||
params = {
|
||||
"is_private": orjson.dumps(True).decode(),
|
||||
"is_default_stream": orjson.dumps(False).decode(),
|
||||
}
|
||||
result = self.client_patch(f"/json/streams/{stream_id}", params)
|
||||
self.assert_json_success(result)
|
||||
stream.refresh_from_db()
|
||||
self.assertTrue(stream.invite_only)
|
||||
self.assertFalse(stream_id in get_default_stream_ids_for_realm(realm.id))
|
||||
|
||||
stream_2 = self.make_stream("stream_2", realm=realm)
|
||||
stream_2_id = self.subscribe(user_profile, "stream_2").id
|
||||
|
||||
bad_params = {
|
||||
"is_default_stream": orjson.dumps(True).decode(),
|
||||
"is_private": orjson.dumps(True).decode(),
|
||||
}
|
||||
result = self.client_patch(f"/json/streams/{stream_2_id}", bad_params)
|
||||
self.assert_json_error(result, "A default stream cannot be private.")
|
||||
stream.refresh_from_db()
|
||||
self.assertFalse(stream_2.invite_only)
|
||||
self.assertFalse(stream_2_id in get_default_stream_ids_for_realm(realm.id))
|
||||
|
||||
private_stream = self.make_stream("private_stream", realm=realm, invite_only=True)
|
||||
private_stream_id = self.subscribe(user_profile, "private_stream").id
|
||||
|
||||
params = {
|
||||
"is_default_stream": orjson.dumps(True).decode(),
|
||||
}
|
||||
result = self.client_patch(f"/json/streams/{private_stream_id}", params)
|
||||
self.assert_json_error(result, "A default stream cannot be private.")
|
||||
self.assertFalse(private_stream_id in get_default_stream_ids_for_realm(realm.id))
|
||||
|
||||
params = {
|
||||
"is_private": orjson.dumps(False).decode(),
|
||||
"is_default_stream": orjson.dumps(True).decode(),
|
||||
}
|
||||
result = self.client_patch(f"/json/streams/{private_stream_id}", params)
|
||||
self.assert_json_success(result)
|
||||
private_stream.refresh_from_db()
|
||||
self.assertFalse(private_stream.invite_only)
|
||||
self.assertTrue(private_stream_id in get_default_stream_ids_for_realm(realm.id))
|
||||
|
||||
def test_stream_permission_changes_updates_updates_attachments(self) -> None:
|
||||
self.login("desdemona")
|
||||
fp = StringIO("zulip!")
|
||||
|
|
|
@ -262,6 +262,7 @@ def update_stream_backend(
|
|||
),
|
||||
is_private: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
is_announcement_only: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
is_default_stream: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
stream_post_policy: Optional[int] = REQ(
|
||||
json_validator=check_int_in(Stream.STREAM_POST_POLICY_TYPES), default=None
|
||||
),
|
||||
|
@ -290,6 +291,12 @@ def update_stream_backend(
|
|||
else:
|
||||
proposed_is_web_public = stream.is_web_public
|
||||
|
||||
if is_default_stream is not None:
|
||||
proposed_is_default_stream = is_default_stream
|
||||
else:
|
||||
default_stream_ids = get_default_stream_ids_for_realm(stream.realm_id)
|
||||
proposed_is_default_stream = stream.id in default_stream_ids
|
||||
|
||||
if stream.realm.is_zephyr_mirror_realm:
|
||||
# In the Zephyr mirroring model, history is unconditionally
|
||||
# not public to subscribers, even for public streams.
|
||||
|
@ -319,12 +326,11 @@ def update_stream_backend(
|
|||
else:
|
||||
raise JsonableError(_("Invalid parameters"))
|
||||
|
||||
if is_private is not None:
|
||||
# Default streams cannot be made private.
|
||||
default_stream_ids = get_default_stream_ids_for_realm(stream.realm_id)
|
||||
if is_private and stream.id in default_stream_ids:
|
||||
raise JsonableError(_("Default streams cannot be made private."))
|
||||
# Ensure that a stream cannot be both a default stream for new users and private
|
||||
if proposed_is_private and proposed_is_default_stream:
|
||||
raise JsonableError(_("A default stream cannot be private."))
|
||||
|
||||
if is_private is not None:
|
||||
# We require even realm administrators to be actually
|
||||
# subscribed to make a private stream public, via this
|
||||
# stricted access_stream check.
|
||||
|
@ -352,6 +358,12 @@ def update_stream_backend(
|
|||
acting_user=user_profile,
|
||||
)
|
||||
|
||||
if is_default_stream is not None:
|
||||
if is_default_stream:
|
||||
do_add_default_stream(stream)
|
||||
else:
|
||||
do_remove_default_stream(stream)
|
||||
|
||||
if message_retention_days is not None:
|
||||
if not user_profile.is_realm_owner:
|
||||
raise OrganizationOwnerRequiredError
|
||||
|
|
Loading…
Reference in New Issue