Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Enabled when agent idle setting not being respected by routing algorithms #35029

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class AutoSelection implements IRoutingMethod {
);
}

return Users.getNextAgent(ignoreAgentId, extraQuery);
return Users.getNextAgent(ignoreAgentId, extraQuery, settings.get<boolean>('Livechat_enabled_when_agent_idle'));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Users } from '@rocket.chat/models';

import { RoutingManager } from '../../../../../../app/livechat/server/lib/RoutingManager';
import { settings } from '../../../../../../app/settings/server';
import type { IRoutingManagerConfig } from '../../../../../../definition/IRoutingManagerConfig';

/* Load Balancing Queuing method:
Expand Down Expand Up @@ -28,7 +29,11 @@ class LoadBalancing {
}

async getNextAgent(department?: string, ignoreAgentId?: string) {
const nextAgent = await Users.getNextLeastBusyAgent(department, ignoreAgentId);
const nextAgent = await Users.getNextLeastBusyAgent(
department,
ignoreAgentId,
settings.get<boolean>('Livechat_enabled_when_agent_idle'),
);
if (!nextAgent) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { IOmnichannelCustomAgent } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';

import { RoutingManager } from '../../../../../../app/livechat/server/lib/RoutingManager';
import { settings } from '../../../../../../app/settings/server';
import type { IRoutingManagerConfig } from '../../../../../../definition/IRoutingManagerConfig';

/* Load Rotation Queuing method:
Expand All @@ -27,7 +28,11 @@ class LoadRotation {
}

public async getNextAgent(department?: string, ignoreAgentId?: string): Promise<IOmnichannelCustomAgent | undefined> {
const nextAgent = await Users.getLastAvailableAgentRouted(department, ignoreAgentId);
const nextAgent = await Users.getLastAvailableAgentRouted(
department,
ignoreAgentId,
settings.get<boolean>('Livechat_enabled_when_agent_idle'),
);
if (!nextAgent) {
return;
}
Expand Down
80 changes: 56 additions & 24 deletions apps/meteor/tests/end-to-end/api/livechat/24-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { password } from '../../../data/user';
import { createUser, deleteUser, login, setUserActiveStatus, setUserStatus } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';

(IS_EE ? describe : describe.skip)('Omnichannel - Routing', () => {
(IS_EE ? describe.only : describe.skip)('Omnichannel - Routing', () => {
KevLehman marked this conversation as resolved.
Show resolved Hide resolved
before((done) => getCredentials(done));

after(async () => {
Expand Down Expand Up @@ -98,6 +98,7 @@ import { IS_EE } from '../../../e2e/config/constants';
deleteUser(testUser2.user),
deleteUser(testUser3.user),
updateSetting('Livechat_assign_new_conversation_to_bot', false),
updateSetting('Livechat_enabled_when_agent_idle', true),
]),
);

Expand All @@ -124,20 +125,17 @@ import { IS_EE } from '../../../e2e/config/constants';
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
});
(IS_EE ? it : it.skip)(
'should route to contact manager if it is online and Livechat_assign_new_conversation_to_bot is enabled',
async () => {
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
const room = await createLivechatRoom(visitor.token);
it('should route to contact manager if it is online and Livechat_assign_new_conversation_to_bot is enabled', async () => {
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
const room = await createLivechatRoom(visitor.token);

await sleep(5000);
await sleep(5000);

const roomInfo = await getLivechatRoomInfo(room._id);
const roomInfo = await getLivechatRoomInfo(room._id);

expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser3.user._id);
},
);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser3.user._id);
});
it('should fail to start a conversation if there is noone available and Livechat_accept_chats_with_no_agents is false', async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', false);
await makeAgentUnavailable(testUser.credentials);
Expand Down Expand Up @@ -195,21 +193,28 @@ import { IS_EE } from '../../../e2e/config/constants';
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.undefined;
});
(IS_EE ? it : it.skip)(
'should route to another available agent if contact manager is unavailable and Livechat_assign_new_conversation_to_bot is enabled',
async () => {
await makeAgentAvailable(testUser.credentials);
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
const room = await createLivechatRoom(visitor.token);
it('should route to an idle user', async () => {
await updateSetting('Livechat_enabled_when_agent_idle', true);
await makeAgentAvailable(testUser.credentials);
await setUserStatus(testUser.credentials, UserStatus.AWAY);

await sleep(5000);
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);

const roomInfo = await getLivechatRoomInfo(room._id);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
});
it('should route to another available agent if contact manager is unavailable and Livechat_assign_new_conversation_to_bot is enabled', async () => {
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
const room = await createLivechatRoom(visitor.token);

expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
},
);
await sleep(5000);

const roomInfo = await getLivechatRoomInfo(room._id);

expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
});
});
describe('Load Balancing', () => {
before(async () => {
Expand Down Expand Up @@ -299,6 +304,19 @@ import { IS_EE } from '../../../e2e/config/constants';
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
});
it('should route to agents even if theyre idle when setting is enabled', async () => {
await setUserStatus(testUser.credentials, UserStatus.AWAY);
await setUserStatus(testUser2.credentials, UserStatus.AWAY);

const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);

await sleep(5000);

const roomInfo = await getLivechatRoomInfo(room._id);
// Not checking who, just checking it's served
expect(roomInfo.servedBy).to.be.an('object');
});
});
describe('Load Rotation', () => {
before(async () => {
Expand Down Expand Up @@ -388,5 +406,19 @@ import { IS_EE } from '../../../e2e/config/constants';
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
});
it('should route to agents even if theyre idle when setting is enabled', async () => {
await updateSetting('Livechat_enabled_when_agent_idle', true);
await setUserStatus(testUser.credentials, UserStatus.AWAY);
await setUserStatus(testUser2.credentials, UserStatus.AWAY);

const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);

await sleep(5000);

const roomInfo = await getLivechatRoomInfo(room._id);
// Not checking who, just checking it's served
expect(roomInfo.servedBy).to.be.an('object');
});
});
});
8 changes: 7 additions & 1 deletion packages/model-typings/src/models/IUsersModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ export interface IUsersModel extends IBaseModel<IUser> {
getNextLeastBusyAgent(
department: any,
ignoreAgentId: any,
isEnabledWhenAgentIdle?: boolean,
): Promise<{ agentId: string; username: string; lastRoutingTime: Date; departments: any[]; count: number }>;
getLastAvailableAgentRouted(
department: any,
ignoreAgentId: any,
isEnabledWhenAgentIdle?: boolean,
): Promise<{ agentId: string; username: string; lastRoutingTime: Date; departments: any[] }>;

setLastRoutingTime(userId: any): Promise<number>;
Expand Down Expand Up @@ -274,7 +276,11 @@ export interface IUsersModel extends IBaseModel<IUser> {
): Promise<ILivechatAgent | null>;
findAgents(): FindCursor<ILivechatAgent>;
countAgents(): Promise<number>;
getNextAgent(ignoreAgentId?: string, extraQuery?: Filter<IUser>): Promise<{ agentId: string; username: string } | null>;
getNextAgent(
ignoreAgentId?: string,
extraQuery?: Filter<IUser>,
enabledWhenAgentIdle?: boolean,
): Promise<{ agentId: string; username: string } | null>;
getNextBotAgent(ignoreAgentId?: string): Promise<{ agentId: string; username: string } | null>;
setLivechatStatus(userId: string, status: ILivechatAgentStatus): Promise<UpdateResult>;
makeAgentUnavailableAndUnsetExtension(userId: string): Promise<UpdateResult>;
Expand Down
28 changes: 8 additions & 20 deletions packages/models/src/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,16 +483,10 @@ export class UsersRaw extends BaseRaw {
return this.col.distinct('federation.origin', { federation: { $exists: true } });
}

async getNextLeastBusyAgent(department, ignoreAgentId) {
async getNextLeastBusyAgent(department, ignoreAgentId, isEnabledWhenAgentIdle) {
const match = queryStatusAgentOnline({ ...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }) }, isEnabledWhenAgentIdle);
const aggregate = [
{
$match: {
status: { $exists: true, $ne: 'offline' },
statusLivechat: 'available',
roles: 'livechat-agent',
...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }),
},
},
{ $match: match },
{
$lookup: {
from: 'rocketchat_subscription',
Expand Down Expand Up @@ -549,16 +543,10 @@ export class UsersRaw extends BaseRaw {
return agent;
}

async getLastAvailableAgentRouted(department, ignoreAgentId) {
async getLastAvailableAgentRouted(department, ignoreAgentId, isEnabledWhenAgentIdle) {
const match = queryStatusAgentOnline({ ...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }) }, isEnabledWhenAgentIdle);
const aggregate = [
{
$match: {
status: { $exists: true, $ne: 'offline' },
statusLivechat: 'available',
roles: 'livechat-agent',
...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }),
},
},
{ $match: match },
{
$lookup: {
from: 'rocketchat_livechat_department_agents',
Expand Down Expand Up @@ -1748,7 +1736,7 @@ export class UsersRaw extends BaseRaw {
}

// 2
async getNextAgent(ignoreAgentId, extraQuery) {
async getNextAgent(ignoreAgentId, extraQuery, enabledWhenAgentIdle) {
// TODO: Create class Agent
// fetch all unavailable agents, and exclude them from the selection
const unavailableAgents = (await this.getUnavailableAgents(null, extraQuery)).map((u) => u.username);
Expand All @@ -1758,7 +1746,7 @@ export class UsersRaw extends BaseRaw {
username: { $nin: unavailableAgents },
};

const query = queryStatusAgentOnline(extraFilters);
const query = queryStatusAgentOnline(extraFilters, enabledWhenAgentIdle);

const sort = {
livechatCount: 1,
Expand Down
Loading