[IMPROVE] Livechat realtime dashboard (#15792)
* LIvechat conversations totalizers REST endpoint * Add conversations metrics * Change the query to ignore livechat system messages * Add total of livechat abandoned chats * Fix projection query * Livechat Productivty metrics using REST * Invert query logic on livechat analytics functions * Convert chats chart data to REST * Remove publication chart updates * Change livechat agents metrics chart data to REST * Change livechat agents status metrics chart data by REST * Replace livechat:visitors publication by REST * Change livechat chats by department chat data by REST * Change livechat timings chart data by REST * remove unnecessary analytics functions * Fix time converter * Add more productivity metrics to the livechat dashboard * Re add livechat departments metric * Fix test * Split livechat agents metrics * Improve metrics * Move some metrics to another endpoints * improve layout * Fix wrong query filters * fix percentage of abandoned rooms query * fix livechat analytics query * Fix dashboard style.pull/16006/head^2
parent
f2eed8af99
commit
c439d9e0d4
@ -1,3 +0,0 @@ |
||||
import { Mongo } from 'meteor/mongo'; |
||||
|
||||
export const AgentUsers = new Mongo.Collection('agentUsers'); |
@ -1,3 +0,0 @@ |
||||
import { Mongo } from 'meteor/mongo'; |
||||
|
||||
export const LivechatDepartment = new Mongo.Collection('rocketchat_livechat_department'); |
@ -1,3 +0,0 @@ |
||||
import { Mongo } from 'meteor/mongo'; |
||||
|
||||
export const LivechatMonitoring = new Mongo.Collection('livechatMonitoring'); |
@ -1,199 +0,0 @@ |
||||
/** |
||||
* |
||||
* @param {Object} dbCursor cursor to minimongo result |
||||
* @return {Object} |
||||
*/ |
||||
const calculateResponseTimings = (dbCursor) => { |
||||
let art = 0; |
||||
let longest = 0; |
||||
let count = 0; |
||||
|
||||
dbCursor.forEach(({ metrics }) => { |
||||
if (metrics && metrics.response && metrics.response.avg) { |
||||
art += metrics.response.avg; |
||||
count++; |
||||
} |
||||
if (metrics && metrics.response && metrics.response.ft) { |
||||
longest = Math.max(longest, metrics.response.ft); |
||||
} |
||||
}); |
||||
|
||||
const avgArt = count ? art / count : 0; |
||||
|
||||
return { |
||||
avg: Math.round(avgArt * 100) / 100, |
||||
longest, |
||||
}; |
||||
}; |
||||
|
||||
/** |
||||
* |
||||
* @param {Object} dbCursor cursor to minimongo result |
||||
* @return {Object} |
||||
*/ |
||||
const calculateReactionTimings = (dbCursor) => { |
||||
let arnt = 0; |
||||
let longest = 0; |
||||
let count = 0; |
||||
|
||||
dbCursor.forEach(({ metrics }) => { |
||||
if (metrics && metrics.reaction && metrics.reaction.ft) { |
||||
arnt += metrics.reaction.ft; |
||||
longest = Math.max(longest, metrics.reaction.ft); |
||||
count++; |
||||
} |
||||
}); |
||||
|
||||
const avgArnt = count ? arnt / count : 0; |
||||
|
||||
return { |
||||
avg: Math.round(avgArnt * 100) / 100, |
||||
longest, |
||||
}; |
||||
}; |
||||
|
||||
/** |
||||
* |
||||
* @param {Object} dbCursor cursor to minimongo result |
||||
* @return {Object} |
||||
*/ |
||||
const calculateDurationData = (dbCursor) => { |
||||
let total = 0; |
||||
let longest = 0; |
||||
let count = 0; |
||||
|
||||
dbCursor.forEach(({ metrics }) => { |
||||
if (metrics && metrics.chatDuration) { |
||||
total += metrics.chatDuration; |
||||
longest = Math.max(longest, metrics.chatDuration); |
||||
count++; |
||||
} |
||||
}); |
||||
|
||||
const avgCD = count ? total / count : 0; |
||||
|
||||
return { |
||||
avg: Math.round(avgCD * 100) / 100, |
||||
longest, |
||||
}; |
||||
}; |
||||
|
||||
/** |
||||
* return readable time format from seconds |
||||
* @param {Double} sec seconds |
||||
* @return {String} Readable string format |
||||
*/ |
||||
const secondsToHHMMSS = (sec) => { |
||||
sec = parseFloat(sec); |
||||
|
||||
let hours = Math.floor(sec / 3600); |
||||
let minutes = Math.floor((sec - (hours * 3600)) / 60); |
||||
let seconds = Math.round(sec - (hours * 3600) - (minutes * 60)); |
||||
|
||||
if (hours < 10) { hours = `0${ hours }`; } |
||||
if (minutes < 10) { minutes = `0${ minutes }`; } |
||||
if (seconds < 10) { seconds = `0${ seconds }`; } |
||||
|
||||
if (hours > 0) { |
||||
return `${ hours }:${ minutes }:${ seconds }`; |
||||
} |
||||
if (minutes > 0) { |
||||
return `${ minutes }:${ seconds }`; |
||||
} |
||||
return sec; |
||||
}; |
||||
|
||||
|
||||
/** |
||||
* |
||||
* @param {Object} dbCursor cursor to minimongo result |
||||
* @return {Object} |
||||
*/ |
||||
export const getTimingsChartData = (dbCursor) => { |
||||
const data = {}; |
||||
|
||||
data.reaction = calculateReactionTimings(dbCursor); |
||||
data.response = calculateResponseTimings(dbCursor); |
||||
data.chatDuration = calculateDurationData(dbCursor); |
||||
|
||||
return data; |
||||
}; |
||||
|
||||
/** |
||||
* |
||||
* @param {Object} dbCursor cursor to minimongo result |
||||
* @return {Object} |
||||
*/ |
||||
export const getAgentStatusData = (dbCursor) => { |
||||
const data = { |
||||
offline: 0, |
||||
available: 0, |
||||
away: 0, |
||||
busy: 0, |
||||
}; |
||||
|
||||
dbCursor.forEach((doc) => { |
||||
if (doc.status === 'offline' || doc.status === 'away' || doc.status === 'busy') { |
||||
data[doc.status]++; |
||||
} else if (doc.status === 'online' && doc.statusLivechat === 'available') { |
||||
data[doc.statusLivechat]++; |
||||
} else { |
||||
data.offline++; |
||||
} |
||||
}); |
||||
|
||||
return data; |
||||
}; |
||||
|
||||
/** |
||||
* |
||||
* @param {Object} dbCursor cursor to minimongo result |
||||
* @return {Array(Object)} |
||||
*/ |
||||
export const getConversationsOverviewData = (dbCursor) => { |
||||
let total = 0; |
||||
let totalMessages = 0; |
||||
|
||||
dbCursor.forEach(function(doc) { |
||||
total++; |
||||
if (doc.msgs) { |
||||
totalMessages += doc.msgs; |
||||
} |
||||
}); |
||||
|
||||
return [{ |
||||
title: 'Total_conversations', |
||||
value: total || 0, |
||||
}, { |
||||
title: 'Total_messages', |
||||
value: totalMessages || 0, |
||||
}]; |
||||
}; |
||||
|
||||
|
||||
/** |
||||
* |
||||
* @param {Object} dbCursor cursor to minimongo result |
||||
* @return {Array(Object)} |
||||
*/ |
||||
export const getTimingsOverviewData = (dbCursor) => { |
||||
let total = 0; |
||||
let totalResponseTime = 0; |
||||
let totalReactionTime = 0; |
||||
|
||||
dbCursor.forEach(function(doc) { |
||||
total++; |
||||
if (doc.metrics && doc.metrics.response) { |
||||
totalResponseTime += doc.metrics.response.avg; |
||||
totalReactionTime += doc.metrics.reaction.ft; |
||||
} |
||||
}); |
||||
|
||||
return [{ |
||||
title: 'Avg_reaction_time', |
||||
value: total ? secondsToHHMMSS((totalReactionTime / total).toFixed(2)) : '-', |
||||
}, { |
||||
title: 'Avg_response_time', |
||||
value: total ? secondsToHHMMSS((totalResponseTime / total).toFixed(2)) : '-', |
||||
}]; |
||||
}; |
@ -0,0 +1,219 @@ |
||||
import { check } from 'meteor/check'; |
||||
|
||||
import { API } from '../../../../api'; |
||||
import { hasPermission } from '../../../../authorization/server'; |
||||
import { |
||||
findAllChatsStatus, |
||||
getProductivityMetrics, |
||||
getConversationsMetrics, |
||||
findAllChatMetricsByAgent, |
||||
findAllAgentsStatus, |
||||
findAllChatMetricsByDepartment, |
||||
findAllResponseTimeMetrics, |
||||
getAgentsProductivityMetrics, |
||||
getChatsMetrics, |
||||
} from '../../../server/lib/analytics/dashboards'; |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/conversation-totalizers', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
|
||||
const totalizers = getConversationsMetrics({ start, end }); |
||||
return API.v1.success(totalizers); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/agents-productivity-totalizers', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
|
||||
const totalizers = getAgentsProductivityMetrics({ start, end }); |
||||
return API.v1.success(totalizers); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/chats-totalizers', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
|
||||
const totalizers = getChatsMetrics({ start, end }); |
||||
return API.v1.success(totalizers); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/productivity-totalizers', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
|
||||
const totalizers = getProductivityMetrics({ start, end }); |
||||
|
||||
return API.v1.success(totalizers); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/charts/chats', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
const result = findAllChatsStatus({ start, end }); |
||||
|
||||
return API.v1.success(result); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-agent', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
const result = findAllChatMetricsByAgent({ start, end }); |
||||
|
||||
return API.v1.success(result); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/charts/agents-status', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
const result = findAllAgentsStatus({}); |
||||
|
||||
return API.v1.success(result); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-department', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
const result = findAllChatMetricsByDepartment({ start, end }); |
||||
|
||||
return API.v1.success(result); |
||||
}, |
||||
}); |
||||
|
||||
API.v1.addRoute('livechat/analytics/dashboards/charts/timings', { authRequired: true }, { |
||||
get() { |
||||
if (!hasPermission(this.userId, 'view-livechat-manager')) { |
||||
return API.v1.unauthorized(); |
||||
} |
||||
let { start, end } = this.requestParams(); |
||||
check(start, String); |
||||
check(end, String); |
||||
|
||||
if (isNaN(Date.parse(start))) { |
||||
return API.v1.failure('The "start" query parameter must be a valid date.'); |
||||
} |
||||
start = new Date(start); |
||||
|
||||
if (isNaN(Date.parse(end))) { |
||||
return API.v1.failure('The "end" query parameter must be a valid date.'); |
||||
} |
||||
end = new Date(end); |
||||
const result = findAllResponseTimeMetrics({ start, end }); |
||||
|
||||
return API.v1.success(result); |
||||
}, |
||||
}); |
@ -0,0 +1,270 @@ |
||||
import moment from 'moment'; |
||||
|
||||
import { LivechatRooms, Users, LivechatVisitors, LivechatAgentActivity } from '../../../../models/server/raw'; |
||||
import { Livechat } from '../Livechat'; |
||||
import { secondsToHHMMSS } from '../../../../utils/server'; |
||||
import { |
||||
findPercentageOfAbandonedRoomsAsync, |
||||
findAllAverageOfChatDurationTimeAsync, |
||||
findAllAverageWaitingTimeAsync, |
||||
findAllNumberOfAbandonedRoomsAsync, |
||||
findAllAverageServiceTimeAsync, |
||||
} from './departments'; |
||||
|
||||
|
||||
const findAllChatsStatusAsync = async ({ |
||||
start, |
||||
end, |
||||
departmentId = undefined, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
return { |
||||
open: await LivechatRooms.countAllOpenChatsBetweenDate({ start, end, departmentId }), |
||||
closed: await LivechatRooms.countAllClosedChatsBetweenDate({ start, end, departmentId }), |
||||
queued: await LivechatRooms.countAllQueuedChatsBetweenDate({ start, end, departmentId }), |
||||
}; |
||||
}; |
||||
|
||||
const getProductivityMetricsAsync = async ({ |
||||
start, |
||||
end, |
||||
departmentId = undefined, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
const totalizers = Livechat.Analytics.getAnalyticsOverviewData({ |
||||
daterange: { |
||||
from: start, |
||||
to: end, |
||||
}, |
||||
analyticsOptions: { |
||||
name: 'Productivity', |
||||
}, |
||||
}); |
||||
const averageWaitingTime = await findAllAverageWaitingTimeAsync({ |
||||
start, |
||||
end, |
||||
departmentId, |
||||
}); |
||||
|
||||
const totalOfWaitingTime = averageWaitingTime.departments.length; |
||||
|
||||
const sumOfWaitingTime = averageWaitingTime.departments.reduce((acc, serviceTime) => { |
||||
acc += serviceTime.averageWaitingTimeInSeconds; |
||||
return acc; |
||||
}, 0); |
||||
const totalOfAvarageWaitingTime = totalOfWaitingTime === 0 ? 0 : sumOfWaitingTime / totalOfWaitingTime; |
||||
|
||||
return { |
||||
totalizers: [ |
||||
...totalizers, |
||||
{ title: 'Avg_of_waiting_time', value: secondsToHHMMSS(totalOfAvarageWaitingTime) }, |
||||
], |
||||
}; |
||||
}; |
||||
|
||||
const getAgentsProductivityMetricsAsync = async ({ |
||||
start, |
||||
end, |
||||
departmentId = undefined, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
const averageOfAvailableServiceTime = (await LivechatAgentActivity.findAllAverageAvailableServiceTime({ |
||||
date: parseInt(moment(start).format('YYYYMMDD')), |
||||
departmentId, |
||||
}))[0]; |
||||
const averageOfServiceTime = await findAllAverageServiceTimeAsync({ |
||||
start, |
||||
end, |
||||
departmentId, |
||||
}); |
||||
const totalizers = Livechat.Analytics.getAnalyticsOverviewData({ |
||||
daterange: { |
||||
from: start, |
||||
to: end, |
||||
}, |
||||
analyticsOptions: { |
||||
name: 'Conversations', |
||||
}, |
||||
}); |
||||
|
||||
const totalOfServiceTime = averageOfServiceTime.departments.length; |
||||
|
||||
const sumOfServiceTime = averageOfServiceTime.departments.reduce((acc, serviceTime) => { |
||||
acc += serviceTime.averageServiceTimeInSeconds; |
||||
return acc; |
||||
}, 0); |
||||
const totalOfAverageAvailableServiceTime = averageOfAvailableServiceTime ? averageOfAvailableServiceTime.averageAvailableServiceTimeInSeconds : 0; |
||||
const totalOfAverageServiceTime = totalOfServiceTime === 0 ? 0 : sumOfServiceTime / totalOfServiceTime; |
||||
|
||||
return { |
||||
totalizers: [ |
||||
...totalizers.filter((metric) => metric.title === 'Busiest_time'), |
||||
{ title: 'Avg_of_available_service_time', value: secondsToHHMMSS(totalOfAverageAvailableServiceTime) }, |
||||
{ title: 'Avg_of_service_time', value: secondsToHHMMSS(totalOfAverageServiceTime) }, |
||||
], |
||||
}; |
||||
}; |
||||
|
||||
const getChatsMetricsAsync = async ({ |
||||
start, |
||||
end, |
||||
departmentId = undefined, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
const abandonedRooms = await findAllNumberOfAbandonedRoomsAsync({ |
||||
start, |
||||
end, |
||||
departmentId, |
||||
}); |
||||
const averageOfAbandonedRooms = await findPercentageOfAbandonedRoomsAsync({ |
||||
start, |
||||
end, |
||||
departmentId, |
||||
}); |
||||
const averageOfChatDurationTime = await findAllAverageOfChatDurationTimeAsync({ |
||||
start, |
||||
end, |
||||
departmentId, |
||||
}); |
||||
|
||||
const totalOfAbandonedRooms = averageOfAbandonedRooms.departments.length; |
||||
const totalOfChatDurationTime = averageOfChatDurationTime.departments.length; |
||||
|
||||
const sumOfPercentageOfAbandonedRooms = averageOfAbandonedRooms.departments.reduce((acc, abandonedRoom) => { |
||||
acc += abandonedRoom.percentageOfAbandonedChats; |
||||
return acc; |
||||
}, 0); |
||||
const sumOfChatDurationTime = averageOfChatDurationTime.departments.reduce((acc, chatDurationTime) => { |
||||
acc += chatDurationTime.averageChatDurationTimeInSeconds; |
||||
return acc; |
||||
}, 0); |
||||
const totalAbandonedRooms = abandonedRooms.departments.reduce((acc, item) => { |
||||
acc += item.abandonedRooms; |
||||
return acc; |
||||
}, 0); |
||||
|
||||
const totalOfAverageAbandonedRooms = totalOfAbandonedRooms === 0 ? 0 : sumOfPercentageOfAbandonedRooms / totalOfAbandonedRooms; |
||||
const totalOfAverageChatDurationTime = totalOfChatDurationTime === 0 ? 0 : sumOfChatDurationTime / totalOfChatDurationTime; |
||||
|
||||
return { |
||||
totalizers: [ |
||||
{ title: 'Total_abandoned_chats', value: totalAbandonedRooms }, |
||||
{ title: 'Avg_of_abandoned_chats', value: `${ totalOfAverageAbandonedRooms }%` }, |
||||
{ title: 'Avg_of_chat_duration_time', value: secondsToHHMMSS(totalOfAverageChatDurationTime) }, |
||||
], |
||||
}; |
||||
}; |
||||
|
||||
const getConversationsMetricsAsync = async ({ |
||||
start, |
||||
end, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
const totalizers = Livechat.Analytics.getAnalyticsOverviewData({ |
||||
daterange: { |
||||
from: start, |
||||
to: end, |
||||
}, |
||||
analyticsOptions: { |
||||
name: 'Conversations', |
||||
}, |
||||
}); |
||||
const metrics = ['Total_conversations', 'Open_conversations', 'Total_messages']; |
||||
const visitorsCount = await LivechatVisitors.getVisitorsBetweenDate({ start, end }).count(); |
||||
return { |
||||
totalizers: [ |
||||
...totalizers.filter((metric) => metrics.includes(metric.title)), |
||||
{ title: 'Total_visitors', value: visitorsCount }, |
||||
], |
||||
}; |
||||
}; |
||||
|
||||
const findAllChatMetricsByAgentAsync = async ({ |
||||
start, |
||||
end, |
||||
departmentId = undefined, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
const open = await LivechatRooms.countAllOpenChatsByAgentBetweenDate({ start, end, departmentId }); |
||||
const closed = await LivechatRooms.countAllClosedChatsByAgentBetweenDate({ start, end, departmentId }); |
||||
const result = {}; |
||||
(open || []).forEach((agent) => { |
||||
result[agent._id] = { open: agent.chats, closed: 0 }; |
||||
}); |
||||
(closed || []).forEach((agent) => { |
||||
result[agent._id] = { open: result[agent._id] ? result[agent._id].open : 0, closed: agent.chats }; |
||||
}); |
||||
return result; |
||||
}; |
||||
|
||||
const findAllAgentsStatusAsync = async ({ departmentId = undefined }) => (await Users.countAllAgentsStatus({ departmentId }))[0]; |
||||
|
||||
const findAllChatMetricsByDepartmentAsync = async ({ |
||||
start, |
||||
end, |
||||
departmentId = undefined, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
const open = await LivechatRooms.countAllOpenChatsByDepartmentBetweenDate({ start, end, departmentId }); |
||||
const closed = await LivechatRooms.countAllClosedChatsByDepartmentBetweenDate({ start, end, departmentId }); |
||||
const result = {}; |
||||
(open || []).forEach((department) => { |
||||
result[department.name] = { open: department.chats, closed: 0 }; |
||||
}); |
||||
(closed || []).forEach((department) => { |
||||
result[department.name] = { open: result[department.name] ? result[department.name].open : 0, closed: department.chats }; |
||||
}); |
||||
return result; |
||||
}; |
||||
|
||||
const findAllResponseTimeMetricsAsync = async ({ |
||||
start, |
||||
end, |
||||
departmentId = undefined, |
||||
}) => { |
||||
if (!start || !end) { |
||||
throw new Error('"start" and "end" must be provided'); |
||||
} |
||||
const responseTimes = (await LivechatRooms.calculateResponseTimingsBetweenDates({ start, end, departmentId }))[0]; |
||||
const reactionTimes = (await LivechatRooms.calculateReactionTimingsBetweenDates({ start, end, departmentId }))[0]; |
||||
const durationTimings = (await LivechatRooms.calculateDurationTimingsBetweenDates({ start, end, departmentId }))[0]; |
||||
|
||||
return { |
||||
response: { |
||||
avg: responseTimes ? responseTimes.avg : 0, |
||||
longest: responseTimes ? responseTimes.longest : 0, |
||||
}, |
||||
reaction: { |
||||
avg: reactionTimes ? reactionTimes.avg : 0, |
||||
longest: reactionTimes ? reactionTimes.longest : 0, |
||||
}, |
||||
chatDuration: { |
||||
avg: durationTimings ? durationTimings.avg : 0, |
||||
longest: durationTimings ? durationTimings.longest : 0, |
||||
}, |
||||
}; |
||||
}; |
||||
|
||||
export const findAllChatsStatus = ({ start, end, departmentId = undefined }) => Promise.await(findAllChatsStatusAsync({ start, end, departmentId })); |
||||
export const getProductivityMetrics = ({ start, end, departmentId = undefined }) => Promise.await(getProductivityMetricsAsync({ start, end, departmentId })); |
||||
export const getAgentsProductivityMetrics = ({ start, end, departmentId = undefined }) => Promise.await(getAgentsProductivityMetricsAsync({ start, end, departmentId })); |
||||
export const getConversationsMetrics = ({ start, end, departmentId = undefined }) => Promise.await(getConversationsMetricsAsync({ start, end, departmentId })); |
||||
export const findAllChatMetricsByAgent = ({ start, end, departmentId = undefined }) => Promise.await(findAllChatMetricsByAgentAsync({ start, end, departmentId })); |
||||
export const findAllChatMetricsByDepartment = ({ start, end, departmentId = undefined }) => Promise.await(findAllChatMetricsByDepartmentAsync({ start, end, departmentId })); |
||||
export const findAllResponseTimeMetrics = ({ start, end, departmentId = undefined }) => Promise.await(findAllResponseTimeMetricsAsync({ start, end, departmentId })); |
||||
export const getChatsMetrics = ({ start, end, departmentId = undefined }) => Promise.await(getChatsMetricsAsync({ start, end, departmentId })); |
||||
export const findAllAgentsStatus = ({ departmentId = undefined }) => Promise.await(findAllAgentsStatusAsync({ departmentId })); |
@ -0,0 +1,67 @@ |
||||
import { BaseRaw } from './BaseRaw'; |
||||
|
||||
export class LivechatAgentActivityRaw extends BaseRaw { |
||||
findAllAverageAvailableServiceTime({ date, departmentId }) { |
||||
const match = { $match: { date } }; |
||||
const lookup = { |
||||
$lookup: { |
||||
from: 'rocketchat_livechat_department_agents', |
||||
localField: 'agentId', |
||||
foreignField: 'agentId', |
||||
as: 'departments', |
||||
}, |
||||
}; |
||||
const unwind = { |
||||
$unwind: { |
||||
path: '$departments', |
||||
preserveNullAndEmptyArrays: true, |
||||
}, |
||||
}; |
||||
const departmentsMatch = { |
||||
$match: { |
||||
'departments.departmentId': departmentId, |
||||
}, |
||||
}; |
||||
const sumAvailableTimeWithCurrentTime = { |
||||
$sum: [ |
||||
{ $divide: [{ $subtract: [new Date(), '$lastStartedAt'] }, 1000] }, |
||||
'$availableTime', |
||||
], |
||||
}; |
||||
const group = { |
||||
$group: { |
||||
_id: null, |
||||
allAvailableTimeInSeconds: { |
||||
$sum: { |
||||
$cond: [{ $ifNull: ['$lastStoppedAt', false] }, |
||||
'$availableTime', |
||||
sumAvailableTimeWithCurrentTime], |
||||
}, |
||||
}, |
||||
rooms: { $sum: 1 }, |
||||
}, |
||||
}; |
||||
const project = { |
||||
$project: { |
||||
averageAvailableServiceTimeInSeconds: { |
||||
$trunc: { |
||||
$cond: [ |
||||
{ $eq: ['$rooms', 0] }, |
||||
0, |
||||
{ $divide: ['$allAvailableTimeInSeconds', '$rooms'] }, |
||||
], |
||||
}, |
||||
}, |
||||
}, |
||||
}; |
||||
const params = [match]; |
||||
if (departmentId) { |
||||
params.push(lookup); |
||||
params.push(unwind); |
||||
params.push(departmentsMatch); |
||||
} |
||||
params.push(group); |
||||
params.push(project); |
||||
return this.col.aggregate(params).toArray(); |
||||
} |
||||
} |
@ -1,489 +1,6 @@ |
||||
import { BaseRaw } from './BaseRaw'; |
||||
import { getValue } from '../../../settings/server/raw'; |
||||
|
||||
export class LivechatDepartmentRaw extends BaseRaw { |
||||
findAllRooms({ start, end, answered, departmentId, 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 match = { |
||||
$match: { |
||||
_id: departmentId, |
||||
}, |
||||
}; |
||||
const params = [lookup, project]; |
||||
if (departmentId) { |
||||
params.unshift(match); |
||||
} |
||||
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, departmentId, 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 match = { |
||||
$match: { |
||||
_id: departmentId, |
||||
}, |
||||
}; |
||||
const params = [lookup, ...projects]; |
||||
if (departmentId) { |
||||
params.unshift(match); |
||||
} |
||||
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, departmentId, 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 match = { |
||||
$match: { |
||||
_id: departmentId, |
||||
}, |
||||
}; |
||||
const params = [lookup, ...projects]; |
||||
if (departmentId) { |
||||
params.unshift(match); |
||||
} |
||||
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, departmentId, 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 match = { |
||||
$match: { |
||||
_id: departmentId, |
||||
}, |
||||
}; |
||||
const params = [lookup, ...projects]; |
||||
if (departmentId) { |
||||
params.unshift(match); |
||||
} |
||||
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(); |
||||
} |
||||
|
||||
findAllNumberOfTransferredRooms({ start, end, departmentId, options = {} }) { |
||||
const messageFilter = [ |
||||
{ $gte: ['$$message.ts', new Date(start)] }, |
||||
{ $lte: ['$$message.ts', new Date(end)] }, |
||||
{ $eq: ['$$message.t', 'livechat_transfer_history'] }, |
||||
]; |
||||
const roomsLookup = { |
||||
$lookup: { |
||||
from: 'rocketchat_room', |
||||
localField: '_id', |
||||
foreignField: 'departmentId', |
||||
as: 'rooms', |
||||
}, |
||||
}; |
||||
const messagesLookup = { |
||||
$lookup: { |
||||
from: 'rocketchat_message', |
||||
localField: 'rooms._id', |
||||
foreignField: 'rid', |
||||
as: 'messages', |
||||
}, |
||||
}; |
||||
const projectRooms = { |
||||
$project: { |
||||
department: '$$ROOT', |
||||
rooms: 1, |
||||
}, |
||||
}; |
||||
const projectMessages = { |
||||
$project: { |
||||
department: '$department', |
||||
messages: { |
||||
$filter: { |
||||
input: '$messages', |
||||
as: 'message', |
||||
cond: { |
||||
$and: messageFilter, |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
}; |
||||
const projectTransfersSize = { |
||||
$project: { |
||||
department: '$department', |
||||
transfers: { $size: { $ifNull: ['$messages', []] } }, |
||||
}, |
||||
}; |
||||
const group = { |
||||
$group: { |
||||
_id: { |
||||
departmentId: '$department._id', |
||||
name: '$department.name', |
||||
description: '$department.description', |
||||
enabled: '$department.enabled', |
||||
}, |
||||
numberOfTransferredRooms: { $sum: '$transfers' }, |
||||
}, |
||||
}; |
||||
const presentationProject = { |
||||
$project: { |
||||
_id: '$_id.departmentId', |
||||
name: '$_id.name', |
||||
description: '$_id.description', |
||||
enabled: '$_id.enabled', |
||||
numberOfTransferredRooms: 1, |
||||
}, |
||||
}; |
||||
const unwind = { |
||||
$unwind: { |
||||
path: '$rooms', |
||||
preserveNullAndEmptyArrays: true, |
||||
}, |
||||
}; |
||||
const match = { |
||||
$match: { |
||||
_id: departmentId, |
||||
}, |
||||
}; |
||||
const params = [roomsLookup, projectRooms, unwind, messagesLookup, projectMessages, projectTransfersSize, group, presentationProject]; |
||||
if (departmentId) { |
||||
params.unshift(match); |
||||
} |
||||
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, departmentId, 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 match = { |
||||
$match: { |
||||
_id: departmentId, |
||||
}, |
||||
}; |
||||
const params = [lookup, ...projects]; |
||||
if (departmentId) { |
||||
params.unshift(match); |
||||
} |
||||
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, departmentId, 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 match = { |
||||
$match: { |
||||
_id: departmentId, |
||||
}, |
||||
}; |
||||
const params = [lookup, projectRooms, unwind, group, presentationProject]; |
||||
if (departmentId) { |
||||
params.unshift(match); |
||||
} |
||||
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(); |
||||
} |
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,14 @@ |
||||
import { BaseRaw } from './BaseRaw'; |
||||
|
||||
export class LivechatVisitorsRaw extends BaseRaw { |
||||
getVisitorsBetweenDate({ start, end }) { |
||||
const query = { |
||||
_updatedAt: { |
||||
$gte: new Date(start), |
||||
$lt: new Date(end), |
||||
}, |
||||
}; |
||||
|
||||
return this.find(query, { fields: { _id: 1 } }); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,24 @@ |
||||
/** |
||||
* return readable time format from seconds |
||||
* @param {Double} sec seconds |
||||
* @return {String} Readable string format |
||||
*/ |
||||
export const secondsToHHMMSS = (sec) => { |
||||
sec = parseFloat(sec); |
||||
|
||||
let hours = Math.floor(sec / 3600); |
||||
let minutes = Math.floor((sec - (hours * 3600)) / 60); |
||||
let seconds = Math.round(sec - (hours * 3600) - (minutes * 60)); |
||||
|
||||
if (hours < 10) { hours = `0${ hours }`; } |
||||
if (minutes < 10) { minutes = `0${ minutes }`; } |
||||
if (seconds < 10) { seconds = `0${ seconds }`; } |
||||
|
||||
if (hours > 0) { |
||||
return `${ hours }:${ minutes }:${ seconds }`; |
||||
} |
||||
if (minutes > 0) { |
||||
return `00:${ minutes }:${ seconds }`; |
||||
} |
||||
return `00:00:${ seconds }`; |
||||
}; |
@ -0,0 +1,314 @@ |
||||
import { getCredentials, api, request, credentials } from '../../../data/api-data.js'; |
||||
import { updatePermission, updateSetting } from '../../../data/permissions.helper'; |
||||
|
||||
describe('LIVECHAT - dashboards', function() { |
||||
this.retries(0); |
||||
|
||||
before((done) => getCredentials(done)); |
||||
|
||||
before((done) => { |
||||
updateSetting('Livechat_enabled', true).then(done); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/conversation-totalizers', () => { |
||||
const expectedMetrics = ['Total_conversations', 'Open_conversations', 'Total_messages', 'Busiest_time', 'Total_abandoned_chats', 'Total_visitors']; |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/conversation-totalizers')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an array of conversation totalizers', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/conversation-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
expect(res.body.totalizers).to.be.an('array'); |
||||
res.body.totalizers.forEach((prop) => expect(expectedMetrics.includes(prop.title)).to.be.true); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/productivity-totalizers', () => { |
||||
const expectedMetrics = [ |
||||
'Avg_response_time', |
||||
'Avg_first_response_time', |
||||
'Avg_reaction_time', |
||||
'Avg_of_waiting_time', |
||||
]; |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/productivity-totalizers')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an array of productivity totalizers', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/productivity-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
expect(res.body.totalizers).to.be.an('array'); |
||||
res.body.totalizers.forEach((prop) => expect(expectedMetrics.includes(prop.title)).to.be.true); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/chats-totalizers', () => { |
||||
const expectedMetrics = [ |
||||
'Total_abandoned_chats', |
||||
'Avg_of_abandoned_chats', |
||||
'Avg_of_chat_duration_time', |
||||
]; |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/chats-totalizers')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an array of chats totalizers', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/chats-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
expect(res.body.totalizers).to.be.an('array'); |
||||
res.body.totalizers.forEach((prop) => expect(expectedMetrics.includes(prop.title)).to.be.true); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/agents-productivity-totalizers', () => { |
||||
const expectedMetrics = [ |
||||
'Busiest_time', |
||||
'Avg_of_available_service_time', |
||||
'Avg_of_service_time', |
||||
]; |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/agents-productivity-totalizers')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an array of agents productivity totalizers', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/agents-productivity-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
expect(res.body.totalizers).to.be.an('array'); |
||||
res.body.totalizers.forEach((prop) => expect(expectedMetrics.includes(prop.title)).to.be.true); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/charts/chats', () => { |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/chats')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an array of productivity totalizers', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/chats?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
expect(res.body).to.have.property('open'); |
||||
expect(res.body).to.have.property('closed'); |
||||
expect(res.body).to.have.property('queued'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/charts/chats-per-agent', () => { |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/chats-per-agent')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an object with open and closed chats by agent', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/chats-per-agent?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/charts/agents-status', () => { |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/agents-status')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an object with agents status metrics', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/agents-status')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
expect(res.body).to.have.property('offline'); |
||||
expect(res.body).to.have.property('away'); |
||||
expect(res.body).to.have.property('busy'); |
||||
expect(res.body).to.have.property('available'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/charts/chats-per-department', () => { |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/chats-per-department')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an object with open and closed chats by department', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/chats-per-department?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('livechat/analytics/dashboards/charts/timings', () => { |
||||
it('should return an "unauthorized error" when the user does not have the necessary permission', (done) => { |
||||
updatePermission('view-livechat-manager', []).then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/timings')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(403) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', false); |
||||
expect(res.body.error).to.be.equal('unauthorized'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
it('should return an object with open and closed chats by department', (done) => { |
||||
updatePermission('view-livechat-manager', ['admin']) |
||||
.then(() => { |
||||
request.get(api('livechat/analytics/dashboards/charts/timings?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) |
||||
.set(credentials) |
||||
.expect('Content-Type', 'application/json') |
||||
.expect(200) |
||||
.expect((res) => { |
||||
expect(res.body).to.have.property('success', true); |
||||
expect(res.body).to.have.property('response'); |
||||
expect(res.body).to.have.property('reaction'); |
||||
expect(res.body).to.have.property('chatDuration'); |
||||
expect(res.body.response).to.have.property('avg'); |
||||
expect(res.body.response).to.have.property('longest'); |
||||
expect(res.body.reaction).to.have.property('avg'); |
||||
expect(res.body.reaction).to.have.property('longest'); |
||||
expect(res.body.chatDuration).to.have.property('avg'); |
||||
expect(res.body.chatDuration).to.have.property('longest'); |
||||
}) |
||||
.end(done); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue