Skip to content

Commit

Permalink
feat: Improve Engagement Dashboard's "Channels" tab performance (#32493)
Browse files Browse the repository at this point in the history
  • Loading branch information
matheusbsilva137 authored Jul 19, 2024
1 parent e6ce4ee commit 439faa8
Show file tree
Hide file tree
Showing 10 changed files with 596 additions and 56 deletions.
6 changes: 6 additions & 0 deletions .changeset/many-tables-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/model-typings": minor
---

Fixed Livechat rooms being displayed in the Engagement Dashboard's "Channels" tab
6 changes: 6 additions & 0 deletions .changeset/proud-waves-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/model-typings": minor
---

Improved Engagement Dashboard's "Channels" tab performance by not returning rooms that had no activity in the analyzed period
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const useChannelsList = ({ period, offset, count }: UseChannelsListOption
end: end.toISOString(),
offset,
count,
hideRoomsWithNoActivity: true,
});

return response
Expand Down
22 changes: 18 additions & 4 deletions apps/meteor/ee/server/api/engagementDashboard/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { check, Match } from 'meteor/check';

import { API } from '../../../../app/api/server';
import { getPaginationItems } from '../../../../app/api/server/helpers/getPaginationItems';
import { findAllChannelsWithNumberOfMessages } from '../../lib/engagementDashboard/channels';
import { apiDeprecationLogger } from '../../../../app/lib/server/lib/deprecationWarningLogger';
import { findChannelsWithNumberOfMessages } from '../../lib/engagementDashboard/channels';
import { isDateISOString, mapDateForAPI } from '../../lib/engagementDashboard/date';

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface Endpoints {
'/v1/engagement-dashboard/channels/list': {
GET: (params: { start: string; end: string; offset?: number; count?: number }) => {
GET: (params: { start: string; end: string; offset?: number; count?: number; hideRoomsWithNoActivity?: boolean }) => {
channels: {
room: {
_id: IRoom['_id'];
Expand Down Expand Up @@ -45,17 +46,30 @@ API.v1.addRoute(
Match.ObjectIncluding({
start: Match.Where(isDateISOString),
end: Match.Where(isDateISOString),
hideRoomsWithNoActivity: Match.Maybe(String),
offset: Match.Maybe(String),
count: Match.Maybe(String),
}),
);

const { start, end } = this.queryParams;
const { start, end, hideRoomsWithNoActivity } = this.queryParams;
const { offset, count } = await getPaginationItems(this.queryParams);

const { channels, total } = await findAllChannelsWithNumberOfMessages({
if (hideRoomsWithNoActivity === undefined) {
apiDeprecationLogger.deprecatedParameterUsage(
this.request.route,
'hideRoomsWithNoActivity',
'7.0.0',
this.response,
({ parameter, endpoint, version }) =>
`Returning rooms that had no activity in ${endpoint} is deprecated and will be removed on version ${version} along with the \`${parameter}\` param. Set \`${parameter}\` as \`true\` to check how the endpoint will behave starting on ${version}`,
);
}

const { channels, total } = await findChannelsWithNumberOfMessages({
start: mapDateForAPI(start),
end: mapDateForAPI(end),
hideRoomsWithNoActivity: hideRoomsWithNoActivity === 'true',
options: { offset, count },
});

Expand Down
76 changes: 65 additions & 11 deletions apps/meteor/ee/server/lib/engagementDashboard/channels.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,69 @@
import type { IDirectMessageRoom, IRoom } from '@rocket.chat/core-typings';
import { Rooms } from '@rocket.chat/models';
import { Analytics, Rooms } from '@rocket.chat/models';
import moment from 'moment';

import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator';
import { convertDateToInt, diffBetweenDaysInclusive } from './date';

export const findChannelsWithNumberOfMessages = async ({
start,
end,
hideRoomsWithNoActivity,
options = {},
}: {
start: Date;
end: Date;
hideRoomsWithNoActivity: boolean;
options: {
offset?: number;
count?: number;
};
}): Promise<{
channels: {
room: {
_id: IRoom['_id'];
name: IRoom['name'] | IRoom['fname'];
ts: IRoom['ts'];
t: IRoom['t'];
_updatedAt: IRoom['_updatedAt'];
usernames?: IDirectMessageRoom['usernames'];
};
messages: number;
lastWeekMessages: number;
diffFromLastWeek: number;
}[];
total: number;
}> => {
if (!hideRoomsWithNoActivity) {
return findAllChannelsWithNumberOfMessages({ start, end, options });
}

const daysBetweenDates = diffBetweenDaysInclusive(end, start);
const endOfLastWeek = moment(start).subtract(1, 'days').toDate();
const startOfLastWeek = moment(endOfLastWeek).subtract(daysBetweenDates, 'days').toDate();
const roomTypes = roomCoordinator.getTypesToShowOnDashboard() as Array<IRoom['t']>;

const aggregationResult = await Analytics.findRoomsByTypesWithNumberOfMessagesBetweenDate({
types: roomTypes,
start: convertDateToInt(start),
end: convertDateToInt(end),
startOfLastWeek: convertDateToInt(startOfLastWeek),
endOfLastWeek: convertDateToInt(endOfLastWeek),
options,
}).toArray();

// The aggregation result may be undefined if there are no matching analytics or corresponding rooms in the period
if (!aggregationResult.length) {
return { channels: [], total: 0 };
}

const [{ channels, total }] = aggregationResult;
return {
channels,
total,
};
};

export const findAllChannelsWithNumberOfMessages = async ({
start,
end,
Expand Down Expand Up @@ -34,24 +94,18 @@ export const findAllChannelsWithNumberOfMessages = async ({
const daysBetweenDates = diffBetweenDaysInclusive(end, start);
const endOfLastWeek = moment(start).subtract(1, 'days').toDate();
const startOfLastWeek = moment(endOfLastWeek).subtract(daysBetweenDates, 'days').toDate();
const roomTypes = roomCoordinator.getTypesToShowOnDashboard() as Array<IRoom['t']>;

const channels = await Rooms.findChannelsWithNumberOfMessagesBetweenDate({
const channels = await Rooms.findChannelsByTypesWithNumberOfMessagesBetweenDate({
types: roomTypes,
start: convertDateToInt(start),
end: convertDateToInt(end),
startOfLastWeek: convertDateToInt(startOfLastWeek),
endOfLastWeek: convertDateToInt(endOfLastWeek),
options,
}).toArray();

const total =
(
await Rooms.countChannelsWithNumberOfMessagesBetweenDate({
start: convertDateToInt(start),
end: convertDateToInt(end),
startOfLastWeek: convertDateToInt(startOfLastWeek),
endOfLastWeek: convertDateToInt(endOfLastWeek),
}).toArray()
)[0]?.total ?? 0;
const total = await Rooms.countDocuments({ t: { $in: roomTypes } });

return {
channels,
Expand Down
123 changes: 120 additions & 3 deletions apps/meteor/server/models/raw/Analytics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IAnalytic, IRoom } from '@rocket.chat/core-typings';
import type { IAnalyticsModel } from '@rocket.chat/model-typings';
import type { IAnalyticsModel, IChannelsWithNumberOfMessagesBetweenDate } from '@rocket.chat/model-typings';
import { Random } from '@rocket.chat/random';
import type { AggregationCursor, FindCursor, Db, IndexDescription, FindOptions, UpdateResult, Document } from 'mongodb';
import type { AggregationCursor, FindCursor, Db, IndexDescription, FindOptions, UpdateResult, Document, Collection } from 'mongodb';

import { readSecondaryPreferred } from '../../database/readSecondaryPreferred';
import { BaseRaw } from './BaseRaw';
Expand All @@ -14,7 +14,11 @@ export class AnalyticsRaw extends BaseRaw<IAnalytic> implements IAnalyticsModel
}

protected modelIndexes(): IndexDescription[] {
return [{ key: { date: 1 } }, { key: { 'room._id': 1, 'date': 1 }, unique: true, partialFilterExpression: { type: 'rooms' } }];
return [
{ key: { date: 1 } },
{ key: { 'room._id': 1, 'date': 1 }, unique: true, partialFilterExpression: { type: 'rooms' } },
{ key: { 'room.t': 1, 'date': 1 }, partialFilterExpression: { type: 'messages' } },
];
}

saveMessageSent({ room, date }: { room: IRoom; date: IAnalytic['date'] }): Promise<Document | UpdateResult> {
Expand Down Expand Up @@ -211,4 +215,117 @@ export class AnalyticsRaw extends BaseRaw<IAnalytic> implements IAnalyticsModel
findByTypeBeforeDate({ type, date }: { type: IAnalytic['type']; date: IAnalytic['date'] }): FindCursor<IAnalytic> {
return this.find({ type, date: { $lte: date } });
}

getRoomsWithNumberOfMessagesBetweenDateQuery({
types,
start,
end,
startOfLastWeek,
endOfLastWeek,
options,
}: {
types: Array<IRoom['t']>;
start: number;
end: number;
startOfLastWeek: number;
endOfLastWeek: number;
options?: any;
}) {
const typeAndDateMatch = {
$match: {
'type': 'messages',
'room.t': { $in: types },
'date': { $gte: startOfLastWeek, $lte: end },
},
};
const roomsGroup = {
$group: {
_id: '$room._id',
room: { $first: '$room' },
messages: { $sum: { $cond: [{ $gte: ['$date', start] }, '$messages', 0] } },
lastWeekMessages: { $sum: { $cond: [{ $lte: ['$date', endOfLastWeek] }, '$messages', 0] } },
},
};
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: '_id',
as: 'room',
},
};
const roomsUnwind = {
$unwind: {
path: '$room',
preserveNullAndEmptyArrays: false,
},
};
const project = {
$project: {
_id: 0,
room: {
_id: '$room._id',
name: { $ifNull: ['$room.name', '$room.fname'] },
ts: '$room.ts',
t: '$room.t',
_updatedAt: '$room._updatedAt',
usernames: '$room.usernames',
},
messages: '$messages',
lastWeekMessages: '$lastWeekMessages',
diffFromLastWeek: { $subtract: ['$messages', '$lastWeekMessages'] },
},
};

const sort = { $sort: options?.sort || { messages: -1 } };
const sortAndPaginationParams: Exclude<Parameters<Collection<IRoom>['aggregate']>[0], undefined> = [sort];
if (options?.offset) {
sortAndPaginationParams.push({ $skip: options.offset });
}

if (options?.count) {
sortAndPaginationParams.push({ $limit: options.count });
}
const facet = {
$facet: {
channels: [...sortAndPaginationParams],
total: [{ $count: 'total' }],
},
};
const totalUnwind = { $unwind: '$total' };
const totalProject = {
$project: {
channels: '$channels',
total: '$total.total',
},
};

const params: Exclude<Parameters<Collection<IRoom>['aggregate']>[0], undefined> = [
typeAndDateMatch,
roomsGroup,
lookup,
roomsUnwind,
project,
facet,
totalUnwind,
totalProject,
];

return params;
}

findRoomsByTypesWithNumberOfMessagesBetweenDate(params: {
types: Array<IRoom['t']>;
start: number;
end: number;
startOfLastWeek: number;
endOfLastWeek: number;
options?: any;
}): AggregationCursor<{ channels: IChannelsWithNumberOfMessagesBetweenDate[]; total: number }> {
const aggregationParams = this.getRoomsWithNumberOfMessagesBetweenDateQuery(params);
return this.col.aggregate<{ channels: IChannelsWithNumberOfMessagesBetweenDate[]; total: number }>(aggregationParams, {
allowDiskUse: true,
readPreference: readSecondaryPreferred(),
});
}
}
Loading

0 comments on commit 439faa8

Please sign in to comment.