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