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
6 changes: 6 additions & 0 deletions .changeset/heavy-boats-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/models': patch
'@rocket.chat/meteor': patch
---

Fixes an issue when sending a message on an omnichannel room would cause the web client to fetch the room data again.
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/lib/RoutingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const RoutingManager: Routing = {
const { servedBy } = room;

if (shouldQueue) {
const queuedInquiry = await LivechatInquiry.queueInquiry(inquiry._id);
const queuedInquiry = await LivechatInquiry.queueInquiry(inquiry._id, room.lastMessage);
if (queuedInquiry) {
inquiry = queuedInquiry;
void notifyOnLivechatInquiryChanged(inquiry, 'updated', {
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/app/livechat/server/lib/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ export async function returnRoomAsInquiry(room: IOmnichannelRoom, departmentId?:
return false;
}

// update inquiry's last message with room's last message to correctly display in the queue
// because we stop updating the inquiry when it's been taken
if (room.lastMessage) {
await LivechatInquiry.setLastMessageById(inquiry._id, room.lastMessage);
}

const transferredBy = normalizeTransferredByData(user, room);
livechatLogger.debug(`Transfering room ${room._id} by user ${transferredBy._id}`);
const transferData = { roomId: room._id, scope: 'queue', departmentId, transferredBy, ...overrideTransferData };
Expand Down
62 changes: 62 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
makeAgentUnavailable,
sendAgentMessage,
fetchInquiry,
takeInquiry,
} from '../../../data/livechat/rooms';
import { saveTags } from '../../../data/livechat/tags';
import { createMonitor, createUnit, deleteUnit } from '../../../data/livechat/units';
Expand Down Expand Up @@ -1170,6 +1171,67 @@ describe('LIVECHAT - rooms', () => {
await deleteDepartment(forwardToOfflineDepartment._id);
});

(IS_EE ? it : it.skip)(
'should update inquiry last message when manager forward to offline department and the inquiry returns to queued',
async () => {
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
const { department: initialDepartment, agent } = await createDepartmentWithAnOnlineAgent();
const { department: forwardToOfflineDepartment, agent: offlineAgent } = await createDepartmentWithAnOfflineAgent({
allowReceiveForwardOffline: true,
});

const newVisitor = await createVisitor(initialDepartment._id);
const newRoom = await createLivechatRoom(newVisitor.token);

const inq = await fetchInquiry(newRoom._id);
await takeInquiry(inq._id, agent.credentials);

const msgText = `return to queue ${Date.now()}`;
await request.post(api('livechat/message')).send({ token: newVisitor.token, rid: newRoom._id, msg: msgText }).expect(200);

await makeAgentUnavailable(offlineAgent.credentials);

const manager = await createUser();
const managerCredentials = await login(manager.username, password);
await createManager(manager.username);

await request.post(api('livechat/room.forward')).set(managerCredentials).send({
roomId: newRoom._id,
departmentId: forwardToOfflineDepartment._id,
clientAction: true,
comment: 'test comment',
});

await request
.get(api(`livechat/queue`))
.set(credentials)
.query({
count: 1,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.queue).to.be.an('array');
expect(res.body.queue[0].chats).not.to.undefined;
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
});

const inquiry = await fetchInquiry(newRoom._id);

expect(inquiry).to.have.property('_id', inquiry._id);
expect(inquiry).to.have.property('rid', newRoom._id);
expect(inquiry).to.have.property('lastMessage');
expect(inquiry.lastMessage).to.have.property('msg', '');
expect(inquiry.lastMessage).to.have.property('t', 'livechat_transfer_history');

await deleteDepartment(initialDepartment._id);
await deleteDepartment(forwardToOfflineDepartment._id);
},
);

let roomId: string;
let visitorToken: string;
(IS_EE ? it : it.skip)('should return a success message when transferring to a fallback department', async () => {
Expand Down
85 changes: 85 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/05-inquiries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
createDepartment,
createLivechatRoom,
createVisitor,
deleteVisitor,
fetchInquiry,
getLivechatRoomInfo,
makeAgentAvailable,
Expand Down Expand Up @@ -456,4 +457,88 @@ describe('LIVECHAT - inquiries', () => {
expect(depInq.length).to.be.equal(1);
});
});

describe('keep inquiry last message updated', () => {
let room: any;
let visitor: any;
let agent: any;

before(async () => {
agent = await createAgent();
visitor = await createVisitor();

await makeAgentAvailable();
room = await createLivechatRoom(visitor.token);
});

after(async () => {
await deleteVisitor(visitor.token);
});

it('should update inquiry last message', async () => {
const msgText = `update inquiry ${Date.now()}`;

await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);

const inquiry = await fetchInquiry(room._id);

expect(inquiry).to.have.property('_id', inquiry._id);
expect(inquiry).to.have.property('rid', room._id);
expect(inquiry).to.have.property('lastMessage');
expect(inquiry.lastMessage).to.have.property('msg', msgText);
});

it('should update room last message after inquiry is taken', async () => {
const msgText = `update room ${Date.now()}`;

const inquiry = await fetchInquiry(room._id);

await request
.post(api('livechat/inquiries.take'))
.set(credentials)
.send({
inquiryId: inquiry._id,
userId: agent._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});

await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);

// check room
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo).to.have.property('lastMessage');
expect(roomInfo.lastMessage).to.have.property('msg', msgText);
});

it('should have the correct last message when room is returned to queue', async () => {
const msgText = `return to queue ${Date.now()}`;

await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);

await request
.post(methodCall('livechat:returnAsInquiry'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:returnAsInquiry',
params: [room._id],
id: 'id',
msg: 'method',
}),
})
.expect('Content-Type', 'application/json')
.expect(200);

const inquiry = await fetchInquiry(room._id);

expect(inquiry).to.have.property('_id', inquiry._id);
expect(inquiry).to.have.property('rid', room._id);
expect(inquiry).to.have.property('lastMessage');
expect(inquiry.lastMessage).to.have.property('msg', msgText);
});
});
});
3 changes: 2 additions & 1 deletion packages/model-typings/src/models/ILivechatInquiryModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface ILivechatInquiryModel extends IBaseModel<ILivechatInquiryRecord
getDistinctQueuedDepartments(options: AggregateOptions): Promise<{ _id: string | null }[]>;
setDepartmentByInquiryId(inquiryId: string, department: string): Promise<ILivechatInquiryRecord | null>;
setLastMessageByRoomId(rid: ILivechatInquiryRecord['rid'], message: IMessage): Promise<ILivechatInquiryRecord | null>;
setLastMessageById(inquiryId: string, lastMessage: IMessage): Promise<UpdateResult>;
findNextAndLock(
queueSortBy: FindOptions<ILivechatInquiryRecord>['sort'],
department: string | null,
Expand All @@ -31,7 +32,7 @@ export interface ILivechatInquiryModel extends IBaseModel<ILivechatInquiryRecord
getQueuedInquiries(options?: FindOptions<ILivechatInquiryRecord>): FindCursor<ILivechatInquiryRecord>;
takeInquiry(inquiryId: string): Promise<void>;
openInquiry(inquiryId: string): Promise<UpdateResult>;
queueInquiry(inquiryId: string): Promise<ILivechatInquiryRecord | null>;
queueInquiry(inquiryId: string, lastMessage?: IMessage): Promise<ILivechatInquiryRecord | null>;
queueInquiryAndRemoveDefaultAgent(inquiryId: string): Promise<UpdateResult>;
readyInquiry(inquiryId: string): Promise<UpdateResult>;
changeDepartmentIdByRoomId(rid: string, department: string): Promise<void>;
Expand Down
21 changes: 18 additions & 3 deletions packages/models/src/models/LivechatInquiry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,19 @@ export class LivechatInquiryRaw extends BaseRaw<ILivechatInquiryRecord> implemen
return this.findOneAndUpdate({ _id: inquiryId }, { $set: { department } }, { returnDocument: 'after' });
}

/**
* Updates the `lastMessage` of inquiries that are not taken yet, after they're taken we only need to update room's `lastMessage`
*/
async setLastMessageByRoomId(rid: ILivechatInquiryRecord['rid'], message: IMessage): Promise<ILivechatInquiryRecord | null> {
return this.findOneAndUpdate({ rid }, { $set: { lastMessage: message } }, { returnDocument: 'after' });
return this.findOneAndUpdate(
{ rid, status: { $ne: LivechatInquiryStatus.TAKEN } },
{ $set: { lastMessage: message } },
{ returnDocument: 'after' },
);
}

async setLastMessageById(inquiryId: string, lastMessage: IMessage): Promise<UpdateResult> {
return this.updateOne({ _id: inquiryId }, { $set: { lastMessage } });
}

async findNextAndLock(
Expand Down Expand Up @@ -316,13 +327,17 @@ export class LivechatInquiryRaw extends BaseRaw<ILivechatInquiryRecord> implemen
);
}

async queueInquiry(inquiryId: string): Promise<ILivechatInquiryRecord | null> {
async queueInquiry(inquiryId: string, lastMessage?: IMessage): Promise<ILivechatInquiryRecord | null> {
return this.findOneAndUpdate(
{
_id: inquiryId,
},
{
$set: { status: LivechatInquiryStatus.QUEUED, queuedAt: new Date() },
$set: {
status: LivechatInquiryStatus.QUEUED,
queuedAt: new Date(),
...(lastMessage && { lastMessage }),
},
$unset: { takenAt: 1 },
},
{ returnDocument: 'after' },
Expand Down
Loading