diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 2ce0a3a66fe40..719996542d057 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -724,7 +724,7 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi } const { servedBy, chatQueued } = roomTaken; - if (!chatQueued && oldServedBy && servedBy && oldServedBy._id === servedBy._id) { + if (!chatQueued && oldServedBy && oldServedBy._id === servedBy?._id) { if (!department?.fallbackForwardDepartment?.length) { logger.debug({ msg: 'Cannot forward room. Chat assigned to agent instead', diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadBalancing.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadBalancing.ts index e10e1b2dc154f..53f7093aaf430 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadBalancing.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadBalancing.ts @@ -31,14 +31,15 @@ class LoadBalancing { } async getNextAgent(department?: string, ignoreAgentId?: string) { + const enabledWhenIdle = settings.get('Livechat_enabled_when_agent_idle'); const extraQuery = await getChatLimitsQuery(department); - const unavailableUsers = await Users.getUnavailableAgents(department, extraQuery); - logger.debug({ msg: 'Ignoring unavailable agents from assignment', unavailableUsers, department }); + const unavailableUsers = await Users.getUnavailableAgents(department, extraQuery, enabledWhenIdle); + logger.debug({ msg: 'Ignoring unavailable agents from assignment', unavailableUsers, department, enabledWhenIdle }); const nextAgent = await Users.getNextLeastBusyAgent( department, ignoreAgentId, - settings.get('Livechat_enabled_when_agent_idle'), + enabledWhenIdle, unavailableUsers.map((u) => u.username), ); if (!nextAgent) { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts index 92726a0477ee5..0f731973c68ed 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/routing/LoadRotation.ts @@ -30,14 +30,16 @@ class LoadRotation { } public async getNextAgent(department?: string, ignoreAgentId?: string): Promise { + const enabledWhenIdle = settings.get('Livechat_enabled_when_agent_idle'); + const extraQuery = await getChatLimitsQuery(department); - const unavailableUsers = await Users.getUnavailableAgents(department, extraQuery); - logger.debug({ msg: 'Ignoring unavailable agents from assignment', unavailableUsers, department }); + const unavailableUsers = await Users.getUnavailableAgents(department, extraQuery, enabledWhenIdle); + logger.debug({ msg: 'Ignoring unavailable agents from assignment', unavailableUsers, department, enabledWhenIdle }); const nextAgent = await Users.getLastAvailableAgentRouted( department, ignoreAgentId, - settings.get('Livechat_enabled_when_agent_idle'), + enabledWhenIdle, unavailableUsers.map((user) => user.username), ); if (!nextAgent?.username) { diff --git a/apps/meteor/ee/server/models/raw/Users.ts b/apps/meteor/ee/server/models/raw/Users.ts index 79fa490e5b077..0912a3c80b03d 100644 --- a/apps/meteor/ee/server/models/raw/Users.ts +++ b/apps/meteor/ee/server/models/raw/Users.ts @@ -1,5 +1,5 @@ import type { RocketChatRecordDeleted, IUser, AvailableAgentsAggregation } from '@rocket.chat/core-typings'; -import { UsersRaw } from '@rocket.chat/models'; +import { queryStatusAgentOnline, UsersRaw } from '@rocket.chat/models'; import type { Db, Collection, Filter } from 'mongodb'; import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred'; @@ -9,6 +9,7 @@ declare module '@rocket.chat/model-typings' { getUnavailableAgents( departmentId: string, customFilter: Filter, + enabledWhenIdle?: boolean, ): Promise[]>; } } @@ -21,6 +22,7 @@ export class UsersEE extends UsersRaw { override getUnavailableAgents( departmentId: string, customFilter: Filter, + enabledWhenIdle = false, ): Promise[]> { // if department is provided, remove the agents that are not from the selected department const departmentFilter = departmentId @@ -51,11 +53,7 @@ export class UsersEE extends UsersRaw { .aggregate( [ { - $match: { - status: { $exists: true, $ne: 'offline' }, - statusLivechat: 'available', - roles: 'livechat-agent', - }, + $match: queryStatusAgentOnline({}, enabledWhenIdle), }, ...departmentFilter, { diff --git a/packages/apps-engine/deno-runtime/lib/require.ts b/packages/apps-engine/deno-runtime/lib/require.ts index 845b38fc1ca2b..7d842d829e598 100644 --- a/packages/apps-engine/deno-runtime/lib/require.ts +++ b/packages/apps-engine/deno-runtime/lib/require.ts @@ -7,7 +7,8 @@ export const require = (mod: string) => { // However, the import maps are configured to look at the source folder for typescript files, but during // runtime those files are not available if (mod.startsWith('@rocket.chat/apps-engine')) { - mod = import.meta.resolve(mod).replace('file://', '').replace('src/', ''); + // Only remove "src/" substring when it comes after "apps-engine/" + mod = import.meta.resolve(mod).replace('file://', '').replace('apps-engine/src/', 'apps-engine/'); } return _require(mod); diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 31b723e24c63e..383db7efaf337 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -255,6 +255,7 @@ export interface IUsersModel extends IBaseModel { getUnavailableAgents( departmentId?: string, extraQuery?: Filter, + isLivechatEnabledWhenIdle?: boolean, ): Promise[]>; findOneOnlineAgentByUserList( userList: string[] | string, diff --git a/packages/models/src/helpers/index.ts b/packages/models/src/helpers/index.ts new file mode 100644 index 0000000000000..598ffe82bf721 --- /dev/null +++ b/packages/models/src/helpers/index.ts @@ -0,0 +1 @@ +export * from './omnichannel'; diff --git a/packages/models/src/helpers/omnichannel/agentStatus.ts b/packages/models/src/helpers/omnichannel/agentStatus.ts new file mode 100644 index 0000000000000..020751a29d35a --- /dev/null +++ b/packages/models/src/helpers/omnichannel/agentStatus.ts @@ -0,0 +1,39 @@ +import { UserStatus } from '@rocket.chat/core-typings'; +import type { IUser } from '@rocket.chat/core-typings'; +import type { Filter } from 'mongodb'; + +export const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdle?: boolean): Filter => ({ + statusLivechat: 'available', + roles: 'livechat-agent', + // ignore deactivated users + active: true, + ...(!isLivechatEnabledWhenAgentIdle && { + $or: [ + { + status: { + $exists: true, + $ne: UserStatus.OFFLINE, + }, + roles: { + $ne: 'bot', + }, + }, + { + roles: 'bot', + }, + ], + }), + ...extraFilters, + ...(isLivechatEnabledWhenAgentIdle === false && { + statusConnection: { $ne: 'away' }, + }), +}); + +export const queryAvailableAgentsForSelection = (extraFilters = {}, isLivechatEnabledWhenAgentIdle?: boolean): Filter => ({ + ...queryStatusAgentOnline(extraFilters, isLivechatEnabledWhenAgentIdle), + $and: [ + { + $or: [{ agentLocked: { $exists: false } }, { agentLockedAt: { $lt: new Date(Date.now() - 5000) } }], + }, + ], +}); diff --git a/packages/models/src/helpers/omnichannel/index.ts b/packages/models/src/helpers/omnichannel/index.ts new file mode 100644 index 0000000000000..45e9978475552 --- /dev/null +++ b/packages/models/src/helpers/omnichannel/index.ts @@ -0,0 +1 @@ +export * from './agentStatus'; diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index b36f38a9cfaa3..4805f237ef585 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -118,6 +118,8 @@ export * from './modelClasses'; export * from './dummy/ReadReceipts'; +export * from './helpers'; + export { registerModel } from './proxify'; export { type Updater, UpdaterImpl } from './updater'; diff --git a/packages/models/src/models/LivechatDepartmentAgents.ts b/packages/models/src/models/LivechatDepartmentAgents.ts index 7fabbd5aea7f1..c9c29a74568d1 100644 --- a/packages/models/src/models/LivechatDepartmentAgents.ts +++ b/packages/models/src/models/LivechatDepartmentAgents.ts @@ -193,7 +193,9 @@ export class LivechatDepartmentAgentsRaw extends BaseRaw user.username).filter(isStringValue); // get fully booked agents, to ignore them from the query - const currentUnavailableAgents = (await Users.getUnavailableAgents(departmentId, extraQuery)).map((u) => u.username); + const currentUnavailableAgents = (await Users.getUnavailableAgents(departmentId, extraQuery, isLivechatEnabledWhenAgentIdle)).map( + (u) => u.username, + ); const query: Filter = { departmentId, diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index d8ff89c93fa7e..95cc31246380a 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -31,42 +31,7 @@ import type { import { Rooms, Subscriptions } from '../index'; import { BaseRaw } from './BaseRaw'; - -const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdle?: boolean): Filter => ({ - statusLivechat: 'available', - roles: 'livechat-agent', - // ignore deactivated users - active: true, - ...(!isLivechatEnabledWhenAgentIdle && { - $or: [ - { - status: { - $exists: true, - $ne: UserStatus.OFFLINE, - }, - roles: { - $ne: 'bot', - }, - }, - { - roles: 'bot', - }, - ], - }), - ...extraFilters, - ...(isLivechatEnabledWhenAgentIdle === false && { - statusConnection: { $ne: 'away' }, - }), -}); - -const queryAvailableAgentsForSelection = (extraFilters = {}, isLivechatEnabledWhenAgentIdle?: boolean): Filter => ({ - ...queryStatusAgentOnline(extraFilters, isLivechatEnabledWhenAgentIdle), - $and: [ - { - $or: [{ agentLocked: { $exists: false } }, { agentLockedAt: { $lt: new Date(Date.now() - 5000) } }], - }, - ], -}); +import { queryAvailableAgentsForSelection, queryStatusAgentOnline } from '../helpers'; export class UsersRaw extends BaseRaw> implements IUsersModel { constructor(db: Db, trash?: Collection>) { @@ -1687,6 +1652,7 @@ export class UsersRaw extends BaseRaw> implements IU async getUnavailableAgents( _departmentId?: string, _extraQuery?: Filter, + _isLivechatEnabledWhenAgentIdle?: boolean, ): Promise[]> { return []; } @@ -1960,7 +1926,7 @@ export class UsersRaw extends BaseRaw> implements IU async getNextAgent(ignoreAgentId?: string, extraQuery?: Filter, enabledWhenAgentIdle?: boolean) { // TODO: Create class Agent // fetch all unavailable agents, and exclude them from the selection - const unavailableAgents = (await this.getUnavailableAgents(undefined, extraQuery)).map((u) => u.username); + const unavailableAgents = (await this.getUnavailableAgents(undefined, extraQuery, enabledWhenAgentIdle)).map((u) => u.username); const extraFilters = { ...(ignoreAgentId && { _id: { $ne: ignoreAgentId } }), // limit query to remove booked agents