[NEW] Livechat analytics functions (#15666)

* Livechat analytics functions

* Move analytics functions to models

* Fix params

* Fix collection name
pull/15764/head^2
Marcos Spessatto Defendi 6 years ago committed by Renato Becker
parent d86a3ed4ad
commit 485210f2f9
  1. 48
      app/livechat/server/lib/analytics/agents.js
  2. 108
      app/livechat/server/lib/analytics/departments.js
  3. 413
      app/models/server/raw/LivechatDepartment.js
  4. 170
      app/models/server/raw/Users.js

@ -0,0 +1,48 @@
import { Users } from '../../../../models/server/raw';
const findAllAverageServiceTimeAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
agents: await Users.findAllAverageServiceTime({ start, end, options }),
total: (await Users.findAllAverageServiceTime({ start, end })).length,
};
};
const findAllServiceTimeAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
agents: await Users.findAllServiceTime({ start, end, options }),
total: (await Users.findAllServiceTime({ start, end })).length,
};
};
const findAvailableServiceTimeHistoryAsync = async ({
start,
end,
fullReport,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
agents: await Users.findAvailableServiceTimeHistory({ start, end, fullReport, options }),
total: (await Users.findAvailableServiceTimeHistory({ start, end, fullReport })).length,
};
};
export const findAllAverageServiceTime = ({ start, end, options }) => Promise.await(findAllAverageServiceTimeAsync({ start, end, options }));
export const findAllServiceTime = ({ start, end, options }) => Promise.await(findAllServiceTimeAsync({ start, end, options }));
export const findAvailableServiceTimeHistory = ({ start, end, fullReport, options }) => Promise.await(findAvailableServiceTimeHistoryAsync({ start, end, fullReport, options }));

@ -0,0 +1,108 @@
import { LivechatDepartment } from '../../../../models/server/raw';
const findAllRoomsAsync = async ({
start,
end,
answered,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
departments: await LivechatDepartment.findAllRooms({ start, answered, end, options }),
total: (await LivechatDepartment.findAllRooms({ start, answered, end })).length,
};
};
const findAllAverageServiceTimeAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
departments: await LivechatDepartment.findAllAverageServiceTime({ start, end, options }),
total: (await LivechatDepartment.findAllAverageServiceTime({ start, end })).length,
};
};
const findAllServiceTimeAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
departments: await LivechatDepartment.findAllServiceTime({ start, end, options }),
total: (await LivechatDepartment.findAllServiceTime({ start, end })).length,
};
};
const findAllAverageWaitingTimeAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
departments: await LivechatDepartment.findAllAverageWaitingTime({ start, end, options }),
total: (await LivechatDepartment.findAllAverageWaitingTime({ start, end })).length,
};
};
const findAllNumberOfTransferedRoomsAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
departments: await LivechatDepartment.findAllNumberOfTransferedRooms({ start, end, options }),
total: (await LivechatDepartment.findAllNumberOfTransferedRooms({ start, end })).length,
};
};
const findAllNumberOfAbandonedRoomsAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
departments: await LivechatDepartment.findAllNumberOfAbandonedRooms({ start, end, options }),
total: (await LivechatDepartment.findAllNumberOfAbandonedRooms({ start, end })).length,
};
};
const findPercentageOfAbandonedRoomsAsync = async ({
start,
end,
options = {},
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
}
return {
departments: await LivechatDepartment.findPercentageOfAbandonedRooms({ start, end, options }),
total: (await LivechatDepartment.findPercentageOfAbandonedRooms({ start, end })).length,
};
};
export const findAllAverageServiceTime = ({ start, end, options }) => Promise.await(findAllAverageServiceTimeAsync({ start, end, options }));
export const findAllRooms = ({ start, end, answered, options }) => Promise.await(findAllRoomsAsync({ start, end, answered, options }));
export const findAllServiceTime = ({ start, end, options }) => Promise.await(findAllServiceTimeAsync({ start, end, options }));
export const findAllAverageWaitingTime = ({ start, end, options }) => Promise.await(findAllAverageWaitingTimeAsync({ start, end, options }));
export const findAllNumberOfTransferedRooms = ({ start, end, options }) => Promise.await(findAllNumberOfTransferedRoomsAsync({ start, end, options }));
export const findAllNumberOfAbandonedRooms = ({ start, end, options }) => Promise.await(findAllNumberOfAbandonedRoomsAsync({ start, end, options }));
export const findPercentageOfAbandonedRooms = ({ start, end, options }) => Promise.await(findPercentageOfAbandonedRoomsAsync({ start, end, options }));

@ -1,5 +1,418 @@
import { BaseRaw } from './BaseRaw';
import { getValue } from '../../../settings/server/raw';
export class LivechatDepartmentRaw extends BaseRaw {
findAllRooms({ start, end, answered, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
];
if (answered !== undefined) {
roomsFilter.push({ [answered ? '$ne' : '$eq']: ['$$room.waitingResponse', true] });
}
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'departmentId',
as: 'rooms',
},
};
const project = {
$project: {
name: 1,
description: 1,
enabled: 1,
rooms: {
$size: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
},
};
const params = [lookup, project];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { name: 1 } });
}
return this.col.aggregate(params).toArray();
}
findAllAverageServiceTime({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
];
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'departmentId',
as: 'rooms',
},
};
const projects = [
{
$project: {
department: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
},
{
$project: {
department: '$department',
chats: { $size: '$rooms' },
chatsDuration: { $sum: '$rooms.metrics.chatDuration' },
},
},
{
$project: {
name: '$department.name',
description: '$department.description',
enabled: '$department.enabled',
averageServiceTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$chats', 0] }, 0, { $divide: ['$chatsDuration', '$chats'] }] } },
},
}];
const params = [lookup, ...projects];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { name: 1 } });
}
return this.col.aggregate(params).toArray();
}
findAllServiceTime({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
];
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'departmentId',
as: 'rooms',
},
};
const projects = [
{
$project: {
department: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
},
{
$project: {
name: '$department.name',
description: '$department.description',
enabled: '$department.enabled',
chats: { $size: '$rooms' },
chatsDuration: { $ceil: { $sum: '$rooms.metrics.chatDuration' } },
},
}];
const params = [lookup, ...projects];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { name: 1 } });
}
return this.col.aggregate(params).toArray();
}
findAllAverageWaitingTime({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
{ $ne: ['$$room.waitingResponse', true] },
];
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'departmentId',
as: 'rooms',
},
};
const projects = [{
$project: {
department: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
},
{
$project: {
department: '$department',
chats: { $size: '$rooms' },
chatsFirstResponses: { $sum: '$rooms.metrics.response.ft' },
},
},
{
$project: {
name: '$department.name',
description: '$department.description',
enabled: '$department.enabled',
averageWaitingTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$chats', 0] }, 0, { $divide: ['$chatsFirstResponses', '$chats'] }] } },
},
}];
const params = [lookup, ...projects];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { name: 1 } });
}
return this.col.aggregate(params).toArray();
}
findAllNumberOfTransferedRooms({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
];
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'departmentId',
as: 'rooms',
},
};
const projectRooms = {
$project: {
department: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
};
const projectTransfersSize = {
$project: {
department: '$department',
transfers: { $size: { $ifNull: ['$rooms.transferHistory', []] } },
},
};
const group = {
$group: {
_id: {
departmentId: '$department._id',
name: '$department.name',
description: '$department.description',
enabled: '$department.enabled',
},
numberOfTransferedRooms: { $sum: '$transfers' },
},
};
const presentationProject = {
$project: {
_id: '$_id.departmentId',
name: '$_id.name',
description: '$_id.description',
enabled: '$_id.enabled',
numberOfTransferedRooms: 1,
},
};
const unwind = {
$unwind: {
path: '$rooms',
preserveNullAndEmptyArrays: true,
},
};
const params = [lookup, projectRooms, unwind, projectTransfersSize, group, presentationProject];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { name: 1 } });
}
return this.col.aggregate(params).toArray();
}
async findAllNumberOfAbandonedRooms({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
{ $gte: ['$$room.metrics.visitorInactivity', await getValue('Livechat_visitor_inactivity_timeout')] },
];
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'departmentId',
as: 'rooms',
},
};
const projects = [{
$project: {
department: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
},
{
$project: {
name: '$department.name',
description: '$department.description',
enabled: '$department.enabled',
abandonedRooms: { $size: '$rooms' },
},
}];
const params = [lookup, ...projects];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { name: 1 } });
}
return this.col.aggregate(params).toArray();
}
findPercentageOfAbandonedRooms({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
];
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'departmentId',
as: 'rooms',
},
};
const projectRooms = {
$project: {
department: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
};
const unwind = {
$unwind: {
path: '$rooms',
preserveNullAndEmptyArrays: true,
},
};
const group = {
$group: {
_id: {
departmentId: '$department._id',
name: '$department.name',
description: '$department.description',
enabled: '$department.enabled',
},
abandonedChats: {
$sum: {
$cond: [{
$and: [
{ $ifNull: ['$rooms.metrics.visitorInactivity', false] },
{ $gte: ['$rooms.metrics.visitorInactivity', 1] },
],
}, 1, 0],
},
},
chats: { $sum: 1 },
},
};
const presentationProject = {
$project: {
_id: '$_id.departmentId',
name: '$_id.name',
description: '$_id.description',
enabled: '$_id.enabled',
percentageOfAbandonedChats: {
$floor: {
$cond: [
{ $eq: ['$chats', 0] },
0,
{ $divide: [{ $multiply: ['$abandonedChats', 100] }, '$chats'] },
],
},
},
},
};
const params = [lookup, projectRooms, unwind, group, presentationProject];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { name: 1 } });
}
return this.col.aggregate(params).toArray();
}
}

@ -1,3 +1,5 @@
import moment from 'moment';
import { BaseRaw } from './BaseRaw';
import { Users } from '..';
@ -94,4 +96,172 @@ export class UsersRaw extends BaseRaw {
{ $group: { _id: '$_id', tokens: { $push: '$tokens' } } },
]).toArray();
}
findAllAverageServiceTime({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
];
const match = { $match: { roles: { $in: ['livechat-agent'] } } };
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'servedBy._id',
as: 'rooms',
},
};
const projects = [
{
$project: {
user: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
},
{
$project: {
user: '$user',
chats: { $size: '$rooms' },
chatsDuration: { $sum: '$rooms.metrics.chatDuration' },
},
},
{
$project: {
username: '$user.username',
name: '$user.name',
active: '$user.active',
averageServiceTimeInSeconds: {
$ceil: {
$cond: [
{ $eq: ['$chats', 0] },
0,
{ $divide: ['$chatsDuration', '$chats'] },
],
},
},
},
}];
const params = [match, lookup, ...projects];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { username: 1 } });
}
return this.col.aggregate(params).toArray();
}
findAllServiceTime({ start, end, options = {} }) {
const roomsFilter = [
{ $gte: ['$$room.ts', new Date(start)] },
{ $lte: ['$$room.ts', new Date(end)] },
];
const match = { $match: { roles: { $in: ['livechat-agent'] } } };
const lookup = {
$lookup: {
from: 'rocketchat_room',
localField: '_id',
foreignField: 'servedBy._id',
as: 'rooms',
},
};
const projects = [
{
$project: {
user: '$$ROOT',
rooms: {
$filter: {
input: '$rooms',
as: 'room',
cond: {
$and: roomsFilter,
},
},
},
},
},
{
$project: {
username: '$user.username',
name: '$user.name',
active: '$user.active',
chats: { $size: '$rooms' },
chatsDuration: { $ceil: { $sum: '$rooms.metrics.chatDuration' } },
},
}];
const params = [match, lookup, ...projects];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { username: 1 } });
}
return this.col.aggregate(params).toArray();
}
findAvailableServiceTimeHistory({ start, end, fullReport, options = {} }) {
const sessionFilter = [
{ $gte: ['$$session.date', parseInt(moment(start).format('YYYYMMDD'))] },
{ $lte: ['$$session.date', parseInt(moment(end).format('YYYYMMDD'))] },
];
const match = { $match: { roles: { $in: ['livechat-agent'] } } };
const lookup = {
$lookup: {
from: 'rocketchat_livechat_agent_activity',
localField: '_id',
foreignField: 'agentId',
as: 'sessions',
},
};
const sessionProject = {
$project: {
user: '$$ROOT',
sessions: {
$filter: {
input: '$sessions',
as: 'session',
cond: {
$and: sessionFilter,
},
},
},
},
};
const presentationProject = {
$project: {
username: '$user.username',
name: '$user.name',
active: '$user.active',
availableTimeInSeconds: { $sum: '$sessions.availableTime' },
},
};
if (fullReport) {
presentationProject.$project['sessions.serviceHistory'] = 1;
}
const params = [match, lookup, sessionProject, presentationProject];
if (options.offset) {
params.push({ $skip: options.offset });
}
if (options.count) {
params.push({ $limit: options.count });
}
if (options.sort) {
params.push({ $sort: { username: 1 } });
}
return this.col.aggregate(params).toArray();
}
}

Loading…
Cancel
Save