Refactor few methods to improve Omnichannel flow (#22321)

pull/22369/head^2
Guilherme Gazzo 5 years ago committed by GitHub
parent 79ee45a02b
commit d70f3158aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/livechat/imports/server/rest/upload.js
  2. 16
      app/livechat/server/api/lib/livechat.js
  3. 24
      app/livechat/server/api/v1/config.js
  4. 6
      app/livechat/server/api/v1/room.js
  5. 119
      app/livechat/server/lib/Livechat.js
  6. 4
      app/models/server/models/LivechatDepartment.js
  7. 16
      app/models/server/models/LivechatDepartmentAgents.js
  8. 65
      app/models/server/models/Users.js
  9. 5
      ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js

@ -3,7 +3,7 @@ import filesize from 'filesize';
import { settings } from '../../../../settings'; import { settings } from '../../../../settings';
import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models'; import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models';
import { fileUploadIsValidContentType } from '../../../../utils'; import { fileUploadIsValidContentType } from '../../../../utils/server';
import { FileUpload } from '../../../../file-upload'; import { FileUpload } from '../../../../file-upload';
import { API } from '../../../../api/server'; import { API } from '../../../../api/server';
import { getUploadFormData } from '../../../../api/server/lib/getUploadFormData'; import { getUploadFormData } from '../../../../api/server/lib/getUploadFormData';

@ -1,8 +1,7 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random'; import { Random } from 'meteor/random';
import _ from 'underscore';
import { LivechatRooms, LivechatVisitors, LivechatDepartment, LivechatTrigger } from '../../../../models'; import { LivechatRooms, LivechatVisitors, LivechatDepartment, LivechatTrigger, EmojiCustom } from '../../../../models/server';
import { Livechat } from '../../lib/Livechat'; import { Livechat } from '../../lib/Livechat';
import { callbacks } from '../../../../callbacks/server'; import { callbacks } from '../../../../callbacks/server';
import { normalizeAgent } from '../../lib/Helper'; import { normalizeAgent } from '../../lib/Helper';
@ -12,11 +11,13 @@ export function online(department) {
} }
export function findTriggers() { export function findTriggers() {
return LivechatTrigger.findEnabled().fetch().map((trigger) => _.pick(trigger, '_id', 'actions', 'conditions', 'runOnce')); return LivechatTrigger.findEnabled().fetch().map(({ _id, actions, conditions, runOnce }) => ({ _id, actions, conditions, runOnce }));
} }
export function findDepartments() { export function findDepartments() {
return LivechatDepartment.findEnabledWithAgents().fetch().map((department) => _.pick(department, '_id', 'name', 'showOnRegistration', 'showOnOfflineForm')); return LivechatDepartment.findEnabledWithAgents({
_id: 1, name: 1, showOnRegistration: 1, showOnOfflineForm: 1,
}).fetch().map(({ _id, name, showOnRegistration, showOnOfflineForm }) => ({ _id, name, showOnRegistration, showOnOfflineForm }));
} }
export function findGuest(token) { export function findGuest(token) {
@ -57,13 +58,10 @@ export function findOpenRoom(token, departmentId) {
}, },
}; };
let room;
const rooms = departmentId ? LivechatRooms.findOpenByVisitorTokenAndDepartmentId(token, departmentId, options).fetch() : LivechatRooms.findOpenByVisitorToken(token, options).fetch(); const rooms = departmentId ? LivechatRooms.findOpenByVisitorTokenAndDepartmentId(token, departmentId, options).fetch() : LivechatRooms.findOpenByVisitorToken(token, options).fetch();
if (rooms && rooms.length > 0) { if (rooms && rooms.length > 0) {
room = rooms[0]; return rooms[0];
} }
return room;
} }
export function getRoom({ guest, rid, roomInfo, agent, extraParams }) { export function getRoom({ guest, rid, roomInfo, agent, extraParams }) {
@ -93,7 +91,7 @@ export function settings() {
const triggers = findTriggers(); const triggers = findTriggers();
const departments = findDepartments(); const departments = findDepartments();
const sound = `${ Meteor.absoluteUrl() }sounds/chime.mp3`; const sound = `${ Meteor.absoluteUrl() }sounds/chime.mp3`;
const emojis = Meteor.call('listEmojiCustom'); const emojis = EmojiCustom.find().fetch();
return { return {
enabled: initSettings.Livechat_enabled, enabled: initSettings.Livechat_enabled,
settings: { settings: {

@ -1,7 +1,8 @@
import { Match, check } from 'meteor/check'; import { Match, check } from 'meteor/check';
import { API } from '../../../../api/server'; import { API } from '../../../../api/server';
import { findGuest, settings, online, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat';
import { settings, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat';
API.v1.addRoute('livechat/config', { API.v1.addRoute('livechat/config', {
get() { get() {
@ -10,28 +11,25 @@ API.v1.addRoute('livechat/config', {
token: Match.Maybe(String), token: Match.Maybe(String),
department: Match.Maybe(String), department: Match.Maybe(String),
}); });
const enabled = Livechat.enabled();
const config = settings(); if (!enabled) {
if (!config.enabled) {
return API.v1.success({ config: { enabled: false } }); return API.v1.success({ config: { enabled: false } });
} }
const config = settings();
const { token, department } = this.queryParams; const { token, department } = this.queryParams;
const status = online(department); const status = Livechat.online(department);
const guest = token && findGuest(token); const guest = token && Livechat.findGuest(token);
let room; const room = guest && findOpenRoom(token);
let agent; const agent = guest && room && room.servedBy && findAgent(room.servedBy._id);
if (guest) {
room = findOpenRoom(token);
agent = room && room.servedBy && findAgent(room.servedBy._id);
}
const extra = Promise.await(getExtraConfigInfo(room)); const extra = Promise.await(getExtraConfigInfo(room));
const { config: extraConfig = {} } = extra || {}; const { config: extraConfig = {} } = extra || {};
Object.assign(config, { online: status, guest, room, agent }, { ...extraConfig });
return API.v1.success({ config }); return API.v1.success({ config: { ...config, online: status, guest, room, agent, ...extraConfig } });
} catch (e) { } catch (e) {
return API.v1.failure(e); return API.v1.failure(e);
} }

@ -36,7 +36,7 @@ API.v1.addRoute('livechat/room', {
const agentObj = agentId && findAgent(agentId); const agentObj = agentId && findAgent(agentId);
if (agentObj) { if (agentObj) {
const { username } = agentObj; const { username } = agentObj;
agent = Object.assign({}, { agentId, username }); agent = { agentId, username };
} }
const rid = roomId || Random.id(); const rid = roomId || Random.id();
@ -199,12 +199,12 @@ API.v1.addRoute('livechat/room.visitor', { authRequired: true }, {
throw new Meteor.Error('invalid-visitor'); throw new Meteor.Error('invalid-visitor');
} }
let room = LivechatRooms.findOneById(rid, { _id: 1 }); let room = LivechatRooms.findOneById(rid, { _id: 1 }); // TODO: check _id
if (!room) { if (!room) {
throw new Meteor.Error('invalid-room'); throw new Meteor.Error('invalid-room');
} }
const { v: { _id: roomVisitorId } = {} } = room; const { v: { _id: roomVisitorId } = {} } = room; // TODO: v it will be undefined
if (roomVisitorId !== oldVisitorId) { if (roomVisitorId !== oldVisitorId) {
throw new Meteor.Error('invalid-room-visitor'); throw new Meteor.Error('invalid-room-visitor');
} }

@ -13,8 +13,8 @@ import UAParser from 'ua-parser-js';
import { QueueManager } from './QueueManager'; import { QueueManager } from './QueueManager';
import { RoutingManager } from './RoutingManager'; import { RoutingManager } from './RoutingManager';
import { Analytics } from './Analytics'; import { Analytics } from './Analytics';
import { settings } from '../../../settings'; import { settings } from '../../../settings/server';
import { callbacks } from '../../../callbacks'; import { callbacks } from '../../../callbacks/server';
import { import {
Users, Users,
LivechatRooms, LivechatRooms,
@ -27,7 +27,7 @@ import {
LivechatCustomField, LivechatCustomField,
LivechatVisitors, LivechatVisitors,
LivechatInquiry, LivechatInquiry,
} from '../../../models'; } from '../../../models/server';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
import { addUserRoles, hasPermission, hasRole, removeUserFromRoles, canAccessRoom } from '../../../authorization'; import { addUserRoles, hasPermission, hasRole, removeUserFromRoles, canAccessRoom } from '../../../authorization';
import * as Mailer from '../../../mailer'; import * as Mailer from '../../../mailer';
@ -50,6 +50,19 @@ export const Livechat = {
}, },
}), }),
findGuest(token) {
return LivechatVisitors.getVisitorByToken(token, {
fields: {
name: 1,
username: 1,
token: 1,
visitorEmails: 1,
department: 1,
},
});
},
online(department) { online(department) {
if (settings.get('Livechat_accept_chats_with_no_agents')) { if (settings.get('Livechat_accept_chats_with_no_agents')) {
return true; return true;
@ -57,13 +70,12 @@ export const Livechat = {
if (settings.get('Livechat_assign_new_conversation_to_bot')) { if (settings.get('Livechat_assign_new_conversation_to_bot')) {
const botAgents = Livechat.getBotAgents(department); const botAgents = Livechat.getBotAgents(department);
if (botAgents && botAgents.count() > 0) { if (botAgents.count() > 0) {
return true; return true;
} }
} }
const onlineAgents = Livechat.getOnlineAgents(department); return Livechat.checkOnlineAgents(department);
return onlineAgents && onlineAgents.count() > 0;
}, },
getNextAgent(department) { getNextAgent(department) {
@ -89,6 +101,18 @@ export const Livechat = {
return Users.findOnlineAgents(); return Users.findOnlineAgents();
}, },
checkOnlineAgents(department, agent) {
if (agent?.agentId) {
return Users.checkOnlineAgents(agent.agentId);
}
if (department) {
return LivechatDepartmentAgents.checkOnlineForDepartment(department);
}
return Users.checkOnlineAgents();
},
getBotAgents(department) { getBotAgents(department) {
if (department) { if (department) {
return LivechatDepartmentAgents.getBotsForDepartment(department); return LivechatDepartmentAgents.getBotsForDepartment(department);
@ -383,8 +407,8 @@ export const Livechat = {
*/ */
Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, room); Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, room);
Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, room); Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, room);
callbacks.runAsync('livechat.closeRoom', room);
}); });
callbacks.runAsync('livechat.closeRoom', room);
return true; return true;
}, },
@ -427,6 +451,10 @@ export const Livechat = {
return LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite); return LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite);
}, },
enabled() {
return settings.get('Livechat_enabled');
},
getInitSettings() { getInitSettings() {
const rcSettings = {}; const rcSettings = {};
@ -464,9 +492,7 @@ export const Livechat = {
rcSettings[setting._id] = setting.value; rcSettings[setting._id] = setting.value;
}); });
settings.get('Livechat_history_monitor_type', (key, value) => { rcSettings.Livechat_history_monitor_type = settings.get('Livechat_history_monitor_type');
rcSettings[key] = value;
});
rcSettings.Livechat_Show_Connecting = this.showConnecting(); rcSettings.Livechat_Show_Connecting = this.showConnecting();
@ -501,8 +527,8 @@ export const Livechat = {
Meteor.defer(() => { Meteor.defer(() => {
Apps.triggerEvent(AppEvents.IPostLivechatRoomSaved, roomData._id); Apps.triggerEvent(AppEvents.IPostLivechatRoomSaved, roomData._id);
callbacks.run('livechat.saveRoom', roomData);
}); });
callbacks.runAsync('livechat.saveRoom', roomData);
if (!_.isEmpty(guestData.name)) { if (!_.isEmpty(guestData.name)) {
const { _id: rid } = roomData; const { _id: rid } = roomData;
@ -533,30 +559,31 @@ export const Livechat = {
}, },
savePageHistory(token, roomId, pageInfo) { savePageHistory(token, roomId, pageInfo) {
if (pageInfo.change === Livechat.historyMonitorType) { if (pageInfo.change !== Livechat.historyMonitorType) {
const user = Users.findOneById('rocket.cat'); return;
}
const pageTitle = pageInfo.title; const user = Users.findOneById('rocket.cat');
const pageUrl = pageInfo.location.href;
const extraData = {
navigation: {
page: pageInfo,
token,
},
};
if (!roomId) { const pageTitle = pageInfo.title;
// keep history of unregistered visitors for 1 month const pageUrl = pageInfo.location.href;
const keepHistoryMiliseconds = 2592000000; const extraData = {
extraData.expireAt = new Date().getTime() + keepHistoryMiliseconds; navigation: {
} page: pageInfo,
token,
},
};
if (!settings.get('Livechat_Visitor_navigation_as_a_message')) { if (!roomId) {
extraData._hidden = true; // keep history of unregistered visitors for 1 month
} const keepHistoryMiliseconds = 2592000000;
extraData.expireAt = new Date().getTime() + keepHistoryMiliseconds;
}
return Messages.createNavigationHistoryWithRoomIdMessageAndUser(roomId, `${ pageTitle } - ${ pageUrl }`, user, extraData); if (!settings.get('Livechat_Visitor_navigation_as_a_message')) {
extraData._hidden = true;
} }
return Messages.createNavigationHistoryWithRoomIdMessageAndUser(roomId, `${ pageTitle } - ${ pageUrl }`, user, extraData);
}, },
saveTransferHistory(room, transferData) { saveTransferHistory(room, transferData) {
@ -638,31 +665,27 @@ export const Livechat = {
throw new Meteor.Error('error-returning-inquiry', 'Error returning inquiry to the queue', { method: 'livechat:returnRoomAsInquiry' }); throw new Meteor.Error('error-returning-inquiry', 'Error returning inquiry to the queue', { method: 'livechat:returnRoomAsInquiry' });
} }
Meteor.defer(() => { callbacks.runAsync('livechat:afterReturnRoomAsInquiry', { room });
callbacks.run('livechat:afterReturnRoomAsInquiry', { room });
});
return true; return true;
}, },
sendRequest(postData, callback, trying = 1) { sendRequest(postData, callback, attempts = 10) {
if (!attempts) {
return;
}
const secretToken = settings.get('Livechat_secret_token');
const headers = { headers: { 'X-RocketChat-Livechat-Token': secretToken } };
const options = { data: postData, ...secretToken !== '' && secretToken !== undefined && { headers } };
try { try {
const options = { data: postData };
const secretToken = settings.get('Livechat_secret_token');
if (secretToken !== '' && secretToken !== undefined) {
Object.assign(options, { headers: { 'X-RocketChat-Livechat-Token': secretToken } });
}
return HTTP.post(settings.get('Livechat_webhookUrl'), options); return HTTP.post(settings.get('Livechat_webhookUrl'), options);
} catch (e) { } catch (e) {
Livechat.logger.webhook.error(`Response error on ${ trying } try ->`, e); Livechat.logger.webhook.error(`Response error on ${ 11 - attempts } try ->`, e);
// try 10 times after 10 seconds each // try 10 times after 10 seconds each
if (trying < 10) { Livechat.logger.webhook.warn('Will try again in 10 seconds ...');
Livechat.logger.webhook.warn('Will try again in 10 seconds ...'); setTimeout(Meteor.bindEnvironment(function() {
trying++; Livechat.sendRequest(postData, callback, attempts--);
setTimeout(Meteor.bindEnvironment(() => { }), 10000);
Livechat.sendRequest(postData, callback, trying);
}), 10000);
}
} }
}, },

@ -92,12 +92,12 @@ export class LivechatDepartment extends Base {
return this.remove(query); return this.remove(query);
} }
findEnabledWithAgents() { findEnabledWithAgents(fields = null) {
const query = { const query = {
numAgents: { $gt: 0 }, numAgents: { $gt: 0 },
enabled: true, enabled: true,
}; };
return this.find(query); return this.find(query, fields && { fields });
} }
findOneByIdOrName(_idOrName, options) { findOneByIdOrName(_idOrName, options) {

@ -85,9 +85,8 @@ export class LivechatDepartmentAgents extends Base {
}; };
const collectionObj = this.model.rawCollection(); const collectionObj = this.model.rawCollection();
const findAndModify = Meteor.wrapAsync(collectionObj.findAndModify, collectionObj);
const agent = findAndModify(query, sort, update); const agent = Promise.await(collectionObj.findAndModify(query, sort, update));
if (agent && agent.value) { if (agent && agent.value) {
return { return {
agentId: agent.value.agentId, agentId: agent.value.agentId,
@ -97,6 +96,19 @@ export class LivechatDepartmentAgents extends Base {
return null; return null;
} }
checkOnlineForDepartment(departmentId) {
const agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return false;
}
const onlineUser = Users.findOneOnlineAgentByUsername(_.pluck(agents, 'username'));
return Boolean(onlineUser);
}
getOnlineForDepartment(departmentId) { getOnlineForDepartment(departmentId) {
const agents = this.findByDepartmentId(departmentId).fetch(); const agents = this.findByDepartmentId(departmentId).fetch();

@ -8,23 +8,16 @@ import { Base } from './_Base';
import Subscriptions from './Subscriptions'; import Subscriptions from './Subscriptions';
import { settings } from '../../../settings/server/functions/settings'; import { settings } from '../../../settings/server/functions/settings';
const queryStatusAgentOnline = (extraFilters = {}) => { const queryStatusAgentOnline = (extraFilters = {}) => ({
if (settings.get('Livechat_enabled_when_agent_idle') === false) { status: {
extraFilters = Object.assign(extraFilters, { statusConnection: { $ne: 'away' } }); $exists: true,
} $ne: 'offline',
},
const query = { statusLivechat: 'available',
status: { roles: 'livechat-agent',
$exists: true, ...extraFilters,
$ne: 'offline', ...settings.get('Livechat_enabled_when_agent_idle') === false && { statusConnection: { $ne: 'away' } },
}, });
statusLivechat: 'available',
roles: 'livechat-agent',
...extraFilters,
};
return query;
};
export class Users extends Base { export class Users extends Base {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
@ -95,7 +88,7 @@ export class Users extends Base {
return this.findOne(query); return this.findOne(query);
} }
setOperator(_id, operator) { setOperator(_id, operator) { // TODO:: Create class Agent
const update = { const update = {
$set: { $set: {
operator, operator,
@ -105,13 +98,19 @@ export class Users extends Base {
return this.update(_id, update); return this.update(_id, update);
} }
findOnlineAgents(agentId) { checkOnlineAgents(agentId) { // TODO:: Create class Agent
const query = queryStatusAgentOnline(agentId && { _id: agentId });
return Boolean(this.findOne(query));
}
findOnlineAgents(agentId) { // TODO:: Create class Agent
const query = queryStatusAgentOnline(agentId && { _id: agentId }); const query = queryStatusAgentOnline(agentId && { _id: agentId });
return this.find(query); return this.find(query);
} }
findBotAgents(usernameList) { findBotAgents(usernameList) { // TODO:: Create class Agent
const query = { const query = {
roles: { roles: {
$all: ['bot', 'livechat-agent'], $all: ['bot', 'livechat-agent'],
@ -126,7 +125,7 @@ export class Users extends Base {
return this.find(query); return this.find(query);
} }
findOneBotAgent() { findOneBotAgent() { // TODO:: Create class Agent
const query = { const query = {
roles: { roles: {
$all: ['bot', 'livechat-agent'], $all: ['bot', 'livechat-agent'],
@ -136,19 +135,19 @@ export class Users extends Base {
return this.findOne(query); return this.findOne(query);
} }
findOneOnlineAgentByUsername(username, options) { findOneOnlineAgentByUsername(username, options) { // TODO:: Create class Agent
const query = queryStatusAgentOnline({ username }); const query = queryStatusAgentOnline({ username });
return this.findOne(query, options); return this.findOne(query, options);
} }
findOneOnlineAgentById(_id) { findOneOnlineAgentById(_id) { // TODO: Create class Agent
const query = queryStatusAgentOnline({ _id }); const query = queryStatusAgentOnline({ _id });
return this.findOne(query); return this.findOne(query);
} }
findOneAgentById(_id, options) { findOneAgentById(_id, options) { // TODO: Create class Agent
const query = { const query = {
_id, _id,
roles: 'livechat-agent', roles: 'livechat-agent',
@ -157,7 +156,7 @@ export class Users extends Base {
return this.findOne(query, options); return this.findOne(query, options);
} }
findAgents() { findAgents() { // TODO: Create class Agent
const query = { const query = {
roles: 'livechat-agent', roles: 'livechat-agent',
}; };
@ -165,7 +164,7 @@ export class Users extends Base {
return this.find(query); return this.find(query);
} }
findOnlineUserFromList(userList) { findOnlineUserFromList(userList) { // TODO: Create class Agent
const username = { const username = {
$in: [].concat(userList), $in: [].concat(userList),
}; };
@ -175,7 +174,7 @@ export class Users extends Base {
return this.find(query); return this.find(query);
} }
getNextAgent(ignoreAgentId) { getNextAgent(ignoreAgentId) { // TODO: Create class Agent
const extraFilters = { const extraFilters = {
...ignoreAgentId && { _id: { $ne: ignoreAgentId } }, ...ignoreAgentId && { _id: { $ne: ignoreAgentId } },
}; };
@ -205,7 +204,7 @@ export class Users extends Base {
return null; return null;
} }
getNextBotAgent(ignoreAgentId) { getNextBotAgent(ignoreAgentId) { // TODO: Create class Agent
const query = { const query = {
roles: { roles: {
$all: ['bot', 'livechat-agent'], $all: ['bot', 'livechat-agent'],
@ -237,7 +236,7 @@ export class Users extends Base {
return null; return null;
} }
setLivechatStatus(userId, status) { setLivechatStatus(userId, status) { // TODO: Create class Agent
const query = { const query = {
_id: userId, _id: userId,
}; };
@ -251,7 +250,7 @@ export class Users extends Base {
return this.update(query, update); return this.update(query, update);
} }
setLivechatData(userId, data = {}) { setLivechatData(userId, data = {}) { // TODO: Create class Agent
const query = { const query = {
_id: userId, _id: userId,
}; };
@ -265,15 +264,15 @@ export class Users extends Base {
return this.update(query, update); return this.update(query, update);
} }
closeOffice() { closeOffice() { // TODO: Create class Agent
this.findAgents().forEach((agent) => this.setLivechatStatus(agent._id, 'not-available')); this.findAgents().forEach((agent) => this.setLivechatStatus(agent._id, 'not-available'));
} }
openOffice() { openOffice() { // TODO: Create class Agent
this.findAgents().forEach((agent) => this.setLivechatStatus(agent._id, 'available')); this.findAgents().forEach((agent) => this.setLivechatStatus(agent._id, 'available'));
} }
getAgentInfo(agentId) { getAgentInfo(agentId) { // TODO: Create class Agent
const query = { const query = {
_id: agentId, _id: agentId,
}; };

@ -7,10 +7,9 @@ callbacks.add('livechat.onLoadConfigApi', async (options = {}) => {
const queueInfo = await getLivechatQueueInfo(room); const queueInfo = await getLivechatQueueInfo(room);
const customFields = getLivechatCustomFields(); const customFields = getLivechatCustomFields();
const config = { return {
...queueInfo && { queueInfo }, ...queueInfo && { queueInfo },
...customFields && { customFields }, ...customFields && { customFields },
...options,
}; };
return Object.assign({ config }, options);
}, callbacks.priority.MEDIUM, 'livechat-on-load-config-api'); }, callbacks.priority.MEDIUM, 'livechat-on-load-config-api');

Loading…
Cancel
Save