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
5 changes: 5 additions & 0 deletions .changeset/cuddly-bugs-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes the usage of `Livechat_enabled_when_agent_idle` setting across the codebase. Goal is to use it wherever is applicable making the feature more predictable.
4 changes: 2 additions & 2 deletions apps/meteor/app/livechat/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ export const forwardRoomToAgent = async (room: IOmnichannelRoom, transferData: T
if (!agentId) {
throw new Error('error-invalid-agent');
}
const user = await Users.findOneOnlineAgentById(agentId);
const user = await Users.findOneOnlineAgentById(agentId, settings.get<boolean>('Livechat_enabled_when_agent_idle'));
if (!user) {
logger.debug(`Agent ${agentId} is offline. Cannot forward`);
throw new Error('error-user-is-offline');
Expand Down Expand Up @@ -603,7 +603,7 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi
const { userId: agentId, clientAction } = transferData;
if (agentId) {
logger.debug(`Forwarding room ${room._id} to department ${departmentId} (to user ${agentId})`);
const user = await Users.findOneOnlineAgentById(agentId);
const user = await Users.findOneOnlineAgentById(agentId, settings.get<boolean>('Livechat_enabled_when_agent_idle'));
if (!user) {
throw new Error('error-user-is-offline');
}
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/app/livechat/server/lib/QueueManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,14 @@ export class QueueManager {
};

let defaultAgent: SelectedAgent | undefined;
if (servedBy?.username && (await Users.findOneOnlineAgentByUserList(servedBy.username))) {
const isAgentAvailable = (username: string) =>
Users.findOneOnlineAgentByUserList(username, { projection: { _id: 1 } }, settings.get<boolean>('Livechat_enabled_when_agent_idle'));

if (servedBy?.username && (await isAgentAvailable(servedBy.username))) {
defaultAgent = { agentId: servedBy._id, username: servedBy.username };
}

// TODO: unarchive to return updated room
await LivechatRooms.unarchiveOneById(rid);
const room = await LivechatRooms.findOneById(rid);
if (!room) {
Expand Down
7 changes: 6 additions & 1 deletion apps/meteor/app/livechat/server/lib/RoutingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ export const RoutingManager: Routing = {
async delegateInquiry(inquiry, agent, options = {}, room) {
const { department, rid } = inquiry;
logger.debug(`Attempting to delegate inquiry ${inquiry._id}`);
if (!agent || (agent.username && !(await Users.findOneOnlineAgentByUserList(agent.username)) && !(await allowAgentSkipQueue(agent)))) {
if (
!agent ||
(agent.username &&
!(await Users.findOneOnlineAgentByUserList(agent.username, {}, settings.get<boolean>('Livechat_enabled_when_agent_idle'))) &&
!(await allowAgentSkipQueue(agent)))
) {
logger.debug(`Agent offline or invalid. Using routing method to get next agent for inquiry ${inquiry._id}`);
agent = await this.getNextAgent(department);
logger.debug(`Routing method returned agent ${agent?.agentId} for inquiry ${inquiry._id}`);
Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/app/livechat/server/lib/routing/External.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ class ExternalQueue implements IRoutingMethod {
const result = (await request.json()) as { username?: string };

if (result?.username) {
const agent = await Users.findOneOnlineAgentByUserList(result.username);
const agent = await Users.findOneOnlineAgentByUserList(
result.username,
{},
settings.get<boolean>('Livechat_enabled_when_agent_idle'),
);

if (!agent?.username) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const getDefaultAgent = async ({ username, id }: { username?: string; id?: strin
}

if (id) {
const agent = await Users.findOneOnlineAgentById(id, undefined, { projection: { _id: 1, username: 1 } });
const agent = await Users.findOneOnlineAgentById(id, settings.get<boolean>('Livechat_enabled_when_agent_idle'), {
projection: { _id: 1, username: 1 },
});
if (agent) {
return normalizeDefaultAgent(agent);
}
Expand All @@ -36,7 +38,13 @@ const getDefaultAgent = async ({ username, id }: { username?: string; id?: strin
return undefined;
}

return normalizeDefaultAgent(await Users.findOneOnlineAgentByUserList(username || [], { projection: { _id: 1, username: 1 } }));
return normalizeDefaultAgent(
await Users.findOneOnlineAgentByUserList(
username || [],
{ projection: { _id: 1, username: 1 } },
settings.get<boolean>('Livechat_enabled_when_agent_idle'),
),
);
};

settings.watch<boolean>('Livechat_last_chatted_agent_routing', (value) => {
Expand Down Expand Up @@ -119,7 +127,11 @@ checkDefaultAgentOnNewRoom.patch(async (_next, defaultAgent, { visitorId, source
return defaultAgent;
}
const lastRoomAgent = normalizeDefaultAgent(
await Users.findOneOnlineAgentByUserList(usernameByRoom, { projection: { _id: 1, username: 1 } }),
await Users.findOneOnlineAgentByUserList(
usernameByRoom,
{ projection: { _id: 1, username: 1 } },
settings.get<boolean>('Livechat_enabled_when_agent_idle'),
),
);
return lastRoomAgent;
});
36 changes: 35 additions & 1 deletion apps/meteor/tests/data/livechat/department.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { ILivechatDepartment, IUser, LivechatDepartmentDTO } from '@rocket.
import { expect } from 'chai';

import { api, credentials, methodCall, request } from '../api-data';
import { createAnOnlineAgent, createAnOfflineAgent } from './users';
import { createAnOnlineAgent, createAnOfflineAgent, createAnAwayAgent } from './users';
import type { WithRequiredProperty } from './utils';

const NewDepartmentData = ((): Partial<ILivechatDepartment> => ({
Expand Down Expand Up @@ -183,6 +183,40 @@ export const createDepartmentWithAnOfflineAgent = async ({
};
};

export const createDepartmentWithAnAwayAgent = async ({
allowReceiveForwardOffline = false,
fallbackForwardDepartment,
departmentsAllowedToForward,
}: {
allowReceiveForwardOffline?: boolean;
fallbackForwardDepartment?: string;
departmentsAllowedToForward?: string[];
}): Promise<{
department: ILivechatDepartment;
agent: {
credentials: Credentials;
user: WithRequiredProperty<IUser, 'username'>;
};
}> => {
const { user, credentials } = await createAnAwayAgent();

const department = (await createDepartmentWithMethod({
allowReceiveForwardOffline,
fallbackForwardDepartment,
departmentsAllowedToForward,
})) as ILivechatDepartment;

await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true);

return {
department,
agent: {
credentials,
user,
},
};
};

export const archiveDepartment = async (departmentId: string): Promise<void> => {
await request
.post(api(`livechat/department/${departmentId}/archive`))
Expand Down
24 changes: 22 additions & 2 deletions apps/meteor/tests/data/livechat/users.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { faker } from '@faker-js/faker';
import type { Credentials } from '@rocket.chat/api-client';
import type { ILivechatAgent, IUser } from '@rocket.chat/core-typings';
import { UserStatus, type ILivechatAgent, type IUser } from '@rocket.chat/core-typings';

import { api, credentials, request, methodCall } from '../api-data';
import { password } from '../user';
import { createUser, login } from '../users.helper';
import { createUser, login, setUserAway, setUserStatus } from '../users.helper';
import { createAgent, makeAgentAvailable, makeAgentUnavailable } from './rooms';

export const createBotAgent = async (): Promise<{
Expand Down Expand Up @@ -77,6 +77,26 @@ export const createAnOfflineAgent = async (): Promise<{
};
};

export const createAnAwayAgent = async (): Promise<{
credentials: Credentials;
user: IUser & { username: string };
}> => {
const username = `user.test.${Date.now()}.away`;
const email = `${username}[email protected]`;
const { body } = await request.post(api('users.create')).set(credentials).send({ email, name: username, username, password });
const agent = body.user;
const createdUserCredentials = await login(agent.username, password);
await createAgent(agent.username);
await makeAgentAvailable(createdUserCredentials);
await setUserStatus(createdUserCredentials, UserStatus.AWAY);
await setUserAway(createdUserCredentials);

return {
credentials: createdUserCredentials,
user: agent,
};
};

export const updateLivechatSettingsForUser = async (
agentId: string,
livechatSettings: Record<string, any>,
Expand Down
89 changes: 88 additions & 1 deletion apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ import type { SuccessResult } from '../../../../app/api/server/definition';
import { getCredentials, api, request, credentials, methodCall } from '../../../data/api-data';
import { apps, APP_URL } from '../../../data/apps/apps-data';
import { createCustomField } from '../../../data/livechat/custom-fields';
import { createDepartmentWithAnOfflineAgent, createDepartmentWithAnOnlineAgent, deleteDepartment } from '../../../data/livechat/department';
import {
createDepartmentWithAnAwayAgent,
createDepartmentWithAnOfflineAgent,
createDepartmentWithAnOnlineAgent,
deleteDepartment,
} from '../../../data/livechat/department';
import { createSLA, getRandomPriority } from '../../../data/livechat/priorities';
import {
createVisitor,
Expand Down Expand Up @@ -1198,6 +1203,88 @@ describe('LIVECHAT - rooms', () => {
await deleteDepartment(forwardToOfflineDepartment._id);
});

(IS_EE ? it : it.skip)(
'when manager forward to offline (agent away, accept when agent idle off) department the inquiry should be set to the queue',
async () => {
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateSetting('Livechat_enabled_when_agent_idle', false);
const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent();
const { department: forwardToOfflineDepartment } = await createDepartmentWithAnAwayAgent({
allowReceiveForwardOffline: true,
});

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

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');
});

await Promise.all([deleteDepartment(initialDepartment._id), deleteDepartment(forwardToOfflineDepartment._id)]);
},
);

(IS_EE ? it : it.skip)(
'when manager forward to online (agent away, accept when agent idle on) department the inquiry should not be set to the queue',
async () => {
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
await updateSetting('Livechat_enabled_when_agent_idle', true);
const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent();
const { department: forwardToOfflineDepartment, agent } = await createDepartmentWithAnAwayAgent({
allowReceiveForwardOffline: true,
});

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

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',
});

const roomInfo = await getLivechatRoomInfo(newRoom._id);

expect(roomInfo.servedBy).to.have.property('_id', agent.user._id);
expect(roomInfo.departmentId).to.be.equal(forwardToOfflineDepartment._id);

await Promise.all([
deleteDepartment(initialDepartment._id),
deleteDepartment(forwardToOfflineDepartment._id),
updateSetting('Livechat_enabled_when_agent_idle', false),
]);
},
);

(IS_EE ? it : it.skip)(
'should update inquiry last message when manager forward to offline department and the inquiry returns to queued',
async () => {
Expand Down
Loading