feat: Improve Engagement Dashboard's "Channels" tab performance (#32493)

pull/32813/head^2
Matheus Barbosa Silva 2 years ago committed by GitHub
parent e6ce4eeb51
commit 439faa87d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/many-tables-love.md
  2. 6
      .changeset/proud-waves-bathe.md
  3. 1
      apps/meteor/client/views/admin/engagementDashboard/channels/useChannelsList.ts
  4. 22
      apps/meteor/ee/server/api/engagementDashboard/channels.ts
  5. 76
      apps/meteor/ee/server/lib/engagementDashboard/channels.ts
  6. 123
      apps/meteor/server/models/raw/Analytics.ts
  7. 51
      apps/meteor/server/models/raw/Rooms.ts
  8. 347
      apps/meteor/tests/end-to-end/api/34-engagement-dashboard.ts
  9. 9
      packages/model-typings/src/models/IAnalyticsModel.ts
  10. 11
      packages/model-typings/src/models/IRoomsModel.ts

@ -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

@ -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

@ -24,6 +24,7 @@ export const useChannelsList = ({ period, offset, count }: UseChannelsListOption
end: end.toISOString(),
offset,
count,
hideRoomsWithNoActivity: true,
});
return response

@ -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'];
@ -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 },
});

@ -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,
@ -34,8 +94,10 @@ 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),
@ -43,15 +105,7 @@ export const findAllChannelsWithNumberOfMessages = async ({
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,

@ -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';
@ -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> {
@ -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(),
});
}
}

@ -413,18 +413,25 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
}
getChannelsWithNumberOfMessagesBetweenDateQuery({
types,
start,
end,
startOfLastWeek,
endOfLastWeek,
options,
}: {
types: Array<IRoom['t']>;
start: number;
end: number;
startOfLastWeek: number;
endOfLastWeek: number;
options?: any;
}) {
const typeMatch = {
$match: {
t: { $in: types },
},
};
const lookup = {
$lookup: {
from: 'rocketchat_analytics',
@ -504,30 +511,32 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
diffFromLastWeek: { $subtract: ['$messages', '$lastWeekMessages'] },
},
};
const firstParams = [
lookup,
messagesProject,
messagesUnwind,
messagesGroup,
lastWeekMessagesUnwind,
lastWeekMessagesGroup,
presentationProject,
];
const firstParams = [typeMatch, lookup, messagesProject, messagesUnwind, messagesGroup];
const lastParams = [lastWeekMessagesUnwind, lastWeekMessagesGroup, presentationProject];
const sort = { $sort: options?.sort || { messages: -1 } };
const params: Exclude<Parameters<Collection<IRoom>['aggregate']>[0], undefined> = [...firstParams, sort];
const sortAndPaginationParams: Exclude<Parameters<Collection<IRoom>['aggregate']>[0], undefined> = [sort];
if (options?.offset) {
params.push({ $skip: options.offset });
sortAndPaginationParams.push({ $skip: options.offset });
}
if (options?.count) {
params.push({ $limit: options.count });
sortAndPaginationParams.push({ $limit: options.count });
}
const params: Exclude<Parameters<Collection<IRoom>['aggregate']>[0], undefined> = [...firstParams];
if (options?.sort) {
params.push(...lastParams, ...sortAndPaginationParams);
} else {
params.push(...sortAndPaginationParams, ...lastParams, sort);
}
return params;
}
findChannelsWithNumberOfMessagesBetweenDate(params: {
findChannelsByTypesWithNumberOfMessagesBetweenDate(params: {
types: Array<IRoom['t']>;
start: number;
end: number;
startOfLastWeek: number;
@ -541,22 +550,6 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
});
}
countChannelsWithNumberOfMessagesBetweenDate(params: {
start: number;
end: number;
startOfLastWeek: number;
endOfLastWeek: number;
options?: any;
}): AggregationCursor<{ total: number }> {
const aggregationParams = this.getChannelsWithNumberOfMessagesBetweenDateQuery(params);
aggregationParams.push({ $count: 'total' });
return this.col.aggregate<{ total: number }>(aggregationParams, {
allowDiskUse: true,
readPreference: readSecondaryPreferred(),
});
}
findOneByNameOrFname(name: NonNullable<IRoom['name'] | IRoom['fname']>, options: FindOptions<IRoom> = {}): Promise<IRoom | null> {
const query = {
$or: [

@ -0,0 +1,347 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../data/api-data';
import { sendSimpleMessage } from '../../data/chat.helper';
import { updatePermission } from '../../data/permissions.helper';
import { createRoom, deleteRoom } from '../../data/rooms.helper';
describe('[Engagement Dashboard]', function () {
this.retries(0);
const isEnterprise = Boolean(process.env.IS_EE);
before((done) => getCredentials(done));
before(() => updatePermission('view-engagement-dashboard', ['admin']));
after(() => updatePermission('view-engagement-dashboard', ['admin']));
(isEnterprise ? describe : describe.skip)('[/engagement-dashboard/channels/list]', () => {
let testRoom: IRoom;
before(async () => {
testRoom = (await createRoom({ type: 'c', name: `channel.test.engagement.${Date.now()}-${Math.random()}` })).body.channel;
});
after(async () => {
await deleteRoom({ type: 'c', roomId: testRoom._id });
});
it('should fail if user does not have the view-engagement-dashboard permission', async () => {
await updatePermission('view-engagement-dashboard', []);
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date().toISOString(),
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
offset: 0,
count: 25,
})
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('User does not have the permissions required for this action [error-unauthorized]');
});
});
it('should fail if start param is not a valid date', async () => {
await updatePermission('view-engagement-dashboard', ['admin']);
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
start: 'invalid-date',
end: new Date().toISOString(),
offset: 0,
count: 25,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('Match error: Failed Match.Where validation in field start');
});
});
it('should fail if end param is not a valid date', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
end: 'invalid-date',
offset: 0,
count: 25,
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('Match error: Failed Match.Where validation in field end');
});
});
it('should fail if start param is not provided', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date(),
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal("Match error: Missing key 'start'");
});
});
it('should fail if end param is not provided', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal("Match error: Missing key 'end'");
});
});
it('should succesfuly return results', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date().toISOString(),
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('offset', 0);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('channels');
expect(res.body.channels).to.be.an('array').that.is.not.empty;
expect(res.body.channels[0]).to.be.an('object').that.is.not.empty;
expect(res.body.channels[0]).to.have.property('messages').that.is.a('number');
expect(res.body.channels[0]).to.have.property('lastWeekMessages').that.is.a('number');
expect(res.body.channels[0]).to.have.property('diffFromLastWeek').that.is.a('number');
expect(res.body.channels[0].room).to.be.an('object').that.is.not.empty;
expect(res.body.channels[0].room).to.have.property('_id').that.is.a('string');
expect(res.body.channels[0].room).to.have.property('name').that.is.a('string');
expect(res.body.channels[0].room).to.have.property('ts').that.is.a('string');
expect(res.body.channels[0].room).to.have.property('t').that.is.a('string');
expect(res.body.channels[0].room).to.have.property('_updatedAt').that.is.a('string');
});
});
it('should not return empty rooms when the hideRoomsWithNoActivity param is provided', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date().toISOString(),
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
hideRoomsWithNoActivity: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('offset', 0);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('channels');
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
expect(channelRecord).to.be.undefined;
});
});
it('should correctly count messages in an empty room', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date().toISOString(),
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('offset', 0);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('channels');
expect(res.body.channels).to.be.an('array').that.is.not.empty;
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
expect(channelRecord).not.to.be.undefined;
expect(channelRecord).to.be.an('object').that.is.not.empty;
expect(channelRecord).to.have.property('messages', 0);
expect(channelRecord).to.have.property('lastWeekMessages', 0);
expect(channelRecord).to.have.property('diffFromLastWeek', 0);
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
expect(channelRecord.room).to.have.property('_id', testRoom._id);
expect(channelRecord.room).to.have.property('name', testRoom.name);
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
expect(channelRecord.room).to.have.property('t', testRoom.t);
expect(channelRecord.room).to.have.property('_updatedAt', testRoom._updatedAt);
});
});
it('should correctly count messages diff compared to last week when the hideRoomsWithNoActivity param is provided and there are messages in a room', async () => {
await sendSimpleMessage({ roomId: testRoom._id });
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date().toISOString(),
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
hideRoomsWithNoActivity: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('offset', 0);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('channels');
expect(res.body.channels).to.be.an('array').that.is.not.empty;
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
expect(channelRecord).not.to.be.undefined;
expect(channelRecord).to.be.an('object').that.is.not.empty;
expect(channelRecord).to.have.property('messages', 1);
expect(channelRecord).to.have.property('lastWeekMessages', 0);
expect(channelRecord).to.have.property('diffFromLastWeek', 1);
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
expect(channelRecord.room).to.have.property('_id', testRoom._id);
expect(channelRecord.room).to.have.property('name', testRoom.name);
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
expect(channelRecord.room).to.have.property('t', testRoom.t);
});
});
it('should correctly count messages diff compared to last week when there are messages in a room', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date().toISOString(),
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('offset', 0);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('channels');
expect(res.body.channels).to.be.an('array').that.is.not.empty;
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
expect(channelRecord).not.to.be.undefined;
expect(channelRecord).to.be.an('object').that.is.not.empty;
expect(channelRecord).to.have.property('messages', 1);
expect(channelRecord).to.have.property('lastWeekMessages', 0);
expect(channelRecord).to.have.property('diffFromLastWeek', 1);
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
expect(channelRecord.room).to.have.property('_id', testRoom._id);
expect(channelRecord.room).to.have.property('name', testRoom.name);
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
expect(channelRecord.room).to.have.property('t', testRoom.t);
});
});
it('should correctly count messages from last week and diff when moving to the next week and providing the hideRoomsWithNoActivity param', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000).toISOString(),
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
hideRoomsWithNoActivity: true,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('offset', 0);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('channels');
expect(res.body.channels).to.be.an('array').that.is.not.empty;
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
expect(channelRecord).not.to.be.undefined;
expect(channelRecord).to.be.an('object').that.is.not.empty;
expect(channelRecord).to.have.property('messages', 0);
expect(channelRecord).to.have.property('lastWeekMessages', 1);
expect(channelRecord).to.have.property('diffFromLastWeek', -1);
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
expect(channelRecord.room).to.have.property('_id', testRoom._id);
expect(channelRecord.room).to.have.property('name', testRoom.name);
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
expect(channelRecord.room).to.have.property('t', testRoom.t);
});
});
it('should correctly count messages from last week and diff when moving to the next week', async () => {
await request
.get(api('engagement-dashboard/channels/list'))
.set(credentials)
.query({
end: new Date(Date.now() + 8 * 24 * 60 * 60 * 1000).toISOString(),
start: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('offset', 0);
expect(res.body).to.have.property('count');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('channels');
expect(res.body.channels).to.be.an('array').that.is.not.empty;
const channelRecord = res.body.channels.find(({ room }: { room: { _id: string } }) => room._id === testRoom._id);
expect(channelRecord).not.to.be.undefined;
expect(channelRecord).to.be.an('object').that.is.not.empty;
expect(channelRecord).to.have.property('messages', 0);
expect(channelRecord).to.have.property('lastWeekMessages', 1);
expect(channelRecord).to.have.property('diffFromLastWeek', -1);
expect(channelRecord.room).to.be.an('object').that.is.not.empty;
expect(channelRecord.room).to.have.property('_id', testRoom._id);
expect(channelRecord.room).to.have.property('name', testRoom.name);
expect(channelRecord.room).to.have.property('ts', testRoom.ts);
expect(channelRecord.room).to.have.property('t', testRoom.t);
});
});
});
});

@ -2,6 +2,7 @@ import type { IAnalytic, IRoom } from '@rocket.chat/core-typings';
import type { AggregationCursor, FindCursor, FindOptions, UpdateResult, Document } from 'mongodb';
import type { IBaseModel } from './IBaseModel';
import type { IChannelsWithNumberOfMessagesBetweenDate } from './IRoomsModel';
export interface IAnalyticsModel extends IBaseModel<IAnalytic> {
saveMessageSent({ room, date }: { room: IRoom; date: IAnalytic['date'] }): Promise<Document | UpdateResult>;
@ -38,4 +39,12 @@ export interface IAnalyticsModel extends IBaseModel<IAnalytic> {
users: number;
}>;
findByTypeBeforeDate({ type, date }: { type: IAnalytic['type']; date: IAnalytic['date'] }): FindCursor<IAnalytic>;
findRoomsByTypesWithNumberOfMessagesBetweenDate(params: {
types: Array<IRoom['t']>;
start: number;
end: number;
startOfLastWeek: number;
endOfLastWeek: number;
options?: any;
}): AggregationCursor<{ channels: IChannelsWithNumberOfMessagesBetweenDate[]; total: number }>;
}

@ -86,7 +86,8 @@ export interface IRoomsModel extends IBaseModel<IRoom> {
setTeamDefaultById(rid: IRoom['_id'], teamDefault: NonNullable<IRoom['teamDefault']>, options?: UpdateOptions): Promise<UpdateResult>;
findChannelsWithNumberOfMessagesBetweenDate(params: {
findChannelsByTypesWithNumberOfMessagesBetweenDate(params: {
types: Array<IRoom['t']>;
start: number;
end: number;
startOfLastWeek: number;
@ -94,14 +95,6 @@ export interface IRoomsModel extends IBaseModel<IRoom> {
options?: any;
}): AggregationCursor<IChannelsWithNumberOfMessagesBetweenDate>;
countChannelsWithNumberOfMessagesBetweenDate(params: {
start: number;
end: number;
startOfLastWeek: number;
endOfLastWeek: number;
options?: any;
}): AggregationCursor<{ total: number }>;
findOneByName(name: NonNullable<IRoom['name']>, options?: FindOptions<IRoom>): Promise<IRoom | null>;
findDefaultRoomsForTeam(teamId: any): FindCursor<IRoom>;

Loading…
Cancel
Save