Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .changeset/brown-comics-cheat.md
Original file line number Diff line number Diff line change
@@ -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`
5 changes: 5 additions & 0 deletions .changeset/khaki-feet-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

feat: Save visitor's activity on agent's interaction
7 changes: 7 additions & 0 deletions .changeset/warm-melons-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/core-typings": patch
"@rocket.chat/omnichannel-services": patch
---

feat: Disable and annonimize visitors instead of removing
2 changes: 1 addition & 1 deletion apps/meteor/app/apps/server/bridges/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export class AppLivechatBridge extends LivechatBridge {
}

return Promise.all(
(await LivechatVisitors.find(query).toArray()).map(
(await LivechatVisitors.findEnabled(query).toArray()).map(
async (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor),
),
);
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/apps/server/converters/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class AppRoomsConverter {

let v;
if (room.visitor) {
const visitor = await LivechatVisitors.findOneById(room.visitor.id);
const visitor = await LivechatVisitors.findOneEnabledById(room.visitor.id);

const { lastMessageTs, phone } = room.visitorChannelInfo;

Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/apps/server/converters/visitors.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class AppVisitorsConverter {
}

async convertById(id) {
const visitor = await LivechatVisitors.findOneById(id);
const visitor = await LivechatVisitors.findOneEnabledById(id);

return this.convertVisitor(visitor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ export type WorkspaceRegistrationData<T> = {
setupComplete: boolean;
connectionDisable: boolean;
npsEnabled: string;
// TODO: Evaluate naming
MAC: number;
// activeContactsBillingMonth: number;
// activeContactsYesterday: number;
};

export async function buildWorkspaceRegistrationData<T extends string | undefined>(contactEmail: T): Promise<WorkspaceRegistrationData<T>> {
Expand Down Expand Up @@ -80,7 +83,8 @@ export async function buildWorkspaceRegistrationData<T extends string | undefine
setupComplete: setupWizardState === 'completed',
connectionDisable: !registerServer,
npsEnabled,
// TODO: add MAC count
MAC: 0,
MAC: stats.omnichannelContactsBySource.contactsCount,
// activeContactsBillingMonth: stats.omnichannelContactsBySource.contactsCount,
// activeContactsYesterday: stats.uniqueContactsOfYesterday.contactsCount,
};
}
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/imports/server/rest/sms.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const defineVisitor = async (smsNumber, targetDepartment) => {
}

const id = await LivechatTyped.registerGuest(data);
return LivechatVisitors.findOneById(id);
return LivechatVisitors.findOneEnabledById(id);
};

const normalizeLocationSharing = (payload) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/api/lib/visitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { callbacks } from '../../../../../lib/callbacks';
import { canAccessRoomAsync } from '../../../../authorization/server/functions/canAccessRoom';

export async function findVisitorInfo({ visitorId }: { visitorId: IVisitor['_id'] }) {
const visitor = await LivechatVisitors.findOneById(visitorId);
const visitor = await LivechatVisitors.findOneEnabledById(visitorId);
if (!visitor) {
throw new Error('visitor-not-found');
}
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/api/v1/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ API.v1.addRoute(
contactId: String,
});

const contact = await LivechatVisitors.findOneById(this.queryParams.contactId);
const contact = await LivechatVisitors.findOneEnabledById(this.queryParams.contactId);

return API.v1.success({ contact });
},
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/api/v1/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ API.v1.addRoute(
guest.connectionData = normalizeHttpHeaderData(this.request.headers);

const visitorId = await LivechatTyped.registerGuest(guest);
visitor = await LivechatVisitors.findOneById(visitorId);
visitor = await LivechatVisitors.findOneEnabledById(visitorId);
}

const sentMessages = await Promise.all(
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/api/v1/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ API.v1.addRoute(
throw new Error('This_conversation_is_already_closed');
}

const guest = await LivechatVisitors.findOneById(room.v?._id);
const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);
const transferedBy = this.user satisfies TransferByData;
transferData.transferredBy = normalizeTransferredByData(transferedBy, room);
if (transferData.userId) {
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/app/livechat/server/api/v1/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ API.v1.addRoute('livechat/visitor', {

const visitorId = await LivechatTyped.registerGuest(guest);

let visitor = await VisitorsRaw.findOneById(visitorId, {});
let visitor = await VisitorsRaw.findOneEnabledById(visitorId, {});
if (visitor) {
const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {});
// If it's updating an existing visitor, it must also update the roomInfo
Expand All @@ -65,7 +65,7 @@ API.v1.addRoute('livechat/visitor', {
}
}

visitor = await VisitorsRaw.findOneById(visitorId, {});
visitor = await VisitorsRaw.findOneEnabledById(visitorId, {});
}

if (!visitor) {
Expand Down Expand Up @@ -122,7 +122,7 @@ API.v1.addRoute('livechat/visitor/:token', {

const { _id } = visitor;
const result = await Livechat.removeGuest(_id);
if (!result) {
if (!result.modifiedCount) {
throw new Meteor.Error('error-removing-visitor', 'An error ocurred while deleting visitor');
}

Expand Down
16 changes: 15 additions & 1 deletion apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoom, isEditedMessage } from '@rocket.chat/core-typings';
import { LivechatRooms } from '@rocket.chat/models';
import { LivechatRooms, LivechatVisitors } from '@rocket.chat/models';
import moment from 'moment';

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

Expand All @@ -26,6 +27,19 @@ callbacks.add(
return message;
}

// Return YYYY-MM from moment
const monthYear = moment().format('YYYY-MM');
const isVisitorActive = await LivechatVisitors.isVisitorActiveOnPeriod(room.v._id, monthYear);
if (!isVisitorActive) {
await LivechatVisitors.markVisitorActiveForPeriod(room.v._id, monthYear);
}

await LivechatRooms.markVisitorActiveForPeriod(room._id, monthYear);

if (room.responseBy) {
await LivechatRooms.setAgentLastMessageTs(room._id);
}

// check if room is yet awaiting for response from visitor
if (!room.waitingResponse) {
// case where agent sends second message or any subsequent message in a room before visitor responds to the first message
Expand Down
26 changes: 11 additions & 15 deletions apps/meteor/app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export const Livechat = {
async forwardOpenChats(userId) {
Livechat.logger.debug(`Transferring open chats for user ${userId}`);
for await (const room of LivechatRooms.findOpenByAgent(userId)) {
const guest = await LivechatVisitors.findOneById(room.v._id);
const guest = await LivechatVisitors.findOneEnabledById(room.v._id);
const user = await Users.findOneById(userId);
const { _id, username, name } = user;
const transferredBy = normalizeTransferredByData({ _id, username, name }, room);
Expand Down Expand Up @@ -462,7 +462,7 @@ export const Livechat = {
},

async getLivechatRoomGuestInfo(room) {
const visitor = await LivechatVisitors.findOneById(room.v._id);
const visitor = await LivechatVisitors.findOneEnabledById(room.v._id);
const agent = await Users.findOneById(room.servedBy && room.servedBy._id);

const ua = new UAParser();
Expand Down Expand Up @@ -604,16 +604,15 @@ export const Livechat = {
},

async removeGuest(_id) {
check(_id, String);
const guest = await LivechatVisitors.findOneById(_id, { projection: { _id: 1 } });
const guest = await LivechatVisitors.findOneEnabledById(_id, { projection: { _id: 1, token: 1 } });
if (!guest) {
throw new Meteor.Error('error-invalid-guest', 'Invalid guest', {
method: 'livechat:removeGuest',
});
}

await this.cleanGuestHistory(_id);
return LivechatVisitors.removeById(_id);
await this.cleanGuestHistory(guest);
return LivechatVisitors.disableById(_id);
},

async setUserStatusLivechat(userId, status) {
Expand All @@ -628,16 +627,13 @@ export const Livechat = {
return user;
},

async cleanGuestHistory(_id) {
const guest = await LivechatVisitors.findOneById(_id);
if (!guest) {
throw new Meteor.Error('error-invalid-guest', 'Invalid guest', {
method: 'livechat:cleanGuestHistory',
});
}

async cleanGuestHistory(guest) {
const { token } = guest;
check(token, String);

// This shouldn't be possible, but just in case
if (!token) {
throw new Error('error-invalid-guest');
}

const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {});
const cursor = LivechatRooms.findByVisitorToken(token, extraQuery);
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/lib/LivechatTyped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ class LivechatClass {
!(await LivechatDepartment.findOneById<Pick<ILivechatDepartment, '_id'>>(guest.department, { projection: { _id: 1 } }))
) {
await LivechatVisitors.removeDepartmentById(guest._id);
const tmpGuest = await LivechatVisitors.findOneById(guest._id);
const tmpGuest = await LivechatVisitors.findOneEnabledById(guest._id);
if (tmpGuest) {
guest = tmpGuest;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/methods/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Meteor.methods<ServerMethods>({
});
}

const guest = await LivechatVisitors.findOneById(room.v?._id);
const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);

const user = await Meteor.userAsync();

Expand Down
32 changes: 32 additions & 0 deletions apps/meteor/app/statistics/server/lib/statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import {
LivechatCustomField,
Subscriptions,
Users,
LivechatRooms,
} from '@rocket.chat/models';
import { MongoInternals } from 'meteor/mongo';
import moment from 'moment';

import { getStatistics as getEnterpriseStatistics } from '../../../../ee/app/license/server/getStatistics';
import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred';
Expand Down Expand Up @@ -269,6 +271,36 @@ export const statistics = {
}),
);

const defaultValue = { contactsCount: 0, conversationsCount: 0, sources: [] };
const billablePeriod = moment.utc().format('YYYY-MM');
statsPms.push(
LivechatRooms.getMACStatisticsForPeriod(billablePeriod).then(([result]) => {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/client/views/admin/info/UsageCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const handleBeforeSaveMessage = async (message: IMessage, room?: IOmnichannelRoo
}
const visitorId = room?.v?._id;
const agent = (await Users.findOneById(agentId, { projection: { name: 1, _id: 1, emails: 1 } })) || {};
const visitor = visitorId && ((await LivechatVisitors.findOneById(visitorId, {})) || {});
const visitor = visitorId && ((await LivechatVisitors.findOneEnabledById(visitorId, {})) || {});

Object.keys(placeholderFields).map((field) => {
const templateKey = `{{${field}}}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ callbacks.add(
}

const { _id: guestId } = defaultGuest;
const guest = await LivechatVisitors.findOneById(guestId, {
const guest = await LivechatVisitors.findOneEnabledById(guestId, {
projection: { lastAgent: 1, token: 1, contactManager: 1 },
});
if (!guest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const resumeOnHoldCommentAndUser = async (room: IOmnichannelRoom): Promise<{ com
v: { _id: visitorId },
_id: rid,
} = room;
const visitor = await LivechatVisitors.findOneById<Pick<ILivechatVisitor, 'name' | 'username'>>(visitorId, {
const visitor = await LivechatVisitors.findOneEnabledById<Pick<ILivechatVisitor, 'name' | 'username'>>(visitorId, {
projection: { name: 1, username: 1 },
});
if (!visitor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export class VisitorInactivityMonitor {
}

private async getDefaultAbandonedCustomMessage(abandonmentAction: 'close' | 'on-hold', visitorId: string) {
const visitor = await LivechatVisitors.findOneById<Pick<ILivechatVisitor, 'name' | 'username'>>(visitorId, {
const visitor = await LivechatVisitors.findOneEnabledById<Pick<ILivechatVisitor, 'name' | 'username'>>(visitorId, {
projection: {
name: 1,
username: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async function resolveOnHoldCommentInfo(options: { clientAction: boolean }, room
const {
v: { _id: visitorId },
} = room;
const visitor = await LivechatVisitors.findOneById<Pick<ILivechatVisitor, 'name' | 'username'>>(visitorId, {
const visitor = await LivechatVisitors.findOneEnabledById<Pick<ILivechatVisitor, 'name' | 'username'>>(visitorId, {
projection: { name: 1, username: 1 },
});
if (!visitor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr
return guest;
}
await LivechatTyped.setDepartmentForGuest({ token: guest.token, department });
return LivechatVisitors.findOneById(guest._id, {});
return LivechatVisitors.findOneEnabledById(guest._id, {});
}
return guest;
}
Expand All @@ -47,7 +47,9 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr
department,
});

const newGuest = await LivechatVisitors.findOneById(userId);
const newGuest = await LivechatVisitors.findOneEnabledById(userId);
logger.debug(`Guest ${userId} for visitor ${email} created`);

if (newGuest) {
return newGuest;
}
Expand Down
Loading