[IMPROVE] Replace livechat:rooms publication by REST (#15968)

* Replace livechat:rooms publication by REST

* Improve livechat rooms endpoint to return department data

* remove log

* Fix load rooms

* Fix lint

* Fix wrong end date

* Fix wrong var name
pull/15792/head
Marcos Spessatto Defendi 6 years ago committed by Renato Becker
parent 5cf9c1be6a
commit 89796cb920
  1. 3
      app/livechat/client/collections/LivechatRoom.js
  2. 6
      app/livechat/client/views/app/livechatCurrentChats.html
  3. 91
      app/livechat/client/views/app/livechatCurrentChats.js
  4. 19
      app/livechat/imports/server/rest/rooms.js
  5. 55
      app/livechat/server/api/lib/livechat.js
  6. 53
      app/livechat/server/api/lib/rooms.js
  7. 1
      app/livechat/server/publications/livechatRooms.js
  8. 73
      app/models/server/raw/LivechatRooms.js
  9. 3
      server/stream/rooms/index.js
  10. 14
      tests/end-to-end/api/livechat/00-rooms.js

@ -1,3 +0,0 @@
import { Mongo } from 'meteor/mongo';
export const LivechatRoom = new Mongo.Collection('livechatRoom');

@ -158,7 +158,7 @@
</div>
</div>
</td>
<td>{{lookupDepartment.name}}</td>
<td>{{department.name}}</td>
<td>{{servedBy}}</td>
<td>{{startedAt}}</td>
<td>{{lastMessage}}</td>
@ -174,11 +174,11 @@
{{/requiresPermission}}
</tr>
{{/each}}
{{#unless isReady}}
{{#if isLoading}}
<tr class="table-no-click">
<td colspan="5" class="table-loading-td">{{> loading}}</td>
</tr>
{{/unless}}
{{/if}}
</tbody>
{{/table}}
</div>

@ -1,3 +1,4 @@
import 'moment-timezone';
import _ from 'underscore';
import moment from 'moment';
import { ReactiveVar } from 'meteor/reactive-var';
@ -10,19 +11,21 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { modal, call, popover } from '../../../../ui-utils';
import { t, handleError, APIClient } from '../../../../utils/client';
import { LivechatRoom } from '../../collections/LivechatRoom';
import { hasRole, hasPermission, hasAtLeastOnePermission } from '../../../../authorization';
import './livechatCurrentChats.html';
const ROOMS_COUNT = 50;
Template.livechatCurrentChats.helpers({
hasMore() {
return Template.instance().ready.get() && LivechatRoom.find({ t: 'l' }, { sort: { ts: -1 } }).count() === Template.instance().limit.get();
const instance = Template.instance();
return instance.total.get() > instance.livechatRooms.get().length;
},
isReady() {
return Template.instance().ready.get();
isLoading() {
return Template.instance().isLoading.get();
},
livechatRoom() {
return Template.instance().livechatRoom.get();
return Template.instance().livechatRooms.get();
},
startedAt() {
return moment(this.ts).format('L LTS');
@ -76,7 +79,7 @@ Template.livechatCurrentChats.events({
FlowRouter.go('live', { id: this._id });
},
'click .js-load-more'(event, instance) {
instance.limit.set(instance.limit.get() + 20);
instance.offset.set(instance.offset.get() + ROOMS_COUNT);
},
'click .add-filter-button'(event, instance) {
event.preventDefault();
@ -179,6 +182,7 @@ Template.livechatCurrentChats.events({
}
if (result) {
instance.loadRooms(instance.filter.get(), instance.offset.get());
toastr.success(TAPi18n.__('All_closed_chats_have_been_removed'));
}
});
@ -230,6 +234,9 @@ Template.livechatCurrentChats.events({
}
const value = $(this).val();
if (!value) {
return;
}
if (this.name.startsWith('custom-field-')) {
if (!filter.customFields) {
@ -269,13 +276,13 @@ Template.livechatCurrentChats.events({
const agents = instance.selectedAgents.get();
if (agents && agents.length > 0) {
filter.agent = agents[0]._id;
filter.agents = [agents[0]._id];
}
instance.filter.set(filter);
instance.limit.set(20);
instance.offset.set(0);
},
'click .remove-livechat-room'(event) {
'click .remove-livechat-room'(event, instance) {
event.preventDefault();
event.stopPropagation();
@ -293,6 +300,7 @@ Template.livechatCurrentChats.events({
return;
}
await call('livechat:removeRoom', this._id);
instance.loadRooms(instance.filter.get(), instance.offset.get());
modal.open({
title: t('Deleted'),
text: t('Room_has_been_deleted'),
@ -312,16 +320,69 @@ Template.livechatCurrentChats.events({
});
Template.livechatCurrentChats.onCreated(async function() {
this.ready = new ReactiveVar(false);
this.limit = new ReactiveVar(20);
this.isLoading = new ReactiveVar(false);
this.offset = new ReactiveVar(0);
this.total = new ReactiveVar(0);
this.filter = new ReactiveVar({});
this.livechatRoom = new ReactiveVar([]);
this.livechatRooms = new ReactiveVar([]);
this.selectedAgents = new ReactiveVar([]);
this.customFilters = new ReactiveVar([]);
this.customFields = new ReactiveVar([]);
this.tagFilters = new ReactiveVar([]);
this.departments = new ReactiveVar([]);
const mountArrayQueryParameters = (label, items, index) => items.reduce((acc, item) => {
const isTheLastElement = index === items.length - 1;
acc += `${ label }[]=${ item }${ isTheLastElement ? '' : '&' }`;
return acc;
}, '');
const mountUrlWithParams = (filter, offset) => {
const { status, agents, department, from, to, tags, customFields, name: roomName } = filter;
let url = `livechat/rooms?count=${ ROOMS_COUNT }&offset=${ offset }`;
const dateRange = {};
if (status) {
url += `&open=${ status === 'opened' }`;
}
if (department) {
url += `&departmentId=${ department }`;
}
if (from) {
dateRange.start = `${ moment(new Date(from)).utc().format('YYYY-MM-DDTHH:mm:ss') }Z`;
}
if (to) {
dateRange.end = `${ moment(new Date(to).setHours(23, 59, 59)).utc().format('YYYY-MM-DDTHH:mm:ss') }Z`;
}
if (tags) {
url += `&${ mountArrayQueryParameters('tags', tags) }`;
}
if (agents && Array.isArray(agents) && agents.length) {
url += `&${ mountArrayQueryParameters('agents', agents) }`;
}
if (customFields) {
url += `&customFields=${ JSON.stringify(customFields) }`;
}
if (Object.keys(dateRange).length) {
url += `&createdAt=${ JSON.stringify(dateRange) }`;
}
if (roomName) {
url += `&roomName=${ roomName }`;
}
return url;
};
this.loadRooms = async (filter, offset) => {
this.isLoading.set(true);
const { rooms, total } = await APIClient.v1.get(mountUrlWithParams(filter, offset));
this.total.set(total);
if (offset === 0) {
this.livechatRooms.set(rooms);
} else {
this.livechatRooms.set(this.livechatRooms.get().concat(rooms));
}
this.isLoading.set(false);
};
this.onSelectAgents = ({ item: agent }) => {
this.selectedAgents.set([agent]);
};
@ -331,7 +392,9 @@ Template.livechatCurrentChats.onCreated(async function() {
};
this.autorun(async () => {
this.ready.set(this.subscribe('livechat:rooms', this.filter.get(), 0, this.limit.get()).ready());
const filter = this.filter.get();
const offset = this.offset.get();
this.loadRooms(filter, offset);
});
const { departments } = await APIClient.v1.get('livechat/department?sort={"name": 1}');
@ -342,8 +405,6 @@ Template.livechatCurrentChats.onCreated(async function() {
this.customFields.set(customFields);
}
});
this.livechatRoom.set(LivechatRoom.find({ t: 'l' }, { sort: { ts: -1 } }));
});
Template.livechatCurrentChats.onRendered(function() {

@ -2,7 +2,7 @@ import { Match, check } from 'meteor/check';
import { hasPermission } from '../../../../authorization/server';
import { API } from '../../../../api';
import { findRooms } from '../../../server/api/lib/livechat';
import { findRooms } from '../../../server/api/lib/rooms';
API.v1.addRoute('livechat/rooms', { authRequired: true }, {
get() {
@ -12,12 +12,14 @@ API.v1.addRoute('livechat/rooms', { authRequired: true }, {
}
const { offset, count } = this.getPaginationItems();
const { sort, fields } = this.parseJsonQuery();
const { agents, departmentId, open, tags } = this.requestParams();
const { agents, departmentId, open, tags, roomName } = this.requestParams();
let { createdAt, customFields, closedAt } = this.requestParams();
check(agents, Match.Maybe([String]));
check(roomName, Match.Maybe(String));
check(departmentId, Match.Maybe(String));
check(open, Match.Maybe(String));
check(tags, Match.Maybe([String]));
if (createdAt) {
createdAt = JSON.parse(createdAt);
}
@ -27,8 +29,10 @@ API.v1.addRoute('livechat/rooms', { authRequired: true }, {
if (customFields) {
customFields = JSON.parse(customFields);
}
const { rooms, total } = findRooms({
return API.v1.success(Promise.await(findRooms({
agents,
roomName,
departmentId,
open: open && open === 'true',
createdAt,
@ -36,14 +40,7 @@ API.v1.addRoute('livechat/rooms', { authRequired: true }, {
tags,
customFields,
options: { offset, count, sort, fields },
});
return API.v1.success({
rooms,
count: rooms.length,
offset,
total,
});
})));
} catch (e) {
return API.v1.failure(e);
}

@ -148,58 +148,3 @@ export function settings() {
export async function getExtraConfigInfo(room) {
return callbacks.run('livechat.onLoadConfigApi', room);
}
export function findRooms({
agents,
departmentId,
open,
createdAt,
closedAt,
tags,
customFields,
options = {},
}) {
const query = { t: 'l' };
if (agents) {
query.$or = [{ 'servedBy._id': { $in: agents } }, { 'servedBy.username': { $in: agents } }];
}
if (departmentId) {
query.departmentId = departmentId;
}
if (open !== undefined) {
query.open = { $exists: open };
}
if (createdAt) {
query.ts = {};
if (createdAt.start) {
query.ts.$gte = new Date(createdAt.start);
}
if (createdAt.end) {
query.ts.$lte = new Date(createdAt.end);
}
}
if (closedAt) {
query.closedAt = {};
if (closedAt.start) {
query.closedAt.$gte = new Date(closedAt.start);
}
if (closedAt.end) {
query.closedAt.$lte = new Date(closedAt.end);
}
}
if (tags) {
query.tags = { $in: tags };
}
if (customFields) {
query.$and = Object.keys(customFields).map((key) => ({ [`livechatData.${ key }`]: customFields[key] }));
}
return {
rooms: LivechatRooms.find(query, {
sort: options.sort || { ts: 1 },
skip: options.offset,
limit: options.count,
fields: options.fields,
}).fetch(),
total: LivechatRooms.find(query).count(),
};
}

@ -0,0 +1,53 @@
import { LivechatRooms } from '../../../../models/server/raw';
export async function findRooms({
agents,
roomName,
departmentId,
open,
createdAt,
closedAt,
tags,
customFields,
options: {
offset,
count,
fields,
sort,
},
}) {
const total = (await LivechatRooms.findRoomsWithCriteria({
agents,
roomName,
departmentId,
open,
createdAt,
closedAt,
tags,
customFields,
})).length;
const rooms = await LivechatRooms.findRoomsWithCriteria({
agents,
roomName,
departmentId,
open,
createdAt,
closedAt,
tags,
customFields,
options: {
sort: sort || { ts: 1 },
offset,
count,
fields,
},
});
return {
rooms,
count: rooms.length,
offset,
total,
};
}

@ -17,6 +17,7 @@ const userCanAccessRoom = ({ _id }) => {
};
Meteor.publish('livechat:rooms', function(filter = {}, offset = 0, limit = 20) {
console.warn('The publication "livechat:rooms" is deprecated and will be removed after version v4.0.0');
if (!this.userId) {
return this.error(new Meteor.Error('error-not-authorized', 'Not authorized', { publish: 'livechat:rooms' }));
}

@ -103,4 +103,77 @@ export class LivechatRoomsRaw extends BaseRaw {
return this.find(query, options);
}
findRoomsWithCriteria({ agents, roomName, departmentId, open, createdAt, closedAt, tags, customFields, options = {} }) {
const match = {
$match: {
t: 'l',
},
};
if (agents) {
match.$match.$or = [{ 'servedBy._id': { $in: agents } }, { 'servedBy.username': { $in: agents } }];
}
if (roomName) {
match.$match.fname = new RegExp(roomName, 'i');
}
if (departmentId) {
match.$match.departmentId = departmentId;
}
if (open !== undefined) {
match.$match.open = { $exists: open };
}
if (createdAt) {
match.$match.ts = {};
if (createdAt.start) {
match.$match.ts.$gte = new Date(createdAt.start);
}
if (createdAt.end) {
match.$match.ts.$lte = new Date(createdAt.end);
}
}
if (closedAt) {
match.$match.closedAt = {};
if (closedAt.start) {
match.$match.closedAt.$gte = new Date(closedAt.start);
}
if (closedAt.end) {
match.$match.closedAt.$lte = new Date(closedAt.end);
}
}
if (tags) {
match.$match.tags = { $in: tags };
}
if (customFields) {
match.$match.$and = Object.keys(customFields).map((key) => ({ [`livechatData.${ key }`]: new RegExp(customFields[key], 'i') }));
}
const firstParams = [match];
if (options.offset) {
firstParams.push({ $skip: options.offset });
}
if (options.count) {
firstParams.push({ $limit: options.count });
}
if (options.sort) {
firstParams.push({ $sort: { name: 1 } });
}
const lookup = {
$lookup: {
from: 'rocketchat_livechat_department',
localField: 'departmentId',
foreignField: '_id',
as: 'department',
},
};
const unwind = {
$unwind: {
path: '$department',
preserveNullAndEmptyArrays: true,
},
};
const params = [...firstParams, lookup, unwind];
if (options.fields) {
params.push({ $project: options.fields });
}
return this.col.aggregate(params).toArray();
}
}

@ -12,7 +12,6 @@ roomDataStream.allowWrite('none');
roomDataStream.allowRead(function(rid) {
try {
const room = Meteor.call('canAccessRoom', rid, this.userId);
if (!room) {
return false;
}
@ -28,7 +27,7 @@ roomDataStream.allowRead(function(rid) {
});
export function emitRoomDataEvent(id, data) {
if (!data) {
if (!data || !data.t) {
return;
}

@ -12,7 +12,7 @@ describe('LIVECHAT - rooms', function() {
createAgent()
.then(() => createVisitor())
.then((visitor) => createLivechatRoom(visitor.token))
.then(done);
.then(() => done());
});
});
@ -42,6 +42,16 @@ describe('LIVECHAT - rooms', function() {
.end(done);
});
});
it('should return an error when the "roomName" query parameter is not valid', (done) => {
request.get(api('livechat/rooms?roomName[]=invalid'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
})
.end(done);
});
it('should return an error when the "departmentId" query parameter is not valid', (done) => {
request.get(api('livechat/rooms?departmentId[]=marcos'))
.set(credentials)
@ -119,7 +129,7 @@ describe('LIVECHAT - rooms', function() {
it('should return an array of rooms when the query params is all valid', (done) => {
request.get(api(`livechat/rooms?agents[]=teste&departamentId=123&open=true&createdAt={"start": "2018-01-26T00:11:22.345Z", "end": "2018-01-26T00:11:22.345Z"}
&closedAt={"start": "2018-01-26T00:11:22.345Z", "end": "2018-01-26T00:11:22.345Z"}&tags[]=rocket
&customFields={"docId": "031041"}&count=3&offset=1&sort={"_updatedAt": 1}&fields={"msgs": 1}`))
&customFields={"docId": "031041"}&count=3&offset=1&sort={"_updatedAt": 1}&fields={"msgs": 0}&roomName=test`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)

Loading…
Cancel
Save