Passed
Push — master ( 73900a...d713cb )
by Marcel
08:02
created

app.js ➔ dismiss   F

Complexity

Conditions 64

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 64
eloc 6
c 0
b 0
f 0
dl 0
loc 8
rs 0

How to fix   Complexity   

Complexity

Complex classes like app.js ➔ dismiss often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/**
2
 * Analytics
3
 *
4
 * This file is licensed under the Affero General Public License version 3 or
5
 * later. See the LICENSE.md file.
6
 *
7
 * @author Marcel Scherello <[email protected]>
8
 * @copyright 2020 Marcel Scherello
9
 */
10
/** global: OCA */
11
/** global: OCP */
12
/** global: OC */
13
/** global: table */
14
/** global: Chart */
15
/** global: cloner */
16
17
'use strict';
18
19
if (!OCA.Analytics) {
20
    /**
21
     * @namespace
22
     */
23
    OCA.Analytics = {
24
        TYPE_EMPTY_GROUP: 0,
25
        TYPE_INTERNAL_FILE: 1,
26
        TYPE_INTERNAL_DB: 2,
27
        TYPE_GIT: 3,
28
        TYPE_EXTERNAL_FILE: 4,
29
        TYPE_EXTERNAL_REGEX: 5,
30
        TYPE_SHARED: 99,
31
        SHARE_TYPE_USER: 0,
32
        SHARE_TYPE_LINK: 3,
33
        initialDocumentTitle: null,
34
    };
35
}
36
/**
37
 * @namespace OCA.Analytics.Core
38
 */
39
OCA.Analytics.Core = {
40
    initApplication: function () {
41
        const urlHash = decodeURI(location.hash);
42
        if (urlHash.length > 1) {
43
            if (urlHash[2] === 'f') {
44
                window.location.href = '#';
45
                OCA.Analytics.Backend.createDataset(urlHash.substring(3));
46
            } else if (urlHash[2] === 'r') {
47
                OCA.Analytics.Navigation.init(urlHash.substring(4));
48
            }
49
        } else {
50
            OCA.Analytics.Navigation.init();
51
        }
52
    },
53
};
54
55
OCA.Analytics.UI = {
56
57
    buildDataTable: function (jsondata) {
58
        document.getElementById('tableContainer').style.removeProperty('display');
59
60
        let columns = [];
61
        let data;
62
63
        let header = jsondata.header;
64
        let headerKeys = Object.keys(header);
65
        for (let i = 0; i < headerKeys.length; i++) {
66
            columns[i] = {'title': header[headerKeys[i]]};
67
            if (header[headerKeys[i]].length === 1) {
68
                columns[i]['render'] = $.fn.dataTable.render.number('.', ',', 2, header[headerKeys[i]] + ' ');
69
            }
70
        }
71
        data = jsondata.data;
72
73
        const language = {
74
            search: t('analytics', 'Search'),
75
            lengthMenu: t('analytics', 'Show _MENU_ entries'),
76
            info: t('analytics', 'Showing _START_ to _END_ of _TOTAL_ entries'),
77
            infoEmpty: t('analytics', 'Showing 0 to 0 of 0 entries'),
78
            paginate: {
79
                first: t('analytics', 'first'),
80
                previous: t('analytics', 'previous'),
81
                next: t('analytics', 'next'),
82
                last: t('analytics', 'last')
83
            },
84
        };
85
86
        $('#tableContainer').DataTable({
87
            data: data,
88
            columns: columns,
89
            language: language,
90
            rowCallback: function (row, data, index) {
91
                OCA.Analytics.UI.dataTableRowCallback(row, data, index, jsondata.thresholds)
92
            },
93
        });
94
    },
95
96
    dataTableRowCallback: function (row, data, index, thresholds) {
97
98
        const operators = {
99
            '=': function (a, b) {
100
                return a === b
101
            },
102
            '<': function (a, b) {
103
                return a < b
104
            },
105
            '>': function (a, b) {
106
                return a > b
107
            },
108
            '<=': function (a, b) {
109
                return a <= b
110
            },
111
            '>=': function (a, b) {
112
                return a >= b
113
            },
114
            '!=': function (a, b) {
115
                return a !== b
116
            },
117
        };
118
119
        thresholds = thresholds.filter(p => p.dimension1 === data[0] || p.dimension1 === '*');
120
121
        for (let threshold of thresholds) {
122
            const comparison = operators[threshold['option']](parseFloat(data[2]), parseFloat(threshold['dimension3']));
123
            threshold['severity'] = parseInt(threshold['severity']);
124
            if (comparison === true) {
125
                if (threshold['severity'] === 2) {
126
                    $(row).find('td:eq(2)').css('color', 'red');
127
                } else if (threshold['severity'] === 3) {
128
                    $(row).find('td:eq(2)').css('color', 'orange');
129
                } else if (threshold['severity'] === 4) {
130
                    $(row).find('td:eq(2)').css('color', 'green');
131
                }
132
            }
133
        }
134
    },
135
136
    buildChart: function (jsondata) {
137
138
        document.getElementById('chartContainer').style.removeProperty('display');
139
        document.getElementById('chartMenuContainer').style.removeProperty('display');
140
        let ctx = document.getElementById('myChart').getContext('2d');
141
142
        // flexible mapping depending on type requiered by the used chart library
143
        let chartTypeMapping = {
144
            'datetime': 'line',
145
            'column': 'bar',
146
            'area': 'line',
147
            'line': 'line',
148
            'doughnut': 'doughnut'
149
        };
150
151
        let chartType = jsondata.options.chart;
152
        let datasets = [], xAxisCategories = [];
153
        let lastObject = false;
154
        let dataSeries = -1;
155
        let hidden = false;
156
157
        let header = jsondata.header;
158
        let headerKeys = Object.keys(header).length;
159
        let dataSeriesColumn = headerKeys - 3; //characteristic is taken from the second last column
160
        let characteristicColumn = headerKeys - 2; //characteristic is taken from the second last column
161
        let keyFigureColumn = headerKeys - 1; //key figures is taken from the last column
162
163
        Chart.defaults.global.elements.line.borderWidth = 2;
164
        Chart.defaults.global.elements.line.tension = 0.1;
165
        Chart.defaults.global.elements.line.fill = false;
166
        Chart.defaults.global.elements.point.radius = 1;
167
168
        var chartOptions = {
169
            maintainAspectRatio: false,
170
            responsive: true,
171
            scales: {
172
                yAxes: [{
173
                    ticks: {
174
                        callback: function (value, index, values) {
0 ignored issues
show
Unused Code introduced by
The parameter index is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter values is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
175
                            return value.toLocaleString();
176
                        },
177
                    },
178
                    stacked: false,
179
                    gridLines: {
180
                        display: true,
181
                    },
182
                    display: true,
183
                }],
184
                xAxes: [{
185
                    type: 'category',
186
                    distribution: 'linear',
187
                    gridLines: {
188
                        display: false
189
                    },
190
                    display: true,
191
                }],
192
            },
193
            plugins: {
194
                colorschemes: {
195
                    scheme: 'tableau.ClassicLight10'
196
                }
197
            },
198
            legend: {
199
                display: false,
200
                position: 'bottom'
201
            },
202
            animation: {
203
                duration: 0 // general animation time
204
            },
205
        };
206
207
        for (let values of jsondata.data) {
208
            if (dataSeriesColumn >= 0 && lastObject !== values[dataSeriesColumn]) {
209
                // create new dataseries for every new lable in dataSeriesColumn
210
                datasets.push({label: values[dataSeriesColumn], data: [], hidden: hidden});
211
                dataSeries++;
212
                // default hide > 4th series for better visibility
213
                if (dataSeries === 3) {
214
                    hidden = true;
215
                }
216
                lastObject = values[dataSeriesColumn];
217
            } else if (lastObject === false) {
218
                // when only 2 columns are provided, no label will be set
219
                datasets.push({label: '', data: [], hidden: hidden});
220
                dataSeries++;
221
                lastObject = true;
222
            }
223
224
            if (chartType === 'datetime' || chartType === 'area') {
225
                datasets[dataSeries]['data'].push({
226
                    t: values[characteristicColumn],
227
                    y: parseFloat(values[keyFigureColumn])
228
                });
229
            } else {
230
                datasets[dataSeries]['data'].push(parseFloat(values[keyFigureColumn]));
231
                if (dataSeries === 0) {
232
                    // Add category lables only once and not for every data series.
233
                    // They have to be unique anyway
234
                    xAxisCategories.push(values[characteristicColumn]);
235
                }
236
            }
237
        }
238
        if (chartType === 'datetime') {
239
            chartOptions.scales.xAxes[0].type = 'time';
240
            chartOptions.scales.xAxes[0].distribution = 'linear';
241
        } else if (chartType === 'area') {
242
            chartOptions.scales.xAxes[0].type = 'time';
243
            chartOptions.scales.xAxes[0].distribution = 'linear';
244
            chartOptions.scales.yAxes[0].stacked = true;
245
            Chart.defaults.global.elements.line.fill = true;
246
        } else if (chartType === 'doughnut') {
247
            chartOptions.scales.xAxes[0].display = false;
248
            chartOptions.scales.yAxes[0].display = false;
249
            chartOptions.scales.yAxes[0].gridLines.display = false;
250
            chartOptions.circumference = Math.PI;
251
            chartOptions.rotation = -Math.PI;
252
            chartOptions.legend.display = true;
253
        }
254
255
        //'{"scales": {"xAxes": [{"time": {"unit" : "month"}}]}}'
256
        var userOptions = jsondata.options.chartoptions;
257
        if (userOptions !== '' && userOptions !== null) {
258
            cloner.deep.merge(chartOptions, JSON.parse(userOptions));
259
        }
260
261
        let myChart = new Chart(ctx, {
262
            type: chartTypeMapping[chartType],
263
            data: {
264
                labels: xAxisCategories,
265
                datasets: datasets
266
            },
267
            options: chartOptions,
268
        });
269
270
        document.getElementById('chartLegend').addEventListener('click', function () {
271
            myChart.options.legend.display = !myChart.options.legend.display;
272
            myChart.update();
273
        });
274
    },
275
276
    resetContent: function () {
277
        if (document.getElementById('advanced').value === 'true') {
278
            document.getElementById('analytics-intro').classList.remove('hidden');
279
            document.getElementById('app-sidebar').classList.add('disappear');
280
        } else {
281
            if ($.fn.dataTable.isDataTable('#tableContainer')) {
282
                $('#tableContainer').DataTable().destroy();
283
            }
284
            document.getElementById('chartMenuContainer').style.display = 'none';
285
            document.getElementById('chartContainer').style.display = 'none';
286
            document.getElementById('chartContainer').innerHTML = '';
287
            document.getElementById('chartContainer').innerHTML = '<canvas id="myChart" ></canvas>';
288
            document.getElementById('tableContainer').style.display = 'none';
289
            document.getElementById('tableContainer').innerHTML = '';
290
            document.getElementById('reportHeader').innerHTML = '';
291
            document.getElementById('reportSubHeader').innerHTML = '';
292
            document.getElementById('reportSubHeader').style.display = 'none';
293
            document.getElementById('filterContainer').style.display = 'none';
294
            document.getElementById('noDataContainer').style.display = 'none';
295
        }
296
    },
297
298
    notification: function (type, message) {
299
        if (parseInt(OC.config.versionstring.substr(0, 2)) >= 17) {
300
            if (type === 'success') {
301
                OCP.Toast.success(message)
302
            } else if (type === 'error') {
303
                OCP.Toast.error(message)
304
            } else {
305
                OCP.Toast.info(message)
306
            }
307
        } else {
308
            OC.Notification.showTemporary(message);
309
        }
310
    },
311
312
    whatsNewSuccess: function (data, statusText, xhr, dismissOptions) {
0 ignored issues
show
Unused Code introduced by
The parameter dismissOptions is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
313
        console.debug('querying Whats New data was successful: ' + statusText)
314
        console.debug(data)
315
316
        if (xhr.status !== 200) {
317
            return
318
        }
319
320
        let item, menuItem, text, icon
321
322
        const div = document.createElement('div')
323
        div.classList.add('popovermenu', 'open', 'whatsNewPopover', 'menu-left')
324
325
        const list = document.createElement('ul')
326
327
        // header
328
        item = document.createElement('li')
329
        menuItem = document.createElement('span')
330
        menuItem.className = 'menuitem'
331
332
        text = document.createElement('span')
333
        text.innerText = t('core', 'New in') + ' ' + data['product']
334
        text.className = 'caption'
335
        menuItem.appendChild(text)
336
337
        icon = document.createElement('span')
338
        icon.className = 'icon-close'
339
        icon.onclick = function () {
340
            OCA.Analytics.Backend.whatsnewDismiss(data['version'])
341
        }
342
        menuItem.appendChild(icon)
343
344
        item.appendChild(menuItem)
345
        list.appendChild(item)
346
347
        // Highlights
348
        for (const i in data['whatsNew']['regular']) {
349
            const whatsNewTextItem = data['whatsNew']['regular'][i]
350
            item = document.createElement('li')
351
352
            menuItem = document.createElement('span')
353
            menuItem.className = 'menuitem'
354
355
            icon = document.createElement('span')
356
            icon.className = 'icon-checkmark'
357
            menuItem.appendChild(icon)
358
359
            text = document.createElement('p')
360
            text.innerHTML = _.escape(whatsNewTextItem)
0 ignored issues
show
Bug introduced by
The variable _ seems to be never declared. If this is a global, consider adding a /** global: _ */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
361
            menuItem.appendChild(text)
362
363
            item.appendChild(menuItem)
364
            list.appendChild(item)
365
        }
366
367
        // Changelog URL
368
        if (!_.isUndefined(data['changelogURL'])) {
369
            item = document.createElement('li')
370
371
            menuItem = document.createElement('a')
372
            menuItem.href = data['changelogURL']
373
            menuItem.rel = 'noreferrer noopener'
374
            menuItem.target = '_blank'
375
376
            icon = document.createElement('span')
377
            icon.className = 'icon-link'
378
            menuItem.appendChild(icon)
379
380
            text = document.createElement('span')
381
            text.innerText = t('core', 'View changelog')
382
            menuItem.appendChild(text)
383
384
            item.appendChild(menuItem)
385
            list.appendChild(item)
386
        }
387
388
        div.appendChild(list)
389
        document.body.appendChild(div)
390
    }
391
};
392
393
OCA.Analytics.Backend = {
394
395
    getData: function () {
396
        OCA.Analytics.UI.resetContent();
397
        document.getElementById('analytics-intro').classList.add('hidden');
398
        document.getElementById('analytics-content').removeAttribute('hidden');
399
400
        let url;
401
        if (document.getElementById('sharingToken').value === '') {
402
            const datasetId = document.querySelector('#navigationDatasets .active').dataset.id;
403
            url = OC.generateUrl('apps/analytics/data/') + datasetId;
404
        } else {
405
            const token = document.getElementById('sharingToken').value;
406
            url = OC.generateUrl('apps/analytics/data/public/') + token;
407
        }
408
409
        let filterOptions = [];
410
        if (document.getElementById('sharingToken').value === '') {
411
            filterOptions = JSON.parse(document.getElementById('filterOptions').value);
412
        }
413
414
        $.ajax({
415
            type: 'GET',
416
            url: url,
417
            data: {
418
                'options': filterOptions,
419
            },
420
            success: function (data) {
421
                document.getElementById('reportHeader').innerText = data.options.name;
422
423
                if (data.options.subheader !== '') {
424
                    document.getElementById('reportSubHeader').innerText = data.options.subheader;
425
                    document.getElementById('reportSubHeader').style.removeProperty('display');
426
                }
427
                if (parseInt(data.options.type) === OCA.Analytics.TYPE_INTERNAL_DB && document.getElementById('sharingToken').value === '') {
428
                    document.getElementById('filterDimensions').value = JSON.stringify(data.dimensions);
429
                    document.getElementById('filterContainer').style.removeProperty('display');
430
                }
431
                document.title = data.options.name + ' @ ' + OCA.Analytics.initialDocumentTitle;
432
                if (data.status !== 'nodata') {
433
                    let visualization = data.options.visualization;
434
                    if (visualization === 'chart') {
435
                        OCA.Analytics.UI.buildChart(data);
436
                    } else if (visualization === 'table') {
437
                        OCA.Analytics.UI.buildDataTable(data);
438
                    } else {
439
                        OCA.Analytics.UI.buildChart(data);
440
                        OCA.Analytics.UI.buildDataTable(data);
441
                    }
442
                } else {
443
                    document.getElementById('noDataContainer').style.removeProperty('display');
444
                }
445
            }
446
        });
447
    },
448
449
    getDatasets: function (datasetId) {
450
        $.ajax({
451
            type: 'GET',
452
            url: OC.generateUrl('apps/analytics/dataset'),
453
            success: function (data) {
454
                OCA.Analytics.Navigation.buildNavigation(data);
455
                OCA.Analytics.Sidebar.Dataset.fillSidebarParentDropdown(data);
456
                if (datasetId) {
457
                    OCA.Analytics.Sidebar.hideSidebar();
458
                    document.querySelector('#navigationDatasets [data-id="' + datasetId + '"]').click();
459
                }
460
            }
461
        });
462
    },
463
464
    createDataset: function (file = '') {
465
        $.ajax({
466
            type: 'POST',
467
            url: OC.generateUrl('apps/analytics/dataset'),
468
            data: {
469
                'file': file,
470
            },
471
            success: function (data) {
472
                OCA.Analytics.Navigation.init(data);
473
            }
474
        });
475
    },
476
477
    whatsnew: function (options) {
478
        options = options || {}
479
        $.ajax({
480
            type: 'GET',
481
            url: OC.generateUrl('apps/analytics/whatsnew'),
482
            data: {'format': 'json'},
483
            success: options.success || function (data, statusText, xhr) {
484
                OCA.Analytics.UI.whatsNewSuccess(data, statusText, xhr)
485
            },
486
        });
487
    },
488
489
    whatsnewDismiss: function dismiss(version) {
490
        $.ajax({
491
            type: 'POST',
492
            url: OC.generateUrl('apps/analytics/whatsnew'),
493
            data: {version: encodeURIComponent(version)}
494
        })
495
        $('.whatsNewPopover').remove()
496
    }
497
};
498
499
document.addEventListener('DOMContentLoaded', function () {
500
    OCA.Analytics.initialDocumentTitle = document.title;
501
    document.getElementById('analytics-warning').classList.add('hidden');
502
503
    if (document.getElementById('sharingToken').value === '') {
504
        OCA.Analytics.Backend.whatsnew();
505
        document.getElementById('analytics-intro').attributes.removeNamedItem('hidden');
506
        OCA.Analytics.Core.initApplication();
507
        document.getElementById('newDatasetButton').addEventListener('click', OCA.Analytics.Navigation.handleNewDatasetButton);
508
        if (document.getElementById('advanced').value === 'false') {
509
            document.getElementById('createDemoReport').addEventListener('click', OCA.Analytics.Navigation.createDemoReport);
510
            document.getElementById('addFilterIcon').addEventListener('click', OCA.Analytics.Filter.openFilterDialog);
511
            document.getElementById('drilldownIcon').addEventListener('click', OCA.Analytics.Filter.openDrilldownDialog);
512
            document.getElementById('filterOptions').value = JSON.stringify({
513
                'drilldown': {},
514
                'filter': {'dimension1': {}, 'dimension2': {}}
515
            })
516
        }
517
    } else {
518
        OCA.Analytics.Backend.getData();
519
    }
520
521
    $('#myInput').on('keyup', function () {
522
        table.search(this.value).draw();
523
    });
524
525
    window.addEventListener("beforeprint", function (event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
526
        document.getElementById('chartContainer').style.height = document.getElementById('myChart').style.height;
527
    });
528
529
    // document.getElementById('filterOptions').value = JSON.stringify({
530
    //     'drilldown': {'dimension1': 'true', 'dimension2': 'true'},
531
    //     'filter': {
532
    //         'dimension1': {'enabled': 'true', 'option': 'EQ', 'value': 'Verwaltungsbeirat'},
533
    //         'dimension2': {'enabled': 'true', 'option': 'GT', 'value': '2011'},
534
    //     },
535
    // });
536
537
});
538