Add livechat guest pool queue method

commented out unimplemented method returnAsInquiry

removed console.logs

removed trailing white spaces and dead code

more code styling

more more code styling

fixed bug in takeInquiry, code styling

switched to single quotes

fixed names of files

translations added, type fixed, meteor.userId() used, multi agent take inquiry bug fixed

removed dead code

fixes

string switched to single quote

added return room as inquiry functionality

redirects to home when livechat returned and other code fixes

removed trailing whitespace

Change return to inquiry button icon

Fix eslint errors
pull/3323/head^2
Alec Troemel 10 years ago committed by Diego Sampaio
parent 299163ee95
commit ea7483bcac
  1. 6
      packages/rocketchat-lib/i18n/en.i18n.json
  2. 1
      packages/rocketchat-livechat/client/collections/LivechatInquiry.js
  3. 4
      packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html
  4. 24
      packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.js
  5. 20
      packages/rocketchat-livechat/client/views/sideNav/livechat.html
  6. 26
      packages/rocketchat-livechat/client/views/sideNav/livechat.js
  7. 10
      packages/rocketchat-livechat/config.js
  8. 8
      packages/rocketchat-livechat/package.js
  9. 57
      packages/rocketchat-livechat/server/lib/Livechat.js
  10. 114
      packages/rocketchat-livechat/server/lib/QueueMethods.js
  11. 20
      packages/rocketchat-livechat/server/methods/returnAsInquiry.js
  12. 40
      packages/rocketchat-livechat/server/methods/takeInquiry.js
  13. 27
      packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js
  14. 45
      packages/rocketchat-livechat/server/models/LivechatInquiry.js
  15. 12
      packages/rocketchat-livechat/server/publications/livechatInquiries.js
  16. 3
      packages/rocketchat-ui-sidenav/package.js
  17. 11
      packages/rocketchat-ui-sidenav/side-nav/livechatInquiryItem.html
  18. 65
      packages/rocketchat-ui-sidenav/side-nav/livechatInquiryItem.js

@ -629,6 +629,11 @@
"Livechat_title" : "Livechat Title",
"Livechat_title_color" : "Livechat Title Background Color",
"Livechat_Users" : "Livechat Users",
"Livechat_Routing_Method" : "Livechat Routing Method",
"Livechat_Take_Confirm" : "Do you want to take this client?",
"Livechat_Inquiry_Already_Taken" : "Livechat inquiry already taken",
"Least_Amount" : "Least Amount",
"Guest_Pool" : "Guest Pool",
"Load_more" : "Load more",
"Loading..." : "Loading...",
"Loading_more_from_history" : "Loading more from history",
@ -892,6 +897,7 @@
"Reset_password" : "Reset password",
"Restart" : "Restart",
"Restart_the_server" : "Restart the server",
"Would_you_like_to_return_the_inquiry" : "Would you like to return the inquiry?",
"Role" : "Role",
"Role_Editing" : "Role Editing",
"Role_removed" : "Role removed",

@ -0,0 +1 @@
this.LivechatInquiry = new Mongo.Collection('rocketchat_livechat_inquiry');

@ -45,6 +45,10 @@
{{#if roomOpen}}
<button class='button close-livechat button-block'><span><i class='icon-download'></i> {{_ "Close"}}</span></button>
{{/if}}
{{#if guestPool}}
<button class="button return-inquiry button-block"><span><i class="icon-ccw"></i> {{_ "Return"}}</span></button>
{{/if}}
<!-- <button class="button pvt-msg"><span><i class="icon-forward"></i> {{_ "Forward"}}</span></button> -->
</nav>

@ -95,6 +95,10 @@ Template.visitorInfo.helpers({
const room = ChatRoom.findOne({ _id: this.rid });
return room.open;
},
guestPool() {
return RocketChat.settings.get('Livechat_Routing_Method') === 'Guest_Pool';
}
});
@ -137,6 +141,26 @@ Template.visitorInfo.events({
});
});
});
},
'click .return-inquiry'(event) {
event.preventDefault();
swal({
title: t('Would_you_like_to_return_the_inquiry'),
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: t('yes')
}, () => {
Meteor.call('livechat:returnAsInquiry', this.rid, function(error/*, result*/) {
if (error) {
console.log(error);
} else {
FlowRouter.go('/home');
}
});
});
}
});

@ -2,11 +2,29 @@
<div class="livechat-section {{livechatAvailable}}">
<h3 class="{{isActive}}">
{{_ "Livechat"}}
{{#with available}}
<i class="livechat-status {{status}} {{icon}}" title="{{hint}}"></i>
{{/with}}
</h3>
{{#if guestPool}}
<h3 class="{{isActive}}">
{{_ "Incoming Livechats"}}
</h3>
<ul>
{{#each inquiries}}
{{> livechatInquiryItem }}
{{else}}
<p class="empty">{{_ "No_livechats" }}</p>
{{/each}}
</ul>
<h3 class="{{isActive}}">
{{_ "Open Livechats"}}
</h3>
{{/if}}
<ul>
{{#each rooms}}
{{> chatRoomItem }}

@ -1,3 +1,4 @@
/* globals LivechatInquiry, KonchatNotification */
Template.livechat.helpers({
isActive() {
if (ChatSubscription.findOne({
@ -36,6 +37,27 @@ Template.livechat.helpers({
}
});
},
inquiries() {
// get all inquiries of the department
var inqs = LivechatInquiry.find({
agents: Meteor.userId(),
status: 'open'
}, {
sort: {
'ts' : 1
}
});
// for notification sound
inqs.forEach(function(inq) {
KonchatNotification.newRoom(inq.rid);
});
return inqs;
},
guestPool() {
return RocketChat.settings.get('Livechat_Routing_Method') === 'Guest_Pool';
},
available() {
const user = Meteor.user();
return {
@ -62,3 +84,7 @@ Template.livechat.events({
});
}
});
Template.livechat.onCreated(function() {
this.subscribe('livechat:inquiry');
});

@ -139,4 +139,14 @@ Meteor.startup(function() {
{ key: 'title', i18nLabel: 'Page_title' }
]
});
RocketChat.settings.add('Livechat_Routing_Method', 'Least_Amount', {
type: 'select',
group: 'Livechat',
public: true,
values: [
{key: 'Least_Amount', i18nLabel: 'Least_Amount'},
{key: 'Guest_Pool', i18nLabel: 'Guest_Pool'}
]
});
});

@ -57,6 +57,7 @@ Package.onUse(function(api) {
api.addFiles('client/collections/LivechatDepartmentAgents.js', 'client');
api.addFiles('client/collections/LivechatPageVisited.js', 'client');
api.addFiles('client/collections/LivechatTrigger.js', 'client');
api.addFiles('client/collections/LivechatInquiry.js', 'client');
api.addFiles('client/methods/changeLivechatStatus.js', 'client');
@ -128,6 +129,8 @@ Package.onUse(function(api) {
api.addFiles('server/methods/sendOfflineMessage.js', 'server');
api.addFiles('server/methods/setCustomField.js', 'server');
api.addFiles('server/methods/webhookTest.js', 'server');
api.addFiles('server/methods/takeInquiry.js', 'server');
api.addFiles('server/methods/returnAsInquiry.js', 'server');
// models
api.addFiles('server/models/Users.js', 'server');
@ -139,14 +142,18 @@ Package.onUse(function(api) {
api.addFiles('server/models/LivechatPageVisited.js', 'server');
api.addFiles('server/models/LivechatTrigger.js', 'server');
api.addFiles('server/models/indexes.js', 'server');
api.addFiles('server/models/LivechatInquiry.js', 'server');
// server lib
api.addFiles('server/lib/Livechat.js', 'server');
api.addFiles('server/lib/QueueMethods.js', 'server');
api.addFiles('server/sendMessageBySMS.js', 'server');
api.addFiles('server/externalMessageHook.js', 'server');
api.addFiles('server/forwardUnclosedLivechats.js', 'server');
api.addFiles('server/setupWebhook.js', 'server');
// publications
api.addFiles('server/publications/customFields.js', 'server');
api.addFiles('server/publications/departmentAgents.js', 'server');
@ -158,6 +165,7 @@ Package.onUse(function(api) {
api.addFiles('server/publications/visitorHistory.js', 'server');
api.addFiles('server/publications/visitorInfo.js', 'server');
api.addFiles('server/publications/visitorPageVisited.js', 'server');
api.addFiles('server/publications/livechatInquiries.js', 'server');
// api
api.addFiles('server/api.js', 'server');

@ -14,6 +14,13 @@ RocketChat.Livechat = {
return RocketChat.models.Users.getNextAgent();
}
},
getAgents(department) {
if (department) {
return RocketChat.models.LivechatDepartmentAgents.getForDepartment(department);
} else {
return RocketChat.models.Users.findOnlineAgents();
}
},
sendMessage({ guest, message, roomInfo }) {
var room = RocketChat.models.Rooms.findOneById(message.rid);
var newRoom = false;
@ -24,7 +31,6 @@ RocketChat.Livechat = {
}
if (room == null) {
// if no department selected verify if there is only one active and use it
if (!guest.department) {
var departments = RocketChat.models.LivechatDepartment.findEnabledWithAgents();
@ -33,52 +39,9 @@ RocketChat.Livechat = {
}
}
const agent = RocketChat.Livechat.getNextAgent(guest.department);
if (!agent) {
throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
}
const roomCode = RocketChat.models.Rooms.getNextLivechatRoomCode();
room = _.extend({
_id: message.rid,
msgs: 1,
lm: new Date(),
code: roomCode,
label: guest.name || guest.username,
usernames: [agent.username, guest.username],
t: 'l',
ts: new Date(),
v: {
_id: guest._id,
token: message.token
},
servedBy: {
_id: agent.agentId,
username: agent.username
},
open: true
}, roomInfo);
let subscriptionData = {
rid: message.rid,
name: guest.name || guest.username,
alert: true,
open: true,
unread: 1,
answered: false,
code: roomCode,
u: {
_id: agent.agentId,
username: agent.username
},
t: 'l',
desktopNotifications: 'all',
mobilePushNotifications: 'all',
emailNotifications: 'all'
};
RocketChat.models.Rooms.insert(room);
RocketChat.models.Subscriptions.insert(subscriptionData);
// delegate room creation to QueueMethods
const routingMethod = RocketChat.settings.get('Livechat_Routing_Method');
room = RocketChat.QueueMethods[routingMethod](guest, message, roomInfo);
newRoom = true;
} else {

@ -0,0 +1,114 @@
RocketChat.QueueMethods = {
/* Least Amount Queuing method:
*
* default method where the agent with the least number
* of open chats is paired with the incoming livechat
*/
'Least_Amount' : function(guest, message, roomInfo) {
const agent = RocketChat.Livechat.getNextAgent(guest.department);
if (!agent) {
throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
}
const roomCode = RocketChat.models.Rooms.getNextLivechatRoomCode();
const room = _.extend({
_id: message.rid,
msgs: 1,
lm: new Date(),
code: roomCode,
label: guest.name || guest.username,
usernames: [agent.username, guest.username],
t: 'l',
ts: new Date(),
v: {
_id: guest._id,
token: message.token
},
servedBy: {
_id: agent.agentId,
username: agent.username
},
open: true
}, roomInfo);
let subscriptionData = {
rid: message.rid,
name: guest.name || guest.username,
alert: true,
open: true,
unread: 1,
answered: false,
code: roomCode,
u: {
_id: agent.agentId,
username: agent.username
},
t: 'l',
desktopNotifications: 'all',
mobilePushNotifications: 'all',
emailNotifications: 'all'
};
RocketChat.models.Rooms.insert(room);
RocketChat.models.Subscriptions.insert(subscriptionData);
return room;
},
/* Guest Pool Queuing Method:
*
* An incomming livechat is created as an Inquiry
* which is picked up from an agent.
* An Inquiry is visible to all agents (TODO: in the correct department)
*
* A room is still created with the initial message, but it is occupied by
* only the client until paired with an agent
*/
'Guest_Pool' : function(guest, message, roomInfo) {
const agents = RocketChat.Livechat.getAgents(guest.department);
if (!agents) {
throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
}
const roomCode = RocketChat.models.Rooms.getNextLivechatRoomCode();
const agentIds = [];
agents.forEach((agent) => {
if (guest.department) {
agentIds.push(agent.agentId);
} else {
agentIds.push(agent._id);
}
});
var inquiry = {
rid: message.rid,
message: message.msg,
name: guest.name || guest.username,
ts: new Date(),
code: roomCode,
department: guest.department,
agents: agentIds,
status: 'open'
};
const room = _.extend({
_id: message.rid,
msgs: 1,
lm: new Date(),
code: roomCode,
label: guest.name || guest.username,
usernames: [guest.username],
t: 'l',
ts: new Date(),
v: {
_id: guest._id,
token: message.token
},
open: true
}, roomInfo);
RocketChat.models.LivechatInquiry.insert(inquiry);
RocketChat.models.Rooms.insert(room);
return room;
}
};

@ -0,0 +1,20 @@
Meteor.methods({
'livechat:returnAsInquiry'(rid) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'view-l-room')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveDepartment' });
}
// //delete agent and room subscription
RocketChat.models.Subscriptions.removeByRoomId(rid);
// remove user from room
var username = Meteor.user().name;
RocketChat.models.Rooms.removeUsernameById(rid, username);
// find inquiry corresponding to room
var inquiry = RocketChat.models.LivechatInquiry.findOne({rid: rid});
// mark inquiry as open
return RocketChat.models.LivechatInquiry.openInquiry(inquiry._id);
}
});

@ -0,0 +1,40 @@
Meteor.methods({
'livechat:takeInquiry'(inquiry, agent) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'view-l-room')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:takeInquiry' });
}
if (RocketChat.models.LivechatInquiry.getStatus(inquiry._id) === 'taken') {
throw new Meteor.Error('error-not-allowed', 'Inquiry already taken', { method: 'livechat:takeInquiry' });
}
// add subscription
var subscriptionData = {
rid: inquiry.rid,
name: inquiry.name,
alert: true,
open: true,
unread: 1,
code: inquiry.code,
u: {
_id: agent._id,
username: agent.username
},
t: 'l',
desktopNotifications: 'all',
mobilePushNotifications: 'all',
emailNotifications: 'all',
answered: 'false'
};
RocketChat.models.Subscriptions.insert(subscriptionData);
// add user to room
RocketChat.models.Rooms.addUsernameById(inquiry.rid, agent.username);
// mark inquiry as taken
RocketChat.models.LivechatInquiry.takeInquiry(inquiry._id);
// return room corresponding to inquiry (for redirecting agent to the room route)
return RocketChat.models.Rooms.findOneById(inquiry.rid);
}
});

@ -70,6 +70,33 @@ class LivechatDepartmentAgents extends RocketChat.models._Base {
return null;
}
}
getForDepartment(departmentId) {
var agents = this.findByDepartmentId(departmentId).fetch();
if (agents.length === 0) {
return;
}
var onlineUsers = RocketChat.models.Users.findOnlineUserFromList(_.pluck(agents, 'username'));
var onlineUsernames = _.pluck(onlineUsers.fetch(), 'username');
var query = {
departmentId: departmentId,
username: {
$in: onlineUsernames
}
};
var depAgents = this.find(query);
if (depAgents) {
return depAgents;
} else {
return null;
}
}
}
RocketChat.models.LivechatDepartmentAgents = new LivechatDepartmentAgents();

@ -0,0 +1,45 @@
class LivechatInquiry extends RocketChat.models._Base {
constructor() {
super();
this._initModel('livechat_inquiry');
this.tryEnsureIndex({ 'rid': 1 }); // room id corresponding to this inquiry
this.tryEnsureIndex({ 'name': 1 }); // name of the inquiry (client name for now)
this.tryEnsureIndex({ 'message': 1 }); // message sent by the client
this.tryEnsureIndex({ 'ts': 1 }); // timestamp
this.tryEnsureIndex({ 'code': 1 }); // (for routing)
this.tryEnsureIndex({ 'agents': 1}); // Id's of the agents who can see the inquiry (handle departments)
this.tryEnsureIndex({ 'status': 1}); // 'open', 'taken'
}
/*
* mark the inquiry as taken
*/
takeInquiry(inquiryId) {
this.update({
'_id': inquiryId
}, {
$set: { status: 'taken' }
});
}
/*
* mark inquiry as open
*/
openInquiry(inquiryId) {
this.update({
'_id': inquiryId
}, {
$set: { status: 'open' }
});
}
/*
* return the status of the inquiry (open or taken)
*/
getStatus(inquiryId) {
return this.findOne({'_id': inquiryId}).status;
}
}
RocketChat.models.LivechatInquiry = new LivechatInquiry();

@ -0,0 +1,12 @@
Meteor.publish('livechat:inquiry', function() {
if (!this.userId) {
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:inquiry' }));
}
if (!RocketChat.authz.hasPermission(this.userId, 'view-l-room')) {
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:inquiry' }));
}
return RocketChat.models.LivechatInquiry.find();
});

@ -40,6 +40,7 @@ Package.onUse(function(api) {
api.addFiles('side-nav/starredRooms.html', 'client');
api.addFiles('side-nav/unreadRooms.html', 'client');
api.addFiles('side-nav/userStatus.html', 'client');
api.addFiles('side-nav/livechatInquiryItem.html', 'client');
api.addFiles('side-nav/accountBox.coffee', 'client');
api.addFiles('side-nav/channels.coffee', 'client');
@ -58,7 +59,7 @@ Package.onUse(function(api) {
api.addFiles('side-nav/sideNav.coffee', 'client');
api.addFiles('side-nav/starredRooms.coffee', 'client');
api.addFiles('side-nav/unreadRooms.coffee', 'client');
api.addFiles('side-nav/livechatInquiryItem.js', 'client');
});
Npm.depends({

@ -0,0 +1,11 @@
<template name="livechatInquiryItem">
<li class="link-room-{{rid}} {{active}} {{#if unread}}has-unread{{/if}} {{#if alert}}has-alert{{/if}}">
<a class="open-room" href="{{route}}" title="{{name}}">
<!-- {{#if unread}} -->
<!-- <span class="unread">{{unread}}</span> -->
<!-- {{/if}} -->
<i class="{{roomIcon}} {{userStatus}}" aria-label=""></i>
<span class='name'>{{name}}</span>
</a>
</li>
</template>

@ -0,0 +1,65 @@
/* globals KonchatNotification */
Template.livechatInquiryItem.helpers({
alert: function() {
if (FlowRouter.getParam('_id') !== this.rid || !document.hasFocus()) {
return this.alert;
}
},
unread: function() {
return 1;
},
userStatus: function() {
return 'status-' + (Session.get('user_' + this.name + '_status') || 'offline');
},
name: function() {
return this.name;
},
roomIcon: function() {
return RocketChat.roomTypes.getIcon(this.t);
},
active: function() {
if (Session.get('openedRoom') === this.rid) {
return 'active';
}
},
route: function() {
return RocketChat.roomTypes.getRouteLink(this.t, this);
}
});
Template.livechatInquiryItem.rendered = function() {
if (!((FlowRouter.getParam('_id') != null) && FlowRouter.getParam('_id') === this.data.rid) && !this.data.ls && this.data.alert === true) {
return KonchatNotification.newRoom(this.data.rid);
}
};
Template.livechatInquiryItem.events({
'click .open-room': function(e) {
e.stopPropagation();
e.preventDefault();
var inquiry = this;
swal({
title: t('Livechat_Take_Confirm'),
text: t('Message') + ': ' + inquiry.message,
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Take it!'
}, function(isConfirm) {
if (isConfirm) {
// make server method call
Meteor.call('livechat:takeInquiry', inquiry, Meteor.user(), function(error, result) {
if (!error) {
FlowRouter.go(RocketChat.roomTypes.getRouteLink(result.t, result));
}
});
}
});
}
});
Template.livechatInquiryItem.onDestroyed(function() {
swal.close();
});
Loading…
Cancel
Save