Skip to content

Commit

Permalink
[Patch] [EE] Improve Forwarding Department behaviour with Waiting que…
Browse files Browse the repository at this point in the history
…ue feature (#22077)

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

(cherry picked from commit 2b50040)

* add waiting queue feature enabled check

(cherry picked from commit d9fe3e3)

* Refactor

(cherry picked from commit 1b5c973)

* Fix forwarding of agents not working

(cherry picked from commit b4b3c3d)

* Apply suggestions from code review

Co-authored-by: Renato Becker <renato.augusto.becker@gmail.com>
(cherry picked from commit 0c6f7ec)

* Handle transfer api response properly on client + refactor

(cherry picked from commit d750c59)

* Avoid passing unnecessary params

(cherry picked from commit 53eec95)

* Remove throw error.

(cherry picked from commit deccab9)

* Improve throw message logic.

(cherry picked from commit 85b015d)

* Fix conflicts

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

(cherry picked from commit d49c8fe)

* up fuselage version

* add string-helpers package

* [EE] Improve Forwarding Department behaviour with Waiting queue feature (#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>

Co-authored-by: Renato Becker <renato.augusto.becker@gmail.com>
Co-authored-by: Tiago Evangelista Pinto <tiago.evangelista@rocket.chat>
  • Loading branch information
3 people authored May 19, 2021
1 parent a1d4be4 commit 29dac28
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 85 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
6 changes: 3 additions & 3 deletions client/components/GenericModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box, Button, ButtonGroup, Icon, Modal, ButtonProps } from '@rocket.chat/fuselage';
import React, { FC } from 'react';
import { Box, Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage';
import React, { FC, ComponentProps } from 'react';

import { useTranslation } from '../contexts/TranslationContext';
import { withDoNotAskAgain, RequiredModalProps } from './withDoNotAskAgain';
Expand All @@ -24,7 +24,7 @@ const iconMap = {
success: 'check',
};

const getButtonProps = (variant: VariantType): ButtonProps => {
const getButtonProps = (variant: VariantType): ComponentProps<typeof Button> => {
switch (variant) {
case 'danger':
return { primary: true, danger: true };
Expand Down
6 changes: 3 additions & 3 deletions client/components/Page/PageScrollableContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Box, ScrollableProps } from '@rocket.chat/fuselage';
import React, { forwardRef } from 'react';
import { Box, Scrollable } from '@rocket.chat/fuselage';
import React, { forwardRef, ComponentProps } from 'react';

import ScrollableContentWrapper, { CustomScrollbarsProps } from '../ScrollableContentWrapper';

type PageScrollableContentProps = {
onScrollContent?: ScrollableProps['onScrollContent'];
onScrollContent?: ComponentProps<typeof Scrollable>['onScrollContent'];
};

const PageScrollableContent = forwardRef<HTMLElement, PageScrollableContentProps>(
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');
2 changes: 1 addition & 1 deletion ee/app/livechat-enterprise/server/methods/resumeOnHold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ Meteor.methods({
throw new Meteor.Error('room-closed', 'Room is not OnHold', { method: 'livechat:resumeOnHold' });
}

const { servedBy: { _id: agentId, username } } = room;

const inquiry = LivechatInquiry.findOneByRoomId(roomId, {});
if (!inquiry) {
throw new Meteor.Error('inquiry-not-found', 'Error! No inquiry found for this room', { method: 'livechat:resumeOnHold' });
}

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
Loading

0 comments on commit 29dac28

Please sign in to comment.