Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMPROVE] Omnichannel aggregations performance improvements #16755

Merged
merged 12 commits into from
Mar 19, 2020
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