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