invites: Frontend changes for adding the new realm setting.

This commit does the changes in frontend required
for adding the new setting `Who can create multiuse invite link.`

Fixes #15159.
This commit is contained in:
Ujjawal Modi 2023-08-03 19:36:40 +05:30 committed by Tim Abbott
parent 21b1298c1d
commit 9a96d19315
21 changed files with 172 additions and 24 deletions

View File

@ -57,8 +57,6 @@ permission to invite users.
## Create a reusable invitation link ## Create a reusable invitation link
{!admin-only.md!}
{start_tabs} {start_tabs}
{!invite-users.md!} {!invite-users.md!}

View File

@ -16,9 +16,8 @@ account and how users access their accounts:
to sign up (default), or you can [allow anyone to to sign up (default), or you can [allow anyone to
join](#set-whether-invitations-are-required-to-join) without an invitation. join](#set-whether-invitations-are-required-to-join) without an invitation.
* You can [restrict who can invite users](#change-who-can-send-invitations) to * You can [restrict the ability to invite new users](#change-who-can-send-invitations) to
your organization. To protect your organization, creating *reusable* invite join your Zulip organzation to specific [roles](/help/roles-and-permissions).
links is always limited to administrators.
Regardless of whether invitations are required, you can: Regardless of whether invitations are required, you can:
@ -49,19 +48,13 @@ Regardless of whether invitations are required, you can:
## Change who can send invitations ## Change who can send invitations
{!owner-only.md!}
You can restrict the ability to invite new users to join your Zulip organization
to specific [roles](/help/roles-and-permissions). To protect your organization,
while permission to send out individual email invitations is configurable, creating
*reusable* invitation links is always limited to administrators.
{start_tabs} {start_tabs}
{settings_tab|organization-permissions} {settings_tab|organization-permissions}
1. Under **Joining the organization**, configure 1. Under **Joining the organization**, configure
**Who can send email invitations to new users**. **Who can send email invitations to new users** and
**Who can create reusable invitation links**.
{!save-changes.md!} {!save-changes.md!}

View File

@ -160,6 +160,7 @@ export function build_page() {
email_address_visibility_values: settings_config.email_address_visibility_values, email_address_visibility_values: settings_config.email_address_visibility_values,
waiting_period_threshold_dropdown_values: waiting_period_threshold_dropdown_values:
settings_config.waiting_period_threshold_dropdown_values, settings_config.waiting_period_threshold_dropdown_values,
can_create_multiuse_invite: settings_data.user_can_create_multiuse_invite(),
can_invite_users_by_email: settings_data.user_can_invite_users_by_email(), can_invite_users_by_email: settings_data.user_can_invite_users_by_email(),
realm_invite_required: page_params.realm_invite_required, realm_invite_required: page_params.realm_invite_required,
can_edit_user_groups: settings_data.user_can_edit_user_groups(), can_edit_user_groups: settings_data.user_can_edit_user_groups(),

View File

@ -126,6 +126,7 @@ export function initialize(): void {
server_needs_upgrade: page_params.server_needs_upgrade, server_needs_upgrade: page_params.server_needs_upgrade,
version_display_string: version_display_string(), version_display_string: version_display_string(),
apps_page_url: page_params.apps_page_url, apps_page_url: page_params.apps_page_url,
can_create_multiuse_invite: settings_data.user_can_create_multiuse_invite(),
can_invite_users_by_email: settings_data.user_can_invite_users_by_email(), can_invite_users_by_email: settings_data.user_can_invite_users_by_email(),
corporate_enabled: page_params.corporate_enabled, corporate_enabled: page_params.corporate_enabled,
is_guest: page_params.is_guest, is_guest: page_params.is_guest,

View File

@ -15,6 +15,15 @@ const group_permission_config_dict = new Map<string, GroupPermissionSetting>([
allow_nobody_group: false, allow_nobody_group: false,
}, },
], ],
[
"create_multiuse_invite_group",
{
require_system_group: true,
allow_internet_group: false,
allow_owners_group: false,
allow_nobody_group: true,
},
],
]); ]);
export function get_group_permission_setting_config( export function get_group_permission_setting_config(

View File

@ -273,7 +273,7 @@ function open_invite_user_modal(e) {
$("#invite-user-modal .dialog_submit_button").prop("disabled", true); $("#invite-user-modal .dialog_submit_button").prop("disabled", true);
$("#email_invite_radio").prop("checked", true); $("#email_invite_radio").prop("checked", true);
if (!page_params.is_admin) { if (!settings_data.user_can_create_multiuse_invite()) {
$("#generate_multiuse_invite_radio").prop("disabled", true); $("#generate_multiuse_invite_radio").prop("disabled", true);
$("#generate_multiuse_invite_radio_container").addClass("control-label-disabled"); $("#generate_multiuse_invite_radio_container").addClass("control-label-disabled");
$("#generate_multiuse_invite_radio_container").addClass("disabled_setting_tooltip"); $("#generate_multiuse_invite_radio_container").addClass("disabled_setting_tooltip");
@ -359,6 +359,16 @@ function open_invite_user_modal(e) {
if (!user_has_email_set) { if (!user_has_email_set) {
$("#invite-user-form :input").prop("disabled", !user_has_email_set); $("#invite-user-form :input").prop("disabled", !user_has_email_set);
} }
if (!settings_data.user_can_invite_users_by_email()) {
$("#email_invite_radio").prop("disabled", true);
$("#email_invite_radio_container").addClass(
"control-label-disabled disabled_setting_tooltip",
);
$("#generate_multiuse_invite_radio").prop("checked", true);
$("#generate_multiuse_invite_radio").trigger("change");
}
} }
function invite_users() { function invite_users() {

View File

@ -25,6 +25,7 @@ export const page_params: {
promote_sponsoring_zulip: boolean; promote_sponsoring_zulip: boolean;
realm_add_custom_emoji_policy: number; realm_add_custom_emoji_policy: number;
realm_avatar_changes_disabled: boolean; realm_avatar_changes_disabled: boolean;
realm_create_multiuse_invite_group: number;
realm_create_private_stream_policy: number; realm_create_private_stream_policy: number;
realm_create_public_stream_policy: number; realm_create_public_stream_policy: number;
realm_create_web_public_stream_policy: number; realm_create_web_public_stream_policy: number;

View File

@ -197,6 +197,7 @@ export function dispatch_normal_event(event) {
user_group_edit_policy: noop, user_group_edit_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,
create_multiuse_invite_group: noop,
create_public_stream_policy: noop, create_public_stream_policy: noop,
create_private_stream_policy: noop, create_private_stream_policy: noop,
create_web_public_stream_policy: noop, create_web_public_stream_policy: noop,
@ -279,6 +280,12 @@ export function dispatch_normal_event(event) {
settings_org.sync_realm_settings(key); settings_org.sync_realm_settings(key);
} }
if (key === "create_multiuse_invite_group") {
settings_invites.update_invite_user_panel();
ui_init.update_invite_user_option();
gear_menu.initialize();
}
if (key === "edit_topic_policy") { if (key === "edit_topic_policy") {
message_live_update.rerender_messages_view(); message_live_update.rerender_messages_view();
} }

View File

@ -1,5 +1,6 @@
import {page_params} from "./page_params"; import {page_params} from "./page_params";
import * as settings_config from "./settings_config"; import * as settings_config from "./settings_config";
import * as user_groups from "./user_groups";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
let user_join_date: Date; let user_join_date: Date;
@ -141,6 +142,16 @@ export function user_can_invite_users_by_email(): boolean {
return user_has_permission(page_params.realm_invite_to_realm_policy); return user_has_permission(page_params.realm_invite_to_realm_policy);
} }
export function user_can_create_multiuse_invite(): boolean {
if (!page_params.user_id) {
return false;
}
return user_groups.is_user_in_group(
page_params.realm_create_multiuse_invite_group,
page_params.user_id,
);
}
export function user_can_subscribe_other_users(): boolean { export function user_can_subscribe_other_users(): boolean {
return user_has_permission(page_params.realm_invite_to_stream_policy); return user_has_permission(page_params.realm_invite_to_stream_policy);
} }

View File

@ -296,7 +296,10 @@ export function update_invite_users_setting_tip() {
export function update_invite_user_panel() { export function update_invite_user_panel() {
update_invite_users_setting_tip(); update_invite_users_setting_tip();
if (!settings_data.user_can_invite_users_by_email()) { if (
!settings_data.user_can_invite_users_by_email() &&
!settings_data.user_can_create_multiuse_invite()
) {
$("#admin-invites-list .invite-user-link").hide(); $("#admin-invites-list .invite-user-link").hide();
} else { } else {
$("#admin-invites-list .invite-user-link").show(); $("#admin-invites-list .invite-user-link").show();

View File

@ -28,6 +28,7 @@ import * as stream_data from "./stream_data";
import * as stream_edit from "./stream_edit"; import * as stream_edit from "./stream_edit";
import * as stream_settings_data from "./stream_settings_data"; import * as stream_settings_data from "./stream_settings_data";
import * as ui_report from "./ui_report"; import * as ui_report from "./ui_report";
import * as user_groups from "./user_groups";
const meta = { const meta = {
loaded: false, loaded: false,
@ -53,7 +54,7 @@ export function maybe_disable_widgets() {
$(".deactivate_realm_button").prop("disabled", true); $(".deactivate_realm_button").prop("disabled", true);
$("#deactivate_realm_button_container").addClass("disabled_setting_tooltip"); $("#deactivate_realm_button_container").addClass("disabled_setting_tooltip");
$("#org-message-retention").find("input, select").prop("disabled", true); $("#org-message-retention").find("input, select").prop("disabled", true);
$("#org-join-settings").find("input, select").prop("disabled", true); $("#org-join-settings").find("input, select, button").prop("disabled", true);
$("#id_realm_invite_required_label").parent().addClass("control-label-disabled"); $("#id_realm_invite_required_label").parent().addClass("control-label-disabled");
return; return;
} }
@ -608,6 +609,7 @@ function update_dependent_subsettings(property_name) {
export let default_code_language_widget = null; export let default_code_language_widget = null;
export let notifications_stream_widget = null; export let notifications_stream_widget = null;
export let signup_notifications_stream_widget = null; export let signup_notifications_stream_widget = null;
export let create_multiuse_invite_group_widget = null;
export function get_widget_for_dropdown_list_settings(property_name) { export function get_widget_for_dropdown_list_settings(property_name) {
switch (property_name) { switch (property_name) {
@ -617,6 +619,8 @@ export function get_widget_for_dropdown_list_settings(property_name) {
return signup_notifications_stream_widget; return signup_notifications_stream_widget;
case "realm_default_code_block_language": case "realm_default_code_block_language":
return default_code_language_widget; return default_code_language_widget;
case "realm_create_multiuse_invite_group":
return create_multiuse_invite_group_widget;
case "can_remove_subscribers_group": case "can_remove_subscribers_group":
return stream_edit.can_remove_subscribers_group_widget; return stream_edit.can_remove_subscribers_group_widget;
default: default:
@ -655,6 +659,7 @@ export function discard_property_element_changes(elem, for_realm_default_setting
case "realm_signup_notifications_stream_id": case "realm_signup_notifications_stream_id":
case "realm_default_code_block_language": case "realm_default_code_block_language":
case "can_remove_subscribers_group": case "can_remove_subscribers_group":
case "realm_create_multiuse_invite_group":
set_dropdown_list_widget_setting_value(property_name, property_value); set_dropdown_list_widget_setting_value(property_name, property_value);
break; break;
case "realm_default_language": case "realm_default_language":
@ -971,6 +976,7 @@ export function check_property_changed(elem, for_realm_default_settings, sub) {
case "realm_signup_notifications_stream_id": case "realm_signup_notifications_stream_id":
case "realm_default_code_block_language": case "realm_default_code_block_language":
case "can_remove_subscribers_group": case "can_remove_subscribers_group":
case "realm_create_multiuse_invite_group":
proposed_val = get_dropdown_list_widget_setting_value($elem); proposed_val = get_dropdown_list_widget_setting_value($elem);
break; break;
case "email_notifications_batching_period_seconds": case "email_notifications_batching_period_seconds":
@ -1157,6 +1163,31 @@ export function init_dropdown_widgets() {
}, },
}); });
default_code_language_widget.setup(); default_code_language_widget.setup();
create_multiuse_invite_group_widget = new dropdown_widget.DropdownWidget({
widget_name: "realm_create_multiuse_invite_group",
get_options: () =>
user_groups.get_realm_user_groups_for_dropdown_list_widget(
"create_multiuse_invite_group",
),
$events_container: $("#settings_overlay_container #organization-permissions"),
item_click_callback(event, dropdown) {
dropdown.hide();
event.preventDefault();
event.stopPropagation();
create_multiuse_invite_group_widget.render();
save_discard_widget_status_handler($("#org-join-settings"));
},
tippy_props: {
placement: "bottom-start",
},
default_id: page_params.realm_create_multiuse_invite_group,
unique_id_type: dropdown_widget.DATA_TYPES.NUMBER,
on_mount_callback(dropdown) {
$(dropdown.popper).css("min-width", "300px");
},
});
create_multiuse_invite_group_widget.setup();
} }
function check_maximum_valid_value($custom_input_elem, property_name) { function check_maximum_valid_value($custom_input_elem, property_name) {

View File

@ -373,6 +373,18 @@ export function initialize() {
}, },
}); });
delegate("body", {
target: ["#email_invite_radio_container.disabled_setting_tooltip"],
content: $t({
defaultMessage:
"You do not have permissions to send email invitations in this organization.",
}),
appendTo: () => document.body,
onHidden(instance) {
instance.destroy();
},
});
delegate("body", { delegate("body", {
target: "#pm_tooltip_container", target: "#pm_tooltip_container",
onShow(instance) { onShow(instance) {

View File

@ -190,7 +190,10 @@ function initialize_left_sidebar() {
} }
export function update_invite_user_option() { export function update_invite_user_option() {
if (!settings_data.user_can_invite_users_by_email()) { if (
!settings_data.user_can_invite_users_by_email() &&
!settings_data.user_can_create_multiuse_invite()
) {
$("#right-sidebar .invite-user-link").hide(); $("#right-sidebar .invite-user-link").hide();
} else { } else {
$("#right-sidebar .invite-user-link").show(); $("#right-sidebar .invite-user-link").show();

View File

@ -420,7 +420,8 @@
/* these are converting grey things to "new grey" */ /* these are converting grey things to "new grey" */
:disabled, :disabled,
input:not([type="radio"]):read-only, input:not([type="radio"]):read-only,
textarea:read-only { textarea:read-only,
#org-join-settings .dropdown-widget-button:disabled {
color: inherit; color: inherit;
opacity: 0.5; opacity: 0.5;
} }
@ -479,7 +480,8 @@
.pill-container, .pill-container,
.user-status-content-wrapper, .user-status-content-wrapper,
#custom-expiration-time-input, #custom-expiration-time-input,
#searchbox #search_query { #searchbox #search_query,
#org-join-settings .dropdown-widget-button {
background-color: hsl(0deg 0% 0% / 20%); background-color: hsl(0deg 0% 0% / 20%);
border-color: hsl(0deg 0% 0% / 60%); border-color: hsl(0deg 0% 0% / 60%);
color: inherit; color: inherit;

View File

@ -775,6 +775,24 @@ input[type="checkbox"] {
margin: 20px 0 0; margin: 20px 0 0;
} }
#org-join-settings {
.dropdown-widget-button {
width: 325px;
color: hsl(0deg 0% 33%);
& i {
font-size: 10px;
margin-right: -3px;
}
&:disabled {
cursor: not-allowed;
background-color: hsl(0deg 0% 93%);
opacity: 0.7;
}
}
}
.progressive-table-wrapper { .progressive-table-wrapper {
position: relative; position: relative;
max-height: calc(95vh - 220px); max-height: calc(95vh - 220px);

View File

@ -2521,6 +2521,10 @@ select.invite-as {
.invite_type_radio_section { .invite_type_radio_section {
margin: 2px 2px 2px 5px; margin: 2px 2px 2px 5px;
& div {
width: fit-content;
}
& input[type="radio"] { & input[type="radio"] {
cursor: pointer; cursor: pointer;
@ -2535,10 +2539,6 @@ select.invite-as {
} }
} }
#generate_multiuse_invite_radio_container {
width: fit-content;
}
#custom-expiration-time-input { #custom-expiration-time-input {
width: 5ch; width: 5ch;
margin-right: 15px; margin-right: 15px;

View File

@ -155,7 +155,7 @@
</li> </li>
{{/if}} {{/if}}
<li class="divider hidden-for-spectators" role="presentation"></li> <li class="divider hidden-for-spectators" role="presentation"></li>
{{#if can_invite_users_by_email}} {{#if (or can_invite_users_by_email can_create_multiuse_invite)}}
<li role="presentation"> <li role="presentation">
<a class="invite-user-link" role="menuitem"> <a class="invite-user-link" role="menuitem">
<i class="fa fa-user-plus" aria-hidden="true"></i> {{t 'Invite users' }} <i class="fa fa-user-plus" aria-hidden="true"></i> {{t 'Invite users' }}

View File

@ -23,6 +23,11 @@
</select> </select>
</div> </div>
{{> ../dropdown_widget_with_label
widget_name="realm_create_multiuse_invite_group"
label=(t 'Who can create reusable invitation links')
value_type="number"}}
<div class="input-group"> <div class="input-group">
<label for="realm_org_join_restrictions" class="dropdown-title">{{t "Restrict email domains of new users?" }}</label> <label for="realm_org_join_restrictions" class="dropdown-title">{{t "Restrict email domains of new users?" }}</label>
<select name="realm_org_join_restrictions" id="id_realm_org_join_restrictions" class="prop-element settings_select bootstrap-focus-style"> <select name="realm_org_join_restrictions" id="id_realm_org_join_restrictions" class="prop-element settings_select bootstrap-focus-style">

View File

@ -544,11 +544,13 @@ run_test("realm settings", ({override}) => {
assert_same(update_called, true); assert_same(update_called, true);
event = event_fixtures.realm__update_dict__default; event = event_fixtures.realm__update_dict__default;
page_params.realm_create_multiuse_invite_group = 1;
page_params.realm_allow_message_editing = false; page_params.realm_allow_message_editing = false;
page_params.realm_message_content_edit_limit_seconds = 0; page_params.realm_message_content_edit_limit_seconds = 0;
page_params.realm_edit_topic_policy = 3; page_params.realm_edit_topic_policy = 3;
override(settings_org, "populate_auth_methods", noop); override(settings_org, "populate_auth_methods", noop);
dispatch(event); dispatch(event);
assert_same(page_params.realm_create_multiuse_invite_group, 3);
assert_same(page_params.realm_allow_message_editing, true); assert_same(page_params.realm_allow_message_editing, true);
assert_same(page_params.realm_message_content_edit_limit_seconds, 5); assert_same(page_params.realm_message_content_edit_limit_seconds, 5);
assert_same(page_params.realm_edit_topic_policy, 4); assert_same(page_params.realm_edit_topic_policy, 4);

View File

@ -374,6 +374,7 @@ exports.fixtures = {
allow_message_editing: true, allow_message_editing: true,
message_content_edit_limit_seconds: 5, message_content_edit_limit_seconds: 5,
edit_topic_policy: 4, edit_topic_policy: 4,
create_multiuse_invite_group: 3,
authentication_methods: { authentication_methods: {
Google: true, Google: true,
}, },

View File

@ -8,6 +8,7 @@ const {page_params, user_settings} = require("./lib/zpage_params");
const settings_data = zrequire("settings_data"); const settings_data = zrequire("settings_data");
const settings_config = zrequire("settings_config"); const settings_config = zrequire("settings_config");
const user_groups = zrequire("user_groups");
/* /*
Some methods in settings_data are fairly Some methods in settings_data are fairly
@ -331,3 +332,42 @@ run_test("user_email_not_configured", () => {
page_params.delivery_email = "name@example.com"; page_params.delivery_email = "name@example.com";
assert.equal(user_email_not_configured(), false); assert.equal(user_email_not_configured(), false);
}); });
run_test("user_can_create_multiuse_invite", () => {
const admin_user_id = 1;
const moderator_user_id = 2;
const member_user_id = 3;
const admins = {
name: "Admins",
id: 1,
members: new Set([admin_user_id]),
is_system_group: true,
direct_subgroup_ids: new Set([]),
};
const moderators = {
name: "Moderators",
id: 2,
members: new Set([moderator_user_id]),
is_system_group: true,
direct_subgroup_ids: new Set([1]),
};
user_groups.initialize({realm_user_groups: [admins, moderators]});
assert.equal(settings_data.user_can_create_multiuse_invite(), false);
page_params.realm_create_multiuse_invite_group = 1;
page_params.user_id = admin_user_id;
assert.equal(settings_data.user_can_create_multiuse_invite(), true);
page_params.user_id = moderator_user_id;
assert.equal(settings_data.user_can_create_multiuse_invite(), false);
page_params.realm_create_multiuse_invite_group = 2;
page_params.user_id = moderator_user_id;
assert.equal(settings_data.user_can_create_multiuse_invite(), true);
page_params.user_id = member_user_id;
assert.equal(settings_data.user_can_create_multiuse_invite(), false);
});