Skip to content

Commit cb6f0fd

Browse files
Vector73adnan-td
authored andcommitted
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 zulip#30078. Co-authored-by: adnan-td <[email protected]>
1 parent d852aef commit cb6f0fd

23 files changed

+323
-29
lines changed

api_docs/changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
2020

2121
## Changes in Zulip 10.0
2222

23+
**Feature level 348**
24+
25+
* [`POST /register`](/api/register-queue), [`POST /events`](/api/get-events),
26+
`PATCH /realm`: Added `enable_guest_user_dm_warning` setting to decide
27+
whether clients should show a warning when a user is composing to a
28+
guest user in the organization.
29+
2330
**Feature level 347**
2431

2532
* [Markdown message formatting](/api/message-formatting#links-to-channels-topics-and-messages):

help/guest-users.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ pricing](/help/zulip-cloud-billing#temporary-users-and-guests) for guest users.
4242

4343
{end_tabs}
4444

45+
## Configure warning for direct messages to guest users
46+
47+
{start_tabs}
48+
49+
{tab|desktop-web}
50+
51+
{settings_tab|organization-permissions}
52+
53+
1. Under **Guests**, toggle **Display a warning when composing a direct message with guest user recipients**.
54+
55+
{!save-changes.md!}
56+
57+
{end_tabs}
58+
4559
## Configure whether guests can see all other users
4660

4761
{!cloud-plus-only.md!}

version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
# new level means in api_docs/changelog.md, as well as "**Changes**"
3535
# entries in the endpoint's documentation in `zulip.yaml`.
3636

37-
API_FEATURE_LEVEL = 347 # Last bumped for /with/ in topic links.
37+
API_FEATURE_LEVEL = 348 # Last bumped for enable_guest_user_dm_warning.
3838

3939
# Bump the minor PROVISION_VERSION to indicate that folks should provision
4040
# only when going from an old version of the code to a newer version. Bump

web/src/admin.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ const admin_settings_label = {
7373
realm_enable_guest_user_indicator: $t({
7474
defaultMessage: "Display “(guest)” after names of guest users",
7575
}),
76+
realm_enable_guest_user_dm_warning: $t({
77+
defaultMessage:
78+
"Display a warning when composing a direct message with guest user recipients",
79+
}),
7680
};
7781

7882
function insert_tip_box(): void {
@@ -246,6 +250,7 @@ export function build_page(): void {
246250
automatically_unmute_topics_in_muted_streams_policy_values:
247251
settings_config.automatically_follow_or_unmute_topics_policy_values,
248252
realm_enable_guest_user_indicator: realm.realm_enable_guest_user_indicator,
253+
realm_enable_guest_user_dm_warning: realm.realm_enable_guest_user_dm_warning,
249254
active_user_list_dropdown_widget_name: settings_users.active_user_list_dropdown_widget_name,
250255
deactivated_user_list_dropdown_widget_name:
251256
settings_users.deactivated_user_list_dropdown_widget_name,

web/src/compose_actions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ function clear_box(): void {
127127
// TODO: Better encapsulate at-mention warnings.
128128
compose_validate.clear_topic_resolved_warning();
129129
compose_validate.clear_stream_wildcard_warnings($("#compose_banners"));
130+
compose_validate.clear_guest_in_dm_recipient_warning();
130131
compose_validate.set_user_acknowledged_stream_wildcard_flag(false);
131132

132133
compose_state.set_recipient_edited_manually(false);
@@ -408,6 +409,8 @@ export let start = (raw_opts: ComposeActionsStartOpts): void => {
408409

409410
// Show a warning if topic is resolved
410411
compose_validate.warn_if_topic_resolved(true);
412+
// Show a warning if dm recipient contains guest
413+
compose_validate.warn_if_guest_in_dm_recipient();
411414
// Show a warning if the user is in a search narrow when replying to a message
412415
if (opts.is_reply) {
413416
compose_validate.warn_if_in_search_view();

web/src/compose_banner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const CLASSNAMES = {
4343
recipient_not_subscribed: "recipient_not_subscribed",
4444
wildcard_warning: "wildcard_warning",
4545
private_stream_warning: "private_stream_warning",
46+
guest_in_dm_recipient_warning: "guest_in_dm_recipient_warning",
4647
unscheduled_message: "unscheduled_message",
4748
search_view: "search_view",
4849
// errors

web/src/compose_recipient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ function update_fade(): void {
114114
export function update_on_recipient_change(): void {
115115
update_fade();
116116
update_narrow_to_recipient_visibility();
117+
compose_validate.warn_if_guest_in_dm_recipient();
117118
drafts.update_compose_draft_count();
118119
check_posting_policy_for_compose_box();
119120
}

web/src/compose_state.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ let last_focused_compose_type_input: HTMLTextAreaElement | undefined;
1717
// the narrow and the user should still be able to see the banner once after
1818
// performing these actions
1919
let recipient_viewed_topic_resolved_banner = false;
20+
let recipient_guest_ids_for_dm_warning: number[] = [];
2021

2122
export function set_recipient_edited_manually(flag: boolean): void {
2223
recipient_edited_manually = flag;
@@ -58,6 +59,14 @@ export function has_recipient_viewed_topic_resolved_banner(): boolean {
5859
return recipient_viewed_topic_resolved_banner;
5960
}
6061

62+
export function set_recipient_guest_ids_for_dm_warning(guest_ids: number[]): void {
63+
recipient_guest_ids_for_dm_warning = guest_ids;
64+
}
65+
66+
export function get_recipient_guest_ids_for_dm_warning(): number[] {
67+
return recipient_guest_ids_for_dm_warning;
68+
}
69+
6170
export function composing(): boolean {
6271
// This is very similar to get_message_type(), but it returns
6372
// a boolean.

web/src/compose_validate.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import $ from "jquery";
2+
import _ from "lodash";
23

34
import * as resolved_topic from "../shared/src/resolved_topic.ts";
45
import render_compose_banner from "../templates/compose_banner/compose_banner.hbs";
6+
import render_guest_in_dm_recipient_warning from "../templates/compose_banner/guest_in_dm_recipient_warning.hbs";
57
import render_not_subscribed_warning from "../templates/compose_banner/not_subscribed_warning.hbs";
68
import render_private_stream_warning from "../templates/compose_banner/private_stream_warning.hbs";
79
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 {
389391
}
390392
}
391393

394+
export function clear_guest_in_dm_recipient_warning(): void {
395+
// We don't call set_recipient_guest_ids_for_dm_warning here, so
396+
// that reopening the same draft won't make the banner reappear.
397+
const classname = compose_banner.CLASSNAMES.guest_in_dm_recipient_warning;
398+
$(`#compose_banners .${CSS.escape(classname)}`).remove();
399+
}
400+
401+
// Only called on recipient change. Adds new banner if not already
402+
// exists or updates the existing banner or removes banner if no
403+
// guest in the dm.
404+
export function warn_if_guest_in_dm_recipient(): void {
405+
if (!compose_state.composing()) {
406+
return;
407+
}
408+
const recipient_ids = compose_pm_pill.get_user_ids();
409+
const guest_ids = people.filter_other_guest_ids(recipient_ids);
410+
411+
if (
412+
!realm.realm_enable_guest_user_dm_warning ||
413+
compose_state.get_message_type() !== "private" ||
414+
guest_ids.length === 0
415+
) {
416+
clear_guest_in_dm_recipient_warning();
417+
compose_state.set_recipient_guest_ids_for_dm_warning([]);
418+
return;
419+
}
420+
// If warning was shown earlier for same guests in the recipients, do nothing.
421+
if (_.isEqual(compose_state.get_recipient_guest_ids_for_dm_warning(), guest_ids)) {
422+
return;
423+
}
424+
425+
const guest_names = people.user_ids_to_full_names_array(guest_ids);
426+
let banner_text: string;
427+
428+
if (guest_names.length === 1) {
429+
banner_text = $t(
430+
{defaultMessage: "{name} is a guest in this organization."},
431+
{name: guest_names[0]},
432+
);
433+
} else {
434+
const names_string = util.format_array_as_list(guest_names, "long", "conjunction");
435+
banner_text = $t(
436+
{defaultMessage: "{names} are guests in this organization."},
437+
{names: names_string},
438+
);
439+
}
440+
441+
const classname = compose_banner.CLASSNAMES.guest_in_dm_recipient_warning;
442+
let $banner = $(`#compose_banners .${CSS.escape(classname)}`);
443+
444+
compose_state.set_recipient_guest_ids_for_dm_warning(guest_ids);
445+
// Update banner text if banner already exists.
446+
if ($banner.length === 1) {
447+
$banner.find(".banner_content").text(banner_text);
448+
return;
449+
}
450+
451+
$banner = $(
452+
render_guest_in_dm_recipient_warning({
453+
banner_text,
454+
classname: compose_banner.CLASSNAMES.guest_in_dm_recipient_warning,
455+
}),
456+
);
457+
compose_banner.append_compose_banner_to_banner_list($banner, $("#compose_banners"));
458+
}
459+
392460
function show_stream_wildcard_warnings(opts: StreamWildcardOptions): void {
393461
const subscriber_count = peer_data.get_subscriber_count(opts.stream_id) || 0;
394462
const stream_name = sub_store.maybe_get_stream_name(opts.stream_id);

web/src/people.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,17 @@ export function pm_with_operand_ids(operand: string): number[] | undefined {
687687
return user_ids;
688688
}
689689

690+
export function filter_other_guest_ids(user_ids: number[]): number[] {
691+
return sort_numerically(
692+
user_ids.filter((id) => id !== current_user.user_id && get_by_user_id(id)?.is_guest),
693+
);
694+
}
695+
696+
export function user_ids_to_full_names_array(user_ids: number[]): string[] {
697+
const names = user_ids.map((user_id) => get_by_user_id(user_id).full_name).sort(util.strcmp);
698+
return names;
699+
}
700+
690701
export function emails_to_slug(emails_string: string): string | undefined {
691702
let slug = reply_to_to_user_ids_string(emails_string);
692703

web/src/server_events_dispatch.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import * as compose_closed_ui from "./compose_closed_ui.ts";
1616
import * as compose_pm_pill from "./compose_pm_pill.ts";
1717
import * as compose_recipient from "./compose_recipient.ts";
1818
import * as compose_state from "./compose_state.ts";
19+
import * as compose_validate from "./compose_validate.ts";
1920
import {electron_bridge} from "./electron_bridge.ts";
2021
import * as emoji from "./emoji.ts";
2122
import * as emoji_picker from "./emoji_picker.ts";
@@ -262,6 +263,7 @@ export function dispatch_normal_event(event) {
262263
want_advertise_in_communities_directory: noop,
263264
wildcard_mention_policy: noop,
264265
enable_read_receipts: settings_account.update_send_read_receipts_tooltip,
266+
enable_guest_user_dm_warning: compose_validate.warn_if_guest_in_dm_recipient,
265267
enable_guest_user_indicator: noop,
266268
};
267269
switch (event.op) {

web/src/state_data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ export const realm_schema = z.object({
337337
}),
338338
),
339339
realm_empty_topic_display_name: z.string(),
340+
realm_enable_guest_user_dm_warning: z.boolean(),
340341
realm_enable_guest_user_indicator: z.boolean(),
341342
realm_enable_read_receipts: z.boolean(),
342343
realm_enable_spectator_access: z.boolean(),
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<div class="above_compose_banner main-view-banner warning-style {{classname}}">
2+
<p class="banner_content">
3+
{{banner_text}}
4+
</p>
5+
<a role="button" class="zulip-icon zulip-icon-close main-view-banner-close-button"></a>
6+
</div>

web/templates/settings/organization_permissions_admin.hbs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@
321321
is_checked=realm_enable_guest_user_indicator
322322
label=admin_settings_label.realm_enable_guest_user_indicator}}
323323

324+
{{> settings_checkbox
325+
setting_name="realm_enable_guest_user_dm_warning"
326+
prefix="id_"
327+
is_checked=realm_enable_guest_user_dm_warning
328+
label=admin_settings_label.realm_enable_guest_user_dm_warning}}
329+
324330
{{> ../dropdown_widget_with_label
325331
widget_name="realm_can_access_all_users_group"
326332
label=group_setting_labels.can_access_all_users_group

web/tests/compose.test.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ test_ui("update_fade", ({override, override_rewire}) => {
630630
update_narrow_to_recipient_visibility_called = true;
631631
});
632632
override_rewire(drafts, "update_compose_draft_count", noop);
633+
override(compose_pm_pill, "get_user_ids", () => []);
633634

634635
compose_state.set_message_type(undefined);
635636
compose_recipient.update_on_recipient_change();

web/tests/compose_actions.test.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const compose_fade = mock_esm("../src/compose_fade", {
4242
});
4343
const compose_pm_pill = mock_esm("../src/compose_pm_pill", {
4444
get_user_ids_string: () => "",
45+
get_user_ids: () => [],
4546
});
4647
const compose_ui = mock_esm("../src/compose_ui", {
4748
autosize_textarea: noop,

0 commit comments

Comments
 (0)