Merge pull request #1734 from RocketChat/livechat-departments

Livechat department improvements and queue
pull/1738/merge
Gabriel Engel 10 years ago
commit 21808dfd9d
  1. 1
      packages/rocketchat-livechat/client/collections/AgentUsers.js
  2. 0
      packages/rocketchat-livechat/client/collections/LivechatDepartment.js
  3. 1
      packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js
  4. 0
      packages/rocketchat-livechat/client/collections/LivechatTrigger.js
  5. 26
      packages/rocketchat-livechat/client/stylesheets/livechat.less
  6. 69
      packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html
  7. 89
      packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js
  8. 5
      packages/rocketchat-livechat/client/views/app/livechatDepartments.js
  9. 2
      packages/rocketchat-livechat/client/views/app/livechatUsers.js
  10. 2
      packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html
  11. 4
      packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js
  12. 6
      packages/rocketchat-livechat/i18n/en.i18n.json
  13. 13
      packages/rocketchat-livechat/package.js
  14. 21
      packages/rocketchat-livechat/server/lib/getNextAgent.js
  15. 4
      packages/rocketchat-livechat/server/methods/saveDepartment.js
  16. 39
      packages/rocketchat-livechat/server/models/LivechatDepartment.js
  17. 71
      packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js
  18. 11
      packages/rocketchat-livechat/server/publications/departmentAgents.js

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

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

@ -411,3 +411,29 @@
}
}
}
.department-agents {
list-style-type: none;
li {
display: inline-block;
background-color: #DDD;
border-radius: 10px;
padding: 2px 8px 2px 2px;
margin: 1px 0;
cursor: pointer;
.icon-plus-circled {
opacity: 0.5;
font-size: 0.8rem;
}
}
}
.agent-info {
input[type='text'] {
width: auto;
line-height: 24px;
height: 24px;
}
}

@ -24,34 +24,51 @@
</div>
<hr />
<h2>{{_ "Agents"}}</h2>
<div class="input-line double-col">
<input type="text" name="agent" placeholder="{{_ "Enter_a_username"}}">
<button name="addAgent" type="button" class="button add-agent">{{_ "Add_agent"}}</button>
</div>
<div class="list">
<table>
<thead>
<tr>
<th width="25%">{{_ "Username"}}</th>
<th>{{_ "Delete"}}</th>
</tr>
</thead>
<tbody>
{{#if agents.length}}
{{#each agents}}
<tr class="agent-info" data-id="{{_id}}">
<td>{{username}}</td>
<td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td>
</tr>
{{/each}}
{{else}}
<fieldset>
<legend>{{_ "Available_agents"}}</legend>
<ul class="department-agents available-agents">
{{#each availableAgents}}
<li><i class="icon-plus-circled"></i>{{username}}</li>
{{/each}}
</ul>
</fieldset>
<fieldset>
<legend>{{_ "Selected_agents"}}</legend>
<div class="list">
<table>
<thead>
<tr>
<td colspan="2">{{_ "There_are_no_agents_added_to_this_department_yet"}}</td>
<th width="25%">{{_ "Username"}}</th>
<th>{{_ "Count"}}</th>
<th>{{_ "Order"}}</th>
<th>&nbsp;</th>
</tr>
{{/if}}
</tbody>
</table>
</div>
</thead>
<tbody>
{{#if selectedAgents}}
{{#each selectedAgents}}
<tr class="agent-info">
<td>{{username}}</td>
<td><input type="text" class="count-{{agentId}}" name="count" value="{{count}}" size="3"></td>
<td><input type="text" class="order-{{agentId}}" name="order" value="{{order}}" size="3"></td>
<td><a href="#remove" class="remove-agent"><i class="icon-trash"></i></a></td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="4">{{_ "There_are_no_agents_added_to_this_department_yet"}}</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
</fieldset>
</fieldset>
<div class="submit">
<button type="button" class="button secondary back"><i class="icon-left-big"></i><span>{{_ "Back"}}</span></button>

@ -1,10 +1,16 @@
Template.livechatDepartmentForm.helpers({
department() {
// return Template.instance().department && !_.isEmpty(Template.instance().department.get()) ? Template.instance().department.get() : { enabled: true };
return Template.instance().department.get();
},
agents() {
return Template.instance().department && !_.isEmpty(Template.instance().department.get()) ? Template.instance().department.get().agents : []
},
selectedAgents() {
return _.sortBy(Template.instance().selectedAgents.get(), 'username');
},
availableAgents() {
var selected = _.pluck(Template.instance().selectedAgents.get(), 'username');
return AgentUsers.find({ username: { $nin: selected }}, { sort: { username: 1 } });
}
});
@ -29,16 +35,22 @@ Template.livechatDepartmentForm.events({
var oldBtnValue = $btn.html();
$btn.html(t('Saving'));
agents = instance.department && !_.isEmpty(instance.department.get()) ? instance.department.get().agents : [];
departmentData = {
var departmentData = {
enabled: enabled === "1" ? true : false,
name: name.trim(),
description: description.trim(),
agents: agents
}
description: description.trim()
};
var departmentAgents = [];
instance.selectedAgents.get().forEach((agent) => {
agent.count = instance.$('.count-' + agent.agentId).val();
agent.order = instance.$('.order-' + agent.agentId).val();
departmentAgents.push(agent);
});
Meteor.call('livechat:saveDepartment', _id, departmentData, function(error, result) {
Meteor.call('livechat:saveDepartment', _id, departmentData, departmentAgents, function(error, result) {
$btn.html(oldBtnValue);
if (error) {
return toastr.error(t(error.reason || error.error));
@ -54,59 +66,44 @@ Template.livechatDepartmentForm.events({
FlowRouter.go('livechat-departments');
},
'click button.add-agent' (e, instance) {
'click .remove-agent' (e, instance) {
e.preventDefault();
var $btn = $(e.currentTarget);
var $agent = instance.$('input[name=agent]')
if ($agent.val().trim() === '') {
return toastr.error(t('Please_fill_a_username'));
}
var oldBtnValue = $btn.html();
$btn.html(t('Saving'));
Meteor.call('livechat:searchAgent', $agent.val(), function(error, user) {
$btn.html(oldBtnValue);
if (error) {
return toastr.error(t(error.reason || error.error));
}
department = instance.department.get() || {};
if (department.agents === undefined || !_.isArray(department.agents)) {
department.agents = [];
}
if (!_.findWhere(department.agents, { _id: user._id })) {
department.agents.push(user);
}
instance.department.set(department);
$agent.val('');
});
var selectedAgents = instance.selectedAgents.get();
selectedAgents = _.reject(selectedAgents, (agent) => { return agent._id === this._id });
instance.selectedAgents.set(selectedAgents);
},
'click a.remove-agent' (e, instance) {
e.preventDefault();
department = instance.department.get();
department.agents = _.reject(department.agents, (agent) => { return agent._id === this._id });
instance.department.set(department);
},
'keydown input[name=agent]' (e, instance) {
if (e.keyCode === 13) {
e.preventDefault();
$("button.add-agent").click();
}
'click .available-agents li' (e, instance) {
var selectedAgents = instance.selectedAgents.get();
var agent = _.clone(this);
agent.agentId = this._id;
delete agent._id;
selectedAgents.push(agent);
instance.selectedAgents.set(selectedAgents);
}
});
Template.livechatDepartmentForm.onCreated(function() {
this.department = new ReactiveVar({ enabled: true });
this.selectedAgents = new ReactiveVar([]);
this.subscribe('livechat:agents');
this.autorun(() => {
var sub = this.subscribe('livechat:departments', FlowRouter.getParam('_id'));
if (sub.ready()) {
department = LivechatDepartment.findOne({ _id: FlowRouter.getParam('_id') });
if (department) {
this.department.set(department);
this.subscribe('livechat:departmentAgents', department._id, () => {
var newSelectedAgents = [];
LivechatDepartmentAgents.find({ departmentId: department._id }).forEach((agent) => {
newSelectedAgents.push(agent);
});
this.selectedAgents.set(newSelectedAgents);
});
}
}
});

@ -1,11 +1,6 @@
Template.livechatDepartments.helpers({
"departments": () => {
return LivechatDepartment.find();
},
"numAgents"() {
if (Array.isArray(this.agents)) {
return this.agents.length;
}
}
});

@ -1,8 +1,6 @@
var AgentUsers;
var ManagerUsers;
Meteor.startup(function() {
AgentUsers = new Mongo.Collection('agentUsers');
ManagerUsers = new Mongo.Collection('managerUsers');
});

@ -10,7 +10,7 @@
<li>
<!-- <a href="{{pathFor 'livechat-dashboard'}}" class="{{active 'livechat-dashboard'}}">{{_ "Dashboard"}}</a> -->
<a href="{{pathFor 'livechat-users'}}" class="{{active 'livechat-users'}}">{{_ "User_management"}}</a>
<a href="{{pathFor 'livechat-departments'}}" class="{{active 'livechat-departments'}}">{{_ "Departments"}}</a>
<a href="{{pathFor 'livechat-departments'}}" class="{{active 'livechat-departments' 'livechat-department-edit'}}">{{_ "Departments"}}</a>
<a href="{{pathFor 'livechat-triggers'}}" class="{{active 'livechat-triggers'}}">{{_ "Triggers"}}</a>
<a href="{{pathFor 'livechat-installation'}}" class="{{active 'livechat-installation'}}">{{_ "Installation"}}</a>
<a href="{{pathFor 'livechat-appearance'}}" class="{{active 'livechat-appearance'}}">{{_ "Appearance"}}</a>

@ -1,7 +1,7 @@
Template.livechatFlex.helpers({
active (route) {
active (...routes) {
FlowRouter.watchPathChange();
if (FlowRouter.current().route.name === route) {
if (routes.indexOf(FlowRouter.current().route.name) !== -1) {
return 'active';
}
}

@ -4,9 +4,11 @@
"Add_manager" : "Add manager",
"Agent_added" : "Agent added",
"Agent_removed" : "Agent removed",
"Available_agents" : "Available agents",
"Back" : "Back",
"Closed" : "Closed",
"Copy_to_clipboard" : "Copy to clipboard",
"Count" : "Count",
"Dashboard" : "Dashboard",
"Department_not_found" : "Department not found",
"Department_removed" : "Department removed",
@ -32,10 +34,12 @@
"New_Department" : "New Department",
"Num_Agents" : "# Agents",
"Opened" : "Opened",
"Order" : "Order",
"Please_fill_a_name" : "Please fill a name",
"Please_fill_a_username" : "Please fill a username",
"Please_select_enabled_yes_or_no" : "Please select an option for Enabled",
"Saved" : "Saved",
"Selected_agents" : "Selected agents",
"Send_a_message" : "Send a message",
"Theme" : "Theme",
"There_are_no_agents_added_to_this_department_yet" : "There are no agents added to this department yet.",
@ -48,4 +52,4 @@
"Username_not_found" : "Username not found",
"Visitor_page_URL" : "Visitor page URL",
"Visitor_time_on_site" : "Visitor time on site"
}
}

@ -38,6 +38,12 @@ Package.onUse(function(api) {
api.addFiles('client/stylesheets/livechat.less', 'client');
// collections
api.addFiles('client/collections/AgentUsers.js', 'client');
api.addFiles('client/collections/LivechatDepartment.js', 'client');
api.addFiles('client/collections/LivechatDepartmentAgents.js', 'client');
api.addFiles('client/collections/LivechatTrigger.js', 'client');
// client views
api.addFiles('client/views/app/livechatAppearance.html', 'client');
api.addFiles('client/views/app/livechatAppearance.js', 'client');
@ -80,13 +86,14 @@ Package.onUse(function(api) {
api.addFiles('server/models/Users.js', 'server');
api.addFiles('server/models/Rooms.js', 'server');
api.addFiles('server/models/LivechatDepartment.js', 'server');
api.addFiles('server/models/LivechatDepartmentAgents.js', 'server');
api.addFiles('server/models/LivechatTrigger.js', 'server');
// collections
api.addFiles('client/lib/LivechatDepartment.js', 'client');
api.addFiles('client/lib/LivechatTrigger.js', 'client');
// server lib
api.addFiles('server/lib/getNextAgent.js', 'server');
// publications
api.addFiles('server/publications/departmentAgents.js', 'server');
api.addFiles('server/publications/livechatAgents.js', 'server');
api.addFiles('server/publications/livechatManagers.js', 'server');
api.addFiles('server/publications/livechatDepartments.js', 'server');

@ -1,27 +1,8 @@
this.getNextAgent = function(department) {
var agentFilter = {};
// find agents from that department
if (department) {
var agents = RocketChat.models.LivechatDepartment.getNextAgent(department);
if (!agents) {
return;
}
// sort = {
// count: 1,
// order: 1,
// 'user.name': 1
// }
// update = {
// $inc: {
// count: 1
// }
// }
// queueUser = findAndModify query, sort, update
return RocketChat.models.LivechatDepartment.getNextAgent(department);
} else {
return RocketChat.models.Users.getNextAgent();
}

@ -1,5 +1,5 @@
Meteor.methods({
'livechat:saveDepartment' (_id, departmentData) {
'livechat:saveDepartment' (_id, departmentData, departmentAgents) {
if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'view-livechat-manager')) {
throw new Meteor.Error("not-authorized");
}
@ -17,6 +17,6 @@ Meteor.methods({
}
}
return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData.enabled, departmentData.name, departmentData.description, departmentData.agents);
return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData.enabled, departmentData.name, departmentData.description, departmentAgents);
}
});

@ -19,23 +19,42 @@ class LivechatDepartment extends RocketChat.models._Base {
return this.find(query, options);
}
// UPSERT
createOrUpdateDepartment(_id, enabled, name, description, agents, extraData) {
record = {
var agents = [].concat(agents);
var record = {
enabled: enabled,
name: name,
description: description,
agents: []
}
numAgents: agents.length
};
_.extend(record, extraData);
if (!_.isEmpty(agents)) {
for (agent of agents) {
record.agents.push({ _id: agent._id, username: agent.username });
}
if (_id) {
this.update({ _id: _id }, { $set: record });
} else {
_id = this.insert(record);
}
_.extend(record, extraData);
this.upsert({ _id: _id }, { $set: record });
var savedAgents = _.pluck(RocketChat.models.LivechatDepartmentAgents.findByDepartmentId(_id).fetch(), 'agentId');
var agentsToSave = _.pluck(agents, 'agentId');
// remove other agents
_.difference(savedAgents, agentsToSave).forEach((agentId) => {
RocketChat.models.LivechatDepartmentAgents.removeByDepartmentIdAndAgentId(_id, agentId);
});
agents.forEach((agent) => {
RocketChat.models.LivechatDepartmentAgents.saveAgent({
agentId: agent.agentId,
departmentId: _id,
username: agent.username,
count: parseInt(agent.count),
order: parseInt(agent.order)
});
});
return _.extend(record, { _id: _id });
}

@ -0,0 +1,71 @@
/**
* Livechat Department model
*/
class LivechatDepartmentAgents extends RocketChat.models._Base {
constructor() {
super();
this._initModel('livechat_department_agents');
}
findByDepartmentId(departmentId) {
return this.find({ departmentId: departmentId });
}
saveAgent(agent) {
if (agent._id) {
return this.update({ _id: _id }, { $set: agent });
} else {
return this.upsert({
agentId: agent.agentId,
departmentId: agent.departmentId
}, {
$set: {
username: agent.username,
count: parseInt(agent.count),
order: parseInt(agent.order)
}
});
}
}
removeByDepartmentIdAndAgentId(departmentId, agentId) {
this.remove({ departmentId: departmentId, agentId: agentId });
}
getNextAgentForDepartment(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 sort = {
count: 1,
sort: 1,
username: 1
};
var update = {
$inc: {
count: 1
}
};
var collectionObj = this.model.rawCollection();
var findAndModify = Meteor.wrapAsync(collectionObj.findAndModify, collectionObj);
return findAndModify(query, sort, update);
}
}
RocketChat.models.LivechatDepartmentAgents = new LivechatDepartmentAgents();

@ -0,0 +1,11 @@
Meteor.publish('livechat:departmentAgents', function(departmentId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
if (!RocketChat.authz.hasPermission(this.userId, 'view-livechat-manager')) {
throw new Meteor.Error('not-authorized');
}
return RocketChat.models.LivechatDepartmentAgents.find({ departmentId: departmentId });
});
Loading…
Cancel
Save