Allow to filter omnichannel analytics dashboards per departments. (#17463)

Co-authored-by: Marcos Spessatto Defendi <marcos.defendi@ulbra.inf.br>
pull/17387/head
Renato Becker 5 years ago committed by GitHub
parent b3f2ce7ce8
commit aaf9083633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/livechat/client/stylesheets/livechat.css
  2. 22
      app/livechat/client/views/app/analytics/livechatAnalytics.html
  3. 49
      app/livechat/client/views/app/analytics/livechatAnalytics.js
  4. 21
      app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.html
  5. 118
      app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js
  6. 48
      app/livechat/imports/server/rest/dashboards.js
  7. 79
      app/livechat/server/lib/Analytics.js
  8. 6
      app/livechat/server/lib/analytics/dashboards.js
  9. 9
      app/models/server/models/LivechatRooms.js
  10. 2
      app/models/server/raw/LivechatAgentActivity.js
  11. 40
      app/models/server/raw/LivechatRooms.js
  12. 3
      app/models/server/raw/LivechatVisitors.js
  13. 2
      app/models/server/raw/Users.js

@ -220,8 +220,7 @@
.lc-analytics-table {
display: flex;
height: ~"calc(100% - 280px)";
height: ~"-webkit-calc-height(100% - 280px)";
height: 100%;
min-height: 300px;
flex-flow: column;

@ -10,6 +10,28 @@
<i class="icon-angle-down"></i>
</div>
{{#if hasDepartments }}
<div class="form-group ">
{{> livechatAutocompleteUser
onClickTag=onClickTagDepartment
list=selectedDepartments
onSelect=onSelectDepartments
collection='CachedDepartmentList'
endpoint='livechat/department.autocomplete'
field='name'
sort='name'
placeholder="Select_a_department"
name="department"
icon="queue"
noMatchTemplate="userSearchEmpty"
templateItem="popupList_item_channel"
template="roomSearch"
noMatchTemplate="roomSearchEmpty"
modifier=departmentModifier
}}
</div>
{{/if}}
<div class="form-group lc-analytics-header">
{{#if showLeftNavButton}}
<button class="lc-daterange-prev">

@ -8,6 +8,7 @@ import { handleError } from '../../../../../utils';
import { popover } from '../../../../../ui-utils';
import { drawLineChart } from '../../../lib/chartHandler';
import { setDateRange, updateDateRange } from '../../../lib/dateHandler';
import { APIClient } from '../../../../../utils/client';
import './livechatAnalytics.html';
let templateInstance; // current template instance/context
@ -62,13 +63,19 @@ const chunkArray = (arr, chunkCount) => { // split array into n almost equal arr
return chunks;
};
const getChartDepartment = (department) => department?._id;
const updateAnalyticsChart = () => {
const [department] = templateInstance.selectedDepartments.get();
const departmentId = getChartDepartment(department);
const options = {
daterange: {
from: moment(templateInstance.daterange.get().from, 'MMM D YYYY').toISOString(),
to: moment(templateInstance.daterange.get().to, 'MMM D YYYY').toISOString(),
},
chartOptions: templateInstance.chartOptions.get(),
...departmentId && { departmentId },
};
Meteor.call('livechat:getAnalyticsChartData', options, async function(error, result) {
@ -97,12 +104,16 @@ const updateAnalyticsChart = () => {
};
const updateAnalyticsOverview = () => {
const [department] = templateInstance.selectedDepartments.get();
const departmentId = getChartDepartment(department);
const options = {
daterange: {
from: moment(templateInstance.daterange.get().from, 'MMM D YYYY').toISOString(),
to: moment(templateInstance.daterange.get().to, 'MMM D YYYY').toISOString(),
},
analyticsOptions: templateInstance.analyticsOptions.get(),
...departmentId && { departmentId },
};
Meteor.call('livechat:getAnalyticsOverviewData', options, (error, result) => {
@ -150,10 +161,28 @@ Template.livechatAnalytics.helpers({
}
return true;
},
departmentModifier() {
return (filter, text = '') => {
const f = filter.get();
return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `<strong>${ part }</strong>`) }`;
};
},
onClickTagDepartment() {
return Template.instance().onClickTagDepartment;
},
selectedDepartments() {
return Template.instance().selectedDepartments.get();
},
onSelectDepartments() {
return Template.instance().onSelectDepartments;
},
hasDepartments() {
return Template.instance().hasDepartments.get();
},
});
Template.livechatAnalytics.onCreated(function() {
Template.livechatAnalytics.onCreated(async function() {
templateInstance = Template.instance();
this.analyticsOverviewData = new ReactiveVar();
@ -161,6 +190,20 @@ Template.livechatAnalytics.onCreated(function() {
this.daterange = new ReactiveVar({});
this.analyticsOptions = new ReactiveVar(analyticsAllOptions()[0]); // default selected first
this.chartOptions = new ReactiveVar(analyticsAllOptions()[0].chartOptions[0]); // default selected first
this.selectedDepartments = new ReactiveVar([]);
this.hasDepartments = new ReactiveVar(false);
this.onSelectDepartments = ({ item: department }) => {
department.text = department.name;
this.selectedDepartments.set([department]);
};
this.onClickTagDepartment = () => {
this.selectedDepartments.set([]);
};
const { departments } = await APIClient.v1.get('livechat/department?count=1');
this.hasDepartments.set(departments?.length > 0);
this.autorun(() => {
templateInstance.daterange.set(setDateRange());
@ -169,7 +212,9 @@ Template.livechatAnalytics.onCreated(function() {
Template.livechatAnalytics.onRendered(() => {
Tracker.autorun(() => {
if (templateInstance.daterange.get() && templateInstance.analyticsOptions.get() && templateInstance.chartOptions.get()) {
if (templateInstance.daterange.get()
&& templateInstance.analyticsOptions.get()
&& templateInstance.chartOptions.get()) {
updateAnalyticsOverview();
updateAnalyticsChart();
}

@ -10,6 +10,27 @@
</select>
<i class="icon-angle-down"></i>
</div>
{{#if hasDepartments }}
<div class="form-group ">
{{> livechatAutocompleteUser
onClickTag=onClickTagDepartment
list=selectedDepartments
onSelect=onSelectDepartments
collection='CachedDepartmentList'
endpoint='livechat/department.autocomplete'
field='name'
sort='name'
placeholder="Select_a_department"
name="department"
icon="queue"
noMatchTemplate="userSearchEmpty"
templateItem="popupList_item_channel"
template="roomSearch"
noMatchTemplate="roomSearchEmpty"
modifier=departmentModifier
}}
</div>
{{/if}}
</form>
{{#if isLoading}}
{{> loading }}

@ -102,6 +102,8 @@ const updateChartData = async (chartId, label, data) => {
let timer;
const getChartDepartment = (department) => department?._id;
const getDaterange = () => {
const today = moment(new Date());
return {
@ -110,8 +112,11 @@ const getDaterange = () => {
};
};
const loadConversationOverview = async ({ start, end }) => {
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/conversation-totalizers?start=${ start }&end=${ end }`);
const parseAdditionalParams = (options = {}, prefix = '') => `${ prefix }${ Object.keys(options).map((key) => `${ key }=${ options[key] }`).join('&') }`;
const loadConversationOverview = async ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/conversation-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
return totalizers;
};
@ -121,8 +126,9 @@ const updateConversationOverview = async (totalizers) => {
}
};
const loadAgentsOverview = async ({ start, end }) => {
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/agents-productivity-totalizers?start=${ start }&end=${ end }`);
const loadAgentsOverview = async ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/agents-productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
return totalizers;
};
@ -131,8 +137,9 @@ const updateAgentsOverview = async (totalizers) => {
templateInstance.agentsOverview.set(totalizers);
}
};
const loadChatsOverview = async ({ start, end }) => {
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/chats-totalizers?start=${ start }&end=${ end }`);
const loadChatsOverview = async ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/chats-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
return totalizers;
};
@ -142,8 +149,9 @@ const updateChatsOverview = async (totalizers) => {
}
};
const loadProductivityOverview = async ({ start, end }) => {
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/productivity-totalizers?start=${ start }&end=${ end }`);
const loadProductivityOverview = async ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
return totalizers;
};
@ -153,7 +161,10 @@ const updateProductivityOverview = async (totalizers) => {
}
};
const loadChatsChartData = ({ start, end }) => APIClient.v1.get(`livechat/analytics/dashboards/charts/chats?start=${ start }&end=${ end }`);
const loadChatsChartData = ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
return APIClient.v1.get(`livechat/analytics/dashboards/charts/chats?start=${ start }&end=${ end }${ additionalParams }`);
};
const updateChatsChart = async ({ open, closed, queued }) => {
await updateChartData('lc-chats-chart', 'Open', [open]);
@ -161,19 +172,26 @@ const updateChatsChart = async ({ open, closed, queued }) => {
await updateChartData('lc-chats-chart', 'Queue', [queued]);
};
const loadChatsPerAgentChartData = async ({ start, end }) => {
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-agent?start=${ start }&end=${ end }`);
const loadChatsPerAgentChartData = async ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-agent?start=${ start }&end=${ end }${ additionalParams }`);
delete result.success;
return result;
};
const updateChatsPerAgentChart = (agents) => {
const updateChatsPerAgentChart = async (agents) => {
// this chart need to reset before new updates
chartContexts['lc-chats-per-agent-chart'] = await initChart['lc-chats-per-agent-chart']();
Object
.keys(agents)
.forEach((agent) => updateChartData('lc-chats-per-agent-chart', agent, [agents[agent].open, agents[agent].closed]));
};
const loadAgentsStatusChartData = () => APIClient.v1.get('livechat/analytics/dashboards/charts/agents-status');
const loadAgentsStatusChartData = ({ departmentId }) => {
const additionalParams = parseAdditionalParams({ departmentId }, '?');
return APIClient.v1.get(`livechat/analytics/dashboards/charts/agents-status${ additionalParams }`);
};
const updateAgentStatusChart = async (statusData) => {
if (!statusData) {
@ -186,19 +204,26 @@ const updateAgentStatusChart = async (statusData) => {
await updateChartData('lc-agents-chart', 'Busy', [statusData.busy]);
};
const loadChatsPerDepartmentChartData = async ({ start, end }) => {
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-department?start=${ start }&end=${ end }`);
const loadChatsPerDepartmentChartData = async ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-department?start=${ start }&end=${ end }${ additionalParams }`);
delete result.success;
return result;
};
const updateDepartmentsChart = (departments) => {
const updateDepartmentsChart = async (departments) => {
// this chart need to reset before new updates
chartContexts['lc-chats-per-dept-chart'] = await initChart['lc-chats-per-dept-chart']();
Object
.keys(departments)
.forEach((department) => updateChartData('lc-chats-per-dept-chart', department, [departments[department].open, departments[department].closed]));
};
const loadTimingsChartData = ({ start, end }) => APIClient.v1.get(`livechat/analytics/dashboards/charts/timings?start=${ start }&end=${ end }`);
const loadTimingsChartData = ({ start, end, ...options }) => {
const additionalParams = parseAdditionalParams(options, '&');
return APIClient.v1.get(`livechat/analytics/dashboards/charts/timings?start=${ start }&end=${ end }${ additionalParams }`);
};
const updateTimingsChart = async (timingsData) => {
const hour = moment(new Date()).format('H');
@ -229,9 +254,27 @@ Template.livechatRealTimeMonitoring.helpers({
isLoading() {
return Template.instance().isLoading.get();
},
departmentModifier() {
return (filter, text = '') => {
const f = filter.get();
return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `<strong>${ part }</strong>`) }`;
};
},
onClickTagDepartment() {
return Template.instance().onClickTagDepartment;
},
selectedDepartments() {
return Template.instance().selectedDepartments.get();
},
onSelectDepartments() {
return Template.instance().onSelectDepartments;
},
hasDepartments() {
return Template.instance().hasDepartments.get();
},
});
Template.livechatRealTimeMonitoring.onCreated(function() {
Template.livechatRealTimeMonitoring.onCreated(async function() {
templateInstance = Template.instance();
this.isLoading = new ReactiveVar(false);
this.conversationsOverview = new ReactiveVar();
@ -240,22 +283,43 @@ Template.livechatRealTimeMonitoring.onCreated(function() {
this.agentsOverview = new ReactiveVar();
this.conversationTotalizers = new ReactiveVar([]);
this.interval = new ReactiveVar(5);
this.selectedDepartments = new ReactiveVar([]);
this.hasDepartments = new ReactiveVar(false);
this.onSelectDepartments = ({ item: department }) => {
department.text = department.name;
this.selectedDepartments.set([department]);
};
this.onClickTagDepartment = () => {
this.selectedDepartments.set([]);
};
const { departments } = await APIClient.v1.get('livechat/department?count=1');
this.hasDepartments.set(departments?.length > 0);
});
Template.livechatRealTimeMonitoring.onRendered(async function() {
await initAllCharts();
this.updateDashboard = async () => {
const [department] = this.selectedDepartments.get();
const departmentId = getChartDepartment(department);
const daterange = getDaterange();
updateConversationOverview(await loadConversationOverview(daterange));
updateProductivityOverview(await loadProductivityOverview(daterange));
updateChatsChart(await loadChatsChartData(daterange));
updateChatsPerAgentChart(await loadChatsPerAgentChartData(daterange));
updateAgentStatusChart(await loadAgentsStatusChartData());
updateDepartmentsChart(await loadChatsPerDepartmentChartData(daterange));
updateTimingsChart(await loadTimingsChartData(daterange));
updateAgentsOverview(await loadAgentsOverview(daterange));
updateChatsOverview(await loadChatsOverview(daterange));
const filters = Object.assign(
{ ...daterange },
departmentId && { departmentId },
);
updateConversationOverview(await loadConversationOverview(filters));
updateProductivityOverview(await loadProductivityOverview(filters));
updateChatsChart(await loadChatsChartData(filters));
updateChatsPerAgentChart(await loadChatsPerAgentChartData(filters));
updateAgentStatusChart(await loadAgentsStatusChartData(filters));
updateDepartmentsChart(await loadChatsPerDepartmentChartData(filters));
updateTimingsChart(await loadTimingsChartData(filters));
updateAgentsOverview(await loadAgentsOverview(filters));
updateChatsOverview(await loadChatsOverview(filters));
};
this.autorun(() => {
if (timer) {

@ -1,4 +1,4 @@
import { check } from 'meteor/check';
import { Match, check } from 'meteor/check';
import { API } from '../../../../api';
import { hasPermission } from '../../../../authorization/server';
@ -20,8 +20,11 @@ API.v1.addRoute('livechat/analytics/dashboards/conversation-totalizers', { authR
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -33,7 +36,7 @@ API.v1.addRoute('livechat/analytics/dashboards/conversation-totalizers', { authR
}
end = new Date(end);
const totalizers = getConversationsMetrics({ start, end });
const totalizers = getConversationsMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
});
@ -44,8 +47,11 @@ API.v1.addRoute('livechat/analytics/dashboards/agents-productivity-totalizers',
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -57,7 +63,7 @@ API.v1.addRoute('livechat/analytics/dashboards/agents-productivity-totalizers',
}
end = new Date(end);
const totalizers = getAgentsProductivityMetrics({ start, end });
const totalizers = getAgentsProductivityMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
});
@ -68,8 +74,11 @@ API.v1.addRoute('livechat/analytics/dashboards/chats-totalizers', { authRequired
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -81,7 +90,7 @@ API.v1.addRoute('livechat/analytics/dashboards/chats-totalizers', { authRequired
}
end = new Date(end);
const totalizers = getChatsMetrics({ start, end });
const totalizers = getChatsMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
});
@ -92,8 +101,11 @@ API.v1.addRoute('livechat/analytics/dashboards/productivity-totalizers', { authR
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -105,7 +117,7 @@ API.v1.addRoute('livechat/analytics/dashboards/productivity-totalizers', { authR
}
end = new Date(end);
const totalizers = getProductivityMetrics({ start, end });
const totalizers = getProductivityMetrics({ start, end, departmentId });
return API.v1.success(totalizers);
},
@ -117,8 +129,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats', { authRequired: tr
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -129,7 +144,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats', { authRequired: tr
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
const result = findAllChatsStatus({ start, end });
const result = findAllChatsStatus({ start, end, departmentId });
return API.v1.success(result);
},
@ -141,8 +156,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-agent', { authRe
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -153,7 +171,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-agent', { authRe
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
const result = findAllChatMetricsByAgent({ start, end });
const result = findAllChatMetricsByAgent({ start, end, departmentId });
return API.v1.success(result);
},
@ -164,7 +182,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/agents-status', { authRequ
if (!hasPermission(this.userId, 'view-livechat-manager')) {
return API.v1.unauthorized();
}
const result = findAllAgentsStatus({});
const { departmentId } = this.requestParams();
check(departmentId, Match.Maybe(String));
const result = findAllAgentsStatus({ departmentId });
return API.v1.success(result);
},
@ -176,8 +198,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-department', { a
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -188,7 +213,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/chats-per-department', { a
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
const result = findAllChatMetricsByDepartment({ start, end });
const result = findAllChatMetricsByDepartment({ start, end, departmentId });
return API.v1.success(result);
},
@ -200,8 +225,11 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/timings', { authRequired:
return API.v1.unauthorized();
}
let { start, end } = this.requestParams();
const { departmentId } = this.requestParams();
check(start, String);
check(end, String);
check(departmentId, Match.Maybe(String));
if (isNaN(Date.parse(start))) {
return API.v1.failure('The "start" query parameter must be a valid date.');
@ -212,7 +240,7 @@ API.v1.addRoute('livechat/analytics/dashboards/charts/timings', { authRequired:
return API.v1.failure('The "end" query parameter must be a valid date.');
}
end = new Date(end);
const result = findAllResponseTimeMetrics({ start, end });
const result = findAllResponseTimeMetrics({ start, end, departmentId });
return API.v1.success(result);
},

@ -18,7 +18,9 @@ export const Analytics = {
return;
}
return this.AgentOverviewData[options.chartOptions.name](from, to);
const { departmentId } = options;
return this.AgentOverviewData[options.chartOptions.name](from, to, departmentId);
},
getAnalyticsChartData(options) {
@ -43,6 +45,8 @@ export const Analytics = {
dataPoints: [],
};
const { departmentId } = options;
if (from.diff(to) === 0) { // data for single day
for (let m = moment(from); m.diff(to, 'days') <= 0; m.add(1, 'hours')) {
const hour = m.format('H');
@ -53,7 +57,7 @@ export const Analytics = {
lt: moment(m).add(1, 'hours'),
};
data.dataPoints.push(this.ChartData[options.chartOptions.name](date));
data.dataPoints.push(this.ChartData[options.chartOptions.name](date, departmentId));
}
} else {
for (let m = moment(from); m.diff(to, 'days') <= 0; m.add(1, 'days')) {
@ -64,7 +68,7 @@ export const Analytics = {
lt: moment(m).add(1, 'days'),
};
data.dataPoints.push(this.ChartData[options.chartOptions.name](date));
data.dataPoints.push(this.ChartData[options.chartOptions.name](date, departmentId));
}
}
@ -74,6 +78,7 @@ export const Analytics = {
getAnalyticsOverviewData(options) {
const from = moment(options.daterange.from);
const to = moment(options.daterange.to);
const { departmentId } = options;
if (!(moment(from).isValid() && moment(to).isValid())) {
console.log('livechat:getAnalyticsOverviewData => Invalid dates');
@ -85,7 +90,7 @@ export const Analytics = {
return;
}
return this.OverviewData[options.analyticsOptions.name](from, to);
return this.OverviewData[options.analyticsOptions.name](from, to, departmentId);
},
ChartData: {
@ -95,15 +100,15 @@ export const Analytics = {
*
* @returns {Integer}
*/
Total_conversations(date) {
return LivechatRooms.getTotalConversationsBetweenDate('l', date);
Total_conversations(date, departmentId) {
return LivechatRooms.getTotalConversationsBetweenDate('l', date, { departmentId });
},
Avg_chat_duration(date) {
Avg_chat_duration(date, departmentId) {
let total = 0;
let count = 0;
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.chatDuration) {
total += metrics.chatDuration;
count++;
@ -114,10 +119,10 @@ export const Analytics = {
return Math.round(avgCD * 100) / 100;
},
Total_messages(date) {
Total_messages(date, departmentId) {
let total = 0;
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ msgs }) => {
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ msgs }) => {
if (msgs) {
total += msgs;
}
@ -132,10 +137,10 @@ export const Analytics = {
*
* @returns {Double}
*/
Avg_first_response_time(date) {
Avg_first_response_time(date, departmentId) {
let frt = 0;
let count = 0;
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.response && metrics.response.ft) {
frt += metrics.response.ft;
count++;
@ -152,10 +157,10 @@ export const Analytics = {
*
* @returns {Double}
*/
Best_first_response_time(date) {
Best_first_response_time(date, departmentId) {
let maxFrt;
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.response && metrics.response.ft) {
maxFrt = maxFrt ? Math.min(maxFrt, metrics.response.ft) : metrics.response.ft;
}
@ -172,10 +177,10 @@ export const Analytics = {
*
* @returns {Double}
*/
Avg_response_time(date) {
Avg_response_time(date, departmentId) {
let art = 0;
let count = 0;
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.response && metrics.response.avg) {
art += metrics.response.avg;
count++;
@ -193,10 +198,10 @@ export const Analytics = {
*
* @returns {Double}
*/
Avg_reaction_time(date) {
Avg_reaction_time(date, departmentId) {
let arnt = 0;
let count = 0;
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({ metrics }) => {
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({ metrics }) => {
if (metrics && metrics.reaction && metrics.reaction.ft) {
arnt += metrics.reaction.ft;
count++;
@ -237,7 +242,7 @@ export const Analytics = {
*
* @returns {Array[Object]}
*/
Conversations(from, to) {
Conversations(from, to, departmentId) {
let totalConversations = 0; // Total conversations
let openConversations = 0; // open conversations
let totalMessages = 0; // total msgs
@ -261,7 +266,7 @@ export const Analytics = {
lt: moment(m).add(1, 'days'),
};
const result = Promise.await(LivechatRooms.getAnalyticsBetweenDate(date).toArray());
const result = Promise.await(LivechatRooms.getAnalyticsBetweenDate(date, { departmentId }).toArray());
totalConversations += result.length;
result.forEach(summarize(m));
@ -278,7 +283,7 @@ export const Analytics = {
gte: h,
lt: moment(h).add(1, 'hours'),
};
Promise.await(LivechatRooms.getAnalyticsBetweenDate(date).toArray()).forEach(({
Promise.await(LivechatRooms.getAnalyticsBetweenDate(date, { departmentId }).toArray()).forEach(({
msgs,
}) => {
const dayHour = h.format('H'); // @int : 0, 1, ... 23
@ -319,7 +324,7 @@ export const Analytics = {
*
* @returns {Array[Object]}
*/
Productivity(from, to) {
Productivity(from, to, departmentId) {
let avgResponseTime = 0;
let firstResponseTime = 0;
let avgReactionTime = 0;
@ -330,7 +335,7 @@ export const Analytics = {
lt: to.add(1, 'days'),
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
}) => {
if (metrics && metrics.response && metrics.reaction) {
@ -395,7 +400,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
Total_conversations(from, to) {
Total_conversations(from, to, departmentId) {
let total = 0;
const agentConversations = new Map(); // stores total conversations for each agent
const date = {
@ -412,7 +417,7 @@ export const Analytics = {
data: [],
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
servedBy,
}) => {
if (servedBy) {
@ -446,7 +451,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
Avg_chat_duration(from, to) {
Avg_chat_duration(from, to, departmentId) {
const agentChatDurations = new Map(); // stores total conversations for each agent
const date = {
gte: from,
@ -462,7 +467,7 @@ export const Analytics = {
data: [],
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@ -506,7 +511,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
Total_messages(from, to) {
Total_messages(from, to, departmentId) {
const agentMessages = new Map(); // stores total conversations for each agent
const date = {
gte: from,
@ -522,7 +527,7 @@ export const Analytics = {
data: [],
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
servedBy,
msgs,
}) => {
@ -550,7 +555,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
Avg_first_response_time(from, to) {
Avg_first_response_time(from, to, departmentId) {
const agentAvgRespTime = new Map(); // stores avg response time for each agent
const date = {
gte: from,
@ -566,7 +571,7 @@ export const Analytics = {
data: [],
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@ -610,7 +615,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
Best_first_response_time(from, to) {
Best_first_response_time(from, to, departmentId) {
const agentFirstRespTime = new Map(); // stores avg response time for each agent
const date = {
gte: from,
@ -626,7 +631,7 @@ export const Analytics = {
data: [],
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@ -662,7 +667,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
Avg_response_time(from, to) {
Avg_response_time(from, to, departmentId) {
const agentAvgRespTime = new Map(); // stores avg response time for each agent
const date = {
gte: from,
@ -678,7 +683,7 @@ export const Analytics = {
data: [],
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {
@ -722,7 +727,7 @@ export const Analytics = {
*
* @returns {Array(Object), Array(Object)}
*/
Avg_reaction_time(from, to) {
Avg_reaction_time(from, to, departmentId) {
const agentAvgReactionTime = new Map(); // stores avg reaction time for each agent
const date = {
gte: from,
@ -738,7 +743,7 @@ export const Analytics = {
data: [],
};
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date).forEach(({
LivechatRooms.getAnalyticsMetricsBetweenDate('l', date, { departmentId }).forEach(({
metrics,
servedBy,
}) => {

@ -43,6 +43,7 @@ const getProductivityMetricsAsync = async ({
analyticsOptions: {
name: 'Productivity',
},
departmentId,
});
const averageWaitingTime = await findAllAverageWaitingTimeAsync({
start,
@ -91,6 +92,7 @@ const getAgentsProductivityMetricsAsync = async ({
analyticsOptions: {
name: 'Conversations',
},
departmentId,
});
const totalOfServiceTime = averageOfServiceTime.departments.length;
@ -166,6 +168,7 @@ const getChatsMetricsAsync = async ({
const getConversationsMetricsAsync = async ({
start,
end,
departmentId,
}) => {
if (!start || !end) {
throw new Error('"start" and "end" must be provided');
@ -178,9 +181,10 @@ const getConversationsMetricsAsync = async ({
analyticsOptions: {
name: 'Conversations',
},
...departmentId && departmentId !== 'undefined' && { departmentId },
});
const metrics = ['Total_conversations', 'Open_conversations', 'Total_messages'];
const visitorsCount = await LivechatVisitors.getVisitorsBetweenDate({ start, end }).count();
const visitorsCount = await LivechatVisitors.getVisitorsBetweenDate({ start, end, department: departmentId }).count();
return {
totalizers: [
...totalizers.filter((metric) => metrics.includes(metric.title)),

@ -351,31 +351,33 @@ export class LivechatRooms extends Base {
}, update);
}
getTotalConversationsBetweenDate(t, date) {
getTotalConversationsBetweenDate(t, date, { departmentId } = {}) {
const query = {
t,
ts: {
$gte: new Date(date.gte), // ISO Date, ts >= date.gte
$lt: new Date(date.lt), // ISODate, ts < date.lt
},
...departmentId && departmentId !== 'undefined' && { departmentId },
};
return this.find(query).count();
}
getAnalyticsMetricsBetweenDate(t, date) {
getAnalyticsMetricsBetweenDate(t, date, { departmentId } = {}) {
const query = {
t,
ts: {
$gte: new Date(date.gte), // ISO Date, ts >= date.gte
$lt: new Date(date.lt), // ISODate, ts < date.lt
},
...departmentId && departmentId !== 'undefined' && { departmentId },
};
return this.find(query, { fields: { ts: 1, departmentId: 1, open: 1, servedBy: 1, metrics: 1, msgs: 1 } });
}
getAnalyticsBetweenDate(date) {
getAnalyticsBetweenDate(date, { departmentId } = {}) {
return this.model.rawCollection().aggregate([
{
$match: {
@ -384,6 +386,7 @@ export class LivechatRooms extends Base {
$gte: new Date(date.gte), // ISO Date, ts >= date.gte
$lt: new Date(date.lt), // ISODate, ts < date.lt
},
...departmentId && departmentId !== 'undefined' && { departmentId },
},
},
{

@ -57,7 +57,7 @@ export class LivechatAgentActivityRaw extends BaseRaw {
},
};
const params = [match];
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
params.push(lookup);
params.push(unwind);
params.push(departmentsMatch);

@ -5,7 +5,7 @@ export class LivechatRoomsRaw extends BaseRaw {
getQueueMetrics({ departmentId, agentId, includeOfflineAgents, options = {} }) {
const match = { $match: { t: 'l', open: true, servedBy: { $exists: true } } };
const matchUsers = { $match: {} };
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
if (agentId) {
@ -118,7 +118,7 @@ export class LivechatRoomsRaw extends BaseRaw {
abandonedRooms: 1,
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@ -176,7 +176,7 @@ export class LivechatRoomsRaw extends BaseRaw {
},
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@ -218,7 +218,7 @@ export class LivechatRoomsRaw extends BaseRaw {
averageChatDurationTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$rooms', 0] }, 0, { $divide: ['$chatsDuration', '$rooms'] }] } },
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@ -260,7 +260,7 @@ export class LivechatRoomsRaw extends BaseRaw {
averageWaitingTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$rooms', 0] }, 0, { $divide: ['$chatsFirstResponses', '$rooms'] }] } },
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@ -303,7 +303,7 @@ export class LivechatRoomsRaw extends BaseRaw {
rooms: 1,
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@ -347,7 +347,7 @@ export class LivechatRoomsRaw extends BaseRaw {
serviceTimeDuration: { $ceil: '$serviceTimeDuration' },
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@ -455,7 +455,7 @@ export class LivechatRoomsRaw extends BaseRaw {
},
};
const firstParams = [match, departmentsLookup, departmentsUnwind];
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
firstParams.push({
$match: {
'departments._id': departmentId,
@ -482,7 +482,7 @@ export class LivechatRoomsRaw extends BaseRaw {
servedBy: { $exists: true },
ts: { $gte: new Date(start), $lte: new Date(end) },
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
return this.find(query).count();
@ -497,7 +497,7 @@ export class LivechatRoomsRaw extends BaseRaw {
servedBy: { $exists: true },
ts: { $gte: new Date(start), $lte: new Date(end) },
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
return this.find(query).count();
@ -509,7 +509,7 @@ export class LivechatRoomsRaw extends BaseRaw {
servedBy: { $exists: false },
ts: { $gte: new Date(start), $lte: new Date(end) },
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
return this.find(query).count();
@ -530,7 +530,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: { $sum: 1 },
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group]).toArray();
@ -552,7 +552,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: { $sum: 1 },
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group]).toArray();
@ -597,7 +597,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: 1,
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const params = [match, lookup, unwind, group, project];
@ -643,7 +643,7 @@ export class LivechatRoomsRaw extends BaseRaw {
chats: 1,
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const params = [match, lookup, unwind, group, project];
@ -689,7 +689,7 @@ export class LivechatRoomsRaw extends BaseRaw {
longest: '$maxFirstResponse',
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group, project]).toArray();
@ -734,7 +734,7 @@ export class LivechatRoomsRaw extends BaseRaw {
longest: '$maxFirstReaction',
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group, project]).toArray();
@ -780,7 +780,7 @@ export class LivechatRoomsRaw extends BaseRaw {
longest: '$maxChatDuration',
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
return this.col.aggregate([match, group, project]).toArray();
@ -811,7 +811,7 @@ export class LivechatRoomsRaw extends BaseRaw {
averageServiceTimeInSeconds: { $ceil: { $cond: [{ $eq: ['$rooms', 0] }, 0, { $divide: ['$allServiceTime', '$rooms'] }] } },
},
};
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
match.$match.departmentId = departmentId;
}
const sort = { $sort: options.sort || { name: 1 } };
@ -848,7 +848,7 @@ export class LivechatRoomsRaw extends BaseRaw {
if (roomName) {
query.fname = new RegExp(roomName, 'i');
}
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
query.departmentId = departmentId;
}
if (open !== undefined) {

@ -1,12 +1,13 @@
import { BaseRaw } from './BaseRaw';
export class LivechatVisitorsRaw extends BaseRaw {
getVisitorsBetweenDate({ start, end }) {
getVisitorsBetweenDate({ start, end, department }) {
const query = {
_updatedAt: {
$gte: new Date(start),
$lt: new Date(end),
},
...department && department !== 'undefined' && { department },
};
return this.find(query, { fields: { _id: 1 } });

@ -215,7 +215,7 @@ export class UsersRaw extends BaseRaw {
},
};
const params = [match];
if (departmentId) {
if (departmentId && departmentId !== 'undefined') {
params.push(lookup);
params.push(unwind);
params.push(departmentsMatch);

Loading…
Cancel
Save