PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.13.5
Independent Analytics – WordPress Analytics Plugin v2.13.5
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 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 1 year 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 10 months ago examiner_header_controller.js 1 year ago export_overview_controller.js 10 months ago export_reports_controller.js 2 years ago filters_controller.js 10 months ago group_controller.js 2 years ago import_reports_controller.js 2 years ago map_controller.js 10 months ago migration_redirect_controller.js 2 years ago modal_controller.js 2 years ago pie_chart_controller.js 1 year ago plugin_group_options_controller.js 1 year ago pruner_controller.js 1 year ago quick_stats_controller.js 1 year ago real_time_controller.js 1 year ago refresh_overview_controller.js 1 year ago rename_report_controller.js 2 years ago report_controller.js 10 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 woocommerce_settings_controller.js 1 year ago
report_controller.js
609 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.table_toolbar)
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 }
364
365 showExaminer(event) {
366 const title = event.currentTarget.dataset.title
367 const url = new URL(event.currentTarget.dataset.url)
368 const updates = {
369 'exact_start': this.exactStart,
370 'exact_end': this.exactEnd,
371 'relative_range_id': this.relativeRangeId,
372 'quick_stats': this.quickStats,
373 'primary_chart_metric_id': this.primaryChartMetricId,
374 'secondary_chart_metric_id': this.secondaryChartMetricId,
375 'chart_interval': this.chartInterval,
376 'group': this.group,
377 }
378 const params = url.searchParams
379
380 for (const [key, value] of Object.entries(updates)) {
381 if (!value) {
382 continue
383 }
384
385 if(Array.isArray(value)) {
386 value.forEach((item) => params.append(key + '[]', item))
387 continue
388 }
389
390 params.set(key, value)
391 }
392
393 url.search = params.toString()
394
395 document.dispatchEvent(
396 new CustomEvent('iawp:showExaminer', {
397 detail: {
398 title,
399 reportName: this.reportNameValue,
400 dateLabel: this.element.querySelector('.date-picker-parent .iawp-label').innerText,
401 url: url.toString(),
402 }
403 })
404 )
405 }
406
407 exportReportTable() {
408 const data = {
409 ...iawpActions.export_report_table,
410 'table_type': jQuery('#data-table').data('table-name'),
411 'columns': this.columns,
412 'filters': this.filters,
413 'exact_start': this.exactStart,
414 'exact_end': this.exactEnd,
415 'relative_range_id': this.relativeRangeId,
416 'sort_column': this.sortColumn,
417 'sort_direction': this.sortDirection,
418 'group': this.group,
419 };
420
421 if(this.isExaminerValue) {
422 const searchParams = new URLSearchParams(document.location.search);
423
424 data['examiner_type'] = searchParams.get('tab')
425 data['examiner_group'] = searchParams.get('group')
426 data['examiner_id'] = searchParams.get('examiner')
427 }
428
429 this.exportReportTableTarget.classList.add('sending')
430 this.exportReportTableTarget.setAttribute('disabled', 'disabled')
431
432 if (Report_Controller.csvRequest) {
433 Report_Controller.csvRequest.abort();
434 }
435
436 Report_Controller.csvRequest = jQuery.post(ajaxurl, data, (response) => {
437 downloadCSV(this.getFileName('csv', 'table'), response.data.csv)
438 this.exportReportTableTarget.classList.add('sent')
439 this.exportReportTableTarget.classList.remove('sending')
440 this.exportReportTableTarget.removeAttribute('disabled')
441
442 setTimeout(() => {
443 this.exportReportTableTarget.classList.remove('sent')
444 }, 1000)
445 })
446 }
447
448 exportReportStatistics() {
449 const data = {
450 ...iawpActions.export_report_statistics,
451 'filters': this.filters,
452 'exact_start': this.exactStart,
453 'exact_end': this.exactEnd,
454 'is_new_date_range': false,
455 'relative_range_id': this.relativeRangeId,
456 'table_type': jQuery('#data-table').data('table-name'),
457 'columns': this.columns,
458 'sort_column': this.sortColumn,
459 'quick_stats': this.quickStats,
460 'sort_direction': this.sortDirection,
461 'group': this.group,
462 'is_new_group': false,
463 'chart_interval': this.chartInterval,
464 'page': this.page,
465 };
466
467 if(this.isExaminerValue) {
468 const searchParams = new URLSearchParams(document.location.search);
469
470 data['examiner_type'] = searchParams.get('tab')
471 data['examiner_group'] = searchParams.get('group')
472 data['examiner_id'] = searchParams.get('examiner')
473 }
474
475 this.exportReportStatisticsTarget.classList.add('sending')
476 this.exportReportStatisticsTarget.setAttribute('disabled', 'disabled')
477
478 if (Report_Controller.csvRequest) {
479 Report_Controller.csvRequest.abort();
480 }
481
482 Report_Controller.csvRequest = jQuery.post(ajaxurl, data, (response) => {
483 downloadCSV(this.getFileName('csv', 'statistics'), response.data.csv)
484 this.exportReportStatisticsTarget.classList.add('sent')
485 this.exportReportStatisticsTarget.classList.remove('sending')
486 this.exportReportStatisticsTarget.removeAttribute('disabled')
487
488 setTimeout(() => {
489 this.exportReportStatisticsTarget.classList.remove('sent')
490 }, 1000)
491 })
492 }
493
494 exportPDF() {
495 this.exportPDFTarget.classList.add('sending')
496 this.exportPDFTarget.setAttribute('disabled', 'disabled')
497
498 setTimeout(async () => {
499 const charts = Object.values(Chart.instances)
500 const mapElements = window.iawpMaps || []
501
502 // Assign a temporary unique id to every chart
503 charts.forEach((chart) => {
504 chart.canvas.dataset.chartExportId = Math.random()
505 })
506
507 // Assign a temporary unique id to every map
508 mapElements.forEach((map) => {
509 map.container.dataset.chartExportId = Math.random()
510 })
511
512 const clonedPage = document.getElementById('wpwrap').cloneNode(true)
513
514 // Set the width to the PDFs page width
515 clonedPage.style.width = 1056 + 'px'
516
517 charts.forEach((chart) => {
518 // Get an image of the chart
519 const base64Image = chart.toBase64Image('image/png', 1);
520
521 // Generate an image element to inline
522 const imageElement = document.createElement('img')
523 imageElement.src = base64Image
524 imageElement.classList.add('chart-converted-to-image')
525
526 // Swap the chart for the image
527 const chartExportId = chart.canvas.dataset.chartExportId
528 clonedPage.querySelector(`[data-chart-export-id='${chartExportId}']`).replaceWith(imageElement)
529
530 // Remove the temporary export id
531 delete chart.canvas.dataset.chartExportId
532 })
533
534 for (const map of mapElements) {
535 // Generate an image element to inline
536 const imageElement = await svgToPng(map.mapImage);
537 imageElement.classList.add('chart-converted-to-image')
538
539 // Swap the chart for the image
540 const chartExportId = map.container.dataset.chartExportId
541 clonedPage.querySelector(`[data-chart-export-id='${chartExportId}']`).replaceWith(imageElement)
542
543 // Remove the temporary export id
544 delete map.container.dataset.chartExportId
545 }
546
547 // Prevent stimulus controllers from firing
548 clonedPage.querySelectorAll('[data-controller]').forEach((element) => {
549 element.removeAttribute('data-controller')
550 })
551
552 // Remove module picker
553 clonedPage.querySelectorAll('.module-picker').forEach((element) => {
554 element.remove()
555 })
556
557 // Preserve selected values in clone by manually adding "selected" to options
558 clonedPage.querySelectorAll('select').forEach((element) => {
559 if (!element.id) {
560 return
561 }
562
563 const originalValue = document.getElementById(element.id).value
564
565 element.options.forEach((option) => {
566 option.toggleAttribute('selected', option.value === originalValue)
567 })
568 })
569
570 const options = {
571 filename: this.getFileName('pdf'),
572 jsPDF: {
573 unit: 'in',
574 format: 'letter',
575 orientation: 'landscape',
576 },
577 }
578
579 html2pdf().set(options).from(clonedPage).toContainer().save().then(() => {
580 this.exportPDFTarget.classList.add('sent')
581 this.exportPDFTarget.classList.remove('sending')
582 this.exportPDFTarget.removeAttribute('disabled')
583
584 setTimeout(() => {
585 this.exportPDFTarget.classList.remove('sent')
586 }, 1000)
587 })
588 }, 250) // Allow animations to finish before exporting blocks things up
589 }
590
591 enableExaminerTabs() {
592 document.querySelectorAll('.examiner-table-tabs button').forEach((button) => {
593 button.removeAttribute('disabled')
594 })
595 }
596
597 getFileName(fileExtension, type = null) {
598 const reportTitleElement = document.querySelector('#report-title-bar .report-title')
599 let reportTitle = reportTitleElement ? reportTitleElement.innerText : 'report'
600
601 if (type) {
602 reportTitle += '-' + type
603 }
604
605 return reportTitle.replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase() + '.' + fileExtension
606 }
607 }
608
609 export default Report_Controller;