PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.14.2
Independent Analytics – WordPress Analytics Plugin v2.14.2
2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / javascript-source / controllers / report_controller.js
independent-analytics / javascript-source / controllers Last commit date
overview 1 year ago campaign_builder_controller.js 1 year ago chart_controller.js 6 months ago chart_interval_controller.js 2 years ago clipboard_controller.js 1 year ago copy_report_controller.js 2 years ago create_report_controller.js 1 year ago delete_data_controller.js 1 year ago delete_report_controller.js 2 years ago easepick_controller.js 2 years ago examiner_controller.js 9 months ago examiner_header_controller.js 11 months ago export_overview_controller.js 9 months ago export_reports_controller.js 2 years ago filters_controller.js 6 months ago group_controller.js 2 years ago import_reports_controller.js 2 years ago journey_controller.js 6 months ago map_controller.js 9 months ago migration_redirect_controller.js 2 years ago modal_controller.js 2 years ago pause_emails_controller.js 6 months ago pie_chart_controller.js 6 months ago plugin_group_options_controller.js 1 year ago pruner_controller.js 5 months ago quick_stats_controller.js 1 year ago real_time_controller.js 6 months ago refresh_overview_controller.js 1 year ago rename_report_controller.js 2 years ago report_controller.js 6 months ago reset_analytics_controller.js 1 year ago reset_overview_controller.js 1 year ago save_report_controller.js 2 years ago select_input_controller.js 2 years ago set_favorite_report_controller.js 2 years ago sort_controller.js 2 years ago sortable_reports_controller.js 2 years ago table_columns_controller.js 2 years ago tooltip_controller.js 6 months ago woocommerce_settings_controller.js 1 year ago
report_controller.js
614 lines
1 import {Controller} from "@hotwired/stimulus"
2 import {downloadCSV} from '../download'
3 import html2pdf from 'html2pdf.js';
4 import {Chart} from 'chart.js'
5 import {svgToPng} from "../utils/svg-to-png";
6
7 class Report_Controller extends Controller {
8 /**
9 * Store the jQuery request so that it can be canceled if a new report is requested before the
10 * current one is fetched.
11 */
12 static request;
13 static targets = ['loadMore', 'exportReportTable', 'exportReportStatistics', 'exportPDF', 'spinner']
14 static values = {
15 isExaminer: Boolean,
16 name: String,
17 reportName: String,
18 relativeRangeId: String,
19 exactStart: String,
20 exactEnd: String,
21 group: String,
22 chartInterval: String,
23 sortColumn: String,
24 sortDirection: String,
25 columns: Array,
26 quickStats: Array,
27 primaryChartMetricId: String,
28 secondaryChartMetricId: String,
29 filters: Array,
30 filterLogic: String
31 }
32
33 tableType = undefined
34 exactStart = undefined
35 exactEnd = undefined
36 relativeRangeId = undefined
37 columns = undefined
38 quickStats = undefined
39 filters = []
40 filterLogic = undefined
41 sortColumn = undefined
42 sortDirection = undefined
43 group = undefined
44 chartInterval = undefined
45 primaryChartMetricId = undefined
46 secondaryChartMetricId = undefined
47 page = 1
48
49 connect() {
50 // If any values are empty strings, set them equal to undefined instead
51 this.exactStart = this.exactStartValue === "" ? undefined : this.exactStartValue;
52 this.exactEnd = this.exactEndValue === "" ? undefined : this.exactEndValue;
53 this.relativeRangeId = this.relativeRangeIdValue === "" ? undefined : this.relativeRangeIdValue
54 this.columns = this.columnsValue
55 this.quickStats = this.quickStatsValue
56 this.group = this.groupValue
57 this.chartInterval = this.chartIntervalValue
58 this.sortColumn = this.sortColumnValue
59 this.sortDirection = this.sortDirectionValue
60 this.primaryChartMetricId = this.primaryChartMetricIdValue
61 this.secondaryChartMetricId = this.secondaryChartMetricIdValue
62 this.filters = this.filtersValue
63 this.filterLogic = this.filterLogicValue
64 this.tableType = jQuery('#data-table').data('table-name')
65 document.addEventListener('iawp:changeDates', this.datesChanged)
66 document.addEventListener('iawp:changeColumns', this.columnsChanged)
67 document.addEventListener('iawp:changeQuickStats', this.quickStatsChanged)
68 document.addEventListener('iawp:changeFilters', this.filtersChanged)
69 document.addEventListener('iawp:changeSort', this.sortChanged)
70 document.addEventListener('iawp:changeGroup', this.changeGroup)
71 document.addEventListener('iawp:changeChartInterval', this.changeChartInterval)
72 document.addEventListener('iawp:changePrimaryChartMetric', this.changePrimaryChartMetric)
73 document.addEventListener('iawp:changeSecondaryChartMetric', this.changeSecondaryChartMetric)
74 document.addEventListener('iawp:fetchingReport', this.onFetchingReport)
75 setTimeout(() => {
76 this.fetch({
77 isInitialFetch: true,
78 showLoadingOverlay: false
79 })
80 }, 0)
81 }
82
83 disconnect() {
84 document.removeEventListener('iawp:changeDates', this.datesChanged)
85 document.removeEventListener('iawp:changeColumns', this.columnsChanged)
86 document.removeEventListener('iawp:changeQuickStats', this.quickStatsChanged)
87 document.removeEventListener('iawp:changeFilters', this.filtersChanged)
88 document.removeEventListener('iawp:changeSort', this.sortChanged)
89 document.removeEventListener('iawp:changeGroup', this.changeGroup)
90 document.removeEventListener('iawp:changeChartInterval', this.changeChartInterval)
91 document.removeEventListener('iawp:changePrimaryChartMetric', this.changePrimaryChartMetric)
92 document.removeEventListener('iawp:changeSecondaryChartMetric', this.changeSecondaryChartMetric)
93 document.removeEventListener('iawp:fetchingReport', this.onFetchingReport)
94 }
95
96 emitChangedOption(detail) {
97 document.dispatchEvent(
98 new CustomEvent('iawp:changedOption', {
99 detail
100 })
101 )
102 }
103
104 changePrimaryChartMetric = (e) => {
105 this.primaryChartMetricId = e.detail.primaryChartMetricId
106 this.emitChangedOption({
107 'primary_chart_metric_id': this.primaryChartMetricId
108 })
109 }
110
111 changeSecondaryChartMetric = (e) => {
112 this.secondaryChartMetricId = e.detail.secondaryChartMetricId
113 this.emitChangedOption({
114 'secondary_chart_metric_id': this.secondaryChartMetricId
115 })
116 }
117
118 datesChanged = (e) => {
119 this.exactStart = e.detail.exactStart
120 this.exactEnd = e.detail.exactEnd
121 this.relativeRangeId = e.detail.relativeRangeId
122 this.page = 1;
123
124 this.emitChangedOption({
125 'exact_start': this.exactStart || null,
126 'exact_end': this.exactEnd || null,
127 'relative_range_id': this.relativeRangeId || null
128 })
129 this.fetch({newDateRange: true})
130 }
131
132 columnsChanged = (e) => {
133 this.columns = e.detail.optionIds
134 this.emitChangedOption({
135 'columns': this.columns
136 })
137 }
138
139 quickStatsChanged = (e) => {
140 this.quickStats = e.detail.optionIds
141 this.emitChangedOption({
142 'quick_stats': this.quickStats
143 })
144 }
145
146 filtersChanged = (e) => {
147 this.filters = e.detail.filters
148 this.filterLogic = e.detail.filterLogic
149 this.page = 1;
150
151 this.emitChangedOption({
152 'filters': this.filters,
153 'filter_logic': this.filterLogic,
154 })
155 this.fetch({showLoadingOverlay: e.detail.showLoadingOverlay})
156 }
157
158 sortChanged = (e) => {
159 this.sortColumn = e.detail.sortColumn
160 this.sortDirection = e.detail.sortDirection
161 this.page = 1;
162
163 this.emitChangedOption({
164 'sort_column': this.sortColumn,
165 'sort_direction': this.sortDirection
166 })
167 this.fetch()
168 }
169
170 changeGroup = (e) => {
171 if (this.group === e.detail.group) {
172 return;
173 }
174
175 this.group = e.detail.group
176 this.page = 1
177 this.emitChangedOption({
178 'group_name': this.group
179 })
180 this.fetch({newGroup: true});
181 }
182
183 changeChartInterval = (e) => {
184 const chartInterval = e.detail.chartInterval
185
186 if (this.chartInterval === chartInterval) {
187 return;
188 }
189
190 this.chartInterval = chartInterval
191 this.emitChangedOption({
192 'chart_interval': this.chartInterval
193 })
194 this.fetch();
195 }
196
197 changeTable = (event) => {
198 this.tableType = event.currentTarget.dataset.tableType
199 this.page = 1;
200
201 event.currentTarget.closest('.examiner-table-tabs').querySelectorAll('.active').forEach((element) => {
202 element.classList.remove('active')
203 })
204
205 event.currentTarget.classList.add('active')
206
207 this.fetch({ newTable: true })
208 }
209
210 onFetchingReport = () => {
211 this.spinnerTarget.classList.remove('hidden')
212
213 document.addEventListener('iawp:fetchedReport', () => {
214 this.spinnerTarget.classList.add('hidden')
215 }, {once: true})
216 }
217
218 loadMore = () => {
219 this.page = this.page + 1
220
221 this.fetch()
222 }
223
224 fetch({
225 isInitialFetch = false,
226 showLoadingOverlay = true,
227 newGroup = false,
228 newTable = false,
229 newDateRange = false
230 } = {}) {
231 const isExaminer = this.isExaminerValue
232
233 if (showLoadingOverlay) {
234 jQuery('#iawp-parent').addClass('loading');
235 }
236
237 const data = {
238 ...iawpActions.filter,
239 'filters': this.filters,
240 'filter_logic': this.filterLogic,
241 'exact_start': this.exactStart,
242 'exact_end': this.exactEnd,
243 'is_new_date_range': newDateRange,
244 'relative_range_id': this.relativeRangeId,
245 'table_type': this.tableType,
246 'columns': this.columns,
247 'report_quick_stats': ['visitors'], // TODO
248 'primary_chart_metric_id': this.primaryChartMetricId,
249 'secondary_chart_metric_id': this.secondaryChartMetricId,
250 'sort_column': this.sortColumn,
251 'quick_stats': this.quickStats,
252 'sort_direction': this.sortDirection,
253 'group': this.group,
254 'is_new_group': newGroup && !newTable,
255 'chart_interval': this.chartInterval,
256 'page': this.page,
257 };
258
259 if(newTable) {
260 data.columns = null
261 }
262
263 if (Report_Controller.request) {
264 Report_Controller.request.abort();
265 }
266
267 if(isExaminer) {
268 const searchParams = new URLSearchParams(document.location.search);
269
270 data['examiner_type'] = searchParams.get('tab')
271 data['examiner_group'] = searchParams.get('group')
272 data['examiner_id'] = searchParams.get('examiner')
273 }
274
275 document.dispatchEvent(
276 new CustomEvent('iawp:fetchingReport')
277 )
278
279 Report_Controller.request = jQuery.post(ajaxurl, data, (response) => {
280 response = response.data
281
282 this.columns = response.columns
283 this.filters = response.filters
284 this.chartInterval = response.chartInterval
285
286
287 document.dispatchEvent(
288 new CustomEvent('iawp:fetchedReport')
289 )
290
291 if (newGroup || isExaminer) {
292 jQuery('#iawp-table-wrapper').replaceWith(response.table)
293 jQuery('[data-plugin-group-options-option-type-value=columns]').replaceWith(response.columnsHTML)
294
295 document.dispatchEvent(
296 new CustomEvent('iawp:changeColumns', {
297 detail: {
298 optionIds: this.columns
299 }
300 })
301 )
302 } else {
303 jQuery('#iawp-rows').replaceWith(response.rows);
304 }
305
306 jQuery('#dates-button span:last-child').text(response.label)
307
308 const parser = new DOMParser();
309 const statsDocument = parser.parseFromString(response.stats, 'text/html');
310
311 jQuery('#quick-stats .iawp-stats').replaceWith(statsDocument.querySelector('.iawp-stats'))
312 jQuery('#quick-stats').removeClass('skeleton-ui');
313
314 if (isInitialFetch && this.filtersValue.length === 0) {
315 // Do not update the chart if there are no filters and it's the initial load
316 } else {
317 jQuery('#independent-analytics-chart').closest('.chart-container').replaceWith(response.chart);
318 }
319
320 if(isExaminer) {
321 jQuery('#table-toolbar').replaceWith(response.tableToolbar)
322 }
323
324 if(isExaminer && isInitialFetch) {
325 this.enableExaminerTabs()
326 }
327
328 document.dispatchEvent(
329 new CustomEvent('iawp:updateColumnsUserInterface')
330 )
331
332 document.dispatchEvent(
333 new CustomEvent('iawp:groupChanged', {
334 detail: {
335 groupId: response.groupId
336 }
337 })
338 )
339
340 document.dispatchEvent(
341 new CustomEvent('iawp:filtersChanged', {
342 detail: {
343 filtersTemplateHTML: response.filtersTemplateHTML,
344 filtersButtonsHTML: response.filtersButtonsHTML,
345 filters: response.filters
346 }
347 })
348 )
349
350 this.exportPDFTarget.removeAttribute('disabled')
351
352 if (response.isLastPage) {
353 this.loadMoreTarget.setAttribute('disabled', 'disabled')
354 } else {
355 this.loadMoreTarget.removeAttribute('disabled')
356 }
357
358 jQuery('#iawp-columns .row-number').text(response.totalNumberOfRows.toLocaleString());
359 document.getElementById('data-table').setAttribute('data-total-number-of-rows', response.totalNumberOfRows)
360
361 jQuery('#iawp-parent').removeClass('loading');
362
363 if(!isInitialFetch && response.groupId === 'journey') {
364 document.body.scrollIntoView({ behavior: 'instant', block: 'start' })
365 }
366 });
367 }
368
369 showExaminer(event) {
370 const title = event.currentTarget.dataset.title
371 const url = new URL(event.currentTarget.dataset.url)
372 const updates = {
373 'exact_start': this.exactStart,
374 'exact_end': this.exactEnd,
375 'relative_range_id': this.relativeRangeId,
376 'quick_stats': this.quickStats,
377 'primary_chart_metric_id': this.primaryChartMetricId,
378 'secondary_chart_metric_id': this.secondaryChartMetricId,
379 'chart_interval': this.chartInterval,
380 'group': this.group,
381 }
382 const params = url.searchParams
383
384 for (const [key, value] of Object.entries(updates)) {
385 if (!value) {
386 continue
387 }
388
389 if(Array.isArray(value)) {
390 value.forEach((item) => params.append(key + '[]', item))
391 continue
392 }
393
394 params.set(key, value)
395 }
396
397 url.search = params.toString()
398
399 document.dispatchEvent(
400 new CustomEvent('iawp:showExaminer', {
401 detail: {
402 title,
403 reportName: this.reportNameValue,
404 dateLabel: this.element.querySelector('.date-picker-parent .iawp-label').innerText,
405 url: url.toString(),
406 }
407 })
408 )
409 }
410
411 exportReportTable() {
412 const data = {
413 ...iawpActions.export_report_table,
414 'table_type': jQuery('#data-table').data('table-name'),
415 'columns': this.columns,
416 'filters': this.filters,
417 'exact_start': this.exactStart,
418 'exact_end': this.exactEnd,
419 'relative_range_id': this.relativeRangeId,
420 'sort_column': this.sortColumn,
421 'sort_direction': this.sortDirection,
422 'group': this.group,
423 };
424
425 if(this.isExaminerValue) {
426 const searchParams = new URLSearchParams(document.location.search);
427
428 data['examiner_type'] = searchParams.get('tab')
429 data['examiner_group'] = searchParams.get('group')
430 data['examiner_id'] = searchParams.get('examiner')
431 }
432
433 this.exportReportTableTarget.classList.add('sending')
434 this.exportReportTableTarget.setAttribute('disabled', 'disabled')
435
436 if (Report_Controller.csvRequest) {
437 Report_Controller.csvRequest.abort();
438 }
439
440 Report_Controller.csvRequest = jQuery.post(ajaxurl, data, (response) => {
441 downloadCSV(this.getFileName('csv', 'table'), response.data.csv)
442 this.exportReportTableTarget.classList.add('sent')
443 this.exportReportTableTarget.classList.remove('sending')
444 this.exportReportTableTarget.removeAttribute('disabled')
445
446 setTimeout(() => {
447 this.exportReportTableTarget.classList.remove('sent')
448 }, 1000)
449 })
450 }
451
452 exportReportStatistics() {
453 const data = {
454 ...iawpActions.export_report_statistics,
455 'filters': this.filters,
456 'exact_start': this.exactStart,
457 'exact_end': this.exactEnd,
458 'is_new_date_range': false,
459 'relative_range_id': this.relativeRangeId,
460 'table_type': jQuery('#data-table').data('table-name'),
461 'columns': this.columns,
462 'sort_column': this.sortColumn,
463 'quick_stats': this.quickStats,
464 'sort_direction': this.sortDirection,
465 'group': this.group,
466 'is_new_group': false,
467 'chart_interval': this.chartInterval,
468 'page': this.page,
469 };
470
471 if(this.isExaminerValue) {
472 const searchParams = new URLSearchParams(document.location.search);
473
474 data['examiner_type'] = searchParams.get('tab')
475 data['examiner_group'] = searchParams.get('group')
476 data['examiner_id'] = searchParams.get('examiner')
477 }
478
479 this.exportReportStatisticsTarget.classList.add('sending')
480 this.exportReportStatisticsTarget.setAttribute('disabled', 'disabled')
481
482 if (Report_Controller.csvRequest) {
483 Report_Controller.csvRequest.abort();
484 }
485
486 Report_Controller.csvRequest = jQuery.post(ajaxurl, data, (response) => {
487 downloadCSV(this.getFileName('csv', 'statistics'), response.data.csv)
488 this.exportReportStatisticsTarget.classList.add('sent')
489 this.exportReportStatisticsTarget.classList.remove('sending')
490 this.exportReportStatisticsTarget.removeAttribute('disabled')
491
492 setTimeout(() => {
493 this.exportReportStatisticsTarget.classList.remove('sent')
494 }, 1000)
495 })
496 }
497
498 exportPDF() {
499 this.exportPDFTarget.classList.add('sending')
500 this.exportPDFTarget.setAttribute('disabled', 'disabled')
501
502 setTimeout(async () => {
503 const charts = Object.values(Chart.instances)
504 const mapElements = window.iawpMaps || []
505
506 // Assign a temporary unique id to every chart
507 charts.forEach((chart) => {
508 chart.canvas.dataset.chartExportId = Math.random()
509 })
510
511 // Assign a temporary unique id to every map
512 mapElements.forEach((map) => {
513 map.container.dataset.chartExportId = Math.random()
514 })
515
516 const clonedPage = document.getElementById('wpwrap').cloneNode(true)
517
518 // Set the width to the PDFs page width
519 clonedPage.style.width = 1056 + 'px'
520
521 charts.forEach((chart) => {
522 // Get an image of the chart
523 const base64Image = chart.toBase64Image('image/png', 1);
524
525 // Generate an image element to inline
526 const imageElement = document.createElement('img')
527 imageElement.src = base64Image
528 imageElement.classList.add('chart-converted-to-image')
529
530 // Swap the chart for the image
531 const chartExportId = chart.canvas.dataset.chartExportId
532 clonedPage.querySelector(`[data-chart-export-id='${chartExportId}']`).replaceWith(imageElement)
533
534 // Remove the temporary export id
535 delete chart.canvas.dataset.chartExportId
536 })
537
538 for (const map of mapElements) {
539 // Generate an image element to inline
540 const imageElement = await svgToPng(map.mapImage);
541 imageElement.classList.add('chart-converted-to-image')
542
543 // Swap the chart for the image
544 const chartExportId = map.container.dataset.chartExportId
545 clonedPage.querySelector(`[data-chart-export-id='${chartExportId}']`).replaceWith(imageElement)
546
547 // Remove the temporary export id
548 delete map.container.dataset.chartExportId
549 }
550
551 // Prevent stimulus controllers from firing
552 clonedPage.querySelectorAll('[data-controller]').forEach((element) => {
553 element.removeAttribute('data-controller')
554 })
555
556 // Remove module picker
557 clonedPage.querySelectorAll('.module-picker').forEach((element) => {
558 element.remove()
559 })
560
561 // Preserve selected values in clone by manually adding "selected" to options
562 clonedPage.querySelectorAll('select').forEach((element) => {
563 if (!element.id) {
564 return
565 }
566
567 const originalValue = document.getElementById(element.id).value
568
569 element.options.forEach((option) => {
570 option.toggleAttribute('selected', option.value === originalValue)
571 })
572 })
573
574 const options = {
575 filename: this.getFileName('pdf'),
576 jsPDF: {
577 unit: 'in',
578 format: 'letter',
579 orientation: 'landscape',
580 },
581 }
582
583 html2pdf().set(options).from(clonedPage).toContainer().save().then(() => {
584 this.exportPDFTarget.classList.add('sent')
585 this.exportPDFTarget.classList.remove('sending')
586 this.exportPDFTarget.removeAttribute('disabled')
587
588 setTimeout(() => {
589 this.exportPDFTarget.classList.remove('sent')
590 }, 1000)
591 })
592 }, 250) // Allow animations to finish before exporting blocks things up
593 }
594
595 enableExaminerTabs() {
596 document.querySelectorAll('.examiner-table-tabs button').forEach((button) => {
597 button.removeAttribute('disabled')
598 })
599 }
600
601 getFileName(fileExtension, type = null) {
602 const reportTitleElement = document.querySelector('#report-title-bar .report-title')
603 let reportTitle = reportTitleElement ? reportTitleElement.innerText : 'report'
604
605 if (type) {
606 reportTitle += '-' + type
607 }
608
609 return reportTitle.replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase() + '.' + fileExtension
610 }
611 }
612
613 export default Report_Controller;
614