Skip to content

Commit b4ce579

Browse files
fix: New livechat conversations are not assigned to contact manager (#34210)
1 parent 53c81bb commit b4ce579

File tree

7 files changed

+118
-30
lines changed

7 files changed

+118
-30
lines changed

.changeset/popular-cameras-grin.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
"@rocket.chat/model-typings": patch
4+
---
5+
6+
Fixes livechat conversations not being assigned to the contact manager even when the "Assign new conversations to the contact manager" setting is enabled

apps/meteor/app/livechat/server/lib/LivechatTyped.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,12 @@ class LivechatClass {
357357
throw new Error('error-contact-channel-blocked');
358358
}
359359

360-
const defaultAgent = await callbacks.run('livechat.checkDefaultAgentOnNewRoom', agent, visitor);
360+
const defaultAgent =
361+
agent ??
362+
(await callbacks.run('livechat.checkDefaultAgentOnNewRoom', agent, {
363+
visitorId: visitor._id,
364+
source: roomInfo.source,
365+
}));
361366
// if no department selected verify if there is at least one active and pick the first
362367
if (!defaultAgent && !visitor.department) {
363368
const department = await getRequiredDepartment();

apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
import type { IUser, SelectedAgent } from '@rocket.chat/core-typings';
2-
import { LivechatVisitors, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models';
2+
import { LivechatVisitors, LivechatContacts, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models';
33

44
import { notifyOnLivechatInquiryChanged } from '../../../../../app/lib/server/lib/notifyListener';
55
import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager';
6+
import { migrateVisitorIfMissingContact } from '../../../../../app/livechat/server/lib/contacts/migrateVisitorIfMissingContact';
67
import { settings } from '../../../../../app/settings/server';
78
import { callbacks } from '../../../../../lib/callbacks';
89

910
let contactManagerPreferred = false;
1011
let lastChattedAgentPreferred = false;
1112

12-
const normalizeDefaultAgent = (agent?: Pick<IUser, '_id' | 'username'> | null): SelectedAgent | null => {
13+
const normalizeDefaultAgent = (agent?: Pick<IUser, '_id' | 'username'> | null): SelectedAgent | undefined => {
1314
if (!agent) {
14-
return null;
15+
return undefined;
1516
}
1617

1718
const { _id: agentId, username } = agent;
1819
return { agentId, username };
1920
};
2021

21-
const getDefaultAgent = async (username?: string): Promise<SelectedAgent | null> => {
22-
if (!username) {
23-
return null;
22+
const getDefaultAgent = async ({ username, id }: { username?: string; id?: string }): Promise<SelectedAgent | undefined> => {
23+
if (!username && !id) {
24+
return undefined;
2425
}
2526

26-
return normalizeDefaultAgent(await Users.findOneOnlineAgentByUserList(username, { projection: { _id: 1, username: 1 } }));
27+
if (id) {
28+
return normalizeDefaultAgent(await Users.findOneOnlineAgentById(id, undefined, { projection: { _id: 1, username: 1 } }));
29+
}
30+
return normalizeDefaultAgent(await Users.findOneOnlineAgentByUserList(username || [], { projection: { _id: 1, username: 1 } }));
2731
};
2832

2933
settings.watch<boolean>('Livechat_last_chatted_agent_routing', (value) => {
@@ -88,30 +92,32 @@ settings.watch<boolean>('Omnichannel_contact_manager_routing', (value) => {
8892

8993
callbacks.add(
9094
'livechat.checkDefaultAgentOnNewRoom',
91-
async (defaultAgent, defaultGuest) => {
92-
if (defaultAgent || !defaultGuest) {
95+
async (defaultAgent, { visitorId, source } = {}) => {
96+
if (defaultAgent || !visitorId || !source) {
9397
return defaultAgent;
9498
}
9599

96-
const { _id: guestId } = defaultGuest;
97-
const guest = await LivechatVisitors.findOneEnabledById(guestId, {
100+
const guest = await LivechatVisitors.findOneEnabledById(visitorId, {
98101
projection: { lastAgent: 1, token: 1, contactManager: 1 },
99102
});
100103
if (!guest) {
101-
return defaultAgent;
104+
return undefined;
102105
}
103106

104-
const { lastAgent, token, contactManager } = guest;
105-
const guestManager = contactManager?.username && contactManagerPreferred && getDefaultAgent(contactManager?.username);
107+
const contactId = await migrateVisitorIfMissingContact(visitorId, source);
108+
const contact = contactId ? await LivechatContacts.findOneById(contactId, { projection: { contactManager: 1 } }) : undefined;
109+
110+
const guestManager = contactManagerPreferred && (await getDefaultAgent({ id: contact?.contactManager }));
106111
if (guestManager) {
107112
return guestManager;
108113
}
109114

110115
if (!lastChattedAgentPreferred) {
111-
return defaultAgent;
116+
return undefined;
112117
}
113118

114-
const guestAgent = lastAgent?.username && getDefaultAgent(lastAgent?.username);
119+
const { lastAgent, token } = guest;
120+
const guestAgent = await getDefaultAgent({ username: lastAgent?.username });
115121
if (guestAgent) {
116122
return guestAgent;
117123
}
@@ -120,19 +126,19 @@ callbacks.add(
120126
projection: { servedBy: 1 },
121127
});
122128
if (!room?.servedBy) {
123-
return defaultAgent;
129+
return undefined;
124130
}
125131

126132
const {
127133
servedBy: { username: usernameByRoom },
128134
} = room;
129135
if (!usernameByRoom) {
130-
return defaultAgent;
136+
return undefined;
131137
}
132138
const lastRoomAgent = normalizeDefaultAgent(
133139
await Users.findOneOnlineAgentByUserList(usernameByRoom, { projection: { _id: 1, username: 1 } }),
134140
);
135-
return lastRoomAgent ?? defaultAgent;
141+
return lastRoomAgent;
136142
},
137143
callbacks.priority.MEDIUM,
138144
'livechat-check-default-agent-new-room',

apps/meteor/lib/callbacks.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
IOmnichannelRoomInfo,
2525
IOmnichannelInquiryExtraData,
2626
IOmnichannelRoomExtraData,
27+
IOmnichannelSource,
2728
} from '@rocket.chat/core-typings';
2829
import type { Updater } from '@rocket.chat/models';
2930
import type { FilterOperators } from 'mongodb';
@@ -118,7 +119,10 @@ type ChainedCallbackSignatures = {
118119
) => Promise<T>;
119120

120121
'livechat.beforeRouteChat': (inquiry: ILivechatInquiryRecord, agent?: { agentId: string; username: string }) => ILivechatInquiryRecord;
121-
'livechat.checkDefaultAgentOnNewRoom': (agent: SelectedAgent, visitor?: ILivechatVisitor) => SelectedAgent | null;
122+
'livechat.checkDefaultAgentOnNewRoom': (
123+
defaultAgent?: SelectedAgent,
124+
params?: { visitorId?: string; source?: IOmnichannelSource },
125+
) => SelectedAgent | undefined;
122126

123127
'livechat.onLoadForwardDepartmentRestrictions': (params: { departmentId: string }) => Record<string, unknown>;
124128

apps/meteor/server/models/raw/Users.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,11 +1657,11 @@ export class UsersRaw extends BaseRaw {
16571657
return this.findOne(query);
16581658
}
16591659

1660-
findOneOnlineAgentById(_id, isLivechatEnabledWhenAgentIdle) {
1660+
findOneOnlineAgentById(_id, isLivechatEnabledWhenAgentIdle, options) {
16611661
// TODO: Create class Agent
16621662
const query = queryStatusAgentOnline({ _id }, isLivechatEnabledWhenAgentIdle);
16631663

1664-
return this.findOne(query);
1664+
return this.findOne(query, options);
16651665
}
16661666

16671667
findAgents() {

apps/meteor/tests/end-to-end/api/livechat/24-routing.ts

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { faker } from '@faker-js/faker';
12
import type { Credentials } from '@rocket.chat/api-client';
2-
import { UserStatus, type ILivechatDepartment, type IUser } from '@rocket.chat/core-typings';
3+
import { UserStatus } from '@rocket.chat/core-typings';
4+
import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings';
35
import { expect } from 'chai';
46
import { after, before, describe, it } from 'mocha';
57

6-
import { getCredentials, request, api } from '../../../data/api-data';
8+
import { getCredentials, request, api, credentials } from '../../../data/api-data';
79
import {
810
createAgent,
911
makeAgentAvailable,
@@ -33,7 +35,9 @@ import { IS_EE } from '../../../e2e/config/constants';
3335

3436
let testUser: { user: IUser; credentials: Credentials };
3537
let testUser2: { user: IUser; credentials: Credentials };
38+
let testUser3: { user: IUser; credentials: Credentials };
3639
let testDepartment: ILivechatDepartment;
40+
let visitorEmail: string;
3741

3842
before(async () => {
3943
const user = await createUser();
@@ -60,14 +64,43 @@ import { IS_EE } from '../../../e2e/config/constants';
6064
});
6165

6266
before(async () => {
63-
testDepartment = await createDepartment([{ agentId: testUser.user._id }]);
67+
const user = await createUser();
68+
await createAgent(user.username);
69+
const credentials3 = await login(user.username, password);
70+
await makeAgentAvailable(credentials3);
71+
72+
testUser3 = {
73+
user,
74+
credentials: credentials3,
75+
};
6476
});
6577

66-
after(async () => {
67-
await deleteUser(testUser.user);
68-
await deleteUser(testUser2.user);
78+
before(async () => {
79+
testDepartment = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser3.user._id }]);
80+
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
81+
82+
const visitorName = faker.person.fullName();
83+
visitorEmail = faker.internet.email().toLowerCase();
84+
await request
85+
.post(api('omnichannel/contacts'))
86+
.set(credentials)
87+
.send({
88+
name: visitorName,
89+
emails: [visitorEmail],
90+
phones: [],
91+
contactManager: testUser3.user._id,
92+
});
6993
});
7094

95+
after(async () =>
96+
Promise.all([
97+
deleteUser(testUser.user),
98+
deleteUser(testUser2.user),
99+
deleteUser(testUser3.user),
100+
updateSetting('Livechat_assign_new_conversation_to_bot', false),
101+
]),
102+
);
103+
71104
it('should route a room to an available agent', async () => {
72105
const visitor = await createVisitor(testDepartment._id);
73106
const room = await createLivechatRoom(visitor.token);
@@ -91,9 +124,24 @@ import { IS_EE } from '../../../e2e/config/constants';
91124
expect(roomInfo.servedBy).to.be.an('object');
92125
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
93126
});
127+
(IS_EE ? it : it.skip)(
128+
'should route to contact manager if it is online and Livechat_assign_new_conversation_to_bot is enabled',
129+
async () => {
130+
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
131+
const room = await createLivechatRoom(visitor.token);
132+
133+
await sleep(5000);
134+
135+
const roomInfo = await getLivechatRoomInfo(room._id);
136+
137+
expect(roomInfo.servedBy).to.be.an('object');
138+
expect(roomInfo.servedBy?._id).to.be.equal(testUser3.user._id);
139+
},
140+
);
94141
it('should fail to start a conversation if there is noone available and Livechat_accept_chats_with_no_agents is false', async () => {
95142
await updateSetting('Livechat_accept_chats_with_no_agents', false);
96143
await makeAgentUnavailable(testUser.credentials);
144+
await makeAgentUnavailable(testUser3.credentials);
97145

98146
const visitor = await createVisitor(testDepartment._id);
99147
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
@@ -147,6 +195,21 @@ import { IS_EE } from '../../../e2e/config/constants';
147195
const roomInfo = await getLivechatRoomInfo(room._id);
148196
expect(roomInfo.servedBy).to.be.undefined;
149197
});
198+
(IS_EE ? it : it.skip)(
199+
'should route to another available agent if contact manager is unavailable and Livechat_assign_new_conversation_to_bot is enabled',
200+
async () => {
201+
await makeAgentAvailable(testUser.credentials);
202+
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
203+
const room = await createLivechatRoom(visitor.token);
204+
205+
await sleep(5000);
206+
207+
const roomInfo = await getLivechatRoomInfo(room._id);
208+
209+
expect(roomInfo.servedBy).to.be.an('object');
210+
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
211+
},
212+
);
150213
});
151214
describe('Load Balancing', () => {
152215
before(async () => {

packages/model-typings/src/models/IUsersModel.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,11 @@ export interface IUsersModel extends IBaseModel<IUser> {
263263
findOnlineAgents(agentId?: string): FindCursor<ILivechatAgent>;
264264
countOnlineAgents(agentId: string): Promise<number>;
265265
findOneBotAgent(): Promise<ILivechatAgent | null>;
266-
findOneOnlineAgentById(agentId: string, isLivechatEnabledWhenAgentIdle?: boolean): Promise<ILivechatAgent | null>;
266+
findOneOnlineAgentById(
267+
agentId: string,
268+
isLivechatEnabledWhenAgentIdle?: boolean,
269+
options?: FindOptions<IUser>,
270+
): Promise<ILivechatAgent | null>;
267271
findAgents(): FindCursor<ILivechatAgent>;
268272
countAgents(): Promise<number>;
269273
getNextAgent(ignoreAgentId?: string, extraQuery?: Filter<IUser>): Promise<{ agentId: string; username: string } | null>;

0 commit comments

Comments
 (0)