Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW][ENTERPRISE] Omnichannel Last-Chatted Agent Preferred option #17666

Merged
merged 18 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
66eb7a8
Add new setting to get the last agent chatted.
renatobecker May 13, 2020
a447b90
Store Visitor last agent info.
renatobecker May 14, 2020
8b2f018
Check room object before get the servedBy property.
renatobecker May 14, 2020
28a2172
Route the conversation to another agent when the last agent is not av…
renatobecker May 16, 2020
14379a5
Merge branch 'develop' into omnichannel/last-chatted-agent-preferred
renatobecker May 16, 2020
197fb4e
Call checkWaitingQueue when the max. number of chats per agent is rea…
renatobecker May 18, 2020
395bd88
Merge branch 'develop' into omnichannel/last-chatted-agent-preferred
renatobecker May 18, 2020
abcfb35
Merge branch 'omnichannel/last-chatted-agent-preferred' of https://gi…
renatobecker May 18, 2020
a57f714
Merge branch 'develop' into omnichannel/last-chatted-agent-preferred
renatobecker May 18, 2020
817323f
Merge branch 'develop' into omnichannel/last-chatted-agent-preferred
renatobecker May 20, 2020
82afdb9
Unified setting name and translations.
renatobecker May 20, 2020
4c3a1a2
Fix file imports.
renatobecker May 20, 2020
31cc1f6
Fix review requests.
renatobecker May 20, 2020
dce6cf4
Merge branch 'develop' into omnichannel/last-chatted-agent-preferred
renatobecker May 20, 2020
03bef15
Add/Removed callback event handlers based on the setting.
renatobecker May 21, 2020
3ada422
Merge branch 'develop' into omnichannel/last-chatted-agent-preferred
renatobecker May 21, 2020
51d0e87
Normalize agent payload before saving.
renatobecker May 21, 2020
50f5821
Merge branch 'develop' into omnichannel/last-chatted-agent-preferred
renatobecker May 21, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@ export const Livechat = {
}

if (room == null) {
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 (!agent && !guest.department) {
if (!defaultAgent && !guest.department) {
const department = this.getRequiredDepartment();

if (department) {
Expand All @@ -127,7 +128,7 @@ export const Livechat = {
}

// delegate room creation to QueueManager
room = await QueueManager.requestRoom({ guest, message, roomInfo, agent, extraData });
room = await QueueManager.requestRoom({ guest, message, roomInfo, agent: defaultAgent, extraData });
newRoom = true;
}

Expand Down
2 changes: 1 addition & 1 deletion app/livechat/server/lib/RoutingManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export const RoutingManager = {
LivechatInquiry.takeInquiry(_id);
const inq = this.assignAgent(inquiry, agent);

callbacks.run('livechat.afterTakeInquiry', inq);
callbacks.runAsync('livechat.afterTakeInquiry', inq, agent);

return LivechatRooms.findOneById(rid);
},
Expand Down
16 changes: 14 additions & 2 deletions app/models/server/models/LivechatInquiry.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class LivechatInquiry extends Base {
_id: inquiryId,
}, {
$set: { status: 'taken' },
$unset: { defaultAgent: 1 },
});
}

Expand All @@ -62,11 +63,14 @@ export class LivechatInquiry extends Base {
/*
* mark inquiry as queued
*/
queueInquiry(inquiryId) {
queueInquiry(inquiryId, defaultAgent) {
return this.update({
_id: inquiryId,
}, {
$set: { status: 'queued' },
$set: {
status: 'queued',
...defaultAgent && { defaultAgent },
},
});
}

Expand Down Expand Up @@ -180,6 +184,14 @@ export class LivechatInquiry extends Base {
return collectionObj.aggregate(aggregate).toArray();
}

removeDefaultAgentById(inquiryId) {
this.update({
renatobecker marked this conversation as resolved.
Show resolved Hide resolved
_id: inquiryId,
}, {
$unset: { defaultAgent: 1 },
});
}

/*
* remove the inquiry by roomId
*/
Expand Down
12 changes: 12 additions & 0 deletions app/models/server/models/LivechatRooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,18 @@ export class LivechatRooms extends Base {
return this.findOne(query, options);
}

findLastServedAndClosedByVisitorToken(visitorToken, options = {}) {
renatobecker marked this conversation as resolved.
Show resolved Hide resolved
const query = {
t: 'l',
'v.token': visitorToken,
closedAt: { $exists: true },
servedBy: { $exists: true },
};

options.sort = { closedAt: -1 };
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
return this.findOne(query, options);
}

findOneByVisitorToken(visitorToken, fields) {
const options = {};

Expand Down
14 changes: 14 additions & 0 deletions app/models/server/models/LivechatVisitors.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ export class LivechatVisitors extends Base {
return this.update(query, update);
}

updateLastAgentByToken(token, lastAgent) {
const query = {
token,
};

const update = {
$set: {
lastAgent,
},
};

return this.update(query, update);
}

/**
* Find a visitor by their phone number
* @return {object} User from db
Expand Down
4 changes: 2 additions & 2 deletions app/models/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ export class Users extends Base {
return this.findOne(query);
}

findOneOnlineAgentByUsername(username) {
findOneOnlineAgentByUsername(username, options) {
const query = queryStatusAgentOnline({ username });

return this.findOne(query);
return this.findOne(query, options);
}

findOneOnlineAgentById(_id) {
Expand Down
4 changes: 2 additions & 2 deletions ee/app/livechat-enterprise/server/hooks/beforeRoutingChat.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { settings } from '../../../../../app/settings';
import { LivechatInquiry } from '../../../../../app/models/server';
import { dispatchInquiryPosition, checkWaitingQueue } from '../lib/Helper';

callbacks.add('livechat.beforeRouteChat', async (inquiry) => {
callbacks.add('livechat.beforeRouteChat', async (inquiry, agent) => {
if (!settings.get('Livechat_waiting_queue')) {
return inquiry;
}
Expand All @@ -18,7 +18,7 @@ callbacks.add('livechat.beforeRouteChat', async (inquiry) => {
return inquiry;
}

LivechatInquiry.queueInquiry(_id);
LivechatInquiry.queueInquiry(_id, agent);

const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ _id });
if (inq) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ callbacks.add('livechat.checkAgentBeforeTakeInquiry', async (agent, inquiry) =>

const { queueInfo: { chats = 0 } = {} } = user;
if (maxNumberSimultaneousChat <= chats) {
callbacks.run('livechat.onMaxNumberSimultaneousChatsReached', inquiry, agent);

if (!RoutingManager.getConfig().autoAssignAgent) {
throw new Meteor.Error('error-max-number-simultaneous-chats-reached', 'Not allowed');
}

return null;
}
return agent;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { callbacks } from '../../../../../app/callbacks';
import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager';
import { settings } from '../../../../../app/settings';
renatobecker marked this conversation as resolved.
Show resolved Hide resolved
import { LivechatRooms, LivechatVisitors, Users } from '../../../../../app/models/server';

callbacks.add('livechat.checkDefaultAgentOnNewRoom', (defaultAgent, defaultGuest) => {
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
if (defaultAgent || !defaultGuest) {
return defaultAgent;
}

if (!RoutingManager.getConfig().autoAssignAgent) {
return defaultAgent;
}

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

const { _id: guestId } = defaultGuest;
const guest = LivechatVisitors.findOneById(guestId, { fields: { lastAgent: 1 } });
const { lastAgent: { username: usernameByVisitor } = {}, token } = guest;
renatobecker marked this conversation as resolved.
Show resolved Hide resolved

const lastGuestAgent = usernameByVisitor && Users.findOneOnlineAgentByUsername(usernameByVisitor, { fields: { _id: 1, username: 1 } });
if (lastGuestAgent) {
return lastGuestAgent;
}

const room = LivechatRooms.findLastServedAndClosedByVisitorToken(token, { fields: { servedBy: 1 } });
if (!room || !room.servedBy) {
return defaultAgent;
}

const { servedBy: { username: usernameByRoom } } = room;
const lastRoomAgent = Users.findOneOnlineAgentByUsername(usernameByRoom, { fields: { _id: 1, username: 1 } });
return lastRoomAgent || defaultAgent;
}, callbacks.priority.MEDIUM, 'livechat-check-default-agent-new-room');
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { callbacks } from '../../../../../app/callbacks';
import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager';
import { settings } from '../../../../../app/settings';
renatobecker marked this conversation as resolved.
Show resolved Hide resolved
import { LivechatInquiry } from '../../../../../app/models/server';
import { checkWaitingQueue } from '../lib/Helper';

callbacks.add('livechat.onMaxNumberSimultaneousChatsReached', (inquiry, agent) => {
if (!inquiry || !inquiry.defaultAgent) {
return inquiry;
}

if (!RoutingManager.getConfig().autoAssignAgent) {
return inquiry;
}

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

const { _id, defaultAgent, department } = inquiry;

LivechatInquiry.removeDefaultAgentById(_id);

const { _id: defaultAgentId } = defaultAgent;
const { agentId } = agent;

if (defaultAgentId === agentId) {
checkWaitingQueue(department);
}

return LivechatInquiry.findOneById(_id);
sampaiodiego marked this conversation as resolved.
Show resolved Hide resolved
}, callbacks.priority.MEDIUM, 'livechat-on-max-number-simultaneous-chats-reached');
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { callbacks } from '../../../../../app/callbacks';
import { settings } from '../../../../../app/settings';
renatobecker marked this conversation as resolved.
Show resolved Hide resolved
import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager';
import { LivechatVisitors } from '../../../../../app/models/server';

callbacks.add('livechat.afterTakeInquiry', (inquiry, agent) => {
if (!inquiry || !agent) {
return inquiry;
}

if (!RoutingManager.getConfig().autoAssignAgent) {
return inquiry;
}

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

const { v: { token } = {} } = inquiry;
if (!token) {
return inquiry;
}

const { agentId: _id, username } = agent;
LivechatVisitors.updateLastAgentByToken(token, { _id, username, ts: new Date() });

return inquiry;
}, callbacks.priority.MEDIUM, 'livechat-save-default-agent-after-take-inquiry');
3 changes: 3 additions & 0 deletions ee/app/livechat-enterprise/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ import './hooks/beforeNewInquiry';
import './hooks/beforeNewRoom';
import './hooks/beforeRoutingChat';
import './hooks/checkAgentBeforeTakeInquiry';
import './hooks/checkDefaultAgentOnNewRoom';
import './hooks/onCheckRoomParamsApi';
import './hooks/onLoadConfigApi';
import './hooks/onMaxNumberSimultaneousChatsReached';
import './hooks/onSetUserStatusLivechat';
import './hooks/onCloseLivechat';
import './hooks/onSaveVisitorInfo';
import './hooks/saveDefaultAgentAfterTakeInquiry';
import './lib/routing/LoadBalancing';
import { onLicense } from '../../license/server';

Expand Down
12 changes: 11 additions & 1 deletion ee/app/livechat-enterprise/server/lib/Helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,23 @@ export const dispatchWaitingQueueStatus = async (department) => {
});
};

const normalizeDefaultAgent = (agent) => {
if (!agent) {
return;
}

const { _id: agentId, username } = agent;
return { agentId, username };
};

const processWaitingQueue = async (department) => {
const inquiry = LivechatInquiry.getNextInquiryQueued(department);
if (!inquiry) {
return;
}

const room = await RoutingManager.delegateInquiry(inquiry);
const { defaultAgent } = inquiry;
const room = await RoutingManager.delegateInquiry(inquiry, normalizeDefaultAgent(defaultAgent));

const propagateAgentDelegated = Meteor.bindEnvironment((rid, agentId) => {
dispatchAgentDelegated(rid, agentId);
Expand Down
7 changes: 7 additions & 0 deletions ee/app/livechat-enterprise/server/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,12 @@ export const createSettings = () => {
enableQuery: { _id: 'Livechat_auto_close_abandoned_rooms', value: true },
});

settings.add('Livechat_last_chatted_agent_routing', false, {
type: 'boolean',
group: 'Omnichannel',
section: 'Routing',
enableQuery: { _id: 'Livechat_Routing_Method', value: { $ne: 'Manual_Selection' } },
});

Settings.addOptionValueById('Livechat_Routing_Method', { key: 'Load_Balancing', i18nLabel: 'Load_Balancing' });
};
2 changes: 2 additions & 0 deletions ee/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"List_of_departments_for_forward": "List of departments allowed for forwarding (Optional)",
"List_of_departments_for_forward_description": "Allow to set a restricted list of departments that can receive chats from this department",
"Livechat_abandoned_rooms_closed_custom_message": "Custom message when room is automatically closed by visitor inactivity",
"Livechat_last_chatted_agent_routing": "Last-Chatted Agent Preferred",
"Livechat_last_chatted_agent_routing_Description": "The Last-Chatted Agent setting allocates chats to the agent who previously interacted with the same visitor if the agent is available when the chat starts.",
"Livechat_Monitors": "Monitors",
"Livechat_monitors": "Livechat monitors",
"Max_number_of_chats_per_agent": "Max. number of simultaneous chats",
Expand Down
2 changes: 2 additions & 0 deletions ee/i18n/pt-BR.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"List_of_departments_for_forward": "Lista de departamentos permitidos para o encaminhamento(Opcional).",
"List_of_departments_for_forward_description": "Permite definir uma lista restrita de departamentos que podem receber conversas desse departamento.",
"Livechat_abandoned_rooms_closed_custom_message": "Mensagem customizada para usar quando a sala for automaticamente fechada por abandono do visitante",
"Livechat_last_chatted_agent_routing": "Agente preferido pela última conversa",
"Livechat_last_chatted_agent_routing_Description": "Agente preferido pela última conversa aloca bate-papos para o agente que interagiu anteriormente com o mesmo visitante, caso o agente esteja disponível quando o bate-papo for iniciado.",
"Livechat_Monitors": "Monitores",
"Livechat_monitors": "Monitores de Livechat",
"Max_number_of_chats_per_agent": "Número máximo de atendimentos simultâneos",
Expand Down