Skip to content

Commit

Permalink
Merge branch 'develop' into improve/monitors-manage-departments-units
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] authored Sep 18, 2024
2 parents ead6d54 + 4202d65 commit e625cb2
Show file tree
Hide file tree
Showing 24 changed files with 884 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/great-humans-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixed a Federation callback not awaiting db call
5 changes: 5 additions & 0 deletions .changeset/rotten-rabbits-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Resolves the issue where outgoing integrations failed to trigger after the version 6.12.0 upgrade by correcting the parameter order from the `afterSaveMessage` callback to listener functions. This ensures the correct room information is passed, restoring the functionality of outgoing webhooks, IRC bridge, Autotranslate, and Engagement Dashboard.
5 changes: 5 additions & 0 deletions .changeset/small-crabs-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Fixed avatar blob image setting in setUserAvatar method by correcting service handling logic.
8 changes: 8 additions & 0 deletions .changeset/soft-mirrors-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-services": minor
"@rocket.chat/model-typings": minor
"@rocket.chat/rest-typings": minor
---

New `teams.listChildren` endpoint that allows users listing rooms & discussions from teams. Only the discussions from the team's main room are returned.
39 changes: 39 additions & 0 deletions apps/meteor/app/api/server/v1/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isTeamsDeleteProps,
isTeamsLeaveProps,
isTeamsUpdateProps,
isTeamsListChildrenProps,
} from '@rocket.chat/rest-typings';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { Match, check } from 'meteor/check';
Expand Down Expand Up @@ -375,6 +376,44 @@ API.v1.addRoute(
},
);

const getTeamByIdOrNameOrParentRoom = async (
params: { teamId: string } | { teamName: string } | { roomId: string },
): Promise<Pick<ITeam, 'type' | 'roomId' | '_id'> | null> => {
if ('teamId' in params && params.teamId) {
return Team.getOneById<ITeam>(params.teamId, { projection: { type: 1, roomId: 1 } });
}
if ('teamName' in params && params.teamName) {
return Team.getOneByName(params.teamName, { projection: { type: 1, roomId: 1 } });
}
if ('roomId' in params && params.roomId) {
return Team.getOneByRoomId(params.roomId, { projection: { type: 1, roomId: 1 } });
}
return null;
};

// This should accept a teamId, filter (search by name on rooms collection) and sort/pagination
// should return a list of rooms/discussions from the team. the discussions will only be returned from the main room
API.v1.addRoute(
'teams.listChildren',
{ authRequired: true, validateParams: isTeamsListChildrenProps },
{
async get() {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();
const { filter, type } = this.queryParams;

const team = await getTeamByIdOrNameOrParentRoom(this.queryParams);
if (!team) {
return API.v1.notFound();
}

const data = await Team.listChildren(this.userId, team, filter, type, sort, offset, count);

return API.v1.success({ ...data, offset, count });
},
},
);

API.v1.addRoute(
'teams.members',
{ authRequired: true },
Expand Down
7 changes: 6 additions & 1 deletion apps/meteor/app/autotranslate/server/autotranslate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ export class TranslationProviderRegistry {
return;
}

callbacks.add('afterSaveMessage', provider.translateMessage.bind(provider), callbacks.priority.MEDIUM, 'autotranslate');
callbacks.add(
'afterSaveMessage',
(message, { room }) => provider.translateMessage(message, { room }),
callbacks.priority.MEDIUM,
'autotranslate',
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getFederationDomain } from '../lib/getFederationDomain';
import { clientLogger } from '../lib/logger';

async function afterUnsetReaction(message, { user, reaction }) {
const room = Rooms.findOneById(message.rid, { fields: { federation: 1 } });
const room = await Rooms.findOneById(message.rid, { projection: { federation: 1 } });

// If there are not federated users on this room, ignore it
if (!hasExternalDomain(room)) {
Expand Down
7 changes: 6 additions & 1 deletion apps/meteor/app/integrations/server/triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ const callbackHandler = function _callbackHandler(eventType: string) {
};
};

callbacks.add('afterSaveMessage', callbackHandler('sendMessage'), callbacks.priority.LOW, 'integrations-sendMessage');
callbacks.add(
'afterSaveMessage',
(message, { room }) => callbackHandler('sendMessage')(message, room),
callbacks.priority.LOW,
'integrations-sendMessage',
);
callbacks.add('afterCreateChannel', callbackHandler('roomCreated'), callbacks.priority.LOW, 'integrations-roomCreated');
callbacks.add('afterCreatePrivateGroup', callbackHandler('roomCreated'), callbacks.priority.LOW, 'integrations-roomCreated');
callbacks.add('afterCreateUser', callbackHandler('userCreated'), callbacks.priority.LOW, 'integrations-userCreated');
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/irc/server/irc-bridge/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ class Bridge {
// Chatting
callbacks.add(
'afterSaveMessage',
this.onMessageReceived.bind(this, 'local', 'onSaveMessage'),
(message, { room }) => this.onMessageReceived('local', 'onSaveMessage', message, room),
callbacks.priority.LOW,
'irc-on-save-message',
);
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/lib/server/functions/isTheLastMessage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import type { IMessage, IRoom, AtLeast } from '@rocket.chat/core-typings';

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

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export const isTheLastMessage = (room: IRoom, message: Pick<IMessage, '_id'>) =>
export const isTheLastMessage = (room: AtLeast<IRoom, 'lastMessage'>, message: Pick<IMessage, '_id'>) =>
settings.get('Store_Last_Message') && (!room.lastMessage || room.lastMessage._id === message._id);
23 changes: 11 additions & 12 deletions apps/meteor/app/lib/server/functions/setUsername.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,23 +102,22 @@ export const _setUsername = async function (userId: string, u: string, fullUser:
// Set new username*
await Users.setUsername(user._id, username);
user.username = username;

if (!previousUsername && settings.get('Accounts_SetDefaultAvatar') === true) {
// eslint-disable-next-line @typescript-eslint/ban-types
const avatarSuggestions = (await getAvatarSuggestionForUser(user)) as {};
let gravatar;
for await (const service of Object.keys(avatarSuggestions)) {
const avatarData = avatarSuggestions[+service as keyof typeof avatarSuggestions];
const avatarSuggestions = await getAvatarSuggestionForUser(user);
let avatarData;
let serviceName = 'gravatar';

for (const service of Object.keys(avatarSuggestions)) {
avatarData = avatarSuggestions[service];
if (service !== 'gravatar') {
// eslint-disable-next-line dot-notation
await setUserAvatar(user, avatarData['blob'], avatarData['contentType'], service);
gravatar = null;
serviceName = service;
break;
}
gravatar = avatarData;
}
if (gravatar != null) {
// eslint-disable-next-line dot-notation
await setUserAvatar(user, gravatar['blob'], gravatar['contentType'], 'gravatar');

if (avatarData) {
await setUserAvatar(user, avatarData.blob, avatarData.contentType, serviceName);
}
}

Expand Down
7 changes: 6 additions & 1 deletion apps/meteor/ee/server/lib/engagementDashboard/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { fillFirstDaysOfMessagesIfNeeded, handleMessagesDeleted, handleMessagesS
import { fillFirstDaysOfUsersIfNeeded, handleUserCreated } from './users';

export const attachCallbacks = (): void => {
callbacks.add('afterSaveMessage', handleMessagesSent, callbacks.priority.MEDIUM, 'engagementDashboard.afterSaveMessage');
callbacks.add(
'afterSaveMessage',
(message, { room }) => handleMessagesSent(message, { room }),
callbacks.priority.MEDIUM,
'engagementDashboard.afterSaveMessage',
);
callbacks.add('afterDeleteMessage', handleMessagesDeleted, callbacks.priority.MEDIUM, 'engagementDashboard.afterDeleteMessage');
callbacks.add('afterCreateUser', handleUserCreated, callbacks.priority.MEDIUM, 'engagementDashboard.afterCreateUser');
};
Expand Down
81 changes: 81 additions & 0 deletions apps/meteor/server/models/raw/Rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2063,4 +2063,85 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {

return this.updateMany(query, update);
}

findChildrenOfTeam(
teamId: string,
teamRoomId: string,
userId: string,
filter?: string,
type?: 'channels' | 'discussions',
options?: FindOptions<IRoom>,
): AggregationCursor<{ totalCount: { count: number }[]; paginatedResults: IRoom[] }> {
const nameFilter = filter ? new RegExp(escapeRegExp(filter), 'i') : undefined;
return this.col.aggregate<{ totalCount: { count: number }[]; paginatedResults: IRoom[] }>([
{
$match: {
$and: [
{
$or: [
...(!type || type === 'channels' ? [{ teamId }] : []),
...(!type || type === 'discussions' ? [{ prid: teamRoomId }] : []),
],
},
...(nameFilter ? [{ $or: [{ fname: nameFilter }, { name: nameFilter }] }] : []),
],
},
},
{
$lookup: {
from: 'rocketchat_subscription',
let: {
roomId: '$_id',
},
pipeline: [
{
$match: {
$and: [
{
$expr: {
$eq: ['$rid', '$$roomId'],
},
},
{
$expr: {
$eq: ['$u._id', userId],
},
},
{
$expr: {
$ne: ['$t', 'c'],
},
},
],
},
},
{
$project: { _id: 1 },
},
],
as: 'subscription',
},
},
{
$match: {
$or: [
{ t: 'c' },
{
$expr: {
$ne: [{ $size: '$subscription' }, 0],
},
},
],
},
},
{ $project: { subscription: 0 } },
{ $sort: options?.sort || { ts: 1 } },
{
$facet: {
totalCount: [{ $count: 'count' }],
paginatedResults: [{ $skip: options?.skip || 0 }, { $limit: options?.limit || 50 }],
},
},
]);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { AtLeast } from '@rocket.chat/core-typings';
import { type IMessage, type IRoom, isMessageFromMatrixFederation, isRoomFederated } from '@rocket.chat/core-typings';

import { isFederationEnabled, isFederationReady } from '../../federation/utils';

export class FederationActions {
public static shouldPerformAction(message: IMessage, room: IRoom): boolean {
public static shouldPerformAction(message: IMessage, room: AtLeast<IRoom, 'federated'>): boolean {
if (isMessageFromMatrixFederation(message) || isRoomFederated(room)) {
return isFederationEnabled() && isFederationReady();
}
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/server/services/messages/service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IMessageService } from '@rocket.chat/core-services';
import { Authorization, ServiceClassInternal } from '@rocket.chat/core-services';
import { type IMessage, type MessageTypesValues, type IUser, type IRoom, isEditedMessage } from '@rocket.chat/core-typings';
import { type IMessage, type MessageTypesValues, type IUser, type IRoom, isEditedMessage, type AtLeast } from '@rocket.chat/core-typings';
import { Messages, Rooms } from '@rocket.chat/models';

import { deleteMessage } from '../../../app/lib/server/functions/deleteMessage';
Expand Down Expand Up @@ -244,7 +244,7 @@ export class MessageService extends ServiceClassInternal implements IMessageServ
// await Room.join({ room, user });
// }

async beforeReacted(message: IMessage, room: IRoom) {
async beforeReacted(message: IMessage, room: AtLeast<IRoom, 'federated'>) {
if (!FederationActions.shouldPerformAction(message, room)) {
throw new FederationMatrixInvalidConfigurationError('Unable to react to message');
}
Expand Down
39 changes: 36 additions & 3 deletions apps/meteor/server/services/team/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -913,8 +913,8 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
});
}

async getOneByRoomId(roomId: string): Promise<ITeam | null> {
const room = await Rooms.findOneById(roomId);
async getOneByRoomId(roomId: string, options?: FindOptions<ITeam>): Promise<ITeam | null> {
const room = await Rooms.findOneById(roomId, { projection: { teamId: 1 } });

if (!room) {
throw new Error('invalid-room');
Expand All @@ -924,7 +924,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
throw new Error('room-not-on-team');
}

return Team.findOneById(room.teamId);
return Team.findOneById(room.teamId, options);
}

async addRolesToMember(teamId: string, userId: string, roles: Array<string>): Promise<boolean> {
Expand Down Expand Up @@ -1078,4 +1078,37 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
const parentRoom = await this.getParentRoom(team);
return { team, ...(parentRoom && { parentRoom }) };
}

// Returns the list of rooms and discussions a user has access to inside a team
// Rooms returned are a composition of the rooms the user is in + public rooms + discussions from the main room (if any)
async listChildren(
userId: string,
team: AtLeast<ITeam, '_id' | 'roomId' | 'type'>,
filter?: string,
type?: 'channels' | 'discussions',
sort?: Record<string, 1 | -1>,
skip = 0,
limit = 10,
): Promise<{ total: number; data: IRoom[] }> {
const mainRoom = await Rooms.findOneById(team.roomId, { projection: { _id: 1 } });
if (!mainRoom) {
throw new Error('error-invalid-team-no-main-room');
}

const isMember = await TeamMember.findOneByUserIdAndTeamId(userId, team._id, {
projection: { _id: 1 },
});

if (!isMember) {
throw new Error('error-invalid-team-not-a-member');
}

const [{ totalCount: [{ count: total }] = [], paginatedResults: data = [] }] =
(await Rooms.findChildrenOfTeam(team._id, mainRoom._id, userId, filter, type, { skip, limit, sort }).toArray()) || [];

return {
total,
data,
};
}
}
19 changes: 14 additions & 5 deletions apps/meteor/tests/data/teams.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import type { ITeam, TEAM_TYPE } from '@rocket.chat/core-typings';

import { api, request } from './api-data';

export const createTeam = async (credentials: Record<string, any>, teamName: string, type: TEAM_TYPE): Promise<ITeam> => {
const response = await request.post(api('teams.create')).set(credentials).send({
name: teamName,
type,
});
export const createTeam = async (
credentials: Record<string, any>,
teamName: string,
type: TEAM_TYPE,
members?: string[],
): Promise<ITeam> => {
const response = await request
.post(api('teams.create'))
.set(credentials)
.send({
name: teamName,
type,
...(members && { members }),
});

return response.body.team;
};
Expand Down
Loading

0 comments on commit e625cb2

Please sign in to comment.