Skip to content

Commit aaf9083

Browse files
renatobeckerMarcos Spessatto Defendi
andauthored
Allow to filter omnichannel analytics dashboards per departments. (#17463)
Co-authored-by: Marcos Spessatto Defendi <[email protected]>
1 parent b3f2ce7 commit aaf9083

File tree

13 files changed

+297
-105
lines changed

13 files changed

+297
-105
lines changed

app/livechat/client/stylesheets/livechat.css

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,7 @@
220220
.lc-analytics-table {
221221
display: flex;
222222

223-
height: ~"calc(100% - 280px)";
224-
height: ~"-webkit-calc-height(100% - 280px)";
223+
height: 100%;
225224

226225
min-height: 300px;
227226
flex-flow: column;

app/livechat/client/views/app/analytics/livechatAnalytics.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,28 @@
1010
<i class="icon-angle-down"></i>
1111
</div>
1212

13+
{{#if hasDepartments }}
14+
<div class="form-group ">
15+
{{> livechatAutocompleteUser
16+
onClickTag=onClickTagDepartment
17+
list=selectedDepartments
18+
onSelect=onSelectDepartments
19+
collection='CachedDepartmentList'
20+
endpoint='livechat/department.autocomplete'
21+
field='name'
22+
sort='name'
23+
placeholder="Select_a_department"
24+
name="department"
25+
icon="queue"
26+
noMatchTemplate="userSearchEmpty"
27+
templateItem="popupList_item_channel"
28+
template="roomSearch"
29+
noMatchTemplate="roomSearchEmpty"
30+
modifier=departmentModifier
31+
}}
32+
</div>
33+
{{/if}}
34+
1335
<div class="form-group lc-analytics-header">
1436
{{#if showLeftNavButton}}
1537
<button class="lc-daterange-prev">

app/livechat/client/views/app/analytics/livechatAnalytics.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { handleError } from '../../../../../utils';
88
import { popover } from '../../../../../ui-utils';
99
import { drawLineChart } from '../../../lib/chartHandler';
1010
import { setDateRange, updateDateRange } from '../../../lib/dateHandler';
11+
import { APIClient } from '../../../../../utils/client';
1112
import './livechatAnalytics.html';
1213

1314
let templateInstance; // current template instance/context
@@ -62,13 +63,19 @@ const chunkArray = (arr, chunkCount) => { // split array into n almost equal arr
6263
return chunks;
6364
};
6465

66+
const getChartDepartment = (department) => department?._id;
67+
6568
const updateAnalyticsChart = () => {
69+
const [department] = templateInstance.selectedDepartments.get();
70+
const departmentId = getChartDepartment(department);
71+
6672
const options = {
6773
daterange: {
6874
from: moment(templateInstance.daterange.get().from, 'MMM D YYYY').toISOString(),
6975
to: moment(templateInstance.daterange.get().to, 'MMM D YYYY').toISOString(),
7076
},
7177
chartOptions: templateInstance.chartOptions.get(),
78+
...departmentId && { departmentId },
7279
};
7380

7481
Meteor.call('livechat:getAnalyticsChartData', options, async function(error, result) {
@@ -97,12 +104,16 @@ const updateAnalyticsChart = () => {
97104
};
98105

99106
const updateAnalyticsOverview = () => {
107+
const [department] = templateInstance.selectedDepartments.get();
108+
const departmentId = getChartDepartment(department);
109+
100110
const options = {
101111
daterange: {
102112
from: moment(templateInstance.daterange.get().from, 'MMM D YYYY').toISOString(),
103113
to: moment(templateInstance.daterange.get().to, 'MMM D YYYY').toISOString(),
104114
},
105115
analyticsOptions: templateInstance.analyticsOptions.get(),
116+
...departmentId && { departmentId },
106117
};
107118

108119
Meteor.call('livechat:getAnalyticsOverviewData', options, (error, result) => {
@@ -150,17 +161,49 @@ Template.livechatAnalytics.helpers({
150161
}
151162
return true;
152163
},
164+
departmentModifier() {
165+
return (filter, text = '') => {
166+
const f = filter.get();
167+
return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `<strong>${ part }</strong>`) }`;
168+
};
169+
},
170+
onClickTagDepartment() {
171+
return Template.instance().onClickTagDepartment;
172+
},
173+
selectedDepartments() {
174+
return Template.instance().selectedDepartments.get();
175+
},
176+
onSelectDepartments() {
177+
return Template.instance().onSelectDepartments;
178+
},
179+
hasDepartments() {
180+
return Template.instance().hasDepartments.get();
181+
},
153182
});
154183

155184

156-
Template.livechatAnalytics.onCreated(function() {
185+
Template.livechatAnalytics.onCreated(async function() {
157186
templateInstance = Template.instance();
158187

159188
this.analyticsOverviewData = new ReactiveVar();
160189
this.agentOverviewData = new ReactiveVar();
161190
this.daterange = new ReactiveVar({});
162191
this.analyticsOptions = new ReactiveVar(analyticsAllOptions()[0]); // default selected first
163192
this.chartOptions = new ReactiveVar(analyticsAllOptions()[0].chartOptions[0]); // default selected first
193+
this.selectedDepartments = new ReactiveVar([]);
194+
this.hasDepartments = new ReactiveVar(false);
195+
196+
this.onSelectDepartments = ({ item: department }) => {
197+
department.text = department.name;
198+
this.selectedDepartments.set([department]);
199+
};
200+
201+
this.onClickTagDepartment = () => {
202+
this.selectedDepartments.set([]);
203+
};
204+
205+
const { departments } = await APIClient.v1.get('livechat/department?count=1');
206+
this.hasDepartments.set(departments?.length > 0);
164207

165208
this.autorun(() => {
166209
templateInstance.daterange.set(setDateRange());
@@ -169,7 +212,9 @@ Template.livechatAnalytics.onCreated(function() {
169212

170213
Template.livechatAnalytics.onRendered(() => {
171214
Tracker.autorun(() => {
172-
if (templateInstance.daterange.get() && templateInstance.analyticsOptions.get() && templateInstance.chartOptions.get()) {
215+
if (templateInstance.daterange.get()
216+
&& templateInstance.analyticsOptions.get()
217+
&& templateInstance.chartOptions.get()) {
173218
updateAnalyticsOverview();
174219
updateAnalyticsChart();
175220
}

app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@
1010
</select>
1111
<i class="icon-angle-down"></i>
1212
</div>
13+
{{#if hasDepartments }}
14+
<div class="form-group ">
15+
{{> livechatAutocompleteUser
16+
onClickTag=onClickTagDepartment
17+
list=selectedDepartments
18+
onSelect=onSelectDepartments
19+
collection='CachedDepartmentList'
20+
endpoint='livechat/department.autocomplete'
21+
field='name'
22+
sort='name'
23+
placeholder="Select_a_department"
24+
name="department"
25+
icon="queue"
26+
noMatchTemplate="userSearchEmpty"
27+
templateItem="popupList_item_channel"
28+
template="roomSearch"
29+
noMatchTemplate="roomSearchEmpty"
30+
modifier=departmentModifier
31+
}}
32+
</div>
33+
{{/if}}
1334
</form>
1435
{{#if isLoading}}
1536
{{> loading }}

app/livechat/client/views/app/analytics/livechatRealTimeMonitoring.js

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ const updateChartData = async (chartId, label, data) => {
102102

103103
let timer;
104104

105+
const getChartDepartment = (department) => department?._id;
106+
105107
const getDaterange = () => {
106108
const today = moment(new Date());
107109
return {
@@ -110,8 +112,11 @@ const getDaterange = () => {
110112
};
111113
};
112114

113-
const loadConversationOverview = async ({ start, end }) => {
114-
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/conversation-totalizers?start=${ start }&end=${ end }`);
115+
const parseAdditionalParams = (options = {}, prefix = '') => `${ prefix }${ Object.keys(options).map((key) => `${ key }=${ options[key] }`).join('&') }`;
116+
117+
const loadConversationOverview = async ({ start, end, ...options }) => {
118+
const additionalParams = parseAdditionalParams(options, '&');
119+
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/conversation-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
115120
return totalizers;
116121
};
117122

@@ -121,8 +126,9 @@ const updateConversationOverview = async (totalizers) => {
121126
}
122127
};
123128

124-
const loadAgentsOverview = async ({ start, end }) => {
125-
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/agents-productivity-totalizers?start=${ start }&end=${ end }`);
129+
const loadAgentsOverview = async ({ start, end, ...options }) => {
130+
const additionalParams = parseAdditionalParams(options, '&');
131+
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/agents-productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
126132
return totalizers;
127133
};
128134

@@ -131,8 +137,9 @@ const updateAgentsOverview = async (totalizers) => {
131137
templateInstance.agentsOverview.set(totalizers);
132138
}
133139
};
134-
const loadChatsOverview = async ({ start, end }) => {
135-
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/chats-totalizers?start=${ start }&end=${ end }`);
140+
const loadChatsOverview = async ({ start, end, ...options }) => {
141+
const additionalParams = parseAdditionalParams(options, '&');
142+
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/chats-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
136143
return totalizers;
137144
};
138145

@@ -142,8 +149,9 @@ const updateChatsOverview = async (totalizers) => {
142149
}
143150
};
144151

145-
const loadProductivityOverview = async ({ start, end }) => {
146-
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/productivity-totalizers?start=${ start }&end=${ end }`);
152+
const loadProductivityOverview = async ({ start, end, ...options }) => {
153+
const additionalParams = parseAdditionalParams(options, '&');
154+
const { totalizers } = await APIClient.v1.get(`livechat/analytics/dashboards/productivity-totalizers?start=${ start }&end=${ end }${ additionalParams }`);
147155
return totalizers;
148156
};
149157

@@ -153,27 +161,37 @@ const updateProductivityOverview = async (totalizers) => {
153161
}
154162
};
155163

156-
const loadChatsChartData = ({ start, end }) => APIClient.v1.get(`livechat/analytics/dashboards/charts/chats?start=${ start }&end=${ end }`);
164+
const loadChatsChartData = ({ start, end, ...options }) => {
165+
const additionalParams = parseAdditionalParams(options, '&');
166+
return APIClient.v1.get(`livechat/analytics/dashboards/charts/chats?start=${ start }&end=${ end }${ additionalParams }`);
167+
};
157168

158169
const updateChatsChart = async ({ open, closed, queued }) => {
159170
await updateChartData('lc-chats-chart', 'Open', [open]);
160171
await updateChartData('lc-chats-chart', 'Closed', [closed]);
161172
await updateChartData('lc-chats-chart', 'Queue', [queued]);
162173
};
163174

164-
const loadChatsPerAgentChartData = async ({ start, end }) => {
165-
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-agent?start=${ start }&end=${ end }`);
175+
const loadChatsPerAgentChartData = async ({ start, end, ...options }) => {
176+
const additionalParams = parseAdditionalParams(options, '&');
177+
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-agent?start=${ start }&end=${ end }${ additionalParams }`);
166178
delete result.success;
167179
return result;
168180
};
169181

170-
const updateChatsPerAgentChart = (agents) => {
182+
const updateChatsPerAgentChart = async (agents) => {
183+
// this chart need to reset before new updates
184+
chartContexts['lc-chats-per-agent-chart'] = await initChart['lc-chats-per-agent-chart']();
185+
171186
Object
172187
.keys(agents)
173188
.forEach((agent) => updateChartData('lc-chats-per-agent-chart', agent, [agents[agent].open, agents[agent].closed]));
174189
};
175190

176-
const loadAgentsStatusChartData = () => APIClient.v1.get('livechat/analytics/dashboards/charts/agents-status');
191+
const loadAgentsStatusChartData = ({ departmentId }) => {
192+
const additionalParams = parseAdditionalParams({ departmentId }, '?');
193+
return APIClient.v1.get(`livechat/analytics/dashboards/charts/agents-status${ additionalParams }`);
194+
};
177195

178196
const updateAgentStatusChart = async (statusData) => {
179197
if (!statusData) {
@@ -186,19 +204,26 @@ const updateAgentStatusChart = async (statusData) => {
186204
await updateChartData('lc-agents-chart', 'Busy', [statusData.busy]);
187205
};
188206

189-
const loadChatsPerDepartmentChartData = async ({ start, end }) => {
190-
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-department?start=${ start }&end=${ end }`);
207+
const loadChatsPerDepartmentChartData = async ({ start, end, ...options }) => {
208+
const additionalParams = parseAdditionalParams(options, '&');
209+
const result = await APIClient.v1.get(`livechat/analytics/dashboards/charts/chats-per-department?start=${ start }&end=${ end }${ additionalParams }`);
191210
delete result.success;
192211
return result;
193212
};
194213

195-
const updateDepartmentsChart = (departments) => {
214+
const updateDepartmentsChart = async (departments) => {
215+
// this chart need to reset before new updates
216+
chartContexts['lc-chats-per-dept-chart'] = await initChart['lc-chats-per-dept-chart']();
217+
196218
Object
197219
.keys(departments)
198220
.forEach((department) => updateChartData('lc-chats-per-dept-chart', department, [departments[department].open, departments[department].closed]));
199221
};
200222

201-
const loadTimingsChartData = ({ start, end }) => APIClient.v1.get(`livechat/analytics/dashboards/charts/timings?start=${ start }&end=${ end }`);
223+
const loadTimingsChartData = ({ start, end, ...options }) => {
224+
const additionalParams = parseAdditionalParams(options, '&');
225+
return APIClient.v1.get(`livechat/analytics/dashboards/charts/timings?start=${ start }&end=${ end }${ additionalParams }`);
226+
};
202227

203228
const updateTimingsChart = async (timingsData) => {
204229
const hour = moment(new Date()).format('H');
@@ -229,9 +254,27 @@ Template.livechatRealTimeMonitoring.helpers({
229254
isLoading() {
230255
return Template.instance().isLoading.get();
231256
},
257+
departmentModifier() {
258+
return (filter, text = '') => {
259+
const f = filter.get();
260+
return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `<strong>${ part }</strong>`) }`;
261+
};
262+
},
263+
onClickTagDepartment() {
264+
return Template.instance().onClickTagDepartment;
265+
},
266+
selectedDepartments() {
267+
return Template.instance().selectedDepartments.get();
268+
},
269+
onSelectDepartments() {
270+
return Template.instance().onSelectDepartments;
271+
},
272+
hasDepartments() {
273+
return Template.instance().hasDepartments.get();
274+
},
232275
});
233276

234-
Template.livechatRealTimeMonitoring.onCreated(function() {
277+
Template.livechatRealTimeMonitoring.onCreated(async function() {
235278
templateInstance = Template.instance();
236279
this.isLoading = new ReactiveVar(false);
237280
this.conversationsOverview = new ReactiveVar();
@@ -240,22 +283,43 @@ Template.livechatRealTimeMonitoring.onCreated(function() {
240283
this.agentsOverview = new ReactiveVar();
241284
this.conversationTotalizers = new ReactiveVar([]);
242285
this.interval = new ReactiveVar(5);
286+
this.selectedDepartments = new ReactiveVar([]);
287+
this.hasDepartments = new ReactiveVar(false);
288+
289+
this.onSelectDepartments = ({ item: department }) => {
290+
department.text = department.name;
291+
this.selectedDepartments.set([department]);
292+
};
293+
294+
this.onClickTagDepartment = () => {
295+
this.selectedDepartments.set([]);
296+
};
297+
298+
const { departments } = await APIClient.v1.get('livechat/department?count=1');
299+
this.hasDepartments.set(departments?.length > 0);
243300
});
244301

245302
Template.livechatRealTimeMonitoring.onRendered(async function() {
246303
await initAllCharts();
247304

248305
this.updateDashboard = async () => {
306+
const [department] = this.selectedDepartments.get();
307+
const departmentId = getChartDepartment(department);
249308
const daterange = getDaterange();
250-
updateConversationOverview(await loadConversationOverview(daterange));
251-
updateProductivityOverview(await loadProductivityOverview(daterange));
252-
updateChatsChart(await loadChatsChartData(daterange));
253-
updateChatsPerAgentChart(await loadChatsPerAgentChartData(daterange));
254-
updateAgentStatusChart(await loadAgentsStatusChartData());
255-
updateDepartmentsChart(await loadChatsPerDepartmentChartData(daterange));
256-
updateTimingsChart(await loadTimingsChartData(daterange));
257-
updateAgentsOverview(await loadAgentsOverview(daterange));
258-
updateChatsOverview(await loadChatsOverview(daterange));
309+
const filters = Object.assign(
310+
{ ...daterange },
311+
departmentId && { departmentId },
312+
);
313+
314+
updateConversationOverview(await loadConversationOverview(filters));
315+
updateProductivityOverview(await loadProductivityOverview(filters));
316+
updateChatsChart(await loadChatsChartData(filters));
317+
updateChatsPerAgentChart(await loadChatsPerAgentChartData(filters));
318+
updateAgentStatusChart(await loadAgentsStatusChartData(filters));
319+
updateDepartmentsChart(await loadChatsPerDepartmentChartData(filters));
320+
updateTimingsChart(await loadTimingsChartData(filters));
321+
updateAgentsOverview(await loadAgentsOverview(filters));
322+
updateChatsOverview(await loadChatsOverview(filters));
259323
};
260324
this.autorun(() => {
261325
if (timer) {

0 commit comments

Comments
 (0)