Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2e68af4
refactor: migrate notifyUsersOnMessage to typescript
ricardogarim Mar 1, 2024
f6ad177
refactor: remove useless loops on beforeGetMentions
ricardogarim Mar 1, 2024
6835ee6
refactor: rename beforeGetMentions to beforeGetTeamMentions
ricardogarim Mar 1, 2024
edee67b
chore: remove unnecessary comment
ricardogarim Mar 4, 2024
60aac2c
fix: add type importing on interfaces and types loading
ricardogarim Mar 4, 2024
2c18390
fix: isolate teamsMentions into a new array filter
ricardogarim Mar 4, 2024
08111f5
chore: add UnreadCountType type checking
ricardogarim Mar 4, 2024
a6d0be8
chore: update setLastReplyForRoomIdAndUserIds signature to comply wit…
ricardogarim Mar 4, 2024
359e5dc
chore: update notifyUsersOnMessage message input type from IMessage t…
ricardogarim Mar 4, 2024
f263593
chore: update incMsgCountAndSetLastMessageById signature to accept me…
ricardogarim Mar 4, 2024
24287f1
chore: update getMentionedTeamMembers signature
ricardogarim Mar 4, 2024
8692b50
chore: update updateThreadUsersSubscriptions and notifyUsersOnReply s…
ricardogarim Mar 4, 2024
df50912
fix: add message related imports to fix packages building
ricardogarim Mar 4, 2024
5f39dfc
fix: eslint and typecheck minors
ricardogarim Mar 4, 2024
500a401
Merge branch 'develop' into refactor/migrate-notifyUsersOnMessage-to-…
Mar 5, 2024
3465796
chore: add callback beforeGetTeamMentions types definition
ricardogarim Mar 5, 2024
ebae2c9
fix: rewrite beforeGetTeamMentions length check and remove useless ty…
ricardogarim Mar 6, 2024
1575b2a
revert: changes out of js > ts migration scope
ricardogarim Mar 6, 2024
e902254
undo some changes
sampaiodiego Mar 7, 2024
cfac4bf
Merge branch 'develop' into refactor/migrate-notifyUsersOnMessage-to-…
ricardogarim Mar 7, 2024
31ccfce
Merge branch 'develop' into refactor/migrate-notifyUsersOnMessage-to-…
ricardogarim Mar 7, 2024
2781ec8
Merge branch 'develop' into refactor/migrate-notifyUsersOnMessage-to-…
ricardogarim Mar 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
import type { IMessage, IRoom, IUser, RoomType } from '@rocket.chat/core-typings';
import { isEditedMessage } from '@rocket.chat/core-typings';
import { Subscriptions, Rooms } from '@rocket.chat/models';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import moment from 'moment';

import { callbacks } from '../../../../lib/callbacks';
import { settings } from '../../../settings/server';

/**
* Chechs if a messages contains a user highlight
*
* @param {string} message
* @param {array|undefined} highlights
*
* @returns {boolean}
*/
function messageContainsHighlight(message, highlights) {
if (!highlights || highlights.length === 0) {
return false;
}
function messageContainsHighlight(message: IMessage, highlights: string[]): boolean {
if (!highlights || highlights.length === 0) return false;

return highlights.some((highlight) => {
return highlights.some((highlight: string) => {
const regexp = new RegExp(escapeRegExp(highlight), 'i');
return regexp.test(message.msg);
});
}

export async function getMentions(message) {
export async function getMentions(message: IMessage): Promise<{ toAll: boolean; toHere: boolean; mentionIds: string[] }> {
const {
mentions,
u: { _id: senderId },
Expand All @@ -41,16 +33,13 @@ export async function getMentions(message) {
const toAll = mentions.some(({ _id }) => _id === 'all');
const toHere = mentions.some(({ _id }) => _id === 'here');

const userMentions = mentions.filter((mention) => !mention.type || mention.type === 'user');
const otherMentions = mentions.filter((mention) => mention?.type !== 'user');

const filteredMentions = userMentions.filter(({ _id }) => _id !== senderId && !['all', 'here'].includes(_id)).map(({ _id }) => _id);
const teamsMentions = mentions.filter((mention) => mention.type === 'team');
const filteredMentions = mentions
.filter((mention) => !mention.type || mention.type === 'user')
.filter(({ _id }) => _id !== senderId && !['all', 'here'].includes(_id))
.map(({ _id }) => _id);

const mentionIds = await callbacks.run('beforeGetMentions', filteredMentions, {
userMentions,
otherMentions,
message,
});
const mentionIds = await callbacks.run('beforeGetMentions', filteredMentions, teamsMentions);

return {
toAll,
Expand All @@ -59,21 +48,31 @@ export async function getMentions(message) {
};
}

const incGroupMentions = async (rid, roomType, excludeUserId, unreadCount) => {
type UnreadCountType = 'all_messages' | 'user_mentions_only' | 'group_mentions_only' | 'user_and_group_mentions_only';

const incGroupMentions = async (
rid: IRoom['_id'],
roomType: RoomType,
excludeUserId: IUser['_id'],
unreadCount: Exclude<UnreadCountType, 'user_mentions_only'>,
): Promise<void> => {
const incUnreadByGroup = ['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount);
const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0;

await Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(rid, excludeUserId, 1, incUnread);
};

const incUserMentions = async (rid, roomType, uids, unreadCount) => {
const incUnreadByUser = ['all_messages', 'user_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount);
const incUserMentions = async (
rid: IRoom['_id'],
roomType: RoomType,
uids: IUser['_id'][],
unreadCount: Exclude<UnreadCountType, 'group_mentions_only'>,
): Promise<void> => {
const incUnreadByUser = new Set(['all_messages', 'user_mentions_only', 'user_and_group_mentions_only']).has(unreadCount);
const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0;

await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread);
};

export const getUserIdsFromHighlights = async (rid, message) => {
export const getUserIdsFromHighlights = async (rid: IRoom['_id'], message: IMessage): Promise<string[]> => {
const highlightOptions = { projection: { 'userHighlights': 1, 'u._id': 1 } };
const subs = await Subscriptions.findByRoomWithUserHighlights(rid, highlightOptions).toArray();

Expand All @@ -84,11 +83,7 @@ export const getUserIdsFromHighlights = async (rid, message) => {
.map(({ u: { _id: uid } }) => uid);
};

/*
* {IRoom['t']} roomType - The type of the room
* @returns {string} - The setting value for unread count
*/
const getUnreadSettingCount = (roomType) => {
const getUnreadSettingCount = (roomType: RoomType): UnreadCountType => {
let unreadSetting = 'Unread_Count';
switch (roomType) {
case 'd': {
Expand All @@ -104,7 +99,7 @@ const getUnreadSettingCount = (roomType) => {
return settings.get(unreadSetting);
};

async function updateUsersSubscriptions(message, room) {
async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise<void> {
// Don't increase unread counter on thread messages
if (room != null && !message.tmid) {
const { toAll, toHere, mentionIds } = await getMentions(message);
Expand All @@ -115,16 +110,16 @@ async function updateUsersSubscriptions(message, room) {

(await getUserIdsFromHighlights(room._id, message)).forEach((uid) => userIds.add(uid));

// give priority to user mentions over group mentions
Comment thread
ricardogarim marked this conversation as resolved.
Resolved
// Give priority to user mentions over group mentions
if (userIds.size > 0) {
await incUserMentions(room._id, room.t, [...userIds], unreadCount);
await incUserMentions(room._id, room.t, [...userIds], unreadCount as Exclude<UnreadCountType, 'group_mentions_only'>);
} else if (toAll || toHere) {
await incGroupMentions(room._id, room.t, message.u._id, unreadCount);
await incGroupMentions(room._id, room.t, message.u._id, unreadCount as Exclude<UnreadCountType, 'user_mentions_only'>);
}

// this shouldn't run only if has group mentions because it will already exclude mentioned users from the query
if (!toAll && !toHere && unreadCount === 'all_messages') {
await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id]);
await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1);
}
}

Expand All @@ -137,30 +132,25 @@ async function updateUsersSubscriptions(message, room) {
]);
}

export async function updateThreadUsersSubscriptions(message, room, replies) {
// const unreadCount = settings.get('Unread_Count');

// incUserMentions(room._id, room.t, replies, unreadCount);
export async function updateThreadUsersSubscriptions(message: IMessage, replies: IUser['_id'][]): Promise<void> {
// Don't increase unread counter on thread messages

await Subscriptions.setAlertForRoomIdAndUserIds(message.rid, replies);

const repliesPlusSender = [...new Set([message.u._id, ...replies])];

await Subscriptions.setOpenForRoomIdAndUserIds(message.rid, repliesPlusSender);

await Subscriptions.setLastReplyForRoomIdAndUserIds(message.rid, repliesPlusSender, new Date());
}

export async function notifyUsersOnMessage(message, room) {
// skips this callback if the message was edited and increments it if the edit was way in the past (aka imported)
if (message.editedAt) {
if (Math.abs(moment(message.editedAt).diff()) > 60000) {
export async function notifyUsersOnMessage(message: IMessage, room: IRoom): Promise<IMessage> {
// Skips this callback if the message was edited and increments it if the edit was way in the past (aka imported)
if (isEditedMessage(message)) {
if (Math.abs(moment(message.editedAt).diff(Date.now())) > 60000) {
// TODO: Review as I am not sure how else to get around this as the incrementing of the msgs count shouldn't be in this callback
await Rooms.incMsgCountById(message.rid, 1);
return message;
}

// only updates last message if it was edited (skip rest of callback)
// Only updates last message if it was edited (skip rest of callback)
if (
settings.get('Store_Last_Message') &&
(!message.tmid || message.tshow) &&
Expand All @@ -172,20 +162,19 @@ export async function notifyUsersOnMessage(message, room) {
return message;
}

if (message.ts && Math.abs(moment(message.ts).diff()) > 60000) {
if (message.ts && Math.abs(moment(message.ts).diff(Date.now())) > 60000) {
await Rooms.incMsgCountById(message.rid, 1);
return message;
}

// if message sent ONLY on a thread, skips the rest as it is done on a callback specific to threads
// If message sent ONLY on a thread, skips the rest as it is done on a callback specific to threads
if (message.tmid && !message.tshow) {
await Rooms.incMsgCountById(message.rid, 1);
return message;
}

// Update all the room activity tracker fields
await Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, settings.get('Store_Last_Message') && message);

await Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, settings.get('Store_Last_Message') ? message : undefined);
await updateUsersSubscriptions(message, room);

return message;
Expand Down
28 changes: 8 additions & 20 deletions apps/meteor/app/mentions/server/getMentionedTeamMembers.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,23 @@
import { Team } from '@rocket.chat/core-services';
import type { IMessage } from '@rocket.chat/core-typings';
import type { MessageMention } from '@rocket.chat/core-typings';

import { callbacks } from '../../../lib/callbacks';
import { settings } from '../../settings/server';

interface IExtraDataForNotification {
userMentions: any[];
otherMentions: any[];
message: IMessage;
}

const beforeGetMentions = async (mentionIds: string[], extra?: IExtraDataForNotification) => {
const { otherMentions } = extra ?? {};

const teamIds = otherMentions?.filter(({ type }) => type === 'team').map(({ _id }) => _id);

if (!teamIds?.length) {
const beforeGetMentions = async (mentionIds: string[], teamMentions: MessageMention[]): Promise<string[]> => {
if (!teamMentions.length) {
return mentionIds;
}

const members = await Team.getMembersByTeamIds(teamIds, { projection: { userId: 1 } });
mentionIds.push(...new Set(members.map(({ userId }) => userId).filter((userId) => !mentionIds.includes(userId))));

return mentionIds;
const teamsIds = teamMentions.map(({ _id }) => _id);
Comment thread
ricardogarim marked this conversation as resolved.
Resolved
const members = await Team.getMembersByTeamIds(teamsIds, { projection: { userId: 1 } });
return [...new Set([...mentionIds, ...members.map(({ userId }) => userId)])];
};

settings.watch<boolean>('Troubleshoot_Disable_Teams_Mention', (value) => {
if (value) {
callbacks.remove('beforeGetMentions', 'before-get-mentions-get-teams');
return;
} else {
callbacks.add('beforeGetMentions', beforeGetMentions, callbacks.priority.MEDIUM, 'before-get-mentions-get-teams');
}

callbacks.add('beforeGetMentions', beforeGetMentions, callbacks.priority.MEDIUM, 'before-get-mentions-get-teams');
});
8 changes: 4 additions & 4 deletions apps/meteor/app/threads/server/hooks/aftersavemessage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
import { isEditedMessage } from '@rocket.chat/core-typings';
import { Messages } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
Expand All @@ -10,13 +10,13 @@ import { sendMessageNotifications } from '../../../lib/server/lib/sendNotificati
import { settings } from '../../../settings/server';
import { reply } from '../functions';

async function notifyUsersOnReply(message: IMessage, replies: string[], room: IRoom) {
async function notifyUsersOnReply(message: IMessage, replies: IUser['_id'][]) {
// skips this callback if the message was edited
if (isEditedMessage(message)) {
return message;
}

await updateThreadUsersSubscriptions(message, room, replies);
await updateThreadUsersSubscriptions(message, replies);

return message;
}
Expand Down Expand Up @@ -59,7 +59,7 @@ export async function processThreads(message: IMessage, room: IRoom) {
]),
].filter((userId) => userId !== message.u._id);

await notifyUsersOnReply(message, replies, room);
await notifyUsersOnReply(message, replies);
await metaData(message, parentMessage, replies);
await notification(message, room, replies);
void broadcastMessageFromData({
Expand Down
3 changes: 2 additions & 1 deletion apps/meteor/lib/callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
AtLeast,
UserStatus,
ILivechatDepartment,
MessageMention,
} from '@rocket.chat/core-typings';
import type { FilterOperators } from 'mongodb';

Expand Down Expand Up @@ -206,6 +207,7 @@ type ChainedCallbackSignatures = {
'archiveRoom': (room: IRoom) => void;
'unarchiveRoom': (room: IRoom) => void;
'roomAvatarChanged': (room: IRoom) => void;
'beforeGetMentions': (mentionIds: string[], teamMentions: MessageMention[]) => Promise<string[]>;
};

export type Hook =
Expand All @@ -217,7 +219,6 @@ export type Hook =
| 'afterSaveUser'
| 'afterValidateNewOAuthUser'
| 'beforeActivateUser'
| 'beforeGetMentions'
| 'beforeReadMessages'
| 'beforeRemoveFromRoom'
| 'beforeValidateLogin'
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/server/models/raw/Rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1516,7 +1516,7 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
_id: IRoom['_id'],
inc = 1,
lastMessageTimestamp: NonNullable<IRoom['lm']>,
lastMessage: IRoom['lastMessage'],
lastMessage?: IMessage,
): Promise<UpdateResult> {
const query: Filter<IRoom> = { _id };

Expand Down
10 changes: 5 additions & 5 deletions apps/meteor/server/models/raw/Subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1287,8 +1287,8 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
}

incGroupMentionsAndUnreadForRoomIdExcludingUserId(
roomId: string,
userId: string,
roomId: IRoom['_id'],
userId: IUser['_id'],
incGroup = 1,
incUnread = 1,
): Promise<UpdateResult | Document> {
Expand All @@ -1314,8 +1314,8 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
}

incUserMentionsAndUnreadForRoomIdAndUserIds(
roomId: string,
userIds: string[],
roomId: IRoom['_id'],
userIds: IUser['_id'][],
incUser = 1,
incUnread = 1,
): Promise<UpdateResult | Document> {
Expand Down Expand Up @@ -1384,7 +1384,7 @@ export class SubscriptionsRaw extends BaseRaw<ISubscription> implements ISubscri
return this.updateMany(query, update);
}

setLastReplyForRoomIdAndUserIds(roomId: string, uids: string, lr: Date): Promise<UpdateResult | Document> {
setLastReplyForRoomIdAndUserIds(roomId: IRoom['_id'], uids: IUser['_id'][], lr: Date): Promise<UpdateResult | Document> {
const query = {
'rid': roomId,
'u._id': { $in: uids },
Expand Down
2 changes: 1 addition & 1 deletion packages/model-typings/src/models/IRoomsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export interface IRoomsModel extends IBaseModel<IRoom> {
archiveById(rid: string): Promise<UpdateResult>;
unarchiveById(rid: string): Promise<UpdateResult>;
setNameById(rid: string, name: string, fname: string): Promise<UpdateResult>;
incMsgCountAndSetLastMessageById(rid: string, inc: number, lastMessageTs: Date, lastMessage: IRoom['lastMessage']): Promise<UpdateResult>;
incMsgCountAndSetLastMessageById(rid: IRoom['_id'], inc: number, lastMessageTs: Date, lastMessage?: IMessage): Promise<UpdateResult>;
incUsersCountById(rid: string, inc: number): Promise<UpdateResult>;
incUsersCountNotDMsByIds(rids: string[], inc: number): Promise<Document | UpdateResult>;
setLastMessageById(rid: string, lastMessage: IRoom['lastMessage']): Promise<UpdateResult>;
Expand Down
10 changes: 5 additions & 5 deletions packages/model-typings/src/models/ISubscriptionsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,22 +187,22 @@ export interface ISubscriptionsModel extends IBaseModel<ISubscription> {
updateDirectNameAndFnameByName(name: string, newName?: string, newFname?: string): Promise<UpdateResult | Document>;

incGroupMentionsAndUnreadForRoomIdExcludingUserId(
roomId: string,
userId: string,
roomId: IRoom['_id'],
userId: IUser['_id'],
incGroup?: number,
incUnread?: number,
): Promise<UpdateResult | Document>;
unsetBlockedByRoomId(rid: string, blocked: string, blocker: string): Promise<UpdateResult[]>;
setLastReplyForRoomIdAndUserIds(roomId: string, uids: string, lr: Date): Promise<UpdateResult | Document>;
setLastReplyForRoomIdAndUserIds(roomId: IRoom['_id'], uids: IUser['_id'][], lr: Date): Promise<UpdateResult | Document>;
updateCustomFieldsByRoomId(rid: string, cfields: Record<string, any>): Promise<UpdateResult | Document>;
setOpenForRoomIdAndUserIds(roomId: string, uids: string[]): Promise<UpdateResult | Document>;

setAlertForRoomIdAndUserIds(roomId: string, uids: string[]): Promise<UpdateResult | Document>;
updateTypeByRoomId(roomId: string, type: ISubscription['t']): Promise<UpdateResult | Document>;
setBlockedByRoomId(rid: string, blocked: string, blocker: string): Promise<UpdateResult[]>;
incUserMentionsAndUnreadForRoomIdAndUserIds(
roomId: string,
userIds: string[],
roomId: IRoom['_id'],
userIds: IUser['_id'][],
incUser?: number,
incUnread?: number,
): Promise<UpdateResult | Document>;
Expand Down