fix: agent role being removed upon user deactivation (#30284)

pull/30321/head
Murtaza Patrawala 2 years ago committed by GitHub
parent cada29b6ce
commit aaefe865a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .changeset/seven-jobs-tickle.md
  2. 28
      apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts
  3. 34
      apps/meteor/app/livechat/server/business-hour/Single.ts
  4. 10
      apps/meteor/app/livechat/server/hooks/afterUserActions.ts
  5. 110
      apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts
  6. 25
      apps/meteor/server/models/raw/Users.js
  7. 1
      packages/model-typings/src/models/IUsersModel.ts

@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/model-typings": patch
---
fix: agent role being removed upon user deactivation

@ -1,13 +1,10 @@
import { ILivechatAgentStatus } from '@rocket.chat/core-typings';
import type { ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings';
import type { ILivechatAgentStatus, ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings';
import type { ILivechatBusinessHoursModel, IUsersModel } from '@rocket.chat/model-typings';
import { LivechatBusinessHours, Users } from '@rocket.chat/models';
import moment from 'moment-timezone';
import type { UpdateFilter } from 'mongodb';
import type { IWorkHoursCronJobsWrapper } from '../../../../server/models/raw/LivechatBusinessHours';
import { businessHourLogger } from '../lib/logger';
import { filterBusinessHoursThatMustBeOpened } from './Helper';
export interface IBusinessHourBehavior {
findHoursToCreateJobs(): Promise<IWorkHoursCronJobsWrapper[]>;
@ -61,29 +58,6 @@ export abstract class AbstractBusinessHourBehavior {
{ livechatStatusSystemModified: true },
);
}
async onNewAgentCreated(agentId: string): Promise<void> {
businessHourLogger.debug(`Executing onNewAgentCreated for agentId: ${agentId}`);
const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour();
if (!defaultBusinessHour) {
businessHourLogger.debug(`No default business hour found for agentId: ${agentId}`);
return;
}
const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]);
if (!businessHourToOpen.length) {
businessHourLogger.debug(
`No business hour to open found for agentId: ${agentId}. Default business hour is closed. Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.NOT_AVAILABLE}`,
);
await Users.setLivechatStatus(agentId, ILivechatAgentStatus.NOT_AVAILABLE);
return;
}
await Users.addBusinessHourByAgentIds([agentId], defaultBusinessHour._id);
businessHourLogger.debug(`Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.AVAILABLE}`);
}
}
export abstract class AbstractBusinessHourType {

@ -1,9 +1,10 @@
import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import { ILivechatAgentStatus, LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import { LivechatBusinessHours, Users } from '@rocket.chat/models';
import { businessHourLogger } from '../lib/logger';
import type { IBusinessHourBehavior } from './AbstractBusinessHour';
import { AbstractBusinessHourBehavior } from './AbstractBusinessHour';
import { openBusinessHourDefault } from './Helper';
import { filterBusinessHoursThatMustBeOpened, openBusinessHourDefault } from './Helper';
export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior {
async openBusinessHoursByDayAndHour(): Promise<void> {
@ -26,6 +27,35 @@ export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior imp
return openBusinessHourDefault();
}
async onNewAgentCreated(agentId: string): Promise<void> {
const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour();
if (!defaultBusinessHour) {
businessHourLogger.debug('No default business hour found for agentId', {
agentId,
});
return;
}
const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]);
if (!businessHourToOpen.length) {
businessHourLogger.debug({
msg: 'No business hours found. Moving agent to NOT_AVAILABLE status',
agentId,
newStatus: ILivechatAgentStatus.NOT_AVAILABLE,
});
await Users.setLivechatStatus(agentId, ILivechatAgentStatus.NOT_AVAILABLE);
return;
}
await Users.addBusinessHourByAgentIds([agentId], defaultBusinessHour._id);
businessHourLogger.debug({
msg: 'Business hours found. Moving agent to AVAILABLE status',
agentId,
newStatus: ILivechatAgentStatus.AVAILABLE,
});
}
afterSaveBusinessHours(): Promise<void> {
return openBusinessHourDefault();
}

@ -1,4 +1,5 @@
import type { IUser } from '@rocket.chat/core-typings';
import { type IUser } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { callbacks } from '../../../../lib/callbacks';
import { Livechat } from '../lib/Livechat';
@ -33,8 +34,11 @@ const handleAgentCreated = async (user: IUser) => {
const handleDeactivateUser = async (user: IUser) => {
if (wasAgent(user)) {
callbackLogger.debug('Removing agent', user._id);
await Livechat.removeAgent(user.username);
callbackLogger.debug({
msg: 'Removing agent extension & making agent unavailable',
userId: user._id,
});
await Users.makeAgentUnavailableAndUnsetExtension(user._id);
}
};

@ -1,12 +1,16 @@
import type { ILivechatDepartment, ILivechatBusinessHour } from '@rocket.chat/core-typings';
import { type ILivechatDepartment, type ILivechatBusinessHour, LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import { LivechatDepartment, LivechatDepartmentAgents, Users } from '@rocket.chat/models';
import moment from 'moment';
import { businessHourManager } from '../../../../../app/livechat/server/business-hour';
import type { IBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour';
import { AbstractBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour';
import { filterBusinessHoursThatMustBeOpened } from '../../../../../app/livechat/server/business-hour/Helper';
import {
filterBusinessHoursThatMustBeOpened,
filterBusinessHoursThatMustBeOpenedByDay,
} from '../../../../../app/livechat/server/business-hour/Helper';
import { settings } from '../../../../../app/settings/server';
import { isTruthy } from '../../../../../lib/isTruthy';
import { bhLogger } from '../lib/logger';
import { closeBusinessHour, openBusinessHour, removeBusinessHourByAgentIds } from './Helper';
@ -248,6 +252,108 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior
return this.UsersRepository.isAgentWithinBusinessHours(agentId);
}
async onNewAgentCreated(agentId: string): Promise<void> {
bhLogger.debug({
msg: 'Executing onNewAgentCreated for agent',
agentId,
});
await this.applyAnyOpenBusinessHourToAgent(agentId);
await Users.updateLivechatStatusBasedOnBusinessHours([agentId]);
}
private async applyAnyOpenBusinessHourToAgent(agentId: string): Promise<void> {
const currentTime = moment().utc();
const day = currentTime.format('dddd');
const allActiveBusinessHoursForEntireWeek = await this.BusinessHourRepository.findActiveBusinessHours({
projection: {
workHours: 1,
timezone: 1,
type: 1,
active: 1,
},
});
const openedBusinessHours = await filterBusinessHoursThatMustBeOpenedByDay(allActiveBusinessHoursForEntireWeek, day);
if (!openedBusinessHours.length) {
bhLogger.debug({
msg: 'Business hour status check failed for agent. No opened business hour found for the current day',
agentId,
});
return;
}
const agentDepartments = await LivechatDepartmentAgents.find(
{ departmentEnabled: true, agentId },
{ projection: { agentId: 1, departmentId: 1 } },
).toArray();
if (!agentDepartments.length) {
// check if default businessHour is active
const isDefaultBHActive = openedBusinessHours.find(({ type }) => type === LivechatBusinessHourTypes.DEFAULT);
if (isDefaultBHActive?._id) {
await Users.openAgentBusinessHoursByBusinessHourIdsAndAgentId([isDefaultBHActive._id], agentId);
bhLogger.debug({
msg: 'Business hour status check passed for agent. Found default business hour to be active',
agentId,
});
return;
}
bhLogger.debug({
msg: 'Business hour status check failed for agent. Found default business hour to be inactive',
agentId,
});
return;
}
// check if any one these departments have a opened business hour linked to it
const departments = (await LivechatDepartment.findInIds(
agentDepartments.map(({ departmentId }) => departmentId),
{ projection: { _id: 1, businessHourId: 1 } },
).toArray()) as Pick<ILivechatDepartment, '_id' | 'businessHourId'>[];
const departmentsWithActiveBH = departments.filter(
({ businessHourId }) => businessHourId && openedBusinessHours.findIndex(({ _id }) => _id === businessHourId) !== -1,
);
if (!departmentsWithActiveBH.length) {
// No opened business hour found for any of the departments connected to the agent
// check if this agent has any departments that is connected to any non-default business hour
// if no such departments found then check default BH and if it is active, then allow the agent to change service status
const hasAtLeastOneDepartmentWithNonDefaultBH = departments.some(({ businessHourId }) => {
// check if business hour is active
return businessHourId && allActiveBusinessHoursForEntireWeek.findIndex(({ _id }) => _id === businessHourId) !== -1;
});
if (!hasAtLeastOneDepartmentWithNonDefaultBH) {
const isDefaultBHActive = openedBusinessHours.find(({ type }) => type === LivechatBusinessHourTypes.DEFAULT);
if (isDefaultBHActive?._id) {
await Users.openAgentBusinessHoursByBusinessHourIdsAndAgentId([isDefaultBHActive._id], agentId);
bhLogger.debug({
msg: 'Business hour status check passed for agentId. Found default business hour to be active and agent has no departments with non-default business hours',
agentId,
});
return;
}
}
bhLogger.debug({
msg: 'Business hour status check failed for agent. No opened business hour found for any of the departments connected to the agent',
agentId,
});
return;
}
const activeBusinessHoursForAgent = departmentsWithActiveBH.map(({ businessHourId }) => businessHourId).filter(isTruthy);
await Users.openAgentBusinessHoursByBusinessHourIdsAndAgentId(activeBusinessHoursForAgent, agentId);
bhLogger.debug({
msg: `Business hour status check passed for agent. Found opened business hour for departments connected to the agent`,
activeBusinessHoursForAgent,
});
}
private async handleRemoveAgentsFromDepartments(department: Record<string, any>, agentsIds: string[], options: any): Promise<any> {
const agentIdsWithoutDepartment: string[] = [];
const agentIdsToRemoveCurrentBusinessHour: string[] = [];

@ -1,3 +1,4 @@
import { ILivechatAgentStatus } from '@rocket.chat/core-typings';
import { Subscriptions } from '@rocket.chat/models';
import { escapeRegExp } from '@rocket.chat/string-helpers';
@ -6,6 +7,8 @@ import { BaseRaw } from './BaseRaw';
const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdle) => ({
statusLivechat: 'available',
roles: 'livechat-agent',
// ignore deactivated users
active: true,
...(!isLivechatEnabledWhenAgentIdle && {
$or: [
{
@ -933,7 +936,7 @@ export class UsersRaw extends BaseRaw {
},
};
return this.updateMany(query, update);
return this.updateOne(query, update);
}
addBusinessHourByAgentIds(agentIds = [], businessHourId) {
@ -1031,6 +1034,8 @@ export class UsersRaw extends BaseRaw {
const query = {
$or: [{ openBusinessHours: { $exists: false } }, { openBusinessHours: { $size: 0 } }],
roles: 'livechat-agent',
// exclude deactivated users
active: true,
// Avoid unnecessary updates
statusLivechat: 'available',
...(Array.isArray(userIds) && userIds.length > 0 && { _id: { $in: userIds } }),
@ -1687,6 +1692,24 @@ export class UsersRaw extends BaseRaw {
return this.updateOne(query, update);
}
makeAgentUnavailableAndUnsetExtension(userId) {
const query = {
_id: userId,
roles: 'livechat-agent',
};
const update = {
$set: {
statusLivechat: ILivechatAgentStatus.NOT_AVAILABLE,
},
$unset: {
extension: 1,
},
};
return this.updateOne(query, update);
}
setLivechatData(userId, data = {}) {
// TODO: Create class Agent
const query = {

@ -258,6 +258,7 @@ export interface IUsersModel extends IBaseModel<IUser> {
getNextAgent(ignoreAgentId?: string, extraQuery?: Filter<IUser>): 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>;
setLivechatData(userId: string, data?: Record<string, any>): Promise<UpdateResult>;
closeOffice(): Promise<void>;
openOffice(): Promise<void>;

Loading…
Cancel
Save