Skip to content

Commit

Permalink
[EE] Improve Forwarding Department behaviour with Waiting queue featu…
Browse files Browse the repository at this point in the history
…re (#22043)

* [EE] Fix Forwarding Department not working with Waiting queue feature

* add waiting queue feature enabled check

* Refactor

* Fix forwarding of agents not working

* Apply suggestions from code review

Co-authored-by: Renato Becker <renato.augusto.becker@gmail.com>

* Handle transfer api response properly on client + refactor

* Avoid passing unnecessary params

* Remove throw error.

* Improve throw message logic.

* Fix on-hold queue. Methods have been removed in another PR.

* Remove all subscription from a chat placed on-hold.

Co-authored-by: Renato Becker <renato.augusto.becker@gmail.com>
  • Loading branch information
murtaza98 and renatobecker authored May 17, 2021
1 parent d86deca commit 63248e1
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 31 deletions.
1 change: 1 addition & 0 deletions app/livechat/client/views/app/tabbar/visitorForward.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Template.visitorForward.events({
const transferData = {
roomId: instance.room.get()._id,
comment: event.target.comment.value,
clientAction: true,
};

const [user] = instance.selectedAgents.get();
Expand Down
25 changes: 17 additions & 8 deletions app/livechat/server/lib/Helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Apps, AppEvents } from '../../../apps/server';
import notifications from '../../../notifications/server/lib/Notifications';
import { sendNotification } from '../../../lib/server';
import { sendMessage } from '../../../lib/server/functions/sendMessage';
import { queueInquiry } from './QueueManager';

export const allowAgentSkipQueue = (agent) => {
check(agent, Match.ObjectIncluding({
Expand Down Expand Up @@ -273,7 +274,7 @@ export const forwardRoomToAgent = async (room, transferData) => {
return false;
}

const { userId: agentId } = transferData;
const { userId: agentId, clientAction } = transferData;
const user = Users.findOneOnlineAgentById(agentId);
if (!user) {
throw new Meteor.Error('error-user-is-offline', 'User is offline', { function: 'forwardRoomToAgent' });
Expand All @@ -291,9 +292,11 @@ export const forwardRoomToAgent = async (room, transferData) => {

const { username } = user;
const agent = { agentId, username };
// There are some Enterprise features that may interrupt the fowarding process
// Remove department from inquiry to make sure the routing algorithm treat this as forwarding to agent and not as forwarding to department
inquiry.department = undefined;
// There are some Enterprise features that may interrupt the forwarding process
// Due to that we need to check whether the agent has been changed or not
const roomTaken = await RoutingManager.takeInquiry(inquiry, agent);
const roomTaken = await RoutingManager.takeInquiry(inquiry, agent, { ...clientAction && { clientAction } });
if (!roomTaken) {
return false;
}
Expand Down Expand Up @@ -356,7 +359,7 @@ export const forwardRoomToDepartment = async (room, guest, transferData) => {
throw new Meteor.Error('error-forwarding-chat-same-department', 'The selected department and the current room department are the same', { function: 'forwardRoomToDepartment' });
}

const { userId: agentId } = transferData;
const { userId: agentId, clientAction } = transferData;
if (agentId) {
let user = Users.findOneOnlineAgentById(agentId);
if (!user) {
Expand All @@ -378,26 +381,32 @@ export const forwardRoomToDepartment = async (room, guest, transferData) => {
// Fake the department to forward the inquiry - Case the forward process does not success
// the inquiry will stay in the same original department
inquiry.department = departmentId;
const roomTaken = await RoutingManager.delegateInquiry(inquiry, agent);
const roomTaken = await RoutingManager.delegateInquiry(inquiry, agent, { forwardingToDepartment: { oldDepartmentId }, ...clientAction && { clientAction } });
if (!roomTaken) {
return false;
}

const { servedBy } = roomTaken;
if (oldServedBy && servedBy && oldServedBy._id === servedBy._id) {
const { servedBy, chatQueued } = roomTaken;
if (!chatQueued && oldServedBy && servedBy && oldServedBy._id === servedBy._id) {
return false;
}

Livechat.saveTransferHistory(room, transferData);
if (oldServedBy) {
removeAgentFromSubscription(rid, oldServedBy);
}
if (servedBy) {
if (!chatQueued && servedBy) {
Messages.createUserJoinWithRoomIdAndUser(rid, servedBy);
}

updateChatDepartment({ rid, newDepartmentId: departmentId, oldDepartmentId });

if (chatQueued) {
LivechatInquiry.readyInquiry(inquiry._id);
const newInquiry = LivechatInquiry.findOneById(inquiry._id);
await queueInquiry(room, newInquiry);
}

const { token } = guest;
Livechat.setDepartmentForGuest({ token, department: departmentId });

Expand Down
7 changes: 3 additions & 4 deletions app/livechat/server/lib/RoutingManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const RoutingManager = {
return this.getMethod().getNextAgent(department, ignoreAgentId);
},

async delegateInquiry(inquiry, agent) {
async delegateInquiry(inquiry, agent, options = {}) {
const { department, rid } = inquiry;
if (!agent || (agent.username && !Users.findOneOnlineAgentByUsername(agent.username) && !allowAgentSkipQueue(agent))) {
agent = await this.getNextAgent(department);
Expand All @@ -53,7 +53,7 @@ export const RoutingManager = {
return LivechatRooms.findOneById(rid);
}

return this.takeInquiry(inquiry, agent);
return this.takeInquiry(inquiry, agent, options);
},

assignAgent(inquiry, agent) {
Expand Down Expand Up @@ -134,8 +134,7 @@ export const RoutingManager = {

agent = await callbacks.run('livechat.checkAgentBeforeTakeInquiry', { agent, inquiry, options });
if (!agent) {
await callbacks.run('livechat.onAgentAssignmentFailed', { inquiry, room, options });
return null;
return callbacks.run('livechat.onAgentAssignmentFailed', { inquiry, room, options });
}

if (room.onHold) {
Expand Down
1 change: 1 addition & 0 deletions app/livechat/server/methods/transfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Meteor.methods({
userId: Match.Optional(String),
departmentId: Match.Optional(String),
comment: Match.Optional(String),
clientAction: Match.Optional(Boolean),
});

const room = LivechatRooms.findOneById(transferData.roomId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,14 @@ const QuickActions: FC<QuickActionsProps> = ({ room, className }) => {
}
const transferData: {
roomId: string;
clientAction: boolean;
comment?: string;
departmentId?: string;
userId?: string;
} = {
roomId: rid,
comment,
clientAction: true,
};

if (departmentId) {
Expand All @@ -153,10 +155,15 @@ const QuickActions: FC<QuickActionsProps> = ({ room, className }) => {
}

try {
await forwardChat(transferData);
closeModal();
const result = await forwardChat(transferData);
if (!result) {
throw new Error(
departmentId ? t('error-no-agents-online-in-department') : t('error-forwarding-chat'),
);
}
toastr.success(t('Transferred'));
FlowRouter.go('/');
closeModal();
} catch (error) {
handleError(error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ callbacks.add('livechat.checkAgentBeforeTakeInquiry', async ({ agent, inquiry, o
const { queueInfo: { chats = 0 } = {} } = user;
if (maxNumberSimultaneousChat <= chats) {
callbacks.run('livechat.onMaxNumberSimultaneousChatsReached', inquiry);
if (options.clientAction) {
if (options.clientAction && !options.forwardingToDepartment) {
throw new Meteor.Error('error-max-number-simultaneous-chats-reached', 'Not allowed');
}

Expand Down
42 changes: 31 additions & 11 deletions ee/app/livechat-enterprise/server/hooks/onAgentAssignmentFailed.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
import { callbacks } from '../../../../../app/callbacks/server';
import { LivechatInquiry, Subscriptions, LivechatRooms } from '../../../../../app/models/server';
import { queueInquiry } from '../../../../../app/livechat/server/lib/QueueManager';
import { settings } from '../../../../../app/settings/server';

const handleOnAgentAssignmentFailed = async ({ inquiry, room }: { inquiry: any; room: any }): Promise<any> => {
if (!inquiry || !room || !room.onHold) {
const handleOnAgentAssignmentFailed = async ({ inquiry, room, options }: { inquiry: any; room: any; options: { forwardingToDepartment?: { oldDepartmentId: string; transferData: any }; clientAction?: boolean} }): Promise<any> => {
if (!inquiry || !room) {
return;
}

const { _id: roomId, servedBy } = room;
if (room.onHold) {
const { _id: roomId } = room;

const { _id: inquiryId } = inquiry;
LivechatInquiry.readyInquiry(inquiryId);
LivechatInquiry.removeDefaultAgentById(inquiryId);
LivechatRooms.removeAgentByRoomId(roomId);
if (servedBy?._id) {
Subscriptions.removeByRoomIdAndUserId(roomId, servedBy._id);
const { _id: inquiryId } = inquiry;
LivechatInquiry.readyInquiry(inquiryId);
LivechatInquiry.removeDefaultAgentById(inquiryId);
LivechatRooms.removeAgentByRoomId(roomId);
Subscriptions.removeByRoomId(roomId);
const newInquiry = LivechatInquiry.findOneById(inquiryId);

await queueInquiry(room, newInquiry);

return;
}

if (!settings.get('Livechat_waiting_queue')) {
return;
}

const newInquiry = LivechatInquiry.findOneById(inquiryId);
const { forwardingToDepartment: { oldDepartmentId } = {}, forwardingToDepartment } = options;
if (!forwardingToDepartment) {
return;
}

const { department: newDepartmentId } = inquiry;

if (!newDepartmentId || !oldDepartmentId || newDepartmentId === oldDepartmentId) {
return;
}

await queueInquiry(room, newInquiry);
room.chatQueued = true;
return room;
};

callbacks.add('livechat.onAgentAssignmentFailed', handleOnAgentAssignmentFailed, callbacks.priority.HIGH, 'livechat-agent-assignment-failed');
4 changes: 3 additions & 1 deletion ee/app/livechat-enterprise/server/lib/LivechatEnterprise.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Users } from '../../../../../app/models';
import { LivechatInquiry, OmnichannelQueue } from '../../../../../app/models/server/raw';
import LivechatUnit from '../../../models/server/models/LivechatUnit';
import LivechatTag from '../../../models/server/models/LivechatTag';
import { LivechatRooms, Messages } from '../../../../../app/models/server';
import { LivechatRooms, Subscriptions, Messages } from '../../../../../app/models/server';
import LivechatPriority from '../../../models/server/models/LivechatPriority';
import { addUserRoles, removeUserFromRoles } from '../../../../../app/authorization/server';
import { processWaitingQueue, removePriorityFromRooms, updateInquiryQueuePriority, updatePriorityInquiries, updateRoomPriorityHistory } from './Helper';
Expand Down Expand Up @@ -173,6 +173,7 @@ export const LivechatEnterprise = {
return false;
}
LivechatRooms.setOnHold(roomId);
Subscriptions.setOnHold(roomId);

Messages.createOnHoldHistoryWithRoomIdMessageAndUser(roomId, comment, onHoldBy);
Meteor.defer(() => {
Expand All @@ -190,6 +191,7 @@ export const LivechatEnterprise = {

await AutoCloseOnHoldScheduler.unscheduleRoom(roomId);
LivechatRooms.unsetAllOnHoldFieldsByRoomId(roomId);
Subscriptions.unsetOnHold(roomId);
},
};

Expand Down
5 changes: 3 additions & 2 deletions ee/app/livechat-enterprise/server/methods/resumeOnHold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';

import { LivechatRooms, LivechatInquiry, Messages, Users, LivechatVisitors } from '../../../../../app/models/server';
import { LivechatEnterprise } from '../lib/LivechatEnterprise';
import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager';
import { callbacks } from '../../../../../app/callbacks/server';

const resolveOnHoldCommentInfo = (options: { clientAction: boolean }, room: any, onHoldChatResumedBy: any): string => {
Expand Down Expand Up @@ -38,7 +38,8 @@ Meteor.methods({
throw new Meteor.Error('inquiry-not-found', 'Error! No inquiry found for this room', { method: 'livechat:resumeOnHold' });
}

LivechatEnterprise.releaseOnHoldChat(room);
const { servedBy: { _id: agentId, username } } = room;
await RoutingManager.takeInquiry(inquiry, { agentId, username }, options);

const onHoldChatResumedBy = options.clientAction ? Meteor.user() : Users.findOneById('rocket.cat');

Expand Down
4 changes: 3 additions & 1 deletion packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,7 @@
"error-essential-app-disabled": "Error: a Rocket.Chat App that is essential for this is disabled. Please contact your administrator",
"error-field-unavailable": "<strong>__field__</strong> is already in use :(",
"error-file-too-large": "File is too large",
"error-forwarding-chat": "Something went wrong while forwarding the chat, Please try again later.",
"error-forwarding-chat-same-department": "The selected department and the current room department are the same",
"error-forwarding-department-target-not-allowed": "The forwarding to the target department is not allowed.",
"error-guests-cant-have-other-roles": "Guest users can't have any other role.",
Expand Down Expand Up @@ -1672,6 +1673,7 @@
"error-message-size-exceeded": "Message size exceeds Message_MaxAllowedSize",
"error-missing-unsubscribe-link": "You must provide the [unsubscribe] link.",
"error-no-tokens-for-this-user": "There are no tokens for this user",
"error-no-agents-online-in-department": "No agents online in the department",
"error-no-message-for-unread": "There are no messages to mark unread",
"error-not-allowed": "Not allowed",
"error-not-authorized": "Not authorized",
Expand Down Expand Up @@ -4501,4 +4503,4 @@
"Your_temporary_password_is_password": "Your temporary password is <strong>[password]</strong>.",
"Your_TOTP_has_been_reset": "Your Two Factor TOTP has been reset.",
"Your_workspace_is_ready": "Your workspace is ready to use 🎉"
}
}
3 changes: 2 additions & 1 deletion packages/rocketchat-i18n/i18n/pt-BR.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,7 @@
"error-message-editing-blocked": "Edição de mensagens está bloqueada",
"error-message-size-exceeded": "O tamanho da mensagem excede Message_MaxAllowedSize",
"error-missing-unsubscribe-link": "Você deve fornecer o link para desinscrever-se: [unsubscribe].",
"error-no-agents-online-in-department": "Nenhum agente online no departamento",
"error-no-tokens-for-this-user": "Não existem tokens para este usuário",
"error-no-message-for-unread": "Não há mensagens para serem marcadas como não lidas",
"error-not-allowed": "Não permitido",
Expand Down Expand Up @@ -3661,4 +3662,4 @@
"Your_question": "A sua pergunta",
"Your_server_link": "O link do seu servidor",
"Your_workspace_is_ready": "O seu espaço de trabalho está pronto a usar 🎉"
}
}

0 comments on commit 63248e1

Please sign in to comment.