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