Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
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);
Comment thread
KevLehman marked this conversation as resolved.
}

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
14 changes: 14 additions & 0 deletions apps/meteor/server/models/raw/LivechatRooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2448,6 +2448,20 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive
return this.updateOne(query, update);
}

markVisitorActiveForPeriod(rid: string, period: string): Promise<UpdateResult> {
const query = {
_id: rid,
};

const update = {
$addToSet: {
'v.activity': period,
},
};

return this.updateOne(query, update);
}

async unsetAllPredictedVisitorAbandonment(): Promise<void> {
throw new Error('Method not implemented.');
}
Expand Down
27 changes: 27 additions & 0 deletions apps/meteor/server/models/raw/LivechatVisitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class LivechatVisitorsRaw extends BaseRaw<ILivechatVisitor> implements IL
{ key: { username: 1 } },
{ key: { 'contactMananger.username': 1 }, sparse: true },
{ key: { 'livechatData.$**': 1 } },
{ key: { activity: 1 }, partialFilterExpression: { activity: { $exists: true } } },
];
}

Expand Down Expand Up @@ -365,6 +366,32 @@ export class LivechatVisitorsRaw extends BaseRaw<ILivechatVisitor> implements IL
},
);
}

isVisitorActiveOnPeriod(visitorId: string, period: string): Promise<boolean> {
const query = {
_id: visitorId,
activity: period,
};

return this.findOne(query, { projection: { _id: 1 } }).then(Boolean);
}

markVisitorActiveForPeriod(visitorId: string, period: string): Promise<UpdateResult> {
const query = {
_id: visitorId,
};

const update = {
$push: {
activity: {
$each: [period],
$slice: -12,
Comment thread
MarcosSpessatto marked this conversation as resolved.
},
},
};

return this.updateOne(query, update);
}
}

type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };
24 changes: 23 additions & 1 deletion apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { faker } from '@faker-js/faker';
import type { ILivechatVisitor } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import moment from 'moment';
import { type Response } from 'supertest';

import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
Expand Down Expand Up @@ -334,6 +335,27 @@ describe('LIVECHAT - visitors', function () {
});
});

it('should return visitor activity field when visitor was active on month', async () => {
// Activity is determined by a conversation in which an agent has engaged (sent a message)
// For a visitor to be considered active, they must have had a conversation in the last 30 days
const period = moment().format('YYYY-MM');
const { visitor, room } = await startANewLivechatRoomAndTakeIt();
// agent should send a message on the room
await request
.post(api('chat.sendMessage'))
.set(credentials)
.send({
message: {
rid: room._id,
msg: 'test',
},
});

const activeVisitor = await getLivechatVisitorByToken(visitor.token);
expect(activeVisitor).to.have.property('activity');
expect(activeVisitor.activity).to.include(period);
});

it("should return a 'error-removing-visitor' error when removeGuest's result is false", async () => {
await request
.delete(api('livechat/visitor/123'))
Expand Down
1 change: 1 addition & 0 deletions packages/core-typings/src/ILivechatVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface ILivechatVisitor extends IRocketChatRecord {
contactManager?: {
username: string;
};
activity?: string[];
}

export interface ILivechatVisitorDTO {
Expand Down
6 changes: 5 additions & 1 deletion packages/core-typings/src/IRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ export enum OmnichannelSourceType {

export interface IOmnichannelGenericRoom extends Omit<IRoom, 'default' | 'featured' | 'broadcast' | ''> {
t: 'l' | 'v';
v: Pick<ILivechatVisitor, '_id' | 'username' | 'status' | 'name' | 'token'> & { lastMessageTs?: Date; phone?: string };
v: Pick<ILivechatVisitor, '_id' | 'username' | 'status' | 'name' | 'token'> & {
lastMessageTs?: Date;
phone?: string;
activity?: string[];
};
email?: {
// Data used when the room is created from an email, via email Integration.
inbox: string;
Expand Down
1 change: 1 addition & 0 deletions packages/model-typings/src/models/ILivechatRoomsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,5 @@ export interface ILivechatRoomsModel extends IBaseModel<IOmnichannelRoom> {
setVisitorInactivityInSecondsById(roomId: string, visitorInactivity: any): Promise<UpdateResult>;
changeVisitorByRoomId(roomId: string, visitor: { _id: string; username: string; token: string }): Promise<UpdateResult>;
unarchiveOneById(roomId: string): Promise<UpdateResult>;
markVisitorActiveForPeriod(rid: string, period: string): Promise<UpdateResult>;
}
2 changes: 2 additions & 0 deletions packages/model-typings/src/models/ILivechatVisitorsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ export interface ILivechatVisitorsModel extends IBaseModel<ILivechatVisitor> {
updateById(_id: string, update: UpdateFilter<ILivechatVisitor>): Promise<Document | UpdateResult>;

saveGuestEmailPhoneById(_id: string, emails: string[], phones: string[]): Promise<UpdateResult | Document | void>;
isVisitorActiveOnPeriod(visitorId: string, period: string): Promise<boolean>;
markVisitorActiveForPeriod(visitorId: string, period: string): Promise<UpdateResult>;
}