diff --git a/app/livechat/client/stylesheets/livechat.css b/app/livechat/client/stylesheets/livechat.css index 3a13815dbe116..f80d646c0b5ca 100644 --- a/app/livechat/client/stylesheets/livechat.css +++ b/app/livechat/client/stylesheets/livechat.css @@ -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; diff --git a/app/livechat/client/views/app/analytics/livechatAnalytics.html b/app/livechat/client/views/app/analytics/livechatAnalytics.html index 6ae03f99cb5f8..35ace4bfd8014 100644 --- a/app/livechat/client/views/app/analytics/livechatAnalytics.html +++ b/app/livechat/client/views/app/analytics/livechatAnalytics.html @@ -10,6 +10,28 @@ + {{#if hasDepartments }} +
+ {{> 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 + }} +
+ {{/if}} +
{{#if showLeftNavButton}}
+ {{#if hasDepartments }} +
+ {{> 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 + }} +
+ {{/if}} {{#if isLoading}} {{> loading }} diff --git a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js b/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js index 153bb24440d74..584e0f003b1f2 100644 --- a/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js +++ b/app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js @@ -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) => `${ part }`) }`; + }; + }, + 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) { diff --git a/app/livechat/imports/server/rest/dashboards.js b/app/livechat/imports/server/rest/dashboards.js index 0e1f8cedb277d..2537942e74dce 100644 --- a/app/livechat/imports/server/rest/dashboards.js +++ b/app/livechat/imports/server/rest/dashboards.js @@ -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); }, diff --git a/app/livechat/server/lib/Analytics.js b/app/livechat/server/lib/Analytics.js index 6832a8f982d62..b55486751f820 100644 --- a/app/livechat/server/lib/Analytics.js +++ b/app/livechat/server/lib/Analytics.js @@ -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, }) => { diff --git a/app/livechat/server/lib/analytics/dashboards.js b/app/livechat/server/lib/analytics/dashboards.js index 62fa53e8ee42a..e3d9c2db552c6 100644 --- a/app/livechat/server/lib/analytics/dashboards.js +++ b/app/livechat/server/lib/analytics/dashboards.js @@ -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)), diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 7e9ceb876ce2b..25e2e51c6be2b 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -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 }, }, }, { diff --git a/app/models/server/raw/LivechatAgentActivity.js b/app/models/server/raw/LivechatAgentActivity.js index 7a86cf1bac8af..9d48cf45a8328 100644 --- a/app/models/server/raw/LivechatAgentActivity.js +++ b/app/models/server/raw/LivechatAgentActivity.js @@ -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); diff --git a/app/models/server/raw/LivechatRooms.js b/app/models/server/raw/LivechatRooms.js index e996eac6e3681..39fa94546222b 100644 --- a/app/models/server/raw/LivechatRooms.js +++ b/app/models/server/raw/LivechatRooms.js @@ -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) { diff --git a/app/models/server/raw/LivechatVisitors.js b/app/models/server/raw/LivechatVisitors.js index 4faadab58a7d9..7ee3f02f2cca5 100644 --- a/app/models/server/raw/LivechatVisitors.js +++ b/app/models/server/raw/LivechatVisitors.js @@ -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 } }); diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index 803e1c82e1282..245ba2b579df1 100644 --- a/app/models/server/raw/Users.js +++ b/app/models/server/raw/Users.js @@ -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);