Skip to content

Commit

Permalink
[IMPROVE] Omnichannel aggregations performance improvements (#16755)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcosSpessatto authored Mar 19, 2020
1 parent f1d8563 commit 2c729b6
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 588 deletions.
76 changes: 41 additions & 35 deletions app/livechat/imports/server/rest/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,51 @@ import { hasPermission } from '../../../../authorization/server';
import { API } from '../../../../api';
import { findRooms } from '../../../server/api/lib/rooms';

const validateDateParams = (property, date) => {
if (date) {
date = JSON.parse(date);
}
if (date && date.start && isNaN(Date.parse(date.start))) {
throw new Error(`The "${ property }.start" query parameter must be a valid date.`);
}
if (date && date.end && isNaN(Date.parse(date.end))) {
throw new Error(`The "${ property }.end" query parameter must be a valid date.`);
}
return date;
};

API.v1.addRoute('livechat/rooms', { authRequired: true }, {
get() {
try {
if (!hasPermission(this.userId, 'view-livechat-manager')) {
return API.v1.unauthorized();
}
const { offset, count } = this.getPaginationItems();
const { sort, fields } = this.parseJsonQuery();
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 (!hasPermission(this.userId, 'view-livechat-manager')) {
return API.v1.unauthorized();
}
const { offset, count } = this.getPaginationItems();
const { sort, fields } = this.parseJsonQuery();
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);
}
if (closedAt) {
closedAt = JSON.parse(closedAt);
}
if (customFields) {
customFields = JSON.parse(customFields);
}
createdAt = validateDateParams('createdAt', createdAt);
closedAt = validateDateParams('closedAt', closedAt);

return API.v1.success(Promise.await(findRooms({
agents,
roomName,
departmentId,
open: open && open === 'true',
createdAt,
closedAt,
tags,
customFields,
options: { offset, count, sort, fields },
})));
} catch (e) {
return API.v1.failure(e);
if (customFields) {
customFields = JSON.parse(customFields);
}

return API.v1.success(Promise.await(findRooms({
agents,
roomName,
departmentId,
open: open && open === 'true',
createdAt,
closedAt,
tags,
customFields,
options: { offset, count, sort, fields },
})));
},
});
4 changes: 3 additions & 1 deletion app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,13 @@ export const Livechat = {
const extraData = callbacks.run('livechat.beforeCloseRoom', { room, options });

const now = new Date();
const { _id: rid, servedBy } = room;
const serviceTimeDuration = servedBy && (now.getTime() - servedBy.ts) / 1000;

const closeData = {
closedAt: now,
chatDuration: (now.getTime() - room.ts) / 1000,
...serviceTimeDuration && { serviceTimeDuration },
...extraData,
};

Expand All @@ -343,7 +346,6 @@ export const Livechat = {
};
}

const { _id: rid, servedBy } = room;
LivechatRooms.closeByRoomId(rid, closeData);
LivechatInquiry.removeByRoomId(rid);

Expand Down
17 changes: 10 additions & 7 deletions app/livechat/server/lib/analytics/agents.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Users } from '../../../../models/server/raw';
import { LivechatRooms, LivechatAgentActivity } from '../../../../models/server/raw';

const findAllAverageServiceTimeAsync = async ({
start,
Expand All @@ -8,9 +8,10 @@ const findAllAverageServiceTimeAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatRooms.findAllAverageServiceTimeByAgents({ start, end, onlyCount: true }).toArray();
return {
agents: await Users.findAllAverageServiceTime({ start, end, options }),
total: (await Users.findAllAverageServiceTime({ start, end })).length,
agents: await LivechatRooms.findAllAverageServiceTimeByAgents({ start, end, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -22,9 +23,10 @@ const findAllServiceTimeAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatRooms.findAllServiceTimeByAgent({ start, end, onlyCount: true }).toArray();
return {
agents: await Users.findAllServiceTime({ start, end, options }),
total: (await Users.findAllServiceTime({ start, end })).length,
agents: await LivechatRooms.findAllServiceTimeByAgent({ start, end, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -37,9 +39,10 @@ const findAvailableServiceTimeHistoryAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatAgentActivity.findAvailableServiceTimeHistory({ start, end, fullReport, onlyCount: true }).toArray();
return {
agents: await Users.findAvailableServiceTimeHistory({ start, end, fullReport, options }),
total: (await Users.findAvailableServiceTimeHistory({ start, end, fullReport })).length,
agents: await LivechatAgentActivity.findAvailableServiceTimeHistory({ start, end, fullReport, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand Down
42 changes: 25 additions & 17 deletions app/livechat/server/lib/analytics/departments.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LivechatRooms } from '../../../../models/server/raw';
import { LivechatRooms, Messages } from '../../../../models/server/raw';

export const findAllRoomsAsync = async ({
start,
Expand All @@ -10,9 +10,10 @@ export const findAllRoomsAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatRooms.findAllRooms({ start, answered, end, departmentId, onlyCount: true }).toArray();
return {
departments: await LivechatRooms.findAllRooms({ start, answered, end, departmentId, options }),
total: (await LivechatRooms.findAllRooms({ start, answered, end, departmentId })).length,
departments: await LivechatRooms.findAllRooms({ start, answered, end, departmentId, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -25,9 +26,10 @@ export const findAllAverageOfChatDurationTimeAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatRooms.findAllAverageOfChatDurationTime({ start, end, departmentId, onlyCount: true }).toArray();
return {
departments: await LivechatRooms.findAllAverageOfChatDurationTime({ start, end, departmentId, options }),
total: (await LivechatRooms.findAllAverageOfChatDurationTime({ start, end, departmentId })).length,
departments: await LivechatRooms.findAllAverageOfChatDurationTime({ start, end, departmentId, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -40,9 +42,10 @@ export const findAllAverageServiceTimeAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatRooms.findAllAverageOfServiceTime({ start, end, departmentId, onlyCount: true }).toArray();
return {
departments: await LivechatRooms.findAllAverageOfServiceTime({ start, end, departmentId, options }),
total: (await LivechatRooms.findAllAverageOfServiceTime({ start, end, departmentId })).length,
departments: await LivechatRooms.findAllAverageOfServiceTime({ start, end, departmentId, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -55,9 +58,10 @@ export const findAllServiceTimeAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatRooms.findAllServiceTime({ start, end, departmentId, onlyCount: true }).toArray();
return {
departments: await LivechatRooms.findAllServiceTime({ start, end, departmentId, options }),
total: (await LivechatRooms.findAllServiceTime({ start, end, departmentId })).length,
departments: await LivechatRooms.findAllServiceTime({ start, end, departmentId, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -70,9 +74,10 @@ export const findAllAverageWaitingTimeAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await LivechatRooms.findAllAverageWaitingTime({ start, end, departmentId, onlyCount: true }).toArray();
return {
departments: await LivechatRooms.findAllAverageWaitingTime({ start, end, departmentId, options }),
total: (await LivechatRooms.findAllAverageWaitingTime({ start, end, departmentId })).length,
departments: await LivechatRooms.findAllAverageWaitingTime({ start, end, departmentId, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -85,9 +90,10 @@ export const findAllNumberOfTransferredRoomsAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await Messages.findAllNumberOfTransferredRooms({ start, end, departmentId, onlyCount: true }).toArray();
return {
departments: await LivechatRooms.findAllNumberOfTransferredRooms({ start, end, departmentId, options }),
total: (await LivechatRooms.findAllNumberOfTransferredRooms({ start, end, departmentId })).length,
departments: await Messages.findAllNumberOfTransferredRooms({ start, end, departmentId, options }).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -100,9 +106,10 @@ export const findAllNumberOfAbandonedRoomsAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await(await LivechatRooms.findAllNumberOfAbandonedRooms({ start, end, departmentId, onlyCount: true })).toArray();
return {
departments: await LivechatRooms.findAllNumberOfAbandonedRooms({ start, end, departmentId, options }),
total: (await LivechatRooms.findAllNumberOfAbandonedRooms({ start, end, departmentId })).length,
departments: await(await LivechatRooms.findAllNumberOfAbandonedRooms({ start, end, departmentId, options })).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand All @@ -115,9 +122,10 @@ export const findPercentageOfAbandonedRoomsAsync = async ({
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
const total = await (await LivechatRooms.findPercentageOfAbandonedRooms({ start, end, departmentId, onlyCount: true })).toArray();
return {
departments: await LivechatRooms.findPercentageOfAbandonedRooms({ start, end, departmentId, options }),
total: (await LivechatRooms.findPercentageOfAbandonedRooms({ start, end, departmentId })).length,
departments: await(await LivechatRooms.findPercentageOfAbandonedRooms({ start, end, departmentId, options })).toArray(),
total: total.length ? total[0].total : 0,
};
};

Expand Down
6 changes: 5 additions & 1 deletion app/models/server/models/LivechatRooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export class LivechatRooms extends Base {

this.tryEnsureIndex({ open: 1 }, { sparse: true });
this.tryEnsureIndex({ departmentId: 1 }, { sparse: true });
this.tryEnsureIndex({ 'metrics.chatDuration': 1 }, { sparse: true });
this.tryEnsureIndex({ 'metrics.serviceTimeDuration': 1 }, { sparse: true });
this.tryEnsureIndex({ 'metrics.visitorInactivity': 1 }, { sparse: true });
}

findLivechat(filter = {}, offset = 0, limit = 20) {
Expand Down Expand Up @@ -421,7 +424,7 @@ export class LivechatRooms extends Base {


closeByRoomId(roomId, closeInfo) {
const { closer, closedBy, closedAt, chatDuration, ...extraData } = closeInfo;
const { closer, closedBy, closedAt, chatDuration, serviceTimeDuration, ...extraData } = closeInfo;

return this.update({
_id: roomId,
Expand All @@ -432,6 +435,7 @@ export class LivechatRooms extends Base {
closedBy,
closedAt,
'metrics.chatDuration': chatDuration,
'metrics.serviceTimeDuration': serviceTimeDuration,
'v.status': 'offline',
...extraData,
},
Expand Down
56 changes: 56 additions & 0 deletions app/models/server/raw/LivechatAgentActivity.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import moment from 'moment';

import { BaseRaw } from './BaseRaw';

export class LivechatAgentActivityRaw extends BaseRaw {
Expand Down Expand Up @@ -64,4 +66,58 @@ export class LivechatAgentActivityRaw extends BaseRaw {
params.push(project);
return this.col.aggregate(params).toArray();
}

findAvailableServiceTimeHistory({ start, end, fullReport, onlyCount = false, options = {} }) {
const match = {
$match: {
date: {
$gte: parseInt(moment(start).format('YYYYMMDD')),
$lte: parseInt(moment(end).format('YYYYMMDD')),
},
},
};
const lookup = {
$lookup: {
from: 'users',
localField: 'agentId',
foreignField: '_id',
as: 'user',
},
};
const unwind = {
$unwind: {
path: '$user',
},
};
const group = {
$group: {
_id: { _id: '$user._id', username: '$user.username' },
serviceHistory: { $first: '$serviceHistory' },
availableTimeInSeconds: { $sum: '$availableTime' },
},
};
const project = {
$project: {
_id: 0,
username: '$_id.username',
availableTimeInSeconds: 1,
},
};
if (fullReport) {
project.$project.serviceHistory = 1;
}
const sort = { $sort: options.sort || { username: 1 } };
const params = [match, lookup, unwind, group, project, sort];
if (onlyCount) {
params.push({ $count: 'total' });
return this.col.aggregate(params);
}
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
return this.col.aggregate(params, { allowDiskUse: true });
}
}
Loading

0 comments on commit 2c729b6

Please sign in to comment.