diff --git a/.changeset/fresh-socks-fix.md b/.changeset/fresh-socks-fix.md
new file mode 100644
index 000000000000..004677e019b8
--- /dev/null
+++ b/.changeset/fresh-socks-fix.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": minor
+---
+
+Mentioning users that are not in the channel now dispatches a warning message with actions
diff --git a/apps/meteor/app/lib/server/index.ts b/apps/meteor/app/lib/server/index.ts
index c2d4bdda7472..80aaa2a64a9e 100644
--- a/apps/meteor/app/lib/server/index.ts
+++ b/apps/meteor/app/lib/server/index.ts
@@ -21,6 +21,7 @@ import './methods/createToken';
import './methods/deleteMessage';
import './methods/deleteUserOwnAccount';
import './methods/executeSlashCommandPreview';
+import './startup/mentionUserNotInChannel';
import './methods/getChannelHistory';
import './methods/getRoomJoinCode';
import './methods/getRoomRoles';
diff --git a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.js b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.js
index 4ed882cfe874..395ddfe6d460 100644
--- a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.js
+++ b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.js
@@ -1,6 +1,4 @@
-import { Room } from '@rocket.chat/core-services';
import { Subscriptions, Users } from '@rocket.chat/models';
-import { Meteor } from 'meteor/meteor';
import moment from 'moment';
import { callbacks } from '../../../../lib/callbacks';
@@ -347,50 +345,7 @@ export async function sendAllNotifications(message, room) {
return message;
}
- const { sender, hasMentionToAll, hasMentionToHere, notificationMessage, mentionIds, mentionIdsWithoutGroups } =
- await sendMessageNotifications(message, room);
-
- // on public channels, if a mentioned user is not member of the channel yet, he will first join the channel and then be notified based on his preferences.
- if (room.t === 'c') {
- // get subscriptions from users already in room (to not send them a notification)
- const mentions = [...mentionIdsWithoutGroups];
- const cursor = Subscriptions.findByRoomIdAndUserIds(room._id, mentionIdsWithoutGroups, {
- projection: { 'u._id': 1 },
- });
-
- for await (const subscription of cursor) {
- const index = mentions.indexOf(subscription.u._id);
- if (index !== -1) {
- mentions.splice(index, 1);
- }
- }
-
- const users = await Promise.all(
- mentions.map(async (userId) => {
- await Room.join({ room, user: { _id: userId } });
-
- return userId;
- }),
- ).catch((error) => {
- throw new Meteor.Error(error);
- });
-
- const subscriptions = await Subscriptions.findByRoomIdAndUserIds(room._id, users).toArray();
- users.forEach((userId) => {
- const subscription = subscriptions.find((subscription) => subscription.u._id === userId);
-
- void sendNotification({
- subscription,
- sender,
- hasMentionToAll,
- hasMentionToHere,
- message,
- notificationMessage,
- room,
- mentionIds,
- });
- });
- }
+ await sendMessageNotifications(message, room);
return message;
}
diff --git a/apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts b/apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
new file mode 100644
index 000000000000..8b8836451472
--- /dev/null
+++ b/apps/meteor/app/lib/server/startup/mentionUserNotInChannel.ts
@@ -0,0 +1,142 @@
+import { api } from '@rocket.chat/core-services';
+import type { IMessage } from '@rocket.chat/core-typings';
+import { isDirectMessageRoom, isEditedMessage, isRoomFederated } from '@rocket.chat/core-typings';
+import { Subscriptions, Rooms, Users, Settings } from '@rocket.chat/models';
+import type { ActionsBlock } from '@rocket.chat/ui-kit';
+import moment from 'moment';
+
+import { callbacks } from '../../../../lib/callbacks';
+import { getUserDisplayName } from '../../../../lib/getUserDisplayName';
+import { isTruthy } from '../../../../lib/isTruthy';
+import { i18n } from '../../../../server/lib/i18n';
+import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
+
+const APP_ID = 'mention-core';
+const getBlocks = (mentions: IMessage['mentions'], messageId: string, lng: string | undefined) => {
+ const stringifiedMentions = JSON.stringify(mentions);
+ return {
+ addUsersBlock: {
+ type: 'button',
+ appId: APP_ID,
+ blockId: messageId,
+ value: stringifiedMentions,
+ actionId: 'add-users',
+ text: {
+ type: 'plain_text',
+ text: i18n.t('Add_them', undefined, lng),
+ },
+ },
+ dismissBlock: {
+ type: 'button',
+ appId: APP_ID,
+ blockId: messageId,
+ value: stringifiedMentions,
+ actionId: 'dismiss',
+ text: {
+ type: 'plain_text',
+ text: i18n.t('Do_nothing', undefined, lng),
+ },
+ },
+ dmBlock: {
+ type: 'button',
+ appId: APP_ID,
+ value: stringifiedMentions,
+ blockId: messageId,
+ actionId: 'share-message',
+ text: {
+ type: 'plain_text',
+ text: i18n.t('Let_them_know', undefined, lng),
+ },
+ },
+ } as const;
+};
+
+callbacks.add(
+ 'beforeSaveMessage',
+ async (message) => {
+ // TODO: check if I need to test this 60 second rule.
+ // If the message was edited, or is older than 60 seconds (imported)
+ // the notifications will be skipped, so we can also skip this validation
+ if (isEditedMessage(message) || (message.ts && Math.abs(moment(message.ts).diff(moment())) > 60000) || !message.mentions) {
+ return message;
+ }
+
+ const mentions = message.mentions.filter(({ _id }) => _id !== 'all' && _id !== 'here');
+ if (!mentions.length) {
+ return message;
+ }
+
+ const room = await Rooms.findOneById(message.rid);
+ if (!room || isDirectMessageRoom(room) || isRoomFederated(room) || room.t === 'l') {
+ return message;
+ }
+
+ const subs = await Subscriptions.findByRoomIdAndUserIds(
+ message.rid,
+ mentions.map(({ _id }) => _id),
+ { projection: { u: 1 } },
+ ).toArray();
+
+ // get all users that are mentioned but not in the channel
+ const mentionsUsersNotInChannel = mentions.filter(({ _id }) => !subs.some((sub) => sub.u._id === _id));
+
+ if (!mentionsUsersNotInChannel.length) {
+ return message;
+ }
+
+ const canAddUsersToThisRoom = await hasPermissionAsync(message.u._id, 'add-user-to-joined-room', message.rid);
+ const canAddToAnyRoom = await (room.t === 'c'
+ ? hasPermissionAsync(message.u._id, 'add-user-to-any-c-room')
+ : hasPermissionAsync(message.u._id, 'add-user-to-any-p-room'));
+ const canDMUsers = await hasPermissionAsync(message.u._id, 'create-d'); // TODO: Perhaps check if user has DM with mentioned user (might be too expensive)
+ const canAddUsers = canAddUsersToThisRoom || canAddToAnyRoom;
+ const { language } = (await Users.findOneById(message.u._id)) || {};
+
+ const actionBlocks = getBlocks(mentionsUsersNotInChannel, message._id, language);
+ const elements: ActionsBlock['elements'] = [
+ canAddUsers && actionBlocks.addUsersBlock,
+ (canAddUsers || canDMUsers) && actionBlocks.dismissBlock,
+ canDMUsers && actionBlocks.dmBlock,
+ ].filter(isTruthy);
+
+ const messageLabel = canAddUsers
+ ? 'You_mentioned___mentions__but_theyre_not_in_this_room'
+ : 'You_mentioned___mentions__but_theyre_not_in_this_room_You_can_ask_a_room_admin_to_add_them';
+
+ const { value: useRealName } = (await Settings.findOneById('UI_Use_Real_Name')) || {};
+
+ const usernamesOrNames = mentionsUsersNotInChannel.map(
+ ({ username, name }) => `*${getUserDisplayName(name, username, Boolean(useRealName))}*`,
+ );
+
+ const mentionsText = usernamesOrNames.join(', ');
+
+ // TODO: Mentions style
+ void api.broadcast('notify.ephemeralMessage', message.u._id, message.rid, {
+ msg: '',
+ mentions: mentionsUsersNotInChannel,
+ tmid: message.tmid,
+ blocks: [
+ {
+ appId: APP_ID,
+ type: 'section',
+ text: {
+ type: 'mrkdwn',
+ text: i18n.t(messageLabel, { mentions: mentionsText }, language),
+ },
+ } as const,
+ Boolean(elements.length) &&
+ ({
+ type: 'actions',
+ appId: APP_ID,
+ elements,
+ } as const),
+ ].filter(isTruthy),
+ private: true,
+ });
+
+ return message;
+ },
+ callbacks.priority.LOW,
+ 'mention-user-not-in-channel',
+);
diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
index 1680bab15cb6..de0e3cfed58e 100644
--- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -328,6 +328,7 @@
"add-team-channel_description": "Permission to add a channel to a team",
"add-team-member": "Add Team Member",
"add-team-member_description": "Permission to add members to a team",
+ "Add_them": "Add them",
"add-user": "Add User",
"add-user_description": "Permission to add new users to the server via users screen",
"add-user-to-any-c-room": "Add User to Any Public Channel",
@@ -1705,6 +1706,7 @@
"Do_not_display_unread_counter": "Do not display any counter of this channel",
"Do_not_provide_this_code_to_anyone": "Do not provide this code to anyone.",
"Do_Nothing": "Do Nothing",
+ "Do_nothing": "Do nothing",
"Do_you_have_any_notes_for_this_conversation": "Do you have any notes for this conversation?",
"Do_you_want_to_accept": "Do you want to accept?",
"Do_you_want_to_change_to_s_question": "Do you want to change to %s?",
@@ -3074,6 +3076,7 @@
"leave-p": "Leave Private Groups",
"leave-p_description": "Permission to leave private groups",
"Lets_get_you_new_one": "Let's get you a new one!",
+ "Let_them_know": "Let them know",
"License": "License",
"Line": "Line",
"Link": "Link",
@@ -5841,6 +5844,9 @@
"You_have_not_verified_your_email": "You have not verified your email.",
"You_have_successfully_unsubscribed": "You have successfully unsubscribed from our Mailling List.",
"You_must_join_to_view_messages_in_this_channel": "You must join to view messages in this channel",
+ "You_mentioned___mentions__but_theyre_not_in_this_room": "You mentioned {{mentions}}, but they're not in this room.",
+ "You_mentioned___mentions__but_theyre_not_in_this_room_You_can_ask_a_room_admin_to_add_them": "You mentioned {{mentions}}, but they're not in this room. You can ask a room admin to add them.",
+ "You_mentioned___mentions__but_theyre_not_in_this_room_You_let_them_know_via_dm": "You mentioned {{mentions}}, but they're not in this room. You let them know via DM.",
"You_need_confirm_email": "You need to confirm your email to login!",
"You_need_install_an_extension_to_allow_screen_sharing": "You need install an extension to allow screen sharing",
"You_need_to_change_your_password": "You need to change your password",
@@ -5879,6 +5885,7 @@
"Your_TOTP_has_been_reset": "Your Two Factor TOTP has been reset.",
"Your_web_browser_blocked_Rocket_Chat_from_opening_tab": "Your web browser blocked Rocket.Chat from opening a new tab.",
"Your_workspace_is_ready": "Your workspace is ready to use 🎉",
+ "Youre_not_a_part_of__channel__and_I_mentioned_you_there": "You're not a part of {{channel}} and I mentioned you there",
"Zapier": "Zapier",
"registration.page.login.errors.wrongCredentials": "User not found or incorrect password",
"registration.page.login.errors.invalidEmail": "Invalid Email",
diff --git a/apps/meteor/server/modules/core-apps/mention.module.ts b/apps/meteor/server/modules/core-apps/mention.module.ts
new file mode 100644
index 000000000000..ac615def8a5a
--- /dev/null
+++ b/apps/meteor/server/modules/core-apps/mention.module.ts
@@ -0,0 +1,125 @@
+import { api } from '@rocket.chat/core-services';
+import type { IUiKitCoreApp } from '@rocket.chat/core-services';
+import type { IMessage } from '@rocket.chat/core-typings';
+import { Subscriptions, Messages } from '@rocket.chat/models';
+import { Meteor } from 'meteor/meteor';
+
+import { processWebhookMessage } from '../../../app/lib/server/functions/processWebhookMessage';
+import { addUsersToRoomMethod } from '../../../app/lib/server/methods/addUsersToRoom';
+import { i18n } from '../../lib/i18n';
+import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
+
+const retrieveMentionsFromPayload = (stringifiedMentions: string): Exclude => {
+ try {
+ const mentions = JSON.parse(stringifiedMentions);
+ if (!Array.isArray(mentions) || !mentions.length || !('username' in mentions[0])) {
+ throw new Error('Invalid payload');
+ }
+ return mentions;
+ } catch (error) {
+ throw new Error('Invalid payload');
+ }
+};
+
+export class MentionModule implements IUiKitCoreApp {
+ appId = 'mention-core';
+
+ async blockAction(payload: any): Promise {
+ const {
+ actionId,
+ payload: { value: stringifiedMentions, blockId: referenceMessageId },
+ } = payload;
+
+ const mentions = retrieveMentionsFromPayload(stringifiedMentions);
+
+ const usernames = mentions.map(({ username }) => username);
+
+ const message = await Messages.findOneById(referenceMessageId, { projection: { _id: 1, tmid: 1 } });
+
+ if (!message) {
+ throw new Error('Mention bot - Failed to retrieve message information');
+ }
+
+ const joinedUsernames = `@${usernames.join(', @')}`;
+
+ if (actionId === 'dismiss') {
+ void api.broadcast('notify.ephemeralMessage', payload.user._id, payload.room, {
+ msg: i18n.t(
+ 'You_mentioned___mentions__but_theyre_not_in_this_room',
+ {
+ mentions: joinedUsernames,
+ },
+ payload.user.language,
+ ),
+ _id: payload.message,
+ tmid: message.tmid,
+ mentions,
+ });
+ return;
+ }
+
+ if (actionId === 'add-users') {
+ void addUsersToRoomMethod(payload.user._id, { rid: payload.room, users: usernames as string[] }, payload.user);
+ void api.broadcast('notify.ephemeralMessage', payload.user._id, payload.room, {
+ msg: i18n.t(
+ 'You_mentioned___mentions__but_theyre_not_in_this_room',
+ {
+ mentions: joinedUsernames,
+ },
+ payload.user.language,
+ ),
+ tmid: message.tmid,
+ _id: payload.message,
+ mentions,
+ });
+ return;
+ }
+
+ if (actionId === 'share-message') {
+ const sub = await Subscriptions.findOneByRoomIdAndUserId(payload.room, payload.user._id, { projection: { t: 1, rid: 1, name: 1 } });
+ // this should exist since the event is fired from withing the room (e.g the user sent a message)
+ if (!sub) {
+ throw new Error('Mention bot - Failed to retrieve room information');
+ }
+
+ const roomPath = roomCoordinator.getRouteLink(sub.t, { rid: sub.rid, name: sub.name });
+ if (!roomPath) {
+ throw new Error('Mention bot - Failed to retrieve path to room');
+ }
+
+ const messageText = i18n.t(
+ 'Youre_not_a_part_of__channel__and_I_mentioned_you_there',
+ {
+ channel: `#${sub.name}`,
+ },
+ payload.user.language,
+ );
+
+ const link = new URL(Meteor.absoluteUrl(roomPath));
+ link.searchParams.set('msg', message._id);
+ const text = `[ ](${link.toString()})\n${messageText}`;
+
+ // forwards message to all DMs
+ await processWebhookMessage(
+ {
+ roomId: mentions.map(({ _id }) => _id),
+ text,
+ },
+ payload.user,
+ );
+
+ void api.broadcast('notify.ephemeralMessage', payload.user._id, payload.room, {
+ msg: i18n.t(
+ 'You_mentioned___mentions__but_theyre_not_in_this_room_You_let_them_know_via_dm',
+ {
+ mentions: joinedUsernames,
+ },
+ payload.user.language,
+ ),
+ tmid: message.tmid,
+ _id: payload.message,
+ mentions,
+ });
+ }
+ }
+}
diff --git a/apps/meteor/server/startup/coreApps.ts b/apps/meteor/server/startup/coreApps.ts
index 14b2234f6603..9638d99ab559 100644
--- a/apps/meteor/server/startup/coreApps.ts
+++ b/apps/meteor/server/startup/coreApps.ts
@@ -1,5 +1,6 @@
import { BannerModule } from '../modules/core-apps/banner.module';
import { CloudAnnouncementsModule } from '../modules/core-apps/cloudAnnouncements.module';
+import { MentionModule } from '../modules/core-apps/mention.module';
import { Nps } from '../modules/core-apps/nps.module';
import { VideoConfModule } from '../modules/core-apps/videoconf.module';
import { registerCoreApp } from '../services/uikit-core-app/service';
@@ -8,3 +9,4 @@ registerCoreApp(new CloudAnnouncementsModule());
registerCoreApp(new Nps());
registerCoreApp(new BannerModule());
registerCoreApp(new VideoConfModule());
+registerCoreApp(new MentionModule());
diff --git a/apps/meteor/tests/e2e/message-mentions.spec.ts b/apps/meteor/tests/e2e/message-mentions.spec.ts
index aa3e5b73036e..7645d5b14470 100644
--- a/apps/meteor/tests/e2e/message-mentions.spec.ts
+++ b/apps/meteor/tests/e2e/message-mentions.spec.ts
@@ -1,9 +1,26 @@
+import { faker } from '@faker-js/faker';
+
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
+import { createTargetPrivateChannel } from './utils';
import { test, expect } from './utils/test';
+
test.use({ storageState: Users.admin.state });
+const getMentionText = (username: string, kind?: number): string => {
+ if (kind === 1) {
+ return `You mentioned ${username}, but they're not in this room.`;
+ }
+ if (kind === 2) {
+ return `You mentioned ${username}, but they're not in this room. You can ask a room admin to add them.`;
+ }
+ if (kind === 3) {
+ return `You mentioned ${username}, but they're not in this room. You let them know via DM.`;
+ }
+ return `Hello @${username}, how are you`;
+};
+
test.describe.serial('message-mentions', () => {
let poHomeChannel: HomeChannel;
@@ -20,4 +37,180 @@ test.describe.serial('message-mentions', () => {
await expect(poHomeChannel.content.messagePopupUsers.locator('role=listitem >> text="all"')).toBeVisible();
await expect(poHomeChannel.content.messagePopupUsers.locator('role=listitem >> text="here"')).toBeVisible();
});
+
+ test.describe('users not in channel', () => {
+ let targetChannel: string;
+ let targetChannel2: string;
+ test.beforeAll(async ({ api }) => {
+ targetChannel = await createTargetPrivateChannel(api);
+ });
+
+ test('all actions', async ({ page }) => {
+ const adminPage = new HomeChannel(page);
+ const mentionText = getMentionText(Users.user1.data.username, 1);
+
+ await test.step('receive bot message', async () => {
+ await adminPage.sidenav.openChat(targetChannel);
+ await adminPage.content.sendMessage(getMentionText(Users.user1.data.username));
+ await expect(adminPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(mentionText);
+ });
+
+ await test.step('show "Do nothing" action', async () => {
+ await expect(adminPage.content.lastUserMessage.locator('button >> text="Do nothing"')).toBeVisible();
+ });
+ await test.step('show "Add them" action', async () => {
+ await expect(adminPage.content.lastUserMessage.locator('button >> text="Add them"')).toBeVisible();
+ });
+ await test.step('show "Let them know" action', async () => {
+ await expect(adminPage.content.lastUserMessage.locator('button >> text="Let them know"')).toBeVisible();
+ });
+
+ await test.step('dismiss', async () => {
+ await adminPage.content.lastUserMessage.locator('button >> text="Do nothing"').click();
+ });
+
+ await test.step('receive second bot message', async () => {
+ await adminPage.content.sendMessage(getMentionText(Users.user1.data.username));
+ await expect(adminPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(mentionText);
+ });
+ await test.step('send message to users', async () => {
+ await adminPage.content.lastUserMessage.locator('button >> text="Let them know"').click();
+ await expect(adminPage.content.lastUserMessageBody).toContainText(getMentionText(Users.user1.data.username, 3));
+ });
+
+ await test.step('receive third bot message', async () => {
+ await adminPage.content.sendMessage(getMentionText(Users.user1.data.username));
+ await expect(adminPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(mentionText);
+ });
+ await test.step('add users to room', async () => {
+ await adminPage.content.lastUserMessage.locator('button >> text="Add them"').click();
+ await expect(adminPage.content.lastSystemMessageBody).toContainText('added');
+ });
+ });
+
+ test.describe(() => {
+ test.use({ storageState: Users.user1.state });
+
+ test('dismiss and share message actions', async ({ page }) => {
+ const mentionText = getMentionText(Users.user2.data.username, 1);
+ const userPage = new HomeChannel(page);
+
+ await test.step('receive bot message', async () => {
+ await userPage.sidenav.openChat(targetChannel);
+ await userPage.content.sendMessage(getMentionText(Users.user2.data.username));
+ await expect(userPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(mentionText);
+ });
+
+ await test.step('show "Do nothing" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Do nothing"')).toBeVisible();
+ });
+ await test.step('show "Let them know" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Let them know"')).toBeVisible();
+ });
+ await test.step('not show "Add them action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Add them"')).not.toBeVisible();
+ });
+
+ await test.step('dismiss', async () => {
+ await userPage.content.lastUserMessage.locator('button >> text="Do nothing"').click();
+ });
+
+ await test.step('receive second bot message', async () => {
+ await userPage.sidenav.openChat(targetChannel);
+ await userPage.content.sendMessage(getMentionText(Users.user2.data.username));
+ await expect(userPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(mentionText);
+ });
+ await test.step('send message to users', async () => {
+ await userPage.content.lastUserMessage.locator('button >> text="Let them know"').click();
+ await expect(userPage.content.lastUserMessageBody).toContainText(getMentionText(Users.user2.data.username, 3));
+ });
+ });
+ })
+
+ test.describe(() => {
+ test.use({ storageState: Users.user1.state });
+ test.beforeAll(async ({ api }) => {
+ expect((await api.post('/permissions.update', { permissions: [{ '_id': 'create-d', 'roles': ['admin'] }] })).status()).toBe(200);
+ });
+
+ test.afterAll(async ({ api }) => {
+ expect((await api.post('/permissions.update', { permissions: [{ '_id': 'create-d', 'roles': ['admin', 'user', 'bot', 'app'] }] })).status()).toBe(200);
+ });
+
+ test('dismiss and add users actions', async ({ page }) => {
+ const mentionText = getMentionText(Users.user2.data.username, 1);
+ const userPage = new HomeChannel(page);
+
+ await test.step('create private room', async () => {
+ targetChannel2 = faker.string.uuid();
+
+ await poHomeChannel.sidenav.openNewByLabel('Channel');
+ await poHomeChannel.sidenav.inputChannelName.type(targetChannel2);
+ await poHomeChannel.sidenav.btnCreate.click();
+
+ await expect(page).toHaveURL(`/group/${targetChannel2}`);
+ })
+
+ await test.step('receive bot message', async () => {
+ await userPage.sidenav.openChat(targetChannel2);
+ await userPage.content.sendMessage(getMentionText(Users.user2.data.username));
+ await expect(userPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(mentionText);
+ });
+ await test.step('show "Do nothing" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Do nothing"')).toBeVisible();
+ });
+ await test.step('show "Add them" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Add them"')).toBeVisible();
+ });
+ await test.step('not show "Let them know" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Let them know"')).not.toBeVisible();
+ });
+
+ await test.step('dismiss', async () => {
+ await userPage.content.lastUserMessage.locator('button >> text="Do nothing"').click();
+ });
+
+ await test.step('receive second bot message', async () => {
+ await userPage.sidenav.openChat(targetChannel2);
+ await userPage.content.sendMessage(getMentionText(Users.user2.data.username));
+ await expect(userPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(mentionText);
+ });
+ await test.step('add users to room', async () => {
+ await userPage.content.lastUserMessage.locator('button >> text="Add them"').click();
+ await expect(userPage.content.lastSystemMessageBody).toContainText('added');
+ });
+ });
+ });
+
+ test.describe(() => {
+ test.use({ storageState: Users.user2.state });
+ test.beforeAll(async ({ api }) => {
+ expect((await api.post('/permissions.update', { permissions: [{ '_id': 'create-d', 'roles': ['admin'] }] })).status()).toBe(200);
+ });
+
+ test.afterAll(async ({ api }) => {
+ expect((await api.post('/permissions.update', { permissions: [{ '_id': 'create-d', 'roles': ['admin', 'user', 'bot', 'app'] }] })).status()).toBe(200);
+ });
+ test('no actions', async ({ page }) => {
+ const userPage = new HomeChannel(page);
+
+ await test.step('receive bot message', async () => {
+ await userPage.sidenav.openChat(targetChannel2);
+ await userPage.content.sendMessage(getMentionText(Users.user3.data.username));
+ await expect(userPage.content.lastUserMessage.locator('.rcx-message-block')).toContainText(getMentionText(Users.user3.data.username, 2));
+ });
+
+ await test.step('not show "Do nothing" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Do nothing"')).not.toBeVisible();
+ });
+ await test.step('not show "Add them" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Add them"')).not.toBeVisible();
+ });
+ await test.step('not show "Let them know" action', async () => {
+ await expect(userPage.content.lastUserMessage.locator('button >> text="Let them know"')).not.toBeVisible();
+ });
+ });
+ })
+
+ })
});