mirror of
https://github.com/zulip/zulip.git
synced 2025-10-26 17:43:58 +00:00
realm: Add setting to notify user on DMing guest.
Added `enable_guest_user_dm_warning` setting to decide whether clients should show a warning when a user is composing to a guest user in the organization. Fixes #30078. Co-authored-by: adnan-td <generaladnan139@gmail.com>
This commit is contained in:
@@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
|
|||||||
|
|
||||||
## Changes in Zulip 10.0
|
## Changes in Zulip 10.0
|
||||||
|
|
||||||
|
**Feature level 348**
|
||||||
|
|
||||||
|
* [`POST /register`](/api/register-queue), [`POST /events`](/api/get-events),
|
||||||
|
`PATCH /realm`: Added `enable_guest_user_dm_warning` setting to decide
|
||||||
|
whether clients should show a warning when a user is composing to a
|
||||||
|
guest user in the organization.
|
||||||
|
|
||||||
**Feature level 347**
|
**Feature level 347**
|
||||||
|
|
||||||
* [Markdown message formatting](/api/message-formatting#links-to-channels-topics-and-messages):
|
* [Markdown message formatting](/api/message-formatting#links-to-channels-topics-and-messages):
|
||||||
|
|||||||
@@ -42,6 +42,20 @@ pricing](/help/zulip-cloud-billing#temporary-users-and-guests) for guest users.
|
|||||||
|
|
||||||
{end_tabs}
|
{end_tabs}
|
||||||
|
|
||||||
|
## Configure warning for direct messages to guest users
|
||||||
|
|
||||||
|
{start_tabs}
|
||||||
|
|
||||||
|
{tab|desktop-web}
|
||||||
|
|
||||||
|
{settings_tab|organization-permissions}
|
||||||
|
|
||||||
|
1. Under **Guests**, toggle **Display a warning when composing a direct message with guest user recipients**.
|
||||||
|
|
||||||
|
{!save-changes.md!}
|
||||||
|
|
||||||
|
{end_tabs}
|
||||||
|
|
||||||
## Configure whether guests can see all other users
|
## Configure whether guests can see all other users
|
||||||
|
|
||||||
{!cloud-plus-only.md!}
|
{!cloud-plus-only.md!}
|
||||||
|
|||||||
@@ -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 = 347 # Last bumped for /with/ in topic links.
|
API_FEATURE_LEVEL = 348 # Last bumped for enable_guest_user_dm_warning.
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ const admin_settings_label = {
|
|||||||
realm_enable_guest_user_indicator: $t({
|
realm_enable_guest_user_indicator: $t({
|
||||||
defaultMessage: "Display “(guest)” after names of guest users",
|
defaultMessage: "Display “(guest)” after names of guest users",
|
||||||
}),
|
}),
|
||||||
|
realm_enable_guest_user_dm_warning: $t({
|
||||||
|
defaultMessage:
|
||||||
|
"Display a warning when composing a direct message with guest user recipients",
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
function insert_tip_box(): void {
|
function insert_tip_box(): void {
|
||||||
@@ -246,6 +250,7 @@ export function build_page(): void {
|
|||||||
automatically_unmute_topics_in_muted_streams_policy_values:
|
automatically_unmute_topics_in_muted_streams_policy_values:
|
||||||
settings_config.automatically_follow_or_unmute_topics_policy_values,
|
settings_config.automatically_follow_or_unmute_topics_policy_values,
|
||||||
realm_enable_guest_user_indicator: realm.realm_enable_guest_user_indicator,
|
realm_enable_guest_user_indicator: realm.realm_enable_guest_user_indicator,
|
||||||
|
realm_enable_guest_user_dm_warning: realm.realm_enable_guest_user_dm_warning,
|
||||||
active_user_list_dropdown_widget_name: settings_users.active_user_list_dropdown_widget_name,
|
active_user_list_dropdown_widget_name: settings_users.active_user_list_dropdown_widget_name,
|
||||||
deactivated_user_list_dropdown_widget_name:
|
deactivated_user_list_dropdown_widget_name:
|
||||||
settings_users.deactivated_user_list_dropdown_widget_name,
|
settings_users.deactivated_user_list_dropdown_widget_name,
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ function clear_box(): void {
|
|||||||
// TODO: Better encapsulate at-mention warnings.
|
// TODO: Better encapsulate at-mention warnings.
|
||||||
compose_validate.clear_topic_resolved_warning();
|
compose_validate.clear_topic_resolved_warning();
|
||||||
compose_validate.clear_stream_wildcard_warnings($("#compose_banners"));
|
compose_validate.clear_stream_wildcard_warnings($("#compose_banners"));
|
||||||
|
compose_validate.clear_guest_in_dm_recipient_warning();
|
||||||
compose_validate.set_user_acknowledged_stream_wildcard_flag(false);
|
compose_validate.set_user_acknowledged_stream_wildcard_flag(false);
|
||||||
|
|
||||||
compose_state.set_recipient_edited_manually(false);
|
compose_state.set_recipient_edited_manually(false);
|
||||||
@@ -408,6 +409,8 @@ export let start = (raw_opts: ComposeActionsStartOpts): void => {
|
|||||||
|
|
||||||
// Show a warning if topic is resolved
|
// Show a warning if topic is resolved
|
||||||
compose_validate.warn_if_topic_resolved(true);
|
compose_validate.warn_if_topic_resolved(true);
|
||||||
|
// Show a warning if dm recipient contains guest
|
||||||
|
compose_validate.warn_if_guest_in_dm_recipient();
|
||||||
// Show a warning if the user is in a search narrow when replying to a message
|
// Show a warning if the user is in a search narrow when replying to a message
|
||||||
if (opts.is_reply) {
|
if (opts.is_reply) {
|
||||||
compose_validate.warn_if_in_search_view();
|
compose_validate.warn_if_in_search_view();
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const CLASSNAMES = {
|
|||||||
recipient_not_subscribed: "recipient_not_subscribed",
|
recipient_not_subscribed: "recipient_not_subscribed",
|
||||||
wildcard_warning: "wildcard_warning",
|
wildcard_warning: "wildcard_warning",
|
||||||
private_stream_warning: "private_stream_warning",
|
private_stream_warning: "private_stream_warning",
|
||||||
|
guest_in_dm_recipient_warning: "guest_in_dm_recipient_warning",
|
||||||
unscheduled_message: "unscheduled_message",
|
unscheduled_message: "unscheduled_message",
|
||||||
search_view: "search_view",
|
search_view: "search_view",
|
||||||
// errors
|
// errors
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ function update_fade(): void {
|
|||||||
export function update_on_recipient_change(): void {
|
export function update_on_recipient_change(): void {
|
||||||
update_fade();
|
update_fade();
|
||||||
update_narrow_to_recipient_visibility();
|
update_narrow_to_recipient_visibility();
|
||||||
|
compose_validate.warn_if_guest_in_dm_recipient();
|
||||||
drafts.update_compose_draft_count();
|
drafts.update_compose_draft_count();
|
||||||
check_posting_policy_for_compose_box();
|
check_posting_policy_for_compose_box();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ let last_focused_compose_type_input: HTMLTextAreaElement | undefined;
|
|||||||
// the narrow and the user should still be able to see the banner once after
|
// the narrow and the user should still be able to see the banner once after
|
||||||
// performing these actions
|
// performing these actions
|
||||||
let recipient_viewed_topic_resolved_banner = false;
|
let recipient_viewed_topic_resolved_banner = false;
|
||||||
|
let recipient_guest_ids_for_dm_warning: number[] = [];
|
||||||
|
|
||||||
export function set_recipient_edited_manually(flag: boolean): void {
|
export function set_recipient_edited_manually(flag: boolean): void {
|
||||||
recipient_edited_manually = flag;
|
recipient_edited_manually = flag;
|
||||||
@@ -58,6 +59,14 @@ export function has_recipient_viewed_topic_resolved_banner(): boolean {
|
|||||||
return recipient_viewed_topic_resolved_banner;
|
return recipient_viewed_topic_resolved_banner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function set_recipient_guest_ids_for_dm_warning(guest_ids: number[]): void {
|
||||||
|
recipient_guest_ids_for_dm_warning = guest_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get_recipient_guest_ids_for_dm_warning(): number[] {
|
||||||
|
return recipient_guest_ids_for_dm_warning;
|
||||||
|
}
|
||||||
|
|
||||||
export function composing(): boolean {
|
export function composing(): boolean {
|
||||||
// This is very similar to get_message_type(), but it returns
|
// This is very similar to get_message_type(), but it returns
|
||||||
// a boolean.
|
// a boolean.
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
import * as resolved_topic from "../shared/src/resolved_topic.ts";
|
import * as resolved_topic from "../shared/src/resolved_topic.ts";
|
||||||
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
|
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
|
||||||
|
import render_guest_in_dm_recipient_warning from "../templates/compose_banner/guest_in_dm_recipient_warning.hbs";
|
||||||
import render_not_subscribed_warning from "../templates/compose_banner/not_subscribed_warning.hbs";
|
import render_not_subscribed_warning from "../templates/compose_banner/not_subscribed_warning.hbs";
|
||||||
import render_private_stream_warning from "../templates/compose_banner/private_stream_warning.hbs";
|
import render_private_stream_warning from "../templates/compose_banner/private_stream_warning.hbs";
|
||||||
import render_stream_wildcard_warning from "../templates/compose_banner/stream_wildcard_warning.hbs";
|
import render_stream_wildcard_warning from "../templates/compose_banner/stream_wildcard_warning.hbs";
|
||||||
@@ -389,6 +391,72 @@ export function warn_if_in_search_view(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clear_guest_in_dm_recipient_warning(): void {
|
||||||
|
// We don't call set_recipient_guest_ids_for_dm_warning here, so
|
||||||
|
// that reopening the same draft won't make the banner reappear.
|
||||||
|
const classname = compose_banner.CLASSNAMES.guest_in_dm_recipient_warning;
|
||||||
|
$(`#compose_banners .${CSS.escape(classname)}`).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only called on recipient change. Adds new banner if not already
|
||||||
|
// exists or updates the existing banner or removes banner if no
|
||||||
|
// guest in the dm.
|
||||||
|
export function warn_if_guest_in_dm_recipient(): void {
|
||||||
|
if (!compose_state.composing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const recipient_ids = compose_pm_pill.get_user_ids();
|
||||||
|
const guest_ids = people.filter_other_guest_ids(recipient_ids);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!realm.realm_enable_guest_user_dm_warning ||
|
||||||
|
compose_state.get_message_type() !== "private" ||
|
||||||
|
guest_ids.length === 0
|
||||||
|
) {
|
||||||
|
clear_guest_in_dm_recipient_warning();
|
||||||
|
compose_state.set_recipient_guest_ids_for_dm_warning([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If warning was shown earlier for same guests in the recipients, do nothing.
|
||||||
|
if (_.isEqual(compose_state.get_recipient_guest_ids_for_dm_warning(), guest_ids)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guest_names = people.user_ids_to_full_names_array(guest_ids);
|
||||||
|
let banner_text: string;
|
||||||
|
|
||||||
|
if (guest_names.length === 1) {
|
||||||
|
banner_text = $t(
|
||||||
|
{defaultMessage: "{name} is a guest in this organization."},
|
||||||
|
{name: guest_names[0]},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const names_string = util.format_array_as_list(guest_names, "long", "conjunction");
|
||||||
|
banner_text = $t(
|
||||||
|
{defaultMessage: "{names} are guests in this organization."},
|
||||||
|
{names: names_string},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const classname = compose_banner.CLASSNAMES.guest_in_dm_recipient_warning;
|
||||||
|
let $banner = $(`#compose_banners .${CSS.escape(classname)}`);
|
||||||
|
|
||||||
|
compose_state.set_recipient_guest_ids_for_dm_warning(guest_ids);
|
||||||
|
// Update banner text if banner already exists.
|
||||||
|
if ($banner.length === 1) {
|
||||||
|
$banner.find(".banner_content").text(banner_text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$banner = $(
|
||||||
|
render_guest_in_dm_recipient_warning({
|
||||||
|
banner_text,
|
||||||
|
classname: compose_banner.CLASSNAMES.guest_in_dm_recipient_warning,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
compose_banner.append_compose_banner_to_banner_list($banner, $("#compose_banners"));
|
||||||
|
}
|
||||||
|
|
||||||
function show_stream_wildcard_warnings(opts: StreamWildcardOptions): void {
|
function show_stream_wildcard_warnings(opts: StreamWildcardOptions): void {
|
||||||
const subscriber_count = peer_data.get_subscriber_count(opts.stream_id) || 0;
|
const subscriber_count = peer_data.get_subscriber_count(opts.stream_id) || 0;
|
||||||
const stream_name = sub_store.maybe_get_stream_name(opts.stream_id);
|
const stream_name = sub_store.maybe_get_stream_name(opts.stream_id);
|
||||||
|
|||||||
@@ -687,6 +687,17 @@ export function pm_with_operand_ids(operand: string): number[] | undefined {
|
|||||||
return user_ids;
|
return user_ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filter_other_guest_ids(user_ids: number[]): number[] {
|
||||||
|
return sort_numerically(
|
||||||
|
user_ids.filter((id) => id !== current_user.user_id && get_by_user_id(id)?.is_guest),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function user_ids_to_full_names_array(user_ids: number[]): string[] {
|
||||||
|
const names = user_ids.map((user_id) => get_by_user_id(user_id).full_name).sort(util.strcmp);
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
export function emails_to_slug(emails_string: string): string | undefined {
|
export function emails_to_slug(emails_string: string): string | undefined {
|
||||||
let slug = reply_to_to_user_ids_string(emails_string);
|
let slug = reply_to_to_user_ids_string(emails_string);
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import * as compose_closed_ui from "./compose_closed_ui.ts";
|
|||||||
import * as compose_pm_pill from "./compose_pm_pill.ts";
|
import * as compose_pm_pill from "./compose_pm_pill.ts";
|
||||||
import * as compose_recipient from "./compose_recipient.ts";
|
import * as compose_recipient from "./compose_recipient.ts";
|
||||||
import * as compose_state from "./compose_state.ts";
|
import * as compose_state from "./compose_state.ts";
|
||||||
|
import * as compose_validate from "./compose_validate.ts";
|
||||||
import {electron_bridge} from "./electron_bridge.ts";
|
import {electron_bridge} from "./electron_bridge.ts";
|
||||||
import * as emoji from "./emoji.ts";
|
import * as emoji from "./emoji.ts";
|
||||||
import * as emoji_picker from "./emoji_picker.ts";
|
import * as emoji_picker from "./emoji_picker.ts";
|
||||||
@@ -262,6 +263,7 @@ export function dispatch_normal_event(event) {
|
|||||||
want_advertise_in_communities_directory: noop,
|
want_advertise_in_communities_directory: noop,
|
||||||
wildcard_mention_policy: noop,
|
wildcard_mention_policy: noop,
|
||||||
enable_read_receipts: settings_account.update_send_read_receipts_tooltip,
|
enable_read_receipts: settings_account.update_send_read_receipts_tooltip,
|
||||||
|
enable_guest_user_dm_warning: compose_validate.warn_if_guest_in_dm_recipient,
|
||||||
enable_guest_user_indicator: noop,
|
enable_guest_user_indicator: noop,
|
||||||
};
|
};
|
||||||
switch (event.op) {
|
switch (event.op) {
|
||||||
|
|||||||
@@ -337,6 +337,7 @@ export const realm_schema = z.object({
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
realm_empty_topic_display_name: z.string(),
|
realm_empty_topic_display_name: z.string(),
|
||||||
|
realm_enable_guest_user_dm_warning: z.boolean(),
|
||||||
realm_enable_guest_user_indicator: z.boolean(),
|
realm_enable_guest_user_indicator: z.boolean(),
|
||||||
realm_enable_read_receipts: z.boolean(),
|
realm_enable_read_receipts: z.boolean(),
|
||||||
realm_enable_spectator_access: z.boolean(),
|
realm_enable_spectator_access: z.boolean(),
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="above_compose_banner main-view-banner warning-style {{classname}}">
|
||||||
|
<p class="banner_content">
|
||||||
|
{{banner_text}}
|
||||||
|
</p>
|
||||||
|
<a role="button" class="zulip-icon zulip-icon-close main-view-banner-close-button"></a>
|
||||||
|
</div>
|
||||||
@@ -321,6 +321,12 @@
|
|||||||
is_checked=realm_enable_guest_user_indicator
|
is_checked=realm_enable_guest_user_indicator
|
||||||
label=admin_settings_label.realm_enable_guest_user_indicator}}
|
label=admin_settings_label.realm_enable_guest_user_indicator}}
|
||||||
|
|
||||||
|
{{> settings_checkbox
|
||||||
|
setting_name="realm_enable_guest_user_dm_warning"
|
||||||
|
prefix="id_"
|
||||||
|
is_checked=realm_enable_guest_user_dm_warning
|
||||||
|
label=admin_settings_label.realm_enable_guest_user_dm_warning}}
|
||||||
|
|
||||||
{{> ../dropdown_widget_with_label
|
{{> ../dropdown_widget_with_label
|
||||||
widget_name="realm_can_access_all_users_group"
|
widget_name="realm_can_access_all_users_group"
|
||||||
label=group_setting_labels.can_access_all_users_group
|
label=group_setting_labels.can_access_all_users_group
|
||||||
|
|||||||
@@ -630,6 +630,7 @@ test_ui("update_fade", ({override, override_rewire}) => {
|
|||||||
update_narrow_to_recipient_visibility_called = true;
|
update_narrow_to_recipient_visibility_called = true;
|
||||||
});
|
});
|
||||||
override_rewire(drafts, "update_compose_draft_count", noop);
|
override_rewire(drafts, "update_compose_draft_count", noop);
|
||||||
|
override(compose_pm_pill, "get_user_ids", () => []);
|
||||||
|
|
||||||
compose_state.set_message_type(undefined);
|
compose_state.set_message_type(undefined);
|
||||||
compose_recipient.update_on_recipient_change();
|
compose_recipient.update_on_recipient_change();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const compose_fade = mock_esm("../src/compose_fade", {
|
|||||||
});
|
});
|
||||||
const compose_pm_pill = mock_esm("../src/compose_pm_pill", {
|
const compose_pm_pill = mock_esm("../src/compose_pm_pill", {
|
||||||
get_user_ids_string: () => "",
|
get_user_ids_string: () => "",
|
||||||
|
get_user_ids: () => [],
|
||||||
});
|
});
|
||||||
const compose_ui = mock_esm("../src/compose_ui", {
|
const compose_ui = mock_esm("../src/compose_ui", {
|
||||||
autosize_textarea: noop,
|
autosize_textarea: noop,
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ const {set_current_user, set_realm} = zrequire("state_data");
|
|||||||
const stream_data = zrequire("stream_data");
|
const stream_data = zrequire("stream_data");
|
||||||
const compose_recipient = zrequire("/compose_recipient");
|
const compose_recipient = zrequire("/compose_recipient");
|
||||||
const user_groups = zrequire("user_groups");
|
const user_groups = zrequire("user_groups");
|
||||||
|
const {initialize_user_settings} = zrequire("user_settings");
|
||||||
|
|
||||||
|
mock_esm("../src/ui_util", {
|
||||||
|
place_caret_at_end: noop,
|
||||||
|
});
|
||||||
|
|
||||||
mock_esm("../src/group_permission_settings", {
|
mock_esm("../src/group_permission_settings", {
|
||||||
get_group_permission_setting_config: () => ({
|
get_group_permission_setting_config: () => ({
|
||||||
@@ -33,6 +38,8 @@ const realm = {};
|
|||||||
set_realm(realm);
|
set_realm(realm);
|
||||||
const current_user = {};
|
const current_user = {};
|
||||||
set_current_user(current_user);
|
set_current_user(current_user);
|
||||||
|
const user_settings = {defualt_language: "en"};
|
||||||
|
initialize_user_settings({user_settings});
|
||||||
|
|
||||||
const me = {
|
const me = {
|
||||||
email: "me@example.com",
|
email: "me@example.com",
|
||||||
@@ -54,6 +61,13 @@ const bob = {
|
|||||||
is_admin: true,
|
is_admin: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const guest = {
|
||||||
|
email: "guest@example.com",
|
||||||
|
user_id: 33,
|
||||||
|
full_name: "Guest",
|
||||||
|
is_guest: true,
|
||||||
|
};
|
||||||
|
|
||||||
const social_sub = {
|
const social_sub = {
|
||||||
stream_id: 101,
|
stream_id: 101,
|
||||||
name: "social",
|
name: "social",
|
||||||
@@ -66,6 +80,7 @@ people.initialize_current_user(me.user_id);
|
|||||||
|
|
||||||
people.add_active_user(alice);
|
people.add_active_user(alice);
|
||||||
people.add_active_user(bob);
|
people.add_active_user(bob);
|
||||||
|
people.add_active_user(guest);
|
||||||
|
|
||||||
const welcome_bot = {
|
const welcome_bot = {
|
||||||
email: "welcome-bot@example.com",
|
email: "welcome-bot@example.com",
|
||||||
@@ -116,6 +131,30 @@ function stub_message_row($textarea) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initialize_pm_pill(mock_template) {
|
||||||
|
$.clear_all_elements();
|
||||||
|
|
||||||
|
$("#compose-send-button").prop("disabled", false);
|
||||||
|
$("#compose-send-button").trigger("focus");
|
||||||
|
$("#compose-send-button .loader").hide();
|
||||||
|
|
||||||
|
const $pm_pill_container = $.create("fake-pm-pill-container");
|
||||||
|
$("#private_message_recipient")[0] = {};
|
||||||
|
$("#private_message_recipient").set_parent($pm_pill_container);
|
||||||
|
$pm_pill_container.set_find_results(".input", $("#private_message_recipient"));
|
||||||
|
$("#private_message_recipient").before = noop;
|
||||||
|
|
||||||
|
compose_pm_pill.initialize({
|
||||||
|
on_pill_create_or_remove: compose_recipient.update_placeholder_text,
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#zephyr-mirror-error").is = noop;
|
||||||
|
|
||||||
|
mock_template("input_pill.hbs", false, () => "<div>pill-html</div>");
|
||||||
|
|
||||||
|
mock_banners();
|
||||||
|
}
|
||||||
|
|
||||||
test_ui("validate_stream_message_address_info", ({mock_template}) => {
|
test_ui("validate_stream_message_address_info", ({mock_template}) => {
|
||||||
// For this test we basically only use FakeComposeBox
|
// For this test we basically only use FakeComposeBox
|
||||||
// to set up the DOM environment. We don't assert about
|
// to set up the DOM environment. We don't assert about
|
||||||
@@ -153,30 +192,6 @@ test_ui("validate_stream_message_address_info", ({mock_template}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test_ui("validate", ({mock_template, override}) => {
|
test_ui("validate", ({mock_template, override}) => {
|
||||||
function initialize_pm_pill() {
|
|
||||||
$.clear_all_elements();
|
|
||||||
|
|
||||||
$("#compose-send-button").prop("disabled", false);
|
|
||||||
$("#compose-send-button").trigger("focus");
|
|
||||||
$("#compose-send-button .loader").hide();
|
|
||||||
|
|
||||||
const $pm_pill_container = $.create("fake-pm-pill-container");
|
|
||||||
$("#private_message_recipient")[0] = {};
|
|
||||||
$("#private_message_recipient").set_parent($pm_pill_container);
|
|
||||||
$pm_pill_container.set_find_results(".input", $("#private_message_recipient"));
|
|
||||||
$("#private_message_recipient").before = noop;
|
|
||||||
|
|
||||||
compose_pm_pill.initialize({
|
|
||||||
on_pill_create_or_remove: compose_recipient.update_placeholder_text,
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#zephyr-mirror-error").is = noop;
|
|
||||||
|
|
||||||
mock_template("input_pill.hbs", false, () => "<div>pill-html</div>");
|
|
||||||
|
|
||||||
mock_banners();
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_content_to_compose_box() {
|
function add_content_to_compose_box() {
|
||||||
$("textarea#compose-textarea").val("foobarfoobar");
|
$("textarea#compose-textarea").val("foobarfoobar");
|
||||||
}
|
}
|
||||||
@@ -184,7 +199,7 @@ test_ui("validate", ({mock_template, override}) => {
|
|||||||
// test validating direct messages
|
// test validating direct messages
|
||||||
compose_state.set_message_type("private");
|
compose_state.set_message_type("private");
|
||||||
|
|
||||||
initialize_pm_pill();
|
initialize_pm_pill(mock_template);
|
||||||
add_content_to_compose_box();
|
add_content_to_compose_box();
|
||||||
compose_state.private_message_recipient("");
|
compose_state.private_message_recipient("");
|
||||||
let pm_recipient_error_rendered = false;
|
let pm_recipient_error_rendered = false;
|
||||||
@@ -238,7 +253,7 @@ test_ui("validate", ({mock_template, override}) => {
|
|||||||
assert.ok(compose_validate.validate());
|
assert.ok(compose_validate.validate());
|
||||||
override(realm, "realm_is_zephyr_mirror_realm", false);
|
override(realm, "realm_is_zephyr_mirror_realm", false);
|
||||||
|
|
||||||
initialize_pm_pill();
|
initialize_pm_pill(mock_template);
|
||||||
add_content_to_compose_box();
|
add_content_to_compose_box();
|
||||||
compose_state.private_message_recipient("welcome-bot@example.com");
|
compose_state.private_message_recipient("welcome-bot@example.com");
|
||||||
$("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea"));
|
$("#send_message_form").set_find_results(".message-textarea", $("textarea#compose-textarea"));
|
||||||
@@ -258,7 +273,7 @@ test_ui("validate", ({mock_template, override}) => {
|
|||||||
}
|
}
|
||||||
return "<banner-stub>";
|
return "<banner-stub>";
|
||||||
});
|
});
|
||||||
initialize_pm_pill();
|
initialize_pm_pill(mock_template);
|
||||||
compose_state.private_message_recipient("welcome-bot@example.com");
|
compose_state.private_message_recipient("welcome-bot@example.com");
|
||||||
$("textarea#compose-textarea").toggleClass = (classname, value) => {
|
$("textarea#compose-textarea").toggleClass = (classname, value) => {
|
||||||
assert.equal(classname, "invalid");
|
assert.equal(classname, "invalid");
|
||||||
@@ -281,7 +296,7 @@ test_ui("validate", ({mock_template, override}) => {
|
|||||||
assert.ok(zephyr_checked);
|
assert.ok(zephyr_checked);
|
||||||
assert.ok(zephyr_error_rendered);
|
assert.ok(zephyr_error_rendered);
|
||||||
|
|
||||||
initialize_pm_pill();
|
initialize_pm_pill(mock_template);
|
||||||
add_content_to_compose_box();
|
add_content_to_compose_box();
|
||||||
|
|
||||||
// test validating stream messages
|
// test validating stream messages
|
||||||
@@ -842,3 +857,68 @@ test_ui("test warn_if_topic_resolved", ({override, mock_template}) => {
|
|||||||
compose_validate.warn_if_topic_resolved(false);
|
compose_validate.warn_if_topic_resolved(false);
|
||||||
assert.ok(!error_shown);
|
assert.ok(!error_shown);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test_ui("test_warn_if_guest_in_dm_recipient", ({mock_template}) => {
|
||||||
|
let is_active = false;
|
||||||
|
|
||||||
|
mock_template("compose_banner/guest_in_dm_recipient_warning.hbs", false, (data) => {
|
||||||
|
assert.equal(data.classname, compose_banner.CLASSNAMES.guest_in_dm_recipient_warning);
|
||||||
|
assert.equal(
|
||||||
|
data.banner_text,
|
||||||
|
$t({defaultMessage: "Guest is a guest in this organization."}),
|
||||||
|
);
|
||||||
|
is_active = true;
|
||||||
|
return "<banner-stub>";
|
||||||
|
});
|
||||||
|
|
||||||
|
compose_state.set_message_type("private");
|
||||||
|
initialize_pm_pill(mock_template);
|
||||||
|
compose_state.private_message_recipient("guest@example.com");
|
||||||
|
const classname = compose_banner.CLASSNAMES.guest_in_dm_recipient_warning;
|
||||||
|
let $banner = $(`#compose_banners .${CSS.escape(classname)}`);
|
||||||
|
|
||||||
|
// if setting is disabled, remove warning if exists
|
||||||
|
realm.realm_enable_guest_user_dm_warning = false;
|
||||||
|
compose_validate.warn_if_guest_in_dm_recipient();
|
||||||
|
assert.ok(!is_active);
|
||||||
|
|
||||||
|
// to show warning for guest emails, banner should be created
|
||||||
|
realm.realm_enable_guest_user_dm_warning = true;
|
||||||
|
$banner.length = 0;
|
||||||
|
compose_validate.warn_if_guest_in_dm_recipient();
|
||||||
|
assert.ok(is_active);
|
||||||
|
assert.deepEqual(compose_state.get_recipient_guest_ids_for_dm_warning(), [33]);
|
||||||
|
|
||||||
|
// don't show warning for same guests if user closed the banner.
|
||||||
|
is_active = false;
|
||||||
|
compose_validate.warn_if_guest_in_dm_recipient();
|
||||||
|
assert.ok(!is_active);
|
||||||
|
|
||||||
|
// on modifying the guest recipient, update banner if already shown.
|
||||||
|
is_active = true;
|
||||||
|
const new_guest = {
|
||||||
|
email: "new_guest@example.com",
|
||||||
|
user_id: 34,
|
||||||
|
full_name: "New Guest",
|
||||||
|
is_guest: true,
|
||||||
|
};
|
||||||
|
people.add_active_user(new_guest);
|
||||||
|
|
||||||
|
initialize_pm_pill(mock_template);
|
||||||
|
compose_state.private_message_recipient("guest@example.com, new_guest@example.com");
|
||||||
|
$banner = $(`#compose_banners .${CSS.escape(classname)}`);
|
||||||
|
$banner.length = 1;
|
||||||
|
let is_updated = false;
|
||||||
|
$banner.set_find_results(".banner_content", {
|
||||||
|
text(content) {
|
||||||
|
assert.equal(
|
||||||
|
content,
|
||||||
|
$t({defaultMessage: "Guest and New Guest are guests in this organization."}),
|
||||||
|
);
|
||||||
|
is_updated = true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
compose_validate.warn_if_guest_in_dm_recipient();
|
||||||
|
assert.ok(is_updated);
|
||||||
|
assert.deepEqual(compose_state.get_recipient_guest_ids_for_dm_warning(), [33, 34]);
|
||||||
|
});
|
||||||
|
|||||||
@@ -835,6 +835,45 @@ test_people("dm_matches_search_string", () => {
|
|||||||
assert.ok(!result);
|
assert.ok(!result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test_people("filter_other_guest_ids", ({override}) => {
|
||||||
|
people.add_active_user(emp401);
|
||||||
|
people.add_active_user(emp402);
|
||||||
|
people.add_active_user(guest);
|
||||||
|
|
||||||
|
assert.equal(emp401.user_id, 401);
|
||||||
|
assert.equal(emp402.user_id, 402);
|
||||||
|
assert.equal(guest.user_id, 33);
|
||||||
|
|
||||||
|
let ids = [401, 402];
|
||||||
|
let guest_ids = people.filter_other_guest_ids(ids);
|
||||||
|
assert.equal(guest_ids.length, 0);
|
||||||
|
|
||||||
|
ids = [401, 402, 33];
|
||||||
|
guest_ids = people.filter_other_guest_ids(ids);
|
||||||
|
assert.equal(guest_ids.length, 1);
|
||||||
|
assert.equal(guest_ids[0], 33);
|
||||||
|
|
||||||
|
override(current_user, "is_guest", true);
|
||||||
|
override(current_user, "user_id", 1);
|
||||||
|
// User ID of the current user will not be included in result.
|
||||||
|
guest_ids = people.filter_other_guest_ids([1]);
|
||||||
|
assert.equal(guest_ids.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test_people("user_ids_to_full_names_array", () => {
|
||||||
|
people.add_active_user(emp401);
|
||||||
|
people.add_active_user(emp402);
|
||||||
|
|
||||||
|
assert.equal(emp401.user_id, 401);
|
||||||
|
assert.equal(emp402.user_id, 402);
|
||||||
|
|
||||||
|
const ids = [401, 402];
|
||||||
|
const names = people.user_ids_to_full_names_array(ids);
|
||||||
|
assert.equal(names.length, 2);
|
||||||
|
assert.equal(names[0], emp401.full_name);
|
||||||
|
assert.equal(names[1], emp402.full_name);
|
||||||
|
});
|
||||||
|
|
||||||
test_people("multi_user_methods", () => {
|
test_people("multi_user_methods", () => {
|
||||||
people.add_active_user(emp401);
|
people.add_active_user(emp401);
|
||||||
people.add_active_user(emp402);
|
people.add_active_user(emp402);
|
||||||
|
|||||||
17
zerver/migrations/0663_realm_enable_guest_user_dm_warning.py
Normal file
17
zerver/migrations/0663_realm_enable_guest_user_dm_warning.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-02-06 13:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("zerver", "0662_clear_realm_channel_fields_if_configured_channel_deactivated"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="realm",
|
||||||
|
name="enable_guest_user_dm_warning",
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -627,6 +627,9 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||||||
# Whether clients should display "(guest)" after names of guest users.
|
# Whether clients should display "(guest)" after names of guest users.
|
||||||
enable_guest_user_indicator = models.BooleanField(default=True)
|
enable_guest_user_indicator = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
# Whether to notify client when a DM has a guest recipient.
|
||||||
|
enable_guest_user_dm_warning = models.BooleanField(default=True)
|
||||||
|
|
||||||
# Define the types of the various automatically managed properties
|
# Define the types of the various automatically managed properties
|
||||||
property_types: dict[str, type | UnionType] = dict(
|
property_types: dict[str, type | UnionType] = dict(
|
||||||
allow_edit_history=bool,
|
allow_edit_history=bool,
|
||||||
@@ -640,6 +643,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||||||
disallow_disposable_email_addresses=bool,
|
disallow_disposable_email_addresses=bool,
|
||||||
email_changes_disabled=bool,
|
email_changes_disabled=bool,
|
||||||
emails_restricted_to_domains=bool,
|
emails_restricted_to_domains=bool,
|
||||||
|
enable_guest_user_dm_warning=bool,
|
||||||
enable_guest_user_indicator=bool,
|
enable_guest_user_indicator=bool,
|
||||||
enable_read_receipts=bool,
|
enable_read_receipts=bool,
|
||||||
enable_spectator_access=bool,
|
enable_spectator_access=bool,
|
||||||
|
|||||||
@@ -4748,6 +4748,13 @@ paths:
|
|||||||
Whether [new users joining](/help/restrict-account-creation#configuring-email-domain-restrictions)
|
Whether [new users joining](/help/restrict-account-creation#configuring-email-domain-restrictions)
|
||||||
this organization are required to have an email
|
this organization are required to have an email
|
||||||
address in one of the `realm_domains` configured for the organization.
|
address in one of the `realm_domains` configured for the organization.
|
||||||
|
enable_guest_user_dm_warning:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
Whether clients should show a warning when a user is composing
|
||||||
|
a DM to a guest user in this organization.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 348).
|
||||||
enable_guest_user_indicator:
|
enable_guest_user_indicator:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
@@ -17609,6 +17616,15 @@ paths:
|
|||||||
- 2 = Zulip Cloud free plan (LIMITED)
|
- 2 = Zulip Cloud free plan (LIMITED)
|
||||||
- 3 = Zulip Cloud Standard plan (STANDARD)
|
- 3 = Zulip Cloud Standard plan (STANDARD)
|
||||||
- 4 = Zulip Cloud Standard plan, sponsored for free (STANDARD_FREE)
|
- 4 = Zulip Cloud Standard plan, sponsored for free (STANDARD_FREE)
|
||||||
|
realm_enable_guest_user_dm_warning:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
Present if `realm` is present in `fetch_event_types`.
|
||||||
|
|
||||||
|
Whether clients should show a warning when a user is composing
|
||||||
|
a DM to a guest user in this organization.
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 348).
|
||||||
realm_enable_guest_user_indicator:
|
realm_enable_guest_user_indicator:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
"realm_embedded_bots",
|
"realm_embedded_bots",
|
||||||
"realm_emoji",
|
"realm_emoji",
|
||||||
"realm_empty_topic_display_name",
|
"realm_empty_topic_display_name",
|
||||||
|
"realm_enable_guest_user_dm_warning",
|
||||||
"realm_enable_guest_user_indicator",
|
"realm_enable_guest_user_indicator",
|
||||||
"realm_enable_read_receipts",
|
"realm_enable_read_receipts",
|
||||||
"realm_enable_spectator_access",
|
"realm_enable_spectator_access",
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ def update_realm(
|
|||||||
Json[int | str] | None,
|
Json[int | str] | None,
|
||||||
ApiParamConfig("move_messages_between_streams_limit_seconds"),
|
ApiParamConfig("move_messages_between_streams_limit_seconds"),
|
||||||
] = None,
|
] = None,
|
||||||
|
enable_guest_user_dm_warning: Json[bool] | None = None,
|
||||||
enable_guest_user_indicator: Json[bool] | None = None,
|
enable_guest_user_indicator: Json[bool] | None = None,
|
||||||
can_access_all_users_group: Json[GroupSettingChangeRequest] | None = None,
|
can_access_all_users_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
|||||||
Reference in New Issue
Block a user