diff --git a/.changeset/brown-comics-cheat.md b/.changeset/brown-comics-cheat.md new file mode 100644 index 000000000000..a7907979881b --- /dev/null +++ b/.changeset/brown-comics-cheat.md @@ -0,0 +1,8 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/core-typings": patch +"@rocket.chat/model-typings": patch +--- + +chore: Calculate & Store MAC stats +Added new info to the stats: `omnichannelContactsBySource`, `uniqueContactsOfLastMonth`, `uniqueContactsOfLastWeek`, `uniqueContactsOfYesterday` diff --git a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts index d65897b72094..0474d01fdcb4 100644 --- a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts +++ b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts @@ -31,6 +31,8 @@ type WorkspaceRegistrationData = { setupComplete: boolean; connectionDisable: boolean; npsEnabled: SettingValue; + activeContactsBillingMonth: unknown; + activeContactsYesterday: unknown; }; export async function buildWorkspaceRegistrationData(contactEmail: T): Promise> { @@ -78,5 +80,7 @@ export async function buildWorkspaceRegistrationData { + statistics.omnichannelContactsBySource = result || defaultValue; + }), + ); + + const monthAgo = moment.utc().subtract(30, 'days').toDate(); + const today = moment.utc().toDate(); + statsPms.push( + LivechatRooms.getMACStatisticsBetweenDates(monthAgo, today).then(([result]) => { + statistics.uniqueContactsOfLastMonth = result || defaultValue; + }), + ); + + const weekAgo = moment.utc().subtract(7, 'days').toDate(); + statsPms.push( + LivechatRooms.getMACStatisticsBetweenDates(weekAgo, today).then(([result]) => { + statistics.uniqueContactsOfLastWeek = result || defaultValue; + }), + ); + + const yesterday = moment.utc().subtract(1, 'days').toDate(); + statsPms.push( + LivechatRooms.getMACStatisticsBetweenDates(yesterday, today).then(([result]) => { + statistics.uniqueContactsOfYesterday = result || defaultValue; + }), + ); + // Message statistics statistics.totalChannelMessages = (await Rooms.findByType('c', { projection: { msgs: 1 } }).toArray()).reduce( function _countChannelMessages(num: number, room: IRoom) { diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx index ebb92b040c83..8bb1fec36dff 100644 --- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx @@ -170,6 +170,10 @@ export default { uniqueOSOfYesterday: { data: [], day: 0, month: 0, year: 0 }, uniqueOSOfLastWeek: { data: [], day: 0, month: 0, year: 0 }, uniqueOSOfLastMonth: { data: [], day: 0, month: 0, year: 0 }, + omnichannelContactsBySource: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfLastMonth: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfLastWeek: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfYesterday: { contactsCount: 0, conversationsCount: 0, sources: [] }, apps: { engineVersion: 'x.y.z', enabled: false, diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx index 222f31f88334..ce080728afe5 100644 --- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx +++ b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx @@ -200,6 +200,10 @@ export default { uniqueOSOfYesterday: { data: [], day: 0, month: 0, year: 0 }, uniqueOSOfLastWeek: { data: [], day: 0, month: 0, year: 0 }, uniqueOSOfLastMonth: { data: [], day: 0, month: 0, year: 0 }, + omnichannelContactsBySource: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfLastMonth: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfLastWeek: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfYesterday: { contactsCount: 0, conversationsCount: 0, sources: [] }, apps: { engineVersion: 'x.y.z', enabled: false, diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx index 14a6cac8633d..1084191d5e91 100644 --- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx +++ b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx @@ -148,6 +148,10 @@ export default { uniqueOSOfYesterday: { data: [], day: 0, month: 0, year: 0 }, uniqueOSOfLastWeek: { data: [], day: 0, month: 0, year: 0 }, uniqueOSOfLastMonth: { data: [], day: 0, month: 0, year: 0 }, + omnichannelContactsBySource: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfLastMonth: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfLastWeek: { contactsCount: 0, conversationsCount: 0, sources: [] }, + uniqueContactsOfYesterday: { contactsCount: 0, conversationsCount: 0, sources: [] }, apps: { engineVersion: 'x.y.z', enabled: false, diff --git a/apps/meteor/server/models/raw/LivechatRooms.ts b/apps/meteor/server/models/raw/LivechatRooms.ts index 8db8692e4ccd..bf44a51b7f64 100644 --- a/apps/meteor/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/server/models/raw/LivechatRooms.ts @@ -8,6 +8,7 @@ import type { ILivechatPriority, IOmnichannelServiceLevelAgreements, ReportResult, + MACStats, } from '@rocket.chat/core-typings'; import { UserStatus } from '@rocket.chat/core-typings'; import type { ILivechatRoomsModel } from '@rocket.chat/model-typings'; @@ -74,6 +75,7 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive { key: { departmentId: 1, ts: 1 }, partialFilterExpression: { departmentId: { $exists: true }, t: 'l' } }, { key: { 'tags.0': 1, 'ts': 1 }, partialFilterExpression: { 'tags.0': { $exists: true }, 't': 'l' } }, { key: { servedBy: 1, ts: 1 }, partialFilterExpression: { servedBy: { $exists: true }, t: 'l' } }, + { key: { 'v.activity': 1, 'ts': 1 }, partialFilterExpression: { 'v.activity': { $exists: true }, 't': 'l' } }, ]; } @@ -2462,6 +2464,126 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive return this.updateOne(query, update); } + async getMACStatisticsForPeriod(period: string): Promise { + return this.col + .aggregate([ + { + $match: { + 't': 'l', + 'v.activity': period, + }, + }, + { + $group: { + _id: { + source: { + $ifNull: ['$source.alias', '$source.type'], + }, + }, + contactsCount: { + $addToSet: '$v._id', + }, + conversationsCount: { + $sum: 1, + }, + }, + }, + { + $group: { + _id: null, + sources: { + $push: { + source: '$_id.source', + contactsCount: { + $size: '$contactsCount', + }, + conversationsCount: '$conversationsCount', + }, + }, + totalContactsCount: { + $sum: { + $size: '$contactsCount', + }, + }, + totalConversationsCount: { + $sum: '$conversationsCount', + }, + }, + }, + { + $project: { + _id: 0, + contactsCount: '$totalContactsCount', + conversationsCount: '$totalConversationsCount', + sources: 1, + }, + }, + ]) + .toArray(); + } + + async getMACStatisticsBetweenDates(start: Date, end: Date): Promise { + return this.col + .aggregate([ + { + $match: { + 't': 'l', + 'v.activity': { $exists: true }, + 'ts': { + $gte: start, + $lt: end, + }, + }, + }, + { + $group: { + _id: { + source: { + $ifNull: ['$source.alias', '$source.type'], + }, + }, + contactsCount: { + $addToSet: '$v._id', + }, + conversationsCount: { + $sum: 1, + }, + }, + }, + { + $group: { + _id: null, + sources: { + $push: { + source: '$_id.source', + contactsCount: { + $size: '$contactsCount', + }, + conversationsCount: '$conversationsCount', + }, + }, + totalContactsCount: { + $sum: { + $size: '$contactsCount', + }, + }, + totalConversationsCount: { + $sum: '$conversationsCount', + }, + }, + }, + { + $project: { + _id: 0, + contactsCount: '$totalContactsCount', + conversationsCount: '$totalConversationsCount', + sources: 1, + }, + }, + ]) + .toArray(); + } + async unsetAllPredictedVisitorAbandonment(): Promise { throw new Error('Method not implemented.'); } diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index 670c0c8722ad..7b478bab43d6 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -439,6 +439,12 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL }, ); } + + countVisitorsOnPeriod(period: string): Promise { + return this.countDocuments({ + activity: period, + }); + } } type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index 2ea8115a727c..0be2841f5191 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -3,6 +3,7 @@ import type { CpuInfo } from 'os'; import type { DeviceSessionAggregationResult, OSSessionAggregationResult, UserSessionAggregationResult } from './ISession'; import type { ISettingStatisticsObject } from './ISetting'; import type { ITeamStats } from './ITeam'; +import type { MACStats } from './omnichannel'; export interface IStats { _id: string; @@ -85,6 +86,10 @@ export interface IStats { mongoStorageEngine: string; pushQueue: number; omnichannelSources: { [key: string]: number | string }[]; + omnichannelContactsBySource: MACStats; + uniqueContactsOfLastMonth: MACStats; + uniqueContactsOfLastWeek: MACStats; + uniqueContactsOfYesterday: MACStats; departments: number; archivedDepartments: number; routingAlgorithm: string; diff --git a/packages/core-typings/src/omnichannel/index.ts b/packages/core-typings/src/omnichannel/index.ts index 703cf3b4ca77..c6235175dafc 100644 --- a/packages/core-typings/src/omnichannel/index.ts +++ b/packages/core-typings/src/omnichannel/index.ts @@ -2,3 +2,4 @@ export * from './sms'; export * from './routing'; export * from './queue'; export * from './reports'; +export * from './mac'; diff --git a/packages/core-typings/src/omnichannel/mac.ts b/packages/core-typings/src/omnichannel/mac.ts new file mode 100644 index 000000000000..8591edbb0287 --- /dev/null +++ b/packages/core-typings/src/omnichannel/mac.ts @@ -0,0 +1,5 @@ +export type MACStats = { + contactsCount: number; + conversationsCount: number; + sources: { source: string; contactsCount: number; conversationsCount: number }[]; +}; diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts index 7d3ff9d3c564..20100cbb4f61 100644 --- a/packages/model-typings/src/models/ILivechatRoomsModel.ts +++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts @@ -1,4 +1,11 @@ -import type { IMessage, IOmnichannelRoom, IOmnichannelRoomClosingInfo, ISetting, ILivechatVisitor } from '@rocket.chat/core-typings'; +import type { + IMessage, + IOmnichannelRoom, + IOmnichannelRoomClosingInfo, + ISetting, + ILivechatVisitor, + MACStats, +} from '@rocket.chat/core-typings'; import type { FindCursor, UpdateResult, AggregationCursor, Document, FindOptions, DeleteResult, Filter } from 'mongodb'; import type { FindPaginated } from '..'; @@ -235,4 +242,6 @@ export interface ILivechatRoomsModel extends IBaseModel { changeVisitorByRoomId(roomId: string, visitor: { _id: string; username: string; token: string }): Promise; unarchiveOneById(roomId: string): Promise; markVisitorActiveForPeriod(rid: string, period: string): Promise; + getMACStatisticsForPeriod(period: string): Promise; + getMACStatisticsBetweenDates(start: Date, end: Date): Promise; } diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index dadef2a7c1f3..5c598c6a6a97 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -58,4 +58,6 @@ export interface ILivechatVisitorsModel extends IBaseModel { disableById(_id: string): Promise; findEnabled(query: Filter, options?: FindOptions): FindCursor; + + countVisitorsOnPeriod(period: string): Promise; }