[IMPROVE] Replace livechat:agents pub by REST calls (#15490)

pull/15531/head
Marcos Spessatto Defendi 7 years ago committed by Diego Sampaio
parent 4ac63d84ea
commit ac9159da5d
  1. 46
      app/livechat/client/views/app/livechatAgents.js
  2. 13
      app/livechat/client/views/app/livechatDepartmentForm.js
  3. 10
      app/livechat/client/views/app/livechatQueue.js
  4. 15
      app/livechat/client/views/app/tabbar/visitorForward.js
  5. 52
      app/livechat/imports/server/rest/users.js
  6. 55
      app/livechat/server/api/lib/users.js
  7. 4
      app/livechat/server/publications/livechatDepartments.js
  8. 2
      app/livechat/server/publications/livechatOfficeHours.js
  9. 8
      app/models/server/raw/BaseRaw.js
  10. 11
      tests/data/livechat/rooms.js
  11. 97
      tests/end-to-end/api/livechat/agents.js

@ -6,20 +6,21 @@ import s from 'underscore.string';
import _ from 'underscore';
import { modal, call } from '../../../../ui-utils';
import { t, handleError } from '../../../../utils';
import { AgentUsers } from '../../collections/AgentUsers';
import { t, handleError, APIClient } from '../../../../utils/client';
import './livechatAgents.html';
const loadAgents = async (instance, limit = 50) => {
const { users } = await APIClient.v1.get(`livechat/users/agent?count=${ limit }`);
instance.agents.set(users);
instance.ready.set(true);
};
const getUsername = (user) => user.username;
Template.livechatAgents.helpers({
exceptionsAgents() {
const { selectedAgents } = Template.instance();
return AgentUsers.find({}, { fields: { username: 1 } })
.fetch()
.map(getUsername)
.concat(selectedAgents.get().map(getUsername));
return Template.instance().agents.get()
.map(getUsername).concat(selectedAgents.get().map(getUsername));
},
deleteLastAgent() {
const i = Template.instance();
@ -33,7 +34,7 @@ Template.livechatAgents.helpers({
return Template.instance().state.get('loading');
},
agents() {
return Template.instance().agents();
return Template.instance().getAgentsWithCriteria();
},
emailAddress() {
if (this.emails && this.emails.length > 0) {
@ -82,7 +83,7 @@ Template.livechatAgents.helpers({
const DEBOUNCE_TIME_FOR_SEARCH_AGENTS_IN_MS = 300;
Template.livechatAgents.events({
'click .remove-agent'(e /* , instance*/) {
'click .remove-agent'(e, instance) {
e.preventDefault();
modal.open(
@ -97,12 +98,13 @@ Template.livechatAgents.events({
html: false,
},
() => {
Meteor.call('livechat:removeAgent', this.username, function(
Meteor.call('livechat:removeAgent', this.username, async function(
error /* , result*/
) {
if (error) {
return handleError(error);
}
await loadAgents(instance);
modal.open({
title: t('Removed'),
text: t('Agent_removed'),
@ -130,6 +132,7 @@ Template.livechatAgents.events({
await Promise.all(
users.map(({ username }) => call('livechat:addAgent', username))
);
await loadAgents(instance);
selectedAgents.set([]);
} finally {
state.set('loading', false);
@ -171,6 +174,7 @@ Template.livechatAgents.onCreated(function() {
});
this.ready = new ReactiveVar(true);
this.selectedAgents = new ReactiveVar([]);
this.agents = new ReactiveVar([]);
this.onSelectAgents = ({ item: agent }) => {
this.selectedAgents.set([...this.selectedAgents.curValue, agent]);
@ -181,25 +185,19 @@ Template.livechatAgents.onCreated(function() {
};
this.autorun(function() {
const filter = instance.filter.get();
const limit = instance.limit.get();
const subscription = instance.subscribe('livechat:agents', filter, limit);
instance.ready.set(subscription.ready());
loadAgents(instance, limit);
});
this.agents = function() {
this.getAgentsWithCriteria = function() {
let filter;
let query = {};
if (instance.filter && instance.filter.get()) {
filter = s.trim(instance.filter.get());
}
if (filter) {
const filterReg = new RegExp(s.escapeRegExp(filter), 'i');
query = { $or: [{ username: filterReg }, { name: filterReg }, { 'emails.address': filterReg }] };
}
const limit = instance.limit && instance.limit.get();
return AgentUsers.find(query, { limit, sort: { name: 1 } }).fetch();
const regex = new RegExp(s.escapeRegExp(filter), 'i');
return instance.agents.get()
.filter((agent) => agent.name.match(regex)
|| agent.username.match(regex)
|| agent.emails.some((email) => email.address.match(regex)));
};
});

@ -7,7 +7,6 @@ import toastr from 'toastr';
import { t, handleError } from '../../../../utils';
import { hasPermission } from '../../../../authorization';
import { AgentUsers } from '../../collections/AgentUsers';
import { getCustomFormTemplate } from './customTemplates/register';
import './livechatDepartmentForm.html';
import { APIClient } from '../../../../utils/client';
@ -22,10 +21,6 @@ Template.livechatDepartmentForm.helpers({
selectedAgents() {
return _.sortBy(Template.instance().selectedAgents.get(), 'username');
},
availableAgents() {
const selected = _.pluck(Template.instance().selectedAgents.get(), 'username');
return AgentUsers.find({ username: { $nin: selected } }, { sort: { username: 1 } });
},
showOnRegistration(value) {
const department = Template.instance().department.get();
return department.showOnRegistration === value || (department.showOnRegistration === undefined && value === true);
@ -147,7 +142,7 @@ Template.livechatDepartmentForm.events({
}
input.value = '';
const agent = AgentUsers.findOne({ username });
const agent = Template.instance().agents.get().find((agent) => agent.username === username);
if (!agent) {
return toastr.error(t('The_selected_user_is_not_an_agent'));
}
@ -182,11 +177,13 @@ Template.livechatDepartmentForm.events({
},
});
Template.livechatDepartmentForm.onCreated(function() {
Template.livechatDepartmentForm.onCreated(async function() {
this.department = new ReactiveVar({ enabled: true });
this.selectedAgents = new ReactiveVar([]);
this.agents = new ReactiveVar([]);
this.subscribe('livechat:agents');
const { users } = await APIClient.v1.get('livechat/users/agent');
this.agents.set(users);
this.autorun(async () => {
const id = FlowRouter.getParam('_id');

@ -6,7 +6,6 @@ import { settings } from '../../../../settings';
import { hasPermission } from '../../../../authorization';
import { Users } from '../../../../models';
import { LivechatQueueUser } from '../../collections/LivechatQueueUser';
import { AgentUsers } from '../../collections/AgentUsers';
import './livechatQueue.html';
import { APIClient } from '../../../../utils/client';
@ -31,9 +30,9 @@ Template.livechatQueue.helpers({
}).forEach((user) => {
const options = { fields: { _id: 1 } };
const userFilter = { _id: user.agentId, status: { $ne: 'offline' } };
const agentFilter = { _id: user.agentId, statusLivechat: 'available' };
const agent = Template.instance().agents.get().find((agent) => agent._id === user.agentId && agent.statusLivechat === 'available');
if (showOffline[this._id] || (Meteor.users.findOne(userFilter, options) && AgentUsers.findOne(agentFilter, options))) {
if (showOffline[this._id] || (Meteor.users.findOne(userFilter, options) && agent)) {
users.push(user);
}
});
@ -59,10 +58,13 @@ Template.livechatQueue.events({
Template.livechatQueue.onCreated(async function() {
this.showOffline = new ReactiveVar({});
this.agents = new ReactiveVar([]);
this.departments = new ReactiveVar([]);
this.subscribe('livechat:queue');
this.subscribe('livechat:agents');
const { users } = await APIClient.v1.get('livechat/users/agent');
const { departments } = await APIClient.v1.get('livechat/department?sort={"name": 1}');
this.agents.set(users);
this.departments.set(departments);
});

@ -6,7 +6,6 @@ import toastr from 'toastr';
import { ChatRoom } from '../../../../../models';
import { t } from '../../../../../utils';
import { AgentUsers } from '../../../collections/AgentUsers';
import './visitorForward.html';
import { APIClient } from '../../../../../utils/client';
@ -21,13 +20,8 @@ Template.visitorForward.helpers({
return Template.instance().departments.get().filter((department) => department.enabled === true);
},
agents() {
const query = {
_id: { $ne: Meteor.userId() },
status: { $ne: 'offline' },
statusLivechat: 'available',
};
return AgentUsers.find(query, { sort: { name: 1, username: 1 } });
return Template.instance().agents.get()
.filter((agent) => agent._id !== Meteor.userId() && agent.status !== 'offline' && agent.statusLivechat === 'available');
},
agentName() {
return this.name || this.username;
@ -37,6 +31,7 @@ Template.visitorForward.helpers({
Template.visitorForward.onCreated(async function() {
this.visitor = new ReactiveVar();
this.room = new ReactiveVar();
this.agents = new ReactiveVar([]);
this.departments = new ReactiveVar([]);
this.autorun(() => {
@ -46,8 +41,10 @@ Template.visitorForward.onCreated(async function() {
this.autorun(() => {
this.room.set(ChatRoom.findOne({ _id: Template.currentData().roomId }));
});
this.subscribe('livechat:agents');
const { users } = await APIClient.v1.get('livechat/users/agent?sort={"name": 1, "username": 1}');
const { departments } = await APIClient.v1.get('livechat/department');
this.agents.set(users);
this.departments.set(departments);
});

@ -1,39 +1,41 @@
import { check } from 'meteor/check';
import _ from 'underscore';
import { hasPermission, getUsersInRole } from '../../../../authorization';
import { hasPermission } from '../../../../authorization';
import { API } from '../../../../api';
import { Users } from '../../../../models';
import { Livechat } from '../../../server/lib/Livechat';
import { findAgents, findManagers } from '../../../server/api/lib/users';
API.v1.addRoute('livechat/users/:type', { authRequired: true }, {
get() {
if (!hasPermission(this.userId, 'view-livechat-manager')) {
return API.v1.unauthorized();
check(this.urlParams, {
type: String,
});
const { offset, count } = this.getPaginationItems();
const { sort } = this.parseJsonQuery();
if (this.urlParams.type === 'agent') {
return API.v1.success(Promise.await(findAgents({
userId: this.userId,
pagination: {
offset,
count,
sort,
},
})));
}
try {
check(this.urlParams, {
type: String,
});
let role;
if (this.urlParams.type === 'agent') {
role = 'livechat-agent';
} else if (this.urlParams.type === 'manager') {
role = 'livechat-manager';
} else {
throw new Error('Invalid type');
}
const users = getUsersInRole(role);
return API.v1.success({
users: users.fetch().map((user) => _.pick(user, '_id', 'username', 'name', 'status', 'statusLivechat')),
});
} catch (e) {
return API.v1.failure(e.error);
if (this.urlParams.type === 'manager') {
return API.v1.success(Promise.await(findManagers({
userId: this.userId,
pagination: {
offset,
count,
sort,
},
})));
}
throw new Error('Invalid type');
},
post() {
if (!hasPermission(this.userId, 'view-livechat-manager')) {

@ -0,0 +1,55 @@
import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission';
import { Users } from '../../../../models/server/raw';
async function findUsers({ userId, role, pagination: { offset, count, sort } }) {
if (!await hasPermissionAsync(userId, 'view-livechat-manager') || !await hasPermissionAsync(userId, 'manage-livechat-agents')) {
throw new Error('error-not-authorized');
}
const cursor = await Users.findUsersInRoles(role, undefined, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields: {
username: 1,
name: 1,
status: 1,
statusLivechat: 1,
emails: 1,
},
});
const total = await cursor.count();
const users = await cursor.toArray();
return {
users,
count: users.length,
offset,
total,
};
}
export async function findAgents({ userId, pagination: { offset, count, sort } }) {
return findUsers({
role: 'livechat-agent',
userId,
pagination: {
offset,
count,
sort,
},
});
}
export async function findManagers({ userId, pagination: { offset, count, sort } }) {
return findUsers({
role: 'livechat-manager',
userId,
pagination: {
offset,
count,
sort,
},
});
}

@ -5,11 +5,11 @@ import { LivechatDepartment } from '../../../models';
Meteor.publish('livechat:departments', function(_id, limit = 50) {
if (!this.userId) {
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:agents' }));
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:departments' }));
}
if (!hasPermission(this.userId, 'view-l-room')) {
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:agents' }));
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:departments' }));
}
if (_id) {

@ -5,7 +5,7 @@ import { LivechatOfficeHour } from '../../../models';
Meteor.publish('livechat:officeHour', function() {
if (!hasPermission(this.userId, 'view-l-room')) {
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:agents' }));
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:officeHour' }));
}
return LivechatOfficeHour.find();

@ -11,11 +11,11 @@ export class BaseRaw {
return this.col.findOne(...args);
}
find(...args) {
return this.col.find(...args);
}
findUsersInRoles() {
throw new Error('overwrite-function', 'You must overwrite this function in the extended classes');
}
find(...args) {
return this.col.find(...args);
}
}

@ -2,7 +2,7 @@ import { api, credentials, request } from '../api-data';
import { adminUsername } from '../user';
export const createLivechatRoom = (visitorToken) => new Promise((resolve) => {
request.get(api(`/livechat/room?token=${ visitorToken }`))
request.get(api(`livechat/room?token=${ visitorToken }`))
.set(credentials)
.end((err, res) => resolve(res.body.room));
});
@ -36,3 +36,12 @@ export const createAgent = () => new Promise((resolve) => {
})
.end((err, res) => resolve(res.body.user));
});
export const createManager = () => new Promise((resolve) => {
request.post(api('livechat/users/manager'))
.set(credentials)
.send({
username: adminUsername,
})
.end((err, res) => resolve(res.body.user));
});

@ -0,0 +1,97 @@
import { getCredentials, api, request, credentials } from '../../../data/api-data.js';
import { createAgent, createManager } from '../../../data/livechat/rooms.js';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
describe('LIVECHAT - Agents', function() {
this.retries(0);
let agent;
let manager;
before((done) => getCredentials(done));
before((done) => {
updateSetting('Livechat_enabled', true)
.then(createAgent)
.then((createdAgent) => {
agent = createdAgent;
})
.then(createManager)
.then((createdManager) => {
manager = createdManager;
done();
});
});
describe('livechat/users/:type', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => {
updatePermission('view-livechat-manager', [])
.then(() => updatePermission('manage-livechat-agents', []))
.then(() => {
request.get(api('livechat/users/agent'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('error-not-authorized');
})
.end(done);
});
});
it('should throw an error when the type is invalid', (done) => {
updatePermission('view-livechat-manager', ['admin'])
.then(() => updatePermission('manage-livechat-agents', ['admin']))
.then(() => {
request.get(api('livechat/users/invalid-type'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('Invalid type');
})
.end(done);
});
});
it('should return an array of agents', (done) => {
updatePermission('view-livechat-manager', ['admin'])
.then(() => updatePermission('manage-livechat-agents', ['admin']))
.then(() => {
request.get(api('livechat/users/agent'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
const agentRecentlyCreated = res.body.users.find((user) => agent._id === user._id);
expect(agentRecentlyCreated._id).to.be.equal(agent._id);
})
.end(done);
});
});
it('should return an array of managers', (done) => {
updatePermission('view-livechat-manager', ['admin'])
.then(() => updatePermission('manage-livechat-agents', ['admin']))
.then(() => {
request.get(api('livechat/users/manager'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
const managerRecentlyCreated = res.body.users.find((user) => manager._id === user._id);
expect(managerRecentlyCreated._id).to.be.equal(manager._id);
})
.end(done);
});
});
});
});
Loading…
Cancel
Save