refactor: `LivechatDepartmentAgents` removal (#28472)

pull/28467/head^2
Kevin Aleman 3 years ago committed by GitHub
parent d4098d4b93
commit 528d353deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      apps/meteor/app/lib/server/functions/deleteUser.ts
  2. 6
      apps/meteor/app/lib/server/functions/saveUserIdentity.ts
  3. 4
      apps/meteor/app/livechat/imports/server/rest/departments.ts
  4. 2
      apps/meteor/app/livechat/server/api/v1/agent.ts
  5. 4
      apps/meteor/app/livechat/server/hooks/beforeDelegateAgent.js
  6. 44
      apps/meteor/app/livechat/server/lib/Helper.js
  7. 64
      apps/meteor/app/livechat/server/lib/Livechat.js
  8. 2
      apps/meteor/app/livechat/server/lib/QueueManager.js
  9. 8
      apps/meteor/app/livechat/server/lib/RoutingManager.js
  10. 13
      apps/meteor/app/livechat/server/lib/routing/AutoSelection.ts
  11. 2
      apps/meteor/app/livechat/server/methods/getNextAgent.ts
  12. 15
      apps/meteor/app/livechat/server/roomAccessValidator.compatibility.ts
  13. 4
      apps/meteor/app/livechat/server/roomAccessValidator.internalService.ts
  14. 15
      apps/meteor/app/models/server/index.ts
  15. 29
      apps/meteor/app/models/server/models/LivechatDepartment.js
  16. 231
      apps/meteor/app/models/server/models/LivechatDepartmentAgents.js
  17. 1
      apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts
  18. 2
      apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js
  19. 19
      apps/meteor/ee/app/models/server/raw/LivechatDepartmentAgents.ts
  20. 2
      apps/meteor/server/models/raw/LivechatDepartmentAgents.ts
  21. 2
      packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts

@ -1,11 +1,11 @@
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import type { FileProp } from '@rocket.chat/core-typings';
import { Integrations, FederationServers, LivechatVisitors } from '@rocket.chat/models';
import { Integrations, FederationServers, LivechatVisitors, LivechatDepartmentAgents } from '@rocket.chat/models';
import { api } from '@rocket.chat/core-services';
import { FileUpload } from '../../../file-upload/server';
import { Users, Subscriptions, Messages, Rooms, LivechatDepartmentAgents } from '../../../models/server';
import { Users, Subscriptions, Messages, Rooms } from '../../../models/server';
import { settings } from '../../../settings/server';
import { updateGroupDMsName } from './updateGroupDMsName';
import { relinquishRoomOwnerships } from './relinquishRoomOwnerships';
@ -56,7 +56,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false): Pro
if (user.roles.includes('livechat-agent')) {
// Remove user as livechat agent
LivechatDepartmentAgents.removeByAgentId(userId);
await LivechatDepartmentAgents.removeByAgentId(userId);
}
if (user.roles.includes('livechat-monitor')) {

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { IMessage } from '@rocket.chat/core-typings';
import { VideoConference } from '@rocket.chat/models';
import { VideoConference, LivechatDepartmentAgents } from '@rocket.chat/models';
import { _setUsername } from './setUsername';
import { _setRealName } from './setRealName';
import { Messages, Rooms, Subscriptions, LivechatDepartmentAgents, Users } from '../../../models/server';
import { Messages, Rooms, Subscriptions, Users } from '../../../models/server';
import { FileUpload } from '../../../file-upload/server';
import { updateGroupDMsName } from './updateGroupDMsName';
import { validateName } from './validateName';
@ -68,7 +68,7 @@ export async function saveUserIdentity({
Rooms.replaceUsernameOfUserByUserId(user._id, username);
Subscriptions.setUserUsernameByUserId(user._id, username);
LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username);
await LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username);
const fileStore = FileUpload.getStore('Avatars');
const previousFile = await fileStore.model.findOneByName(previousUsername);

@ -125,7 +125,7 @@ API.v1.addRoute(
}
if (success && agents && permissionToAddAgents) {
success = Livechat.saveDepartmentAgents(_id, { upsert: agents });
success = await Livechat.saveDepartmentAgents(_id, { upsert: agents });
}
if (success) {
@ -280,7 +280,7 @@ API.v1.addRoute(
remove: Array,
}),
);
Livechat.saveDepartmentAgents(this.urlParams._id, this.bodyParams);
await Livechat.saveDepartmentAgents(this.urlParams._id, this.bodyParams);
return API.v1.success();
},

@ -42,7 +42,7 @@ API.v1.addRoute(
let { department } = this.queryParams;
if (!department) {
const requireDeparment = Livechat.getRequiredDepartment();
const requireDeparment = await Livechat.getRequiredDepartment();
if (requireDeparment) {
department = requireDeparment._id;
}

@ -1,6 +1,8 @@
import { LivechatDepartmentAgents } from '@rocket.chat/models';
import { callbacks } from '../../../../lib/callbacks';
import { settings } from '../../../settings/server';
import { Users, LivechatDepartmentAgents } from '../../../models/server';
import { Users } from '../../../models/server';
callbacks.add(
'livechat.beforeDelegateAgent',

@ -5,18 +5,10 @@ import { LivechatTransferEventType } from '@rocket.chat/apps-engine/definition/l
import { OmnichannelSourceType, DEFAULT_SLA_CONFIG } from '@rocket.chat/core-typings';
import { LivechatPriorityWeight } from '@rocket.chat/core-typings/src/ILivechatPriority';
import { api } from '@rocket.chat/core-services';
import { LivechatDepartmentAgents, Users as UsersRaw } from '@rocket.chat/models';
import { hasRole } from '../../../authorization/server';
import {
Messages,
LivechatRooms,
Rooms,
Subscriptions,
Users,
LivechatInquiry,
LivechatDepartment,
LivechatDepartmentAgents,
} from '../../../models/server';
import { Messages, LivechatRooms, Rooms, Subscriptions, Users, LivechatInquiry, LivechatDepartment } from '../../../models/server';
import { Livechat } from './Livechat';
import { RoutingManager } from './RoutingManager';
import { callbacks } from '../../../../lib/callbacks';
@ -276,7 +268,7 @@ export const dispatchAgentDelegated = (rid, agentId) => {
});
};
export const dispatchInquiryQueued = (inquiry, agent) => {
export const dispatchInquiryQueued = async (inquiry, agent) => {
if (!inquiry?._id) {
return;
}
@ -295,7 +287,7 @@ export const dispatchInquiryQueued = (inquiry, agent) => {
}
// Alert only the online agents of the queued request
const onlineAgents = Livechat.getOnlineAgents(department, agent);
const onlineAgents = await Livechat.getOnlineAgents(department, agent);
if (!onlineAgents) {
logger.debug('Cannot notify agents of queued inquiry. No online agents found');
return;
@ -304,12 +296,12 @@ export const dispatchInquiryQueued = (inquiry, agent) => {
logger.debug(`Notifying ${onlineAgents.count()} agents of new inquiry`);
const notificationUserName = v && (v.name || v.username);
onlineAgents.forEach((agent) => {
for await (let agent of onlineAgents) {
if (agent.agentId) {
agent = Users.findOneById(agent.agentId);
agent = await UsersRaw.findOneById(agent.agentId);
}
const { _id, active, emails, language, status, statusConnection, username } = agent;
sendNotification({
await sendNotification({
// fake a subscription in order to make use of the function defined above
subscription: {
rid,
@ -337,7 +329,7 @@ export const dispatchInquiryQueued = (inquiry, agent) => {
room: Object.assign(room, { name: TAPi18n.__('New_chat_in_queue', {}, language) }),
mentionIds: [],
});
});
}
};
export const forwardRoomToAgent = async (room, transferData) => {
@ -456,7 +448,7 @@ export const forwardRoomToDepartment = async (room, guest, transferData) => {
if (!user) {
throw new Error('error-user-is-offline');
}
user = LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(agentId, departmentId);
user = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(agentId, departmentId);
if (!user) {
throw new Error('error-user-not-belong-to-department');
}
@ -550,7 +542,7 @@ export const checkServiceStatus = ({ guest, agent }) => {
return users && users.count() > 0;
};
export const updateDepartmentAgents = (departmentId, agents, departmentEnabled) => {
export const updateDepartmentAgents = async (departmentId, agents, departmentEnabled) => {
check(departmentId, String);
check(
agents,
@ -563,22 +555,22 @@ export const updateDepartmentAgents = (departmentId, agents, departmentEnabled)
const { upsert = [], remove = [] } = agents;
const agentsRemoved = [];
const agentsAdded = [];
remove.forEach(({ agentId }) => {
LivechatDepartmentAgents.removeByDepartmentIdAndAgentId(departmentId, agentId);
for await (const { agentId } of remove) {
await LivechatDepartmentAgents.removeByDepartmentIdAndAgentId(departmentId, agentId);
agentsRemoved.push(agentId);
});
}
if (agentsRemoved.length > 0) {
callbacks.runAsync('livechat.removeAgentDepartment', { departmentId, agentsId: agentsRemoved });
}
upsert.forEach((agent) => {
for await (const agent of upsert) {
const agentFromDb = Users.findOneById(agent.agentId, { fields: { _id: 1, username: 1 } });
if (!agentFromDb) {
return;
continue;
}
LivechatDepartmentAgents.saveAgent({
await LivechatDepartmentAgents.saveAgent({
agentId: agent.agentId,
departmentId,
username: agentFromDb.username,
@ -587,7 +579,7 @@ export const updateDepartmentAgents = (departmentId, agents, departmentEnabled)
departmentEnabled,
});
agentsAdded.push(agent.agentId);
});
}
if (agentsAdded.length > 0) {
callbacks.runAsync('livechat.saveAgentDepartment', {
@ -598,7 +590,7 @@ export const updateDepartmentAgents = (departmentId, agents, departmentEnabled)
if (agentsRemoved.length > 0 || agentsAdded.length > 0) {
const numAgents = LivechatDepartmentAgents.find({ departmentId }).count();
LivechatDepartment.updateNumAgentsById(departmentId, numAgents);
await LivechatDepartment.updateNumAgentsById(departmentId, numAgents);
}
return true;

@ -19,6 +19,7 @@ import {
Subscriptions as SubscriptionsRaw,
Messages as MessagesRaw,
LivechatDepartment as LivechatDepartmentRaw,
LivechatDepartmentAgents,
} from '@rocket.chat/models';
import { VideoConf, api } from '@rocket.chat/core-services';
@ -27,16 +28,7 @@ import { RoutingManager } from './RoutingManager';
import { Analytics } from './Analytics';
import { settings } from '../../../settings/server';
import { callbacks } from '../../../../lib/callbacks';
import {
Users,
LivechatRooms,
Messages,
Subscriptions,
Rooms,
LivechatDepartmentAgents,
LivechatDepartment,
LivechatInquiry,
} from '../../../models/server';
import { Users, LivechatRooms, Messages, Subscriptions, Rooms, LivechatDepartment, LivechatInquiry } from '../../../models/server';
import { Logger } from '../../../logger/server';
import { hasPermission, hasRole, canAccessRoomAsync, roomAccessAttributes } from '../../../authorization/server';
import * as Mailer from '../../../mailer/server/api';
@ -109,7 +101,7 @@ export const Livechat = {
return Users.findAgents();
},
getOnlineAgents(department, agent) {
async getOnlineAgents(department, agent) {
if (agent?.agentId) {
return Users.findOnlineAgents(agent.agentId);
}
@ -120,13 +112,13 @@ export const Livechat = {
return Users.findOnlineAgents();
},
checkOnlineAgents(department, agent, skipFallbackCheck = false) {
async checkOnlineAgents(department, agent, skipFallbackCheck = false) {
if (agent?.agentId) {
return Users.checkOnlineAgents(agent.agentId);
}
if (department) {
const onlineForDep = LivechatDepartmentAgents.checkOnlineForDepartment(department);
const onlineForDep = await LivechatDepartmentAgents.checkOnlineForDepartment(department);
if (onlineForDep || skipFallbackCheck) {
return onlineForDep;
}
@ -142,7 +134,7 @@ export const Livechat = {
return Users.checkOnlineAgents();
},
getBotAgents(department) {
async getBotAgents(department) {
if (department) {
return LivechatDepartmentAgents.getBotsForDepartment(department);
}
@ -150,19 +142,22 @@ export const Livechat = {
return Users.findBotAgents();
},
getRequiredDepartment(onlineRequired = true) {
async getRequiredDepartment(onlineRequired = true) {
const departments = LivechatDepartment.findEnabledWithAgents();
return departments.fetch().find((dept) => {
for await (const dept of departments.fetch()) {
if (!dept.showOnRegistration) {
return false;
continue;
}
if (!onlineRequired) {
return true;
return dept;
}
const onlineAgents = LivechatDepartmentAgents.getOnlineForDepartment(dept._id);
return onlineAgents && onlineAgents.count() > 0;
});
const onlineAgents = await LivechatDepartmentAgents.getOnlineForDepartment(dept._id);
if (onlineAgents && onlineAgents.length) {
return dept;
}
}
},
async getRoom(guest, message, roomInfo, agent, extraData) {
@ -188,7 +183,7 @@ export const Livechat = {
const defaultAgent = callbacks.run('livechat.checkDefaultAgentOnNewRoom', agent, guest);
// if no department selected verify if there is at least one active and pick the first
if (!defaultAgent && !guest.department) {
const department = this.getRequiredDepartment();
const department = await this.getRequiredDepartment();
Livechat.logger.debug(`No department or default agent selected for ${guest._id}`);
if (department) {
@ -721,7 +716,7 @@ export const Livechat = {
return RoutingManager.transferRoom(room, guest, transferData);
},
returnRoomAsInquiry(rid, departmentId, overrideTransferData = {}) {
async returnRoomAsInquiry(rid, departmentId, overrideTransferData = {}) {
Livechat.logger.debug(`Transfering room ${rid} to ${departmentId ? 'department' : ''} queue`);
const room = LivechatRooms.findOneById(rid);
if (!room) {
@ -764,7 +759,7 @@ export const Livechat = {
const transferData = { roomId: rid, scope: 'queue', departmentId, transferredBy, ...overrideTransferData };
try {
this.saveTransferHistory(room, transferData);
RoutingManager.unassignAgent(inquiry, departmentId);
await RoutingManager.unassignAgent(inquiry, departmentId);
} catch (e) {
this.logger.error(e);
throw new Meteor.Error('error-returning-inquiry', 'Error returning inquiry to the queue', {
@ -915,8 +910,12 @@ export const Livechat = {
Users.setOperator(_id, false);
Users.removeLivechatData(_id);
this.setUserStatusLivechat(_id, 'not-available');
LivechatDepartmentAgents.removeByAgentId(_id);
Promise.await(Promise.all([LivechatVisitors.removeContactManagerByUsername(username), UsersRaw.unsetExtension(_id)]));
await Promise.all([
LivechatDepartmentAgents.removeByAgentId(_id),
LivechatVisitors.removeContactManagerByUsername(username),
UsersRaw.unsetExtension(_id),
]);
return true;
}
@ -983,7 +982,7 @@ export const Livechat = {
LivechatInquiry.removeByVisitorToken(token);
},
saveDepartmentAgents(_id, departmentAgents) {
async saveDepartmentAgents(_id, departmentAgents) {
check(_id, String);
check(departmentAgents, {
upsert: Match.Maybe([
@ -1032,7 +1031,10 @@ export const Livechat = {
return true;
},
removeDepartment(_id) {
/*
* @deprecated - Use the equivalent from DepartmentHelpers class
*/
async removeDepartment(_id) {
check(_id, String);
const departmentRemovalEnabled = settings.get('Omnichannel_enable_department_removal');
@ -1051,10 +1053,8 @@ export const Livechat = {
});
}
const ret = LivechatDepartment.removeById(_id);
const agentsIds = LivechatDepartmentAgents.findByDepartmentId(_id)
.fetch()
.map((agent) => agent.agentId);
LivechatDepartmentAgents.removeByDepartmentId(_id);
const agentsIds = (await LivechatDepartmentAgents.findByDepartmentId(_id).toArray()).map((agent) => agent.agentId);
await LivechatDepartmentAgents.removeByDepartmentId(_id);
LivechatDepartment.unsetFallbackDepartmentByDepartmentId(_id);
if (ret) {
Meteor.defer(() => {

@ -16,7 +16,7 @@ export const saveQueueInquiry = (inquiry) => {
};
export const queueInquiry = async (room, inquiry, defaultAgent) => {
const inquiryAgent = RoutingManager.delegateAgent(defaultAgent, inquiry);
const inquiryAgent = await RoutingManager.delegateAgent(defaultAgent, inquiry);
logger.debug(`Delegating inquiry with id ${inquiry._id} to agent ${defaultAgent?.username}`);
await callbacks.run('livechat.beforeRouteChat', inquiry, inquiryAgent);

@ -113,7 +113,7 @@ export const RoutingManager = {
return inquiry;
},
unassignAgent(inquiry, departmentId) {
async unassignAgent(inquiry, departmentId) {
const { rid, department } = inquiry;
const room = LivechatRooms.findOneById(rid);
@ -143,7 +143,7 @@ export const RoutingManager = {
dispatchAgentDelegated(rid, null);
}
dispatchInquiryQueued(inquiry);
await dispatchInquiryQueued(inquiry);
return true;
},
@ -222,7 +222,7 @@ export const RoutingManager = {
return false;
},
delegateAgent(agent, inquiry) {
async delegateAgent(agent, inquiry) {
logger.debug(`Delegating Inquiry ${inquiry._id}`);
const defaultAgent = callbacks.run('livechat.beforeDelegateAgent', agent, {
department: inquiry?.department,
@ -234,7 +234,7 @@ export const RoutingManager = {
}
logger.debug(`Queueing inquiry ${inquiry._id}`);
dispatchInquiryQueued(inquiry, defaultAgent);
await dispatchInquiryQueued(inquiry, defaultAgent);
return defaultAgent;
},

@ -1,8 +1,10 @@
import type { IRoutingMethod, RoutingMethodConfig, SelectedAgent } from '@rocket.chat/core-typings';
import { LivechatDepartmentAgents } from '@rocket.chat/models';
import { RoutingManager } from '../RoutingManager';
import { LivechatDepartmentAgents, Users } from '../../../../models/server';
import { Users } from '../../../../models/server';
import { callbacks } from '../../../../../lib/callbacks';
import { settings } from '../../../../settings/server';
/* Auto Selection Queuing method:
*
@ -24,12 +26,17 @@ class AutoSelection implements IRoutingMethod {
};
}
getNextAgent(department?: string, ignoreAgentId?: string): Promise<SelectedAgent | null | undefined> {
async getNextAgent(department?: string, ignoreAgentId?: string): Promise<SelectedAgent | null | undefined> {
const extraQuery = callbacks.run('livechat.applySimultaneousChatRestrictions', undefined, {
...(department ? { departmentId: department } : {}),
});
if (department) {
return Promise.resolve(LivechatDepartmentAgents.getNextAgentForDepartment(department, ignoreAgentId, extraQuery));
return LivechatDepartmentAgents.getNextAgentForDepartment(
department,
settings.get<boolean>('Livechat_enabled_when_agent_idle'),
ignoreAgentId,
extraQuery,
);
}
return Users.getNextAgent(ignoreAgentId, extraQuery);

@ -25,7 +25,7 @@ Meteor.methods<ServerMethods>({
}
if (!department) {
const requireDeparment = Livechat.getRequiredDepartment();
const requireDeparment = await Livechat.getRequiredDepartment();
if (requireDeparment) {
department = requireDeparment._id;
}

@ -1,7 +1,8 @@
import type { IUser, ILivechatDepartment, ILivechatDepartmentAgents, IOmnichannelRoom } from '@rocket.chat/core-typings';
import type { IUser, ILivechatDepartment, IOmnichannelRoom } from '@rocket.chat/core-typings';
import { LivechatDepartmentAgents } from '@rocket.chat/models';
import { hasPermission, hasRole } from '../../authorization/server';
import { LivechatDepartment, LivechatDepartmentAgents, LivechatInquiry, LivechatRooms } from '../../models/server';
import { LivechatDepartment, LivechatInquiry, LivechatRooms } from '../../models/server';
import { RoutingManager } from './lib/RoutingManager';
type OmniRoomAccessValidator = (room: IOmnichannelRoom, user?: Pick<IUser, '_id'>, extraData?: Record<string, any>) => boolean;
@ -28,7 +29,7 @@ export const validators: OmniRoomAccessValidator[] = [
}
return extraData?.visitorToken && room.v && room.v.token === extraData.visitorToken;
},
function (room, user) {
async function (room, user) {
if (!user?._id) {
return false;
}
@ -39,9 +40,7 @@ export const validators: OmniRoomAccessValidator[] = [
let departmentIds;
if (!hasRole(user._id, 'livechat-manager')) {
const departmentAgents = LivechatDepartmentAgents.findByAgentId(user._id)
.fetch()
.map((d: ILivechatDepartmentAgents) => d.departmentId);
const departmentAgents = (await LivechatDepartmentAgents.findByAgentId(user._id).toArray()).map((d) => d.departmentId);
departmentIds = LivechatDepartment.find({ _id: { $in: departmentAgents }, enabled: true })
.fetch()
.map((d: ILivechatDepartment) => d._id);
@ -65,11 +64,11 @@ export const validators: OmniRoomAccessValidator[] = [
const inquiry = LivechatInquiry.findOne(filter, { fields: { status: 1 } });
return inquiry && inquiry.status === 'queued';
},
function (room, user) {
async function (room, user) {
if (!room.departmentId || room.open || !user?._id) {
return;
}
const agentOfDepartment = LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(user._id, room.departmentId);
const agentOfDepartment = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(user._id, room.departmentId);
if (!agentOfDepartment) {
return;
}

@ -10,8 +10,8 @@ export class AuthorizationLivechat extends ServiceClassInternal implements IAuth
protected internal = true;
async canAccessRoom(room: IOmnichannelRoom, user?: Pick<IUser, '_id'>, extraData?: object): Promise<boolean> {
for (const validator of validators) {
if (validator(room, user, extraData)) {
for await (const validator of validators) {
if (await validator(room, user, extraData)) {
return true;
}
}

@ -5,7 +5,6 @@ import Rooms from './models/Rooms';
import Subscriptions from './models/Subscriptions';
import Users from './models/Users';
import LivechatDepartment from './models/LivechatDepartment';
import LivechatDepartmentAgents from './models/LivechatDepartmentAgents';
import LivechatRooms from './models/LivechatRooms';
import LivechatInquiry from './models/LivechatInquiry';
import AppsModel from './models/apps-model';
@ -13,16 +12,4 @@ import AppsModel from './models/apps-model';
export { AppsLogsModel } from './models/apps-logs-model';
export { AppsPersistenceModel } from './models/apps-persistence-model';
export {
AppsModel,
Base,
BaseDb,
Messages,
Rooms,
Subscriptions,
Users,
LivechatDepartment,
LivechatDepartmentAgents,
LivechatRooms,
LivechatInquiry,
};
export { AppsModel, Base, BaseDb, Messages, Rooms, Subscriptions, Users, LivechatDepartment, LivechatRooms, LivechatInquiry };

@ -1,10 +1,11 @@
import _ from 'underscore';
import { LivechatDepartmentAgents } from '@rocket.chat/models';
import { Base } from './_Base';
import LivechatDepartmentAgents from './LivechatDepartmentAgents';
/**
* Livechat Department model
*/
// Promise.await added here will be removed when this model gets removed, dont panic :)
export class LivechatDepartment extends Base {
constructor(modelOrName) {
super(modelOrName || 'livechat_department');
@ -48,21 +49,19 @@ export class LivechatDepartment extends Base {
_id = this.insert(record);
}
if (oldData && oldData.enabled !== data.enabled) {
LivechatDepartmentAgents.setDepartmentEnabledByDepartmentId(_id, data.enabled);
Promise.await(LivechatDepartmentAgents.setDepartmentEnabledByDepartmentId(_id, data.enabled));
}
return _.extend(record, { _id });
}
saveDepartmentsByAgent(agent, departments = []) {
const { _id: agentId, username } = agent;
const savedDepartments = LivechatDepartmentAgents.findByAgentId(agentId)
.fetch()
.map((d) => d.departmentId);
const savedDepartments = Promise.await(LivechatDepartmentAgents.findByAgentId(agentId).toArray()).map((d) => d.departmentId);
const incNumAgents = (_id, numAgents) => this.update(_id, { $inc: { numAgents } });
// remove other departments
_.difference(savedDepartments, departments).forEach((departmentId) => {
LivechatDepartmentAgents.removeByDepartmentIdAndAgentId(departmentId, agentId);
Promise.await(LivechatDepartmentAgents.removeByDepartmentIdAndAgentId(departmentId, agentId));
incNumAgents(departmentId, -1);
});
@ -70,14 +69,16 @@ export class LivechatDepartment extends Base {
const { enabled: departmentEnabled } = this.findOneById(departmentId, {
fields: { enabled: 1 },
});
const saveResult = LivechatDepartmentAgents.saveAgent({
agentId,
departmentId,
username,
departmentEnabled,
count: 0,
order: 0,
});
const saveResult = Promise.await(
LivechatDepartmentAgents.saveAgent({
agentId,
departmentId,
username,
departmentEnabled,
count: 0,
order: 0,
}),
);
if (saveResult.insertedId) {
incNumAgents(departmentId, 1);

@ -1,231 +0,0 @@
import _ from 'underscore';
import { Base } from './_Base';
import Users from './Users';
/**
* Livechat Department model
*/
class LivechatDepartmentAgents extends Base {
constructor() {
super('livechat_department_agents');
this.tryEnsureIndex({ departmentId: 1 });
this.tryEnsureIndex({ departmentEnabled: 1 });
this.tryEnsureIndex({ agentId: 1 });
this.tryEnsureIndex({ username: 1 });
}
findByDepartmentId(departmentId) {
return this.find({ departmentId });
}
findByAgentId(agentId) {
return this.find({ agentId });
}
findOneByAgentIdAndDepartmentId(agentId, departmentId) {
return this.findOne({ agentId, departmentId });
}
saveAgent(agent) {
return this.upsert(
{
agentId: agent.agentId,
departmentId: agent.departmentId,
},
{
$set: {
username: agent.username,
departmentEnabled: agent.departmentEnabled,
count: parseInt(agent.count),
order: parseInt(agent.order),
},
},
);
}
removeByAgentId(agentId) {
this.remove({ agentId });
}
removeByDepartmentIdAndAgentId(departmentId, agentId) {
this.remove({ departmentId, agentId });
}
getNextAgentForDepartment(departmentId, ignoreAgentId, extraQuery) {
const agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return;
}
const onlineUsers = Users.findOnlineUserFromList(_.pluck(agents, 'username'));
const onlineUsernames = _.pluck(onlineUsers.fetch(), 'username');
// get fully booked agents, to ignore them from the query
const currentUnavailableAgents = Promise.await(Users.getUnavailableAgents(departmentId, extraQuery)).map((u) => u.username);
const query = {
departmentId,
username: {
$in: onlineUsernames,
$nin: currentUnavailableAgents,
},
...(ignoreAgentId && { agentId: { $ne: ignoreAgentId } }),
};
const sort = {
count: 1,
order: 1,
username: 1,
};
const update = {
$inc: {
count: 1,
},
};
const collectionObj = this.model.rawCollection();
const agent = Promise.await(collectionObj.findOneAndUpdate(query, update, { sort, returnNewDocument: 'after' }));
if (agent && agent.value) {
return {
agentId: agent.value.agentId,
username: agent.value.username,
};
}
return null;
}
checkOnlineForDepartment(departmentId) {
const agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return false;
}
const onlineUser = Users.findOneOnlineAgentByUserList(_.pluck(agents, 'username'));
return Boolean(onlineUser);
}
getOnlineForDepartment(departmentId) {
const agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return;
}
const onlineUsers = Users.findOnlineUserFromList(_.pluck(agents, 'username'));
const onlineUsernames = _.pluck(onlineUsers.fetch(), 'username');
const query = {
departmentId,
username: {
$in: onlineUsernames,
},
};
return this.find(query);
}
getBotsForDepartment(departmentId) {
const agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return;
}
const botUsers = Users.findBotAgents(_.pluck(agents, 'username'));
const botUsernames = _.pluck(botUsers.fetch(), 'username');
const query = {
departmentId,
username: {
$in: botUsernames,
},
};
return this.find(query);
}
async getNextBotForDepartment(departmentId, ignoreAgentId) {
const agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return;
}
const botUsers = Users.findBotAgents(_.pluck(agents, 'username'));
const botUsernames = _.pluck(botUsers.fetch(), 'username');
const query = {
departmentId,
username: {
$in: botUsernames,
},
...(ignoreAgentId && { agentId: { $ne: ignoreAgentId } }),
};
const sort = {
count: 1,
order: 1,
username: 1,
};
const update = {
$inc: {
count: 1,
},
};
const bot = await this.model.rawCollection().findOneAndUpdate(query, update, { sort, returnNewDocument: 'after' });
if (bot && bot.value) {
return {
agentId: bot.value.agentId,
username: bot.value.username,
};
}
return null;
}
findUsersInQueue(usersList) {
const query = {};
if (!_.isEmpty(usersList)) {
query.username = {
$in: usersList,
};
}
const options = {
sort: {
departmentId: 1,
count: 1,
order: 1,
username: 1,
},
};
return this.find(query, options);
}
replaceUsernameOfAgentByUserId(userId, username) {
const query = { agentId: userId };
const update = {
$set: {
username,
},
};
return this.update(query, update, { multi: true });
}
setDepartmentEnabledByDepartmentId(departmentId, departmentEnabled) {
return this.update({ departmentId }, { $set: { departmentEnabled } }, { multi: true });
}
}
export default new LivechatDepartmentAgents();

@ -231,6 +231,7 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior
if ((await LivechatDepartmentAgents.findByAgentId(agentId).count()) === 0) {
agentIdsWithoutDepartment.push(agentId);
}
// TODO: We're doing a full fledged aggregation with lookups and getting the whole array just for getting the length? :(
if (!(await LivechatDepartmentAgents.findAgentsByAgentIdAndBusinessHourId(agentId, department.businessHourId)).length) {
// eslint-disable-line no-await-in-loop
agentIdsToRemoveCurrentBusinessHour.push(agentId);

@ -287,7 +287,7 @@ export const LivechatEnterprise = {
const departmentDB = await LivechatDepartmentRaw.createOrUpdateDepartment(_id, departmentData);
if (departmentDB && departmentAgents) {
updateDepartmentAgents(departmentDB._id, departmentAgents, departmentDB.enabled);
await updateDepartmentAgents(departmentDB._id, departmentAgents, departmentDB.enabled);
}
return departmentDB;

@ -1,8 +1,12 @@
import type { ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
import { registerModel } from '@rocket.chat/models';
import { trashCollection } from '../../../../../server/database/trash';
import { db } from '../../../../../server/database/utils';
import { LivechatDepartmentAgentsRaw } from '../../../../../server/models/raw/LivechatDepartmentAgents';
import { overwriteClassOnLicense } from '../../../license/server';
overwriteClassOnLicense('livechat-enterprise', LivechatDepartmentAgentsRaw, {
findAgentsByAgentIdAndBusinessHourId(agentId: string, businessHourId: string): Promise<Record<string, any>> {
class LivechatDepartmentAgents extends LivechatDepartmentAgentsRaw {
findAgentsByAgentIdAndBusinessHourId(agentId: string, businessHourId: string): Promise<ILivechatDepartmentAgents[]> {
const match = {
$match: { agentId },
};
@ -22,7 +26,8 @@ overwriteClassOnLicense('livechat-enterprise', LivechatDepartmentAgentsRaw, {
};
const withBusinessHourId = { $match: { 'departments.businessHourId': businessHourId } };
const project = { $project: { departments: 0 } };
const model = this as unknown as LivechatDepartmentAgentsRaw;
return model.col.aggregate([match, lookup, unwind, withBusinessHourId, project]).toArray();
},
});
return this.col.aggregate<ILivechatDepartmentAgents>([match, lookup, unwind, withBusinessHourId, project]).toArray();
}
}
registerModel('ILivechatDepartmentAgents', new LivechatDepartmentAgents(db, trashCollection));

@ -139,7 +139,7 @@ export class LivechatDepartmentAgentsRaw extends BaseRaw<ILivechatDepartmentAgen
return this.find({ departmentId: { $in: departmentIds } }, options);
}
findAgentsByAgentIdAndBusinessHourId(_agentId: string, _businessHourId: string): [] {
async findAgentsByAgentIdAndBusinessHourId(_agentId: string, _businessHourId: string): Promise<ILivechatDepartmentAgents[]> {
return [];
}

@ -57,7 +57,7 @@ export interface ILivechatDepartmentAgentsModel extends IBaseModel<ILivechatDepa
): FindCursor<ILivechatDepartmentAgents> | FindCursor<P>;
findByDepartmentIds(departmentIds: string[], options?: Record<string, any>): FindCursor<ILivechatDepartmentAgents>;
findAgentsByAgentIdAndBusinessHourId(_agentId: string, _businessHourId: string): [];
findAgentsByAgentIdAndBusinessHourId(_agentId: string, _businessHourId: string): Promise<ILivechatDepartmentAgents[]>;
setDepartmentEnabledByDepartmentId(departmentId: string, departmentEnabled: boolean): Promise<Document | UpdateResult>;
removeByDepartmentId(departmentId: string): Promise<DeleteResult>;
findByDepartmentId(departmentId: string): FindCursor<ILivechatDepartmentAgents>;

Loading…
Cancel
Save