DEV: Use Notice API for mention warnings (#23238)

This PR swaps out the custom pathway to publishing and rendering mention warnings after a message is sent.

ChatPublisher#publish_notice is used, and expanded. Now, instead of only accepting text_content as an argument, component and component_args are accepted and there is a renderer for these components.

Translations moved to server, as notices expect text to be passed in unless a component is rendered

The warnings are rendered at the top now, outside of the scope of the single message that sent it.

I entirely removed the jit_messages_spec b/c it's duplicate testing of other parts of the app. IMO we don't need a backend test for a feature, a component test for the feature AND a system test (that is slow and potentially even flakey due to timing issues with wait) to test the same thing. So jit_messages_spec is gone.
This commit is contained in:
Mark VanLandingham 2023-09-01 09:07:23 -05:00 committed by GitHub
parent ed35ae4dcd
commit 9c65e2140a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 337 additions and 662 deletions

View File

@ -387,31 +387,6 @@ module Chat
end
end
def self.publish_inaccessible_mentions(
user_id,
chat_message,
cannot_chat_users,
without_membership,
too_many_members,
mentions_disabled,
global_mentions_disabled
)
MessageBus.publish(
"/chat/#{chat_message.chat_channel_id}",
{
type: :mention_warning,
chat_message_id: chat_message.id,
cannot_see: cannot_chat_users.map { |u| { username: u.username, id: u.id } }.as_json,
without_membership:
without_membership.map { |u| { username: u.username, id: u.id } }.as_json,
groups_with_too_many_members: too_many_members.map(&:name).as_json,
group_mentions_disabled: mentions_disabled.map(&:name).as_json,
global_mentions_disabled: global_mentions_disabled,
},
user_ids: [user_id],
)
end
def self.publish_kick_users(channel_id, user_ids)
MessageBus.publish(
kick_users_message_bus_channel(channel_id),
@ -478,8 +453,19 @@ module Chat
)
end
def self.publish_notice(user_id:, channel_id:, text_content:)
payload = { type: "notice", text_content: text_content, channel_id: channel_id }
def self.publish_notice(user_id:, channel_id:, text_content: nil, type: nil, data: nil)
# Notices are either plain text sent to the client, or a "type" with data. The
# client will then translate that type and data into a front-end component.
if text_content.blank? && type.blank? && data.blank?
raise "Cannot publish notice without text content or a type"
end
payload = { type: "notice", channel_id: channel_id }
if text_content
payload[:text_content] = text_content
else
payload[:notice_type] = type
payload[:data] = data
end
MessageBus.publish("/chat/#{channel_id}", payload, user_ids: [user_id])
end

View File

@ -118,7 +118,6 @@
@message={{@message}}
@onRetry={{@resendStagedMessage}}
/>
<Chat::Message::MentionWarning @message={{@message}} />
</div>
{{#if this.showThreadIndicator}}

View File

@ -0,0 +1,20 @@
<div class="chat-notices__notice">
{{#if @notice.textContent}}
<p class="chat-notices__notice__content">
{{@notice.textContent}}
</p>
{{else}}
<this.component
@channel={{@channel}}
@notice={{@notice}}
@clearNotice={{this.clearNotice}}
/>
{{/if}}
<DButton
@icon="times"
@action={{this.clearNotice}}
class="btn-flat chat-notices__notice__clear"
/>
</div>

View File

@ -0,0 +1,21 @@
import Component from "@glimmer/component";
import MentionWithoutMembership from "discourse/plugins/chat/discourse/components/chat/notices/mention_without_membership";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
const COMPONENT_DICT = {
mention_without_membership: MentionWithoutMembership,
};
export default class ChatNotices extends Component {
@service("chat-channel-pane-subscriptions-manager") subscriptionsManager;
@action
clearNotice() {
this.subscriptionsManager.clearNotice(this.args.notice);
}
get component() {
return COMPONENT_DICT[this.args.notice.type];
}
}

View File

@ -2,16 +2,6 @@
<ChatRetentionReminder @channel={{@channel}} />
{{#each this.noticesForChannel as |notice|}}
<div class="chat-notices__notice">
<p class="chat-notices__notice__content">
{{notice.textContent}}
</p>
<DButton
@icon="times"
@action={{fn this.clearNotice notice}}
class="btn-flat chat-notices__notice__clear"
/>
</div>
<ChatNotice @notice={{notice}} @channel={{@channel}} />
{{/each}}
</div>

View File

@ -1,6 +1,5 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
export default class ChatNotices extends Component {
@service("chat-channel-pane-subscriptions-manager") subscriptionsManager;
@ -10,9 +9,4 @@ export default class ChatNotices extends Component {
(notice) => notice.channelId === this.args.channel.id
);
}
@action
clearNotice(notice) {
this.subscriptionsManager.clearNotice(notice);
}
}

View File

@ -1,62 +0,0 @@
{{#if this.shouldRender}}
<div class="chat-message-mention-warning alert alert-info">
{{#if this.mentionWarning.invitationSent}}
<span
class="chat-message-mention-warning__invitation-sent"
{{chat/later-fn this.onDismissInvitationSent 3000}}
>
{{d-icon "check"}}
<span>
{{i18n
"chat.mention_warning.invitations_sent"
count=this.mentionWarning.withoutMembership.length
}}
</span>
</span>
{{else}}
<DButton
class="chat-message-mention-warning__dismiss-btn btn-flat"
title={{i18n "chat.mention_warning.dismiss"}}
@action={{this.onDismissMentionWarning}}
@icon="times"
/>
{{#if this.mentionWarning.cannotSee}}
<p class="chat-message-mention-warning__text -cannot-see">
{{this.mentionedCannotSeeText}}
</p>
{{/if}}
{{#if this.mentionWarning.withoutMembership}}
<p class="chat-message-mention-warning__text -without-membership">
<span>{{this.mentionedWithoutMembershipText}}</span>
<a href {{on "click" this.onSendInvite bubbles=false}}>
{{i18n "chat.mention_warning.invite"}}
</a>
</p>
{{/if}}
{{#if this.mentionWarning.groupWithMentionsDisabled}}
<p
class="chat-message-mention-warning__text -groups-with-mentions-disabled"
>
{{this.groupsWithDisabledMentions}}
</p>
{{/if}}
{{#if this.mentionWarning.groupsWithTooManyMembers}}
<p
class="chat-message-mention-warning__text -groups-with-too-many-members"
>
{{this.groupsWithTooManyMembers}}
</p>
{{/if}}
{{#if this.mentionWarning.globalMentionsDisabled}}
<p class="chat-message-mention-warning__text -global-mentions-disabled">
{{i18n "chat.mention_warning.channel_wide_mentions_disallowed"}}
</p>
{{/if}}
{{/if}}
</div>
{{/if}}

View File

@ -1,99 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
import I18n from "I18n";
export default class ChatMessageMentionWarning extends Component {
@service("chat-api") api;
@action
async onSendInvite() {
const userIds = this.mentionWarning.withoutMembership.mapBy("id");
try {
await this.api.invite(this.args.message.channel.id, userIds, {
messageId: this.args.message.id,
});
this.mentionWarning.invitationSent = true;
} catch (error) {
popupAjaxError(error);
}
}
@action
onDismissInvitationSent() {
this.mentionWarning.invitationSent = false;
}
@action
onDismissMentionWarning() {
this.args.message.mentionWarning = null;
}
get shouldRender() {
return (
this.mentionWarning &&
(this.mentionWarning.groupWithMentionsDisabled?.length ||
this.mentionWarning.cannotSee?.length ||
this.mentionWarning.withoutMembership?.length ||
this.mentionWarning.groupsWithTooManyMembers?.length ||
this.mentionWarning.globalMentionsDisabled)
);
}
get mentionWarning() {
return this.args.message.mentionWarning;
}
get mentionedCannotSeeText() {
return this.#findTranslatedWarning(
"chat.mention_warning.cannot_see",
"chat.mention_warning.cannot_see_multiple",
{
username: this.mentionWarning?.cannotSee?.[0]?.username,
count: this.mentionWarning?.cannotSee?.length,
}
);
}
get mentionedWithoutMembershipText() {
return this.#findTranslatedWarning(
"chat.mention_warning.without_membership",
"chat.mention_warning.without_membership_multiple",
{
username: this.mentionWarning?.withoutMembership?.[0]?.username,
count: this.mentionWarning?.withoutMembership?.length,
}
);
}
get groupsWithDisabledMentions() {
return this.#findTranslatedWarning(
"chat.mention_warning.group_mentions_disabled",
"chat.mention_warning.group_mentions_disabled_multiple",
{
group_name: this.mentionWarning?.groupWithMentionsDisabled?.[0],
count: this.mentionWarning?.groupWithMentionsDisabled?.length,
}
);
}
get groupsWithTooManyMembers() {
return this.#findTranslatedWarning(
"chat.mention_warning.too_many_members",
"chat.mention_warning.too_many_members_multiple",
{
group_name: this.mentionWarning.groupsWithTooManyMembers?.[0],
count: this.mentionWarning.groupsWithTooManyMembers?.length,
}
);
}
#findTranslatedWarning(oneKey, multipleKey, args) {
const translationKey = args.count === 1 ? oneKey : multipleKey;
args.count--;
return I18n.t(translationKey, args);
}
}

View File

@ -0,0 +1,29 @@
<div class="mention-without-membership-notice">
{{#if this.invitationsSent}}
<span
class="mention-without-membership-notice__invitation-sent"
{{chat/later-fn @clearNotice 3000}}
>
{{d-icon "check"}}
<span>
{{i18n
"chat.mention_warning.invitations_sent"
count=this.userIds.length
}}
</span>
</span>
{{else}}
<p class="mention-without-membership-notice__body -without-membership">
<span
class="mention-without-membership-notice__body__text"
>{{@notice.data.text}}</span>
<a
class="mention-without-membership-notice__body__link"
href
{{on "click" this.sendInvitations}}
>
{{i18n "chat.mention_warning.invite"}}
</a>
</p>
{{/if}}
</div>

View File

@ -0,0 +1,31 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service";
import { tracked } from "@glimmer/tracking";
export default class MentionWithoutMembership extends Component {
@service("chat-api") chatApi;
@tracked invitationsSent = false;
get userIds() {
return this.args.notice.data.user_ids;
}
@action
async sendInvitations(event) {
// preventDefault to avoid a refresh
event.preventDefault();
try {
await this.chatApi.invite(this.args.channel.id, this.userIds, {
messageId: this.args.notice.data.messageId,
});
this.invitationsSent = true;
} catch (error) {
popupAjaxError(error);
}
}
}

View File

@ -1,35 +0,0 @@
<StyleguideExample @title="<Chat::Message::MentionWarning>">
<Styleguide::Component>
<Chat::Message::MentionWarning @message={{this.message}} />
</Styleguide::Component>
<Styleguide::Controls::Row @name="Cannot see">
<DToggleSwitch
@state={{gt this.message.mentionWarning.cannotSee.length 0}}
{{on "click" this.toggleCannotSee}}
/>
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="Group with mentions disabled">
<DToggleSwitch
@state={{gt
this.message.mentionWarning.groupWithMentionsDisabled.length
0
}}
{{on "click" this.toggleGroupWithMentionsDisabled}}
/>
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="Group with too many members">
<DToggleSwitch
@state={{gt
this.message.mentionWarning.groupsWithTooManyMembers.length
0
}}
{{on "click" this.toggleGroupsWithTooManyMembers}}
/>
</Styleguide::Controls::Row>
<Styleguide::Controls::Row @name="Without membership">
<DToggleSwitch
@state={{gt this.message.mentionWarning.withoutMembership.length 0}}
{{on "click" this.toggleWithoutMembership}}
/>
</Styleguide::Controls::Row>
</StyleguideExample>

View File

@ -1,75 +0,0 @@
import Component from "@glimmer/component";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
export default class ChatMessageMentionWarning extends Component {
@service currentUser;
constructor() {
super(...arguments);
this.message = fabricators.message({ user: this.currentUser });
}
@action
toggleCannotSee() {
if (this.message.mentionWarning?.cannotSee) {
this.message.mentionWarning = null;
} else {
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
cannot_see: [fabricators.user({ username: "bob" })].map((u) => {
return { username: u.username, id: u.id };
}),
}
);
}
}
@action
toggleGroupWithMentionsDisabled() {
if (this.message.mentionWarning?.groupWithMentionsDisabled) {
this.message.mentionWarning = null;
} else {
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
group_mentions_disabled: [fabricators.group()].mapBy("name"),
}
);
}
}
@action
toggleGroupsWithTooManyMembers() {
if (this.message.mentionWarning?.groupsWithTooManyMembers) {
this.message.mentionWarning = null;
} else {
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
groups_with_too_many_members: [
fabricators.group(),
fabricators.group({ name: "Moderators" }),
].mapBy("name"),
}
);
}
}
@action
toggleWithoutMembership() {
if (this.message.mentionWarning?.withoutMembership) {
this.message.mentionWarning = null;
} else {
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
without_membership: [fabricators.user()].map((u) => {
return { username: u.username, id: u.id };
}),
}
);
}
}
}

View File

@ -13,7 +13,6 @@ import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
import ChatThreadPreview from "discourse/plugins/chat/discourse/models/chat-thread-preview";
import ChatDirectMessage from "discourse/plugins/chat/discourse/models/chat-direct-message";
import ChatMessageMentionWarning from "discourse/plugins/chat/discourse/models/chat-message-mention-warning";
import ChatMessageReaction from "discourse/plugins/chat/discourse/models/chat-message-reaction";
import User from "discourse/models/user";
import Bookmark from "discourse/models/bookmark";
@ -157,10 +156,6 @@ function groupFabricator(args = {}) {
});
}
function messageMentionWarningFabricator(message, args = {}) {
return ChatMessageMentionWarning.create(message, args);
}
function uploadFabricator() {
return {
extension: "jpeg",
@ -191,6 +186,5 @@ export default {
upload: uploadFabricator,
category: categoryFabricator,
directMessage: directMessageFabricator,
messageMentionWarning: messageMentionWarningFabricator,
group: groupFabricator,
};

View File

@ -1,23 +0,0 @@
import { tracked } from "@glimmer/tracking";
export default class ChatMessageMentionWarning {
static create(message, args = {}) {
return new ChatMessageMentionWarning(message, args);
}
@tracked invitationSent = false;
@tracked cannotSee;
@tracked withoutMembership;
@tracked groupsWithTooManyMembers;
@tracked groupWithMentionsDisabled;
@tracked globalMentionsDisabled;
constructor(message, args = {}) {
this.message = args.message;
this.cannotSee = args.cannot_see;
this.withoutMembership = args.without_membership;
this.groupsWithTooManyMembers = args.groups_with_too_many_members;
this.groupWithMentionsDisabled = args.group_mentions_disabled;
this.globalMentionsDisabled = args.global_mentions_disabled;
}
}

View File

@ -11,5 +11,7 @@ export default class ChatNotice {
constructor(args = {}) {
this.channelId = args.channel_id;
this.textContent = args.text_content;
this.type = args.notice_type;
this.data = args.data;
}
}

View File

@ -25,14 +25,11 @@ export default class ChatChannelPaneSubscriptionsManager extends ChatPaneBaseSub
}
handleNotice(data) {
this.notices.push(ChatNotice.create(data));
this.notices.pushObject(ChatNotice.create(data));
}
clearNotice(notice) {
const index = this.notices.indexOf(notice);
if (index > -1) {
this.notices.splice(index, 1);
}
this.notices.removeObject(notice);
}
handleThreadOriginalMessageUpdate(data) {

View File

@ -1,6 +1,5 @@
import Service, { inject as service } from "@ember/service";
import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import ChatMessageMentionWarning from "discourse/plugins/chat/discourse/models/chat-message-mention-warning";
import { cloneJSON } from "discourse-common/lib/object";
import { bind } from "discourse-common/utils/decorators";
@ -105,9 +104,6 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
case "restore":
this.handleRestoreMessage(busData);
break;
case "mention_warning":
this.handleMentionWarning(busData);
break;
case "self_flagged":
this.handleSelfFlaggedMessage(busData);
break;
@ -203,13 +199,6 @@ export default class ChatPaneBaseSubscriptionsManager extends Service {
}
}
handleMentionWarning(data) {
const message = this.messagesManager.findMessage(data.chat_message_id);
if (message) {
message.mentionWarning = ChatMessageMentionWarning.create(message, data);
}
}
handleSelfFlaggedMessage(data) {
const message = this.messagesManager.findMessage(data.chat_message_id);
if (message) {

View File

@ -1,19 +0,0 @@
.chat-message-mention-warning {
position: relative;
margin-top: 0.25rem;
font-size: var(--font-down-1);
&__dismiss-btn {
position: absolute;
top: 7px;
right: 5px;
}
&__text {
margin: 0.25rem 0;
}
&__invite-sent {
color: var(--tertiary);
}
}

View File

@ -52,7 +52,6 @@
@import "chat-thread-list-header";
@import "chat-thread-unread-indicator";
@import "chat-thread-participants";
@import "chat-message-mention-warning";
@import "chat-message-error";
@import "chat-message-creator";
@import "chat-user-avatar";

View File

@ -129,28 +129,11 @@ en:
one: "Last hour"
other: "Last %{count} hours"
mention_warning:
dismiss: "dismiss"
cannot_see: "%{username} can't access this channel and was not notified."
cannot_see_multiple:
one: "%{username} and %{count} other user cannot access this channel and were not notified."
other: "%{username} and %{count} other users cannot access this channel and were not notified."
invitations_sent:
one: "Invitation sent"
other: "Invitations sent"
invite: "Invite to channel"
without_membership: "%{username} has not joined this channel."
without_membership_multiple:
one: "%{username} and %{count} other user have not joined this channel."
other: "%{username} and %{count} other users have not joined this channel."
group_mentions_disabled: "%{group_name} doesn't allow mentions."
group_mentions_disabled_multiple:
one: "%{group_name} and %{count} other group don't allow mentions."
other: "%{group_name} and %{count} other groups don't allow mentions."
channel_wide_mentions_disallowed: "@here and @all mentions are disabled in this channel."
too_many_members: "%{group_name} has too many members. No one was notified."
too_many_members_multiple:
one: "%{group_name} and %{count} other group have too many members. No one was notified."
other: "%{group_name} and %{count} other groups have too many members. No one was notified."
groups:
header:
some: "Some users won't be notified"

View File

@ -138,6 +138,29 @@ en:
multi_user_truncated:
one: "%{comma_separated_usernames} and %{count} other"
other: "%{comma_separated_usernames} and %{count} others"
mention_warning:
dismiss: "dismiss"
cannot_see: "%{first_identifier} can't access this channel and was not notified."
cannot_see_multiple:
one: "%{first_identifier} and %{count} other user cannot access this channel and were not notified."
other: "%{first_identifier} and %{count} other users cannot access this channel and were not notified."
invitations_sent:
one: "Invitation sent"
other: "Invitations sent"
invite: "Invite to channel"
without_membership: "%{first_identifier} has not joined this channel."
without_membership_multiple:
one: "%{first_identifier} and %{count} other user have not joined this channel."
other: "%{first_identifier} and %{count} other users have not joined this channel."
group_mentions_disabled: "%{first_identifier} doesn't allow mentions."
group_mentions_disabled_multiple:
one: "%{first_identifier} and %{count} other group don't allow mentions."
other: "%{first_identifier} and %{count} other groups don't allow mentions."
global_mentions_disallowed: "@here and @all mentions are disabled in this channel."
too_many_members: "%{first_identifier} has too many members. No one was notified."
too_many_members_multiple:
one: "%{first_identifier} and %{count} other group have too many members. No one was notified."
other: "%{first_identifier} and %{count} other groups have too many members. No one was notified."
category_channel:
errors:

View File

@ -225,24 +225,100 @@ module Chat
end
def notify_creator_of_inaccessible_mentions(inaccessible)
group_mentions_disabled = @parsed_mentions.groups_with_disabled_mentions.to_a
too_many_members = @parsed_mentions.groups_with_too_many_members.to_a
if inaccessible.values.all?(&:blank?) && group_mentions_disabled.empty? &&
too_many_members.empty? && !global_mentions_disabled
return
# Notify when mentioned users can join channel, but don't have a membership
if inaccessible[:welcome_to_join].any?
publish_inaccessible_mentions(inaccessible[:welcome_to_join])
end
Chat::Publisher.publish_inaccessible_mentions(
@user.id,
@chat_message,
inaccessible[:unreachable].to_a,
inaccessible[:welcome_to_join].to_a,
too_many_members,
group_mentions_disabled,
global_mentions_disabled,
# Notify when mentioned users are not able to access the channel
publish_unreachable_mentions(inaccessible[:unreachable]) if inaccessible[:unreachable].any?
# Notify when `@all` or `@here` is used when channel has global mentions disabled
publish_global_mentions_disabled if global_mentions_disabled
# Notify when groups are mentioned and have mentions disabled
group_mentions_disabled = @parsed_mentions.groups_with_disabled_mentions.to_a
publish_group_mentions_disabled(group_mentions_disabled) if group_mentions_disabled.any?
# Notify when large groups are mentioned, exceeding `max_users_notified_per_group_mention`
too_many_members = @parsed_mentions.groups_with_too_many_members.to_a
publish_too_many_members_in_group_mention(too_many_members) if too_many_members.any?
end
def publish_inaccessible_mentions(users)
Chat::Publisher.publish_notice(
user_id: @user.id,
channel_id: @chat_channel.id,
type: "mention_without_membership",
data: {
user_ids: users.map(&:id),
text:
mention_warning_text(
single: "chat.mention_warning.without_membership",
multiple: "chat.mention_warning.without_membership_multiple",
first_identifier: users.first.username,
count: users.count,
),
message_id: @chat_message.id,
},
)
end
def publish_group_mentions_disabled(groups)
Chat::Publisher.publish_notice(
user_id: @user.id,
channel_id: @chat_channel.id,
text_content:
mention_warning_text(
single: "chat.mention_warning.group_mentions_disabled",
multiple: "chat.mention_warning.group_mentions_disabled_multiple",
first_identifier: groups.first.name,
count: groups.count,
),
)
end
def publish_global_mentions_disabled
Chat::Publisher.publish_notice(
user_id: @user.id,
channel_id: @chat_channel.id,
text_content: I18n.t("chat.mention_warning.global_mentions_disallowed"),
)
end
def publish_unreachable_mentions(users)
Chat::Publisher.publish_notice(
user_id: @user.id,
channel_id: @chat_channel.id,
text_content:
mention_warning_text(
single: "chat.mention_warning.cannot_see",
multiple: "chat.mention_warning.cannot_see_multiple",
first_identifier: users.first.username,
count: users.count,
),
)
end
def publish_too_many_members_in_group_mention(groups)
Chat::Publisher.publish_notice(
user_id: @user.id,
channel_id: @chat_channel.id,
text_content:
mention_warning_text(
single: "chat.mention_warning.too_many_members",
multiple: "chat.mention_warning.too_many_members_multiple",
first_identifier: groups.first.name,
count: groups.count,
),
)
end
def mention_warning_text(single:, multiple:, first_identifier:, count:)
translation_key = count == 1 ? single : multiple
I18n.t(translation_key, first_identifier: first_identifier, count: count - 1)
end
def global_mentions_disabled
return @global_mentions_disabled if defined?(@global_mentions_disabled)

View File

@ -488,7 +488,7 @@ describe Chat::MessageCreator do
end
it "publishes inaccessible mentions when user isn't aren't a part of the channel" do
Chat::Publisher.expects(:publish_inaccessible_mentions).once
Chat::Publisher.expects(:publish_notice).once
described_class.create(
chat_channel: public_chat_channel,
user: admin1,
@ -498,7 +498,7 @@ describe Chat::MessageCreator do
it "publishes inaccessible mentions when user doesn't have chat access" do
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:staff]
Chat::Publisher.expects(:publish_inaccessible_mentions).once
Chat::Publisher.expects(:publish_notice).once
described_class.create(
chat_channel: public_chat_channel,
user: admin1,
@ -507,7 +507,7 @@ describe Chat::MessageCreator do
end
it "doesn't publish inaccessible mentions when user is following channel" do
Chat::Publisher.expects(:publish_inaccessible_mentions).never
Chat::Publisher.expects(:publish_notice).never
described_class.create(
chat_channel: public_chat_channel,
user: admin1,

View File

@ -69,9 +69,10 @@ describe Chat::Notifier do
global_mentions_disabled_message = messages.first
expect(global_mentions_disabled_message).to be_present
expect(global_mentions_disabled_message.data[:type].to_sym).to eq(:mention_warning)
expect(global_mentions_disabled_message.data[:global_mentions_disabled]).to eq(true)
expect(global_mentions_disabled_message.data[:type].to_sym).to eq(:notice)
expect(global_mentions_disabled_message.data[:text_content]).to eq(
I18n.t("chat.mention_warning.global_mentions_disallowed"),
)
end
it "includes all members of a channel except the sender" do
@ -415,10 +416,10 @@ describe Chat::Notifier do
unreachable_msg = messages.first
expect(unreachable_msg).to be_present
expect(unreachable_msg.data[:without_membership]).to be_empty
unreachable_users = unreachable_msg.data[:cannot_see].map { |u| u["id"] }
expect(unreachable_users).to contain_exactly(user_3.id)
expect(unreachable_msg[:data][:type].to_sym).to eq(:notice)
expect(unreachable_msg[:data][:text_content]).to eq(
I18n.t("chat.mention_warning.cannot_see", first_identifier: user_3.username),
)
end
context "when in a personal message" do
@ -452,10 +453,10 @@ describe Chat::Notifier do
unreachable_msg = messages.first
expect(unreachable_msg).to be_present
expect(unreachable_msg.data[:without_membership]).to be_empty
unreachable_users = unreachable_msg.data[:cannot_see].map { |u| u["id"] }
expect(unreachable_users).to contain_exactly(user_3.id)
expect(unreachable_msg[:data][:type].to_sym).to eq(:notice)
expect(unreachable_msg[:data][:text_content]).to eq(
I18n.t("chat.mention_warning.cannot_see", first_identifier: user_3.username),
)
end
it "notify posts of users who are part of the mentioned group but participating" do
@ -477,10 +478,10 @@ describe Chat::Notifier do
unreachable_msg = messages.first
expect(unreachable_msg).to be_present
expect(unreachable_msg.data[:without_membership]).to be_empty
unreachable_users = unreachable_msg.data[:cannot_see].map { |u| u["id"] }
expect(unreachable_users).to contain_exactly(user_3.id)
expect(unreachable_msg[:data][:type].to_sym).to eq(:notice)
expect(unreachable_msg[:data][:text_content]).to eq(
I18n.t("chat.mention_warning.cannot_see", first_identifier: user_3.username),
)
end
end
end
@ -502,11 +503,15 @@ describe Chat::Notifier do
not_participating_msg = messages.first
expect(not_participating_msg).to be_present
expect(not_participating_msg.data[:cannot_see]).to be_empty
not_participating_users =
not_participating_msg.data[:without_membership].map { |u| u["id"] }
expect(not_participating_users).to contain_exactly(user_3.id)
expect(not_participating_msg[:data][:type].to_sym).to eq(:notice)
expect(not_participating_msg[:data][:text_content]).to be_nil
expect(not_participating_msg[:data][:notice_type].to_sym).to eq(:mention_without_membership)
expect(not_participating_msg[:data][:data]).to eq(
user_ids: [user_3.id],
text:
I18n.t("chat.mention_warning.without_membership", first_identifier: user_3.username),
message_id: msg.id,
)
end
it "cannot invite chat user without channel membership if they are ignoring the user who created the message" do
@ -555,11 +560,15 @@ describe Chat::Notifier do
not_participating_msg = messages.first
expect(not_participating_msg).to be_present
expect(not_participating_msg.data[:cannot_see]).to be_empty
not_participating_users =
not_participating_msg.data[:without_membership].map { |u| u["id"] }
expect(not_participating_users).to contain_exactly(user_3.id)
expect(not_participating_msg[:data][:type].to_sym).to eq(:notice)
expect(not_participating_msg[:data][:text_content]).to be_nil
expect(not_participating_msg[:data][:notice_type].to_sym).to eq(:mention_without_membership)
expect(not_participating_msg[:data][:data]).to eq(
user_ids: [user_3.id],
text:
I18n.t("chat.mention_warning.without_membership", first_identifier: user_3.username),
message_id: msg.id,
)
end
it "can invite other group members to channel" do
@ -580,11 +589,15 @@ describe Chat::Notifier do
not_participating_msg = messages.first
expect(not_participating_msg).to be_present
expect(not_participating_msg.data[:cannot_see]).to be_empty
not_participating_users =
not_participating_msg.data[:without_membership].map { |u| u["id"] }
expect(not_participating_users).to contain_exactly(user_3.id)
expect(not_participating_msg[:data][:type].to_sym).to eq(:notice)
expect(not_participating_msg[:data][:text_content]).to be_nil
expect(not_participating_msg[:data][:notice_type].to_sym).to eq(:mention_without_membership)
expect(not_participating_msg[:data][:data]).to eq(
user_ids: [user_3.id],
text:
I18n.t("chat.mention_warning.without_membership", first_identifier: user_3.username),
message_id: msg.id,
)
end
it "cannot invite a member of a group who is ignoring the user who created the message" do
@ -650,9 +663,11 @@ describe Chat::Notifier do
end
too_many_members_msg = messages.first
expect(too_many_members_msg).to be_present
too_many_members = too_many_members_msg.data[:groups_with_too_many_members]
expect(too_many_members).to contain_exactly(group.name)
expect(too_many_members_msg[:data][:type].to_sym).to eq(:notice)
expect(too_many_members_msg[:data][:text_content]).to eq(
I18n.t("chat.mention_warning.too_many_members", first_identifier: group.name),
)
end
it "sends a message to the client signaling the group doesn't allow mentions" do
@ -667,9 +682,11 @@ describe Chat::Notifier do
end
mentions_disabled_msg = messages.first
expect(mentions_disabled_msg).to be_present
mentions_disabled = mentions_disabled_msg.data[:group_mentions_disabled]
expect(mentions_disabled).to contain_exactly(group.name)
expect(mentions_disabled_msg[:data][:type].to_sym).to eq(:notice)
expect(mentions_disabled_msg[:data][:text_content]).to eq(
I18n.t("chat.mention_warning.group_mentions_disabled", first_identifier: group.name),
)
end
end
end

View File

@ -1,94 +0,0 @@
# frozen_string_literal: true
RSpec.describe "JIT messages", type: :system do
fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:user) }
fab!(:other_user) { Fabricate(:user) }
let(:chat) { PageObjects::Pages::Chat.new }
let(:channel) { PageObjects::Pages::ChatChannel.new }
before do
channel_1.add(current_user)
chat_system_bootstrap
sign_in(current_user)
end
context "when mentioning a user" do
context "when user is not on the channel" do
it "displays a mention warning" do
Jobs.run_immediately!
chat.visit_channel(channel_1)
channel.send_message("hi @#{other_user.username}")
expect(page).to have_content(
I18n.t("js.chat.mention_warning.without_membership", username: other_user.username),
wait: 5,
)
end
end
context "when user cant access the channel" do
fab!(:group_1) { Fabricate(:group) }
fab!(:private_channel_1) { Fabricate(:private_category_channel, group: group_1) }
before do
group_1.add(current_user)
private_channel_1.add(current_user)
end
it "displays a mention warning" do
Jobs.run_immediately!
chat.visit_channel(private_channel_1)
channel.send_message("hi @#{other_user.username}")
expect(page).to have_content(
I18n.t("js.chat.mention_warning.cannot_see", username: other_user.username),
wait: 5,
)
end
end
end
context "when category channel permission is readonly for everyone" do
fab!(:group_1) { Fabricate(:group) }
fab!(:private_channel_1) { Fabricate(:private_category_channel, group: group_1) }
before do
group_1.add(current_user)
private_channel_1.add(current_user)
end
it "displays a mention warning" do
Jobs.run_immediately!
chat.visit_channel(private_channel_1)
channel.send_message("hi @#{other_user.username}")
expect(page).to have_content(
I18n.t("js.chat.mention_warning.cannot_see", username: other_user.username),
wait: 5,
)
end
end
context "when mention a group" do
context "when group can't be mentioned" do
fab!(:group_1) { Fabricate(:group, mentionable_level: Group::ALIAS_LEVELS[:nobody]) }
it "displays a mention warning" do
Jobs.run_immediately!
chat.visit_channel(channel_1)
channel.send_message("hi @#{group_1.name}")
expect(page).to have_content(
I18n.t("js.chat.mention_warning.group_mentions_disabled", group_name: group_1.name),
wait: 5,
)
end
end
end
end

View File

@ -1,118 +0,0 @@
import { render } from "@ember/test-helpers";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
module(
"Discourse Chat | Component | Chat::Message::MentionWarning",
function (hooks) {
setupRenderingTest(hooks);
const template = hbs`
<Chat::Message::MentionWarning @message={{this.message}} />
`;
test("without memberships", async function (assert) {
this.message = fabricators.message();
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
without_membership: [fabricators.user()].map((u) => {
return { username: u.username, id: u.id };
}),
}
);
await render(template);
assert
.dom(".chat-message-mention-warning__text.-without-membership")
.exists();
});
test("cannot see channel", async function (assert) {
this.message = fabricators.message();
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
cannot_see: [fabricators.user()].map((u) => {
return { username: u.username, id: u.id };
}),
}
);
await render(template);
assert.dom(".chat-message-mention-warning__text.-cannot-see").exists();
});
test("cannot see channel", async function (assert) {
this.message = fabricators.message();
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
cannot_see: [fabricators.user()].map((u) => {
return { username: u.username, id: u.id };
}),
}
);
await render(template);
assert.dom(".chat-message-mention-warning__text.-cannot-see").exists();
});
test("too many groups", async function (assert) {
this.message = fabricators.message();
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
groups_with_too_many_members: [fabricators.group()].mapBy("name"),
}
);
await render(template);
assert
.dom(
".chat-message-mention-warning__text.-groups-with-too-many-members"
)
.exists();
});
test("groups with mentions disabled", async function (assert) {
this.message = fabricators.message();
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
group_mentions_disabled: [fabricators.group()].mapBy("name"),
}
);
await render(template);
assert
.dom(
".chat-message-mention-warning__text.-groups-with-mentions-disabled"
)
.exists();
});
test("displays a warning when global mentions are disabled", async function (assert) {
this.message = fabricators.message();
this.message.mentionWarning = fabricators.messageMentionWarning(
this.message,
{
global_mentions_disabled: true,
}
);
await render(template);
assert
.dom(".chat-message-mention-warning__text.-global-mentions-disabled")
.exists();
});
}
);

View File

@ -1,3 +1,5 @@
import I18n from "I18n";
import pretender from "discourse/tests/helpers/create-pretender";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import hbs from "htmlbars-inline-precompile";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
@ -62,4 +64,52 @@ module("Discourse Chat | Component | chat-notice", function (hooks) {
"Notice was cleared"
);
});
test("MentionWithoutMembership notice renders", async function (assert) {
this.channel = fabricators.channel();
this.manager = this.container.lookup(
"service:chatChannelPaneSubscriptionsManager"
);
const text = "Joffrey can't chat, hermano";
this.manager.handleNotice({
channel_id: this.channel.id,
notice_type: "mention_without_membership",
data: { user_ids: [1], message_id: 1, text },
});
await render(hbs`<ChatNotices @channel={{this.channel}} />`);
assert.strictEqual(
queryAll(
".chat-notices .chat-notices__notice .mention-without-membership-notice"
).length,
1,
"Notice is present"
);
assert.dom(".mention-without-membership-notice__body__text").hasText(text);
assert
.dom(".mention-without-membership-notice__body__link")
.hasText(I18n.t("chat.mention_warning.invite"));
pretender.put(`/chat/${this.channel.id}/invite`, () => {
return [200, { "Content-Type": "application/json" }, {}];
});
await click(
query(".mention-without-membership-notice__body__link"),
"Invites the user"
);
// I would love to test that the invitation sent text is present here but
// dismiss is called right away instead of waiting 3 seconds.. Not much we can
// do about this - at least we are testing that nothing broke all the way through
// clearing the notice
assert.strictEqual(
queryAll(
".chat-notices .chat-notices__notice .mention-without-membership-notice"
).length,
0,
"Notice has been cleared"
);
});
});