users: Add "(guest)" to names for guest users.

This commit adds code to add "(guest)" to user names of guest
users in the following places -
- right sidebar
- user pills, including the pills in search suggestion typehaead
- typeaheads for user
- sender names in message feed
- user profile popover and modals.
- user name in not subscribed warning banner.

Note that the indicator is shown only if enable_guest_user_indicator
setting is set to true.

As a result of this change, we now translate "deactivated" text
shown in user pills for deactivated users.

Fixes part of #26700.
This commit is contained in:
Sahil Batra 2023-09-13 23:00:52 +05:30 committed by Tim Abbott
parent c51c1d5135
commit 49a047c27f
29 changed files with 100 additions and 9 deletions

View File

@ -140,6 +140,10 @@ IGNORED_PHRASES = [
r"does not apply to moderators and administrators", r"does not apply to moderators and administrators",
# Used in message-delete-time-limit setting label # Used in message-delete-time-limit setting label
r"does not apply to administrators", r"does not apply to administrators",
# Used as indicator with names for guest users.
r"guest",
# Used in pills for deactivated users.
r"deactivated",
] ]
# Sort regexes in descending order of their lengths. As a result, the # Sort regexes in descending order of their lengths. As a result, the

View File

@ -152,6 +152,7 @@ export function info_for(user_id) {
user_circle_class, user_circle_class,
status_text, status_text,
user_list_style, user_list_style,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(user_id),
}; };
} }

View File

@ -173,6 +173,7 @@ export function warn_if_mentioning_unsubscribed_user(mentioned, $textarea) {
can_subscribe_other_users, can_subscribe_other_users,
name: mentioned.full_name, name: mentioned.full_name,
classname: compose_banner.CLASSNAMES.recipient_not_subscribed, classname: compose_banner.CLASSNAMES.recipient_not_subscribed,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(user_id),
}; };
const new_row = render_not_subscribed_warning(context); const new_row = render_not_subscribed_warning(context);

View File

@ -17,6 +17,7 @@ export type InputPillItem<T> = {
img_src?: string; img_src?: string;
deactivated?: boolean; deactivated?: boolean;
status_emoji_info?: EmojiRenderingDetails & {emoji_alt_code: boolean}; // TODO: Move this in user_status.js status_emoji_info?: EmojiRenderingDetails & {emoji_alt_code: boolean}; // TODO: Move this in user_status.js
should_add_guest_user_indicator?: boolean;
} & T; } & T;
type InputPillCreateOptions<T> = { type InputPillCreateOptions<T> = {
@ -55,6 +56,7 @@ type InputPillRenderingDetails = {
deactivated?: boolean; deactivated?: boolean;
has_status?: boolean; has_status?: boolean;
status_emoji_info?: EmojiRenderingDetails & {emoji_alt_code: boolean}; status_emoji_info?: EmojiRenderingDetails & {emoji_alt_code: boolean};
should_add_guest_user_indicator?: boolean;
}; };
// These are the functions that are exposed to other modules. // These are the functions that are exposed to other modules.
@ -156,6 +158,7 @@ export function create<T>(opts: InputPillCreateOptions<T>): InputPillContainer<T
display_value: item.display_value, display_value: item.display_value,
has_image, has_image,
deactivated: item.deactivated, deactivated: item.deactivated,
should_add_guest_user_indicator: item.should_add_guest_user_indicator,
}; };
if (has_image) { if (has_image) {

View File

@ -418,6 +418,8 @@ export class MessageListView {
message_container.sender_is_bot = people.sender_is_bot(message_container.msg); message_container.sender_is_bot = people.sender_is_bot(message_container.msg);
message_container.sender_is_guest = people.sender_is_guest(message_container.msg); message_container.sender_is_guest = people.sender_is_guest(message_container.msg);
message_container.should_add_guest_indicator_for_sender =
people.should_add_guest_user_indicator(message_container.msg.sender_id);
message_container.small_avatar_url = people.small_avatar_url(message_container.msg); message_container.small_avatar_url = people.small_avatar_url(message_container.msg);
if (message_container.msg.stream_id) { if (message_container.msg.stream_id) {

View File

@ -68,6 +68,7 @@ export const page_params: {
realm_private_message_policy: number; realm_private_message_policy: number;
realm_push_notifications_enabled: boolean; realm_push_notifications_enabled: boolean;
realm_sentry_key: string | undefined; realm_sentry_key: string | undefined;
realm_enable_guest_user_indicator: boolean;
realm_upload_quota_mib: number | null; realm_upload_quota_mib: number | null;
realm_uri: string; realm_uri: string;
realm_user_group_edit_policy: number; realm_user_group_edit_policy: number;

View File

@ -770,6 +770,15 @@ export function user_is_bot(user_id: number): boolean {
return user.is_bot; return user.is_bot;
} }
export function should_add_guest_user_indicator(user_id: number): boolean {
if (!page_params.realm_enable_guest_user_indicator) {
return false;
}
const user = get_by_user_id(user_id);
return user.is_guest;
}
export function user_can_direct_message(recipient_ids_string: string): boolean { export function user_can_direct_message(recipient_ids_string: string): boolean {
// Common function for checking if a user can send a direct // Common function for checking if a user can send a direct
// message to the target user (or group of users) represented by a // message to the target user (or group of users) represented by a

View File

@ -34,6 +34,7 @@ function highlight_person(person, highlighter) {
display_value: new Handlebars.SafeString(highlighted_name), display_value: new Handlebars.SafeString(highlighted_name),
has_image: true, has_image: true,
img_src: avatar_url, img_src: avatar_url,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(person.user_id),
}; };
} }

View File

@ -92,6 +92,7 @@ export function render_person(person) {
user_circle_class, user_circle_class,
is_person: true, is_person: true,
status_emoji_info, status_emoji_info,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(person.user_id),
}; };
typeahead_arguments.secondary = person.delivery_email; typeahead_arguments.secondary = person.delivery_email;

View File

@ -289,6 +289,7 @@ function get_user_card_popover_data(
user_mention_syntax: people.get_mention_syntax(user.full_name, user.user_id), user_mention_syntax: people.get_mention_syntax(user.full_name, user.user_id),
date_joined, date_joined,
spectator_view, spectator_view,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(user.user_id),
}; };
if (user.is_bot) { if (user.is_bot) {

View File

@ -53,12 +53,12 @@ export function create_item_from_email(email, current_items) {
img_src: avatar_url, img_src: avatar_url,
deactivated: false, deactivated: false,
status_emoji_info, status_emoji_info,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(user.user_id),
}; };
// We pass deactivated true for a deactivated user // We pass deactivated true for a deactivated user
if (!people.is_person_active(user.user_id)) { if (!people.is_person_active(user.user_id)) {
item.deactivated = true; item.deactivated = true;
item.display_value = user.full_name + " (deactivated)";
} }
return item; return item;
@ -81,6 +81,7 @@ export function append_person(opts) {
email: person.email, email: person.email,
img_src: avatar_url, img_src: avatar_url,
status_emoji_info, status_emoji_info,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(person.user_id),
}; };
pill_widget.appendValidatedData(pill_data); pill_widget.appendValidatedData(pill_data);

View File

@ -366,6 +366,7 @@ export function show_user_profile(user, default_tab_key = "profile-tab") {
user_is_guest: user.is_guest, user_is_guest: user.is_guest,
show_user_subscribe_widget, show_user_subscribe_widget,
can_manage_profile, can_manage_profile,
should_add_guest_user_indicator: people.should_add_guest_user_indicator(user.user_id),
}; };
if (user.is_bot) { if (user.is_bot) {

View File

@ -223,7 +223,7 @@ ul {
display: inline-block; display: inline-block;
text-align: center; text-align: center;
&:not(.popover_action_icon) { &:not(.popover_action_icon, .guest-indicator) {
width: 14px; width: 14px;
} }

View File

@ -1,9 +1,17 @@
{{#> compose_banner }} {{#> compose_banner }}
<p class="banner_message"> <p class="banner_message">
{{#if can_subscribe_other_users}} {{#if can_subscribe_other_users}}
{{#if should_add_guest_user_indicator}}
{{#tr}}<strong>{name}</strong> <i>(guest)</i> is not subscribed to this stream. They will not be notified unless you subscribe them.{{/tr}}
{{else}}
{{#tr}}<strong>{name}</strong> is not subscribed to this stream. They will not be notified unless you subscribe them.{{/tr}} {{#tr}}<strong>{name}</strong> is not subscribed to this stream. They will not be notified unless you subscribe them.{{/tr}}
{{/if}}
{{else}} {{else}}
{{#if should_add_guest_user_indicator}}
{{#tr}}<strong>{name}</strong> <i>(guest)</i> is not subscribed to this stream. They will not be notified if you mention them.{{/tr}}
{{else}}
{{#tr}}<strong>{name}</strong> is not subscribed to this stream. They will not be notified if you mention them.{{/tr}} {{#tr}}<strong>{name}</strong> is not subscribed to this stream. They will not be notified if you mention them.{{/tr}}
{{/if}}
{{/if}} {{/if}}
</p> </p>
{{/compose_banner}} {{/compose_banner}}

View File

@ -4,6 +4,8 @@
{{/if}} {{/if}}
<span class="pill-label"> <span class="pill-label">
<span class="pill-value">{{ display_value }}</span> <span class="pill-value">{{ display_value }}</span>
{{~#if should_add_guest_user_indicator}}&nbsp;<i>({{t 'guest'}})</i>{{~/if~}}
{{~#if deactivated}}&nbsp;({{t 'deactivated'}}){{~/if~}}
{{~#if has_status~}} {{~#if has_status~}}
{{~> status_emoji status_emoji_info~}} {{~> status_emoji status_emoji_info~}}
{{~/if~}} {{~/if~}}

View File

@ -5,7 +5,7 @@
{{#if include_sender}} {{#if include_sender}}
<span class="sender_info_hover sender_name" role="button" tabindex="0"> <span class="sender_info_hover sender_name" role="button" tabindex="0">
<span class="view_user_card_tooltip sender_name_text" data-tooltip-template-id="view-user-card-tooltip-template"> <span class="view_user_card_tooltip sender_name_text" data-tooltip-template-id="view-user-card-tooltip-template">
{{msg/sender_full_name}} {{> user_full_name name=msg/sender_full_name should_add_guest_user_indicator=should_add_guest_indicator_for_sender}}
</span> </span>
{{#unless status_message}} {{#unless status_message}}
{{> status_emoji msg/status_emoji_info}} {{> status_emoji msg/status_emoji_info}}

View File

@ -3,7 +3,7 @@
<div class="user-card-popover-content no-auto-hide-right-sidebar-overlay"> <div class="user-card-popover-content no-auto-hide-right-sidebar-overlay">
<ul class="nav nav-list user-card-popover-actions" id="user_card_popover" data-user-id="{{user_id}}"> <ul class="nav nav-list user-card-popover-actions" id="user_card_popover" data-user-id="{{user_id}}">
<li class="popover_user_name_row"> <li class="popover_user_name_row">
<b class="user_full_name" data-tippy-content="{{user_full_name}}">{{user_full_name}}</b> <b class="user_full_name" data-tippy-content="{{user_full_name}}">{{> ../../user_full_name name=user_full_name}}</b>
{{#if is_active }} {{#if is_active }}
{{#if is_bot}} {{#if is_bot}}
<i class="zulip-icon zulip-icon-bot" aria-hidden="true"></i> <i class="zulip-icon zulip-icon-bot" aria-hidden="true"></i>

View File

@ -8,13 +8,13 @@
{{#if user_list_style.WITH_STATUS}} {{#if user_list_style.WITH_STATUS}}
<div class="user-name-and-status-wrapper"> <div class="user-name-and-status-wrapper">
<div class="user-name-and-status-emoji"> <div class="user-name-and-status-emoji">
<span class="user-name">{{name}}</span> <span class="user-name">{{> user_full_name}}</span>
{{> status_emoji status_emoji_info}} {{> status_emoji status_emoji_info}}
</div> </div>
<span class="status-text">{{status_text}}</span> <span class="status-text">{{status_text}}</span>
</div> </div>
{{else}} {{else}}
<span class="user-name">{{name}}</span> {{> user_full_name}}
{{> status_emoji status_emoji_info}} {{> status_emoji status_emoji_info}}
{{/if}} {{/if}}
</a> </a>

View File

@ -24,6 +24,9 @@
{{~ primary ~}} {{~ primary ~}}
{{/if}} {{/if}}
</strong> </strong>
{{~#if should_add_guest_user_indicator}}
<i>({{t 'guest'}})</i>
{{~/if}}
{{~#if has_status}} {{~#if has_status}}
{{> status_emoji status_emoji_info}} {{> status_emoji status_emoji_info}}
{{~/if}} {{~/if}}

View File

@ -0,0 +1,5 @@
{{#if should_add_guest_user_indicator}}
{{#tr}}{name} <i class="guest-indicator">(guest)</i>{{/tr}}
{{else}}
<span class="user-name">{{name}}</span>
{{/if}}

View File

@ -11,7 +11,7 @@
{{#if is_bot}} {{#if is_bot}}
<i class="zulip-icon zulip-icon-bot" aria-hidden="true"></i> <i class="zulip-icon zulip-icon-bot" aria-hidden="true"></i>
{{/if}} {{/if}}
<span class="user_profile_name">{{full_name}}</span> <span class="user_profile_name">{{> user_full_name name=full_name}}</span>
{{#if is_me}} {{#if is_me}}
<a href="/#settings/profile"> <a href="/#settings/profile">
<i class="fa fa-edit tippy-zulip-tooltip user_profile_manage_own_edit_button" data-tippy-content="{{t 'Edit profile' }}" aria-hidden="true"></i> <i class="fa fa-edit tippy-zulip-tooltip user_profile_manage_own_edit_button" data-tippy-content="{{t 'Edit profile' }}" aria-hidden="true"></i>

View File

@ -400,6 +400,7 @@ test("first/prev/next", ({override, mock_template}) => {
WITH_STATUS: true, WITH_STATUS: true,
WITH_AVATAR: false, WITH_AVATAR: false,
}, },
should_add_guest_user_indicator: false,
}); });
break; break;
case fred.user_id: case fred.user_id:
@ -419,6 +420,7 @@ test("first/prev/next", ({override, mock_template}) => {
WITH_STATUS: true, WITH_STATUS: true,
WITH_AVATAR: false, WITH_AVATAR: false,
}, },
should_add_guest_user_indicator: false,
}); });
break; break;
/* istanbul ignore next */ /* istanbul ignore next */
@ -489,6 +491,7 @@ test("insert_one_user_into_empty_list", ({override, mock_template}) => {
WITH_STATUS: true, WITH_STATUS: true,
WITH_AVATAR: false, WITH_AVATAR: false,
}, },
should_add_guest_user_indicator: false,
}); });
assert.ok(html.startsWith("<li data-user-id=")); assert.ok(html.startsWith("<li data-user-id="));
return html; return html;

View File

@ -519,6 +519,7 @@ test("get_items_for_users", () => {
user_circle_class: "user_circle_green", user_circle_class: "user_circle_green",
user_id: 1001, user_id: 1001,
user_list_style, user_list_style,
should_add_guest_user_indicator: false,
}, },
{ {
faded: false, faded: false,
@ -531,6 +532,7 @@ test("get_items_for_users", () => {
user_circle_class: "user_circle_empty", user_circle_class: "user_circle_empty",
user_id: 1002, user_id: 1002,
user_list_style, user_list_style,
should_add_guest_user_indicator: false,
}, },
{ {
faded: false, faded: false,
@ -543,6 +545,7 @@ test("get_items_for_users", () => {
user_circle_class: "user_circle_empty", user_circle_class: "user_circle_empty",
user_id: 1003, user_id: 1003,
user_list_style, user_list_style,
should_add_guest_user_indicator: false,
}, },
]); ]);
}); });

View File

@ -124,7 +124,7 @@ run_test("pills", ({override, override_rewire}) => {
assert.ok(get_by_email_called); assert.ok(get_by_email_called);
assert.equal(typeof res, "object"); assert.equal(typeof res, "object");
assert.equal(res.user_id, iago.user_id); assert.equal(res.user_id, iago.user_id);
assert.equal(res.display_value, iago.full_name + " (deactivated)"); assert.equal(res.display_value, iago.full_name);
assert.ok(res.deactivated); assert.ok(res.deactivated);
people.add_active_user(iago); people.add_active_user(iago);
})(); })();

View File

@ -37,6 +37,7 @@ mock_esm("../src/rows", {
mock_esm("../src/people", { mock_esm("../src/people", {
sender_is_bot: () => false, sender_is_bot: () => false,
sender_is_guest: () => false, sender_is_guest: () => false,
should_add_guest_user_indicator: () => false,
small_avatar_url: () => "fake/small/avatar/url", small_avatar_url: () => "fake/small/avatar/url",
}); });

View File

@ -1276,6 +1276,19 @@ test_people("get_realm_active_human_users", () => {
assert.deepEqual(humans, [me]); assert.deepEqual(humans, [me]);
}); });
test_people("should_show_guest_user_indicator", () => {
people.add_active_user(charles);
people.add_active_user(guest);
page_params.realm_enable_guest_user_indicator = false;
assert.equal(people.should_add_guest_user_indicator(charles.user_id), false);
assert.equal(people.should_add_guest_user_indicator(guest.user_id), false);
page_params.realm_enable_guest_user_indicator = true;
assert.equal(people.should_add_guest_user_indicator(charles.user_id), false);
assert.equal(people.should_add_guest_user_indicator(guest.user_id), true);
});
// reset to native Date() // reset to native Date()
run_test("reset MockDate", () => { run_test("reset MockDate", () => {
MockDate.reset(); MockDate.reset();

View File

@ -853,6 +853,7 @@ test("people_suggestions", ({override, mock_template}) => {
user_id: 202, user_id: 202,
full_name: "Bob Térry", full_name: "Bob Térry",
avatar_url: example_avatar_url, avatar_url: example_avatar_url,
is_guest: false,
}; };
const alice = { const alice = {
@ -917,6 +918,31 @@ test("people_suggestions", ({override, mock_template}) => {
assert.equal(get_avatar_url("sender:bob@zulip.com"), expectedString); assert.equal(get_avatar_url("sender:bob@zulip.com"), expectedString);
assert.equal(get_avatar_url("dm-including:bob@zulip.com"), expectedString); assert.equal(get_avatar_url("dm-including:bob@zulip.com"), expectedString);
function get_should_add_guest_user_indicator(q) {
return suggestions.lookup_table.get(q).user_pill_context.should_add_guest_user_indicator;
}
page_params.realm_enable_guest_user_indicator = true;
suggestions = get_suggestions(query);
assert.equal(get_should_add_guest_user_indicator("dm:bob@zulip.com"), false);
assert.equal(get_should_add_guest_user_indicator("sender:bob@zulip.com"), false);
assert.equal(get_should_add_guest_user_indicator("dm-including:bob@zulip.com"), false);
bob.is_guest = true;
suggestions = get_suggestions(query);
assert.equal(get_should_add_guest_user_indicator("dm:bob@zulip.com"), true);
assert.equal(get_should_add_guest_user_indicator("sender:bob@zulip.com"), true);
assert.equal(get_should_add_guest_user_indicator("dm-including:bob@zulip.com"), true);
page_params.realm_enable_guest_user_indicator = false;
suggestions = get_suggestions(query);
assert.equal(get_should_add_guest_user_indicator("dm:bob@zulip.com"), false);
assert.equal(get_should_add_guest_user_indicator("sender:bob@zulip.com"), false);
assert.equal(get_should_add_guest_user_indicator("dm-including:bob@zulip.com"), false);
suggestions = get_suggestions("Ted "); // note space suggestions = get_suggestions("Ted "); // note space
expected = ["Ted", "sender:ted@zulip.com", "dm:ted@zulip.com", "dm-including:ted@zulip.com"]; expected = ["Ted", "sender:ted@zulip.com", "dm:ted@zulip.com", "dm-including:ted@zulip.com"];

View File

@ -306,7 +306,7 @@ test_ui("populate_user_groups", ({mock_template, override, override_rewire}) =>
assert.ok(get_by_email_called); assert.ok(get_by_email_called);
assert.equal(typeof res, "object"); assert.equal(typeof res, "object");
assert.equal(res.user_id, bob.user_id); assert.equal(res.user_id, bob.user_id);
assert.equal(res.display_value, bob.full_name + " (deactivated)"); assert.equal(res.display_value, bob.full_name);
assert.ok(res.deactivated); assert.ok(res.deactivated);
people.add_active_user(bob); people.add_active_user(bob);
})(); })();

View File

@ -36,6 +36,7 @@ const isaac_item = {
deactivated: false, deactivated: false,
img_src: `http://zulip.zulipdev.com/avatar/${isaac.user_id}?s=50`, img_src: `http://zulip.zulipdev.com/avatar/${isaac.user_id}?s=50`,
status_emoji_info: undefined, status_emoji_info: undefined,
should_add_guest_user_indicator: false,
}; };
let pill_widget = {}; let pill_widget = {};