UNPKG

29.3 kBJavaScriptView Raw
1/**
2 * joola.io
3 *
4 * Copyright Joola Smart Solutions, Ltd. <info@joo.la>
5 *
6 * Licensed under GNU General Public License 3.0 or later.
7 * Some rights reserved. See LICENSE, AUTHORS.
8 *
9 * @license GPL-3.0+ <http://spdx.org/licenses/GPL-3.0+>
10 */
11
12var
13 router = require('./index'),
14
15 pushU = global.pushU, //for jslint
16 _datasources = require('../lib/objects/datasources'),
17 _datatables = require('../lib/objects/datatables'),
18 _dimensions = require('../lib/objects/dimensions'),
19 _metrics = require('../lib/objects/metrics'),
20 manager = require('../lib/caching/manager'),
21 utils = require('../lib/shared/utils'),
22
23 moment = require('moment'),
24 twix = require('twix');
25
26exports = index = exports.fetch = {
27 name: 'query/fetch',
28 description: 'I will execute a query',
29 inputs: {
30 "required": ['startdate', 'enddate', 'metrics'],
31 "optional": ['dimensions', 'resolution', 'filter', 'sortKey', 'sortDir']
32 },
33 outputExample: {},
34 permission: ['access_system'],
35 run: function (req, res) {
36 var sendResponse = function (merged, dimensions, metrics) {
37 var response = {};
38 response.resultcode = 200;
39 response.resulttext = '';
40 response.results = {};
41 response.results.dimensions = dimensions;
42
43 _.each(metrics, function (m) {
44 if (m.formatter)
45 m.formatter = stringify(m.formatter, 'Function');
46 });
47
48 response.results.metrics = metrics;
49 response.results.rows = merged;
50
51 var runTime = (new Date().getTime() - _timer_start.getTime());
52 joola.logger.info('Action: Query, Finished at ' + runTime.toString() + 'ms.');
53 joola.logger.log('info', 'Query Finished', {level: 103, querytime: runTime});
54
55 return router.responseSuccess(response, req, res);
56 };
57
58 if (!req.user)
59 return router.responseError(new router.AuthErrorTemplate('Missing user token'), req, res);
60
61 try {
62 var _timer_start = new Date();
63 var startdate = new Date(req.params.startdate).fixDate(true, true);
64 //onsole.log(new Date(connection.params.enddate));
65 var enddate = new Date(req.params.enddate).fixDate(true, true);
66
67 var dt = _datatables.get(joola.config.integration.datasources[0].enddate.maxcachedate);
68 dt.datasource = _datasources.get(dt.datasourceid);
69 manager.maxCacheDate(dt, function (maxDate) {
70
71 if (maxDate == null)
72 maxDate = new Date();
73 if (maxDate < enddate)
74 enddate = new Date(maxDate).fixDate(true, true);
75
76 enddate.setMilliseconds(enddate.getMilliseconds() - 1);
77
78 var dimensions = [];
79 var dimensionsForCount = [];
80 var metrics = [];
81 var metricsForCount = [];
82 var metricsToRemove = [];
83
84 var splits = req.params.dimensions.split(',');
85 _.each(splits, function (dimension) {
86 dimension = dimension.trim().toLowerCase();
87 var _dimension = _dimensions.get(dimension);
88
89 if (_dimension) {
90 if (_dimension.class == 'calculated') {
91 _dimension._name = _dimension.name;
92 _dimension.name = _dimension.column;
93 }
94
95 dimensions.push(_dimension);
96
97 //else
98 // joola.logger.warn('User [' + connection.session.user.roles + '] does not have permission for dimension [' + _dimension.name + '].')
99 }
100 });
101
102 splits = req.params.metrics.split(',');
103 _.each(splits, function (metric) {
104 metric = metric.trim().toLowerCase();
105 var _metric;
106 if (metric.class == 'calculated') {
107 _metric = _metrics.get(metric);
108 }
109 else {
110 _metric = _metrics.get(metric);
111 }
112 if (_metric) {
113 if (_metric.class != 'calculated')
114 pushU(metrics, _metric);
115 else
116 pushU(metricsForCount, _metric);
117
118 if (_metric.aggregation == 'count') {
119 exist = _.find(dimensions, function (d) {
120 return d.id == _metric.dimension;
121 });
122 if (!exist) {
123 _dimension = _dimensions.get(_metric.dimension);
124 //dimensions.push(_dimension);
125 dimensionsForCount.push(_dimension);
126 }
127 }
128 else {
129 if (_metric.formula) {
130 var m_left = _metrics.get(_metric.formula.left);
131 var m_right = _metrics.get(_metric.formula.right);
132
133
134 var exist, _dimension;
135
136 if (m_left.aggregation == 'count') {
137 exist = _.find(dimensions, function (d) {
138 return d.id == m_left.dimension;
139 });
140 if (!exist) {
141 _dimension = _dimensions.get(m_left.dimension);
142 //dimensions.push(_dimension);
143 dimensionsForCount.push(_dimension);
144 }
145 }
146
147 if (m_left.class != 'calculated') {
148 if (!_.find(metrics, function (m) {
149 return m.id == m_left.id;
150 })) {
151 m_left.remove = true;
152 pushU(metrics, m_left);
153 pushU(metricsToRemove, m_left);
154 }
155 }
156 else if (!_.find(metricsForCount, function (m) {
157 return m.id == m_left.id;
158 })) {
159 m_left.remove = true;
160 pushU(metricsForCount, m_left);
161 pushU(metricsToRemove, m_left);
162 }
163
164
165 if (m_right.aggregation == 'count') {
166 exist = _.find(dimensions, function (d) {
167 return d.id == m_right.dimension;
168 });
169 if (!exist) {
170 _dimension = _dimensions.get(m_right.dimension);
171 //dimensions.push(_dimension);
172 dimensionsForCount.push(_dimension);
173 }
174 }
175
176 if (m_right.class != 'calculated') {
177 if (!_.find(metrics, function (m) {
178 return m.id == m_right.id;
179 })) {
180 m_right.remove = true;
181 pushU(metrics, m_right);
182 pushU(metricsToRemove, m_right);
183 }
184 }
185 else if (!_.find(metricsForCount, function (m) {
186 return m.id == m_right.id;
187 })) {
188 m_right.remove = true;
189 pushU(metricsForCount, m_right);
190 pushU(metricsToRemove, m_right);
191 }
192
193 }
194 }
195 //else
196 // joola.logger.warn('User [' + connection.session.user.roles + '] does not have permission for metric [' + _metric.name + '].')
197 }
198 });
199
200 var merged = [];
201 var query = {};
202 query.startdate = startdate;
203 query.enddate = enddate;
204 //query.datatable = _datatables.get(metrics[0].datatableid);
205 query.dimensions = dimensions;
206 query.metrics = metrics;
207 query.filtertext = req.params.filter || '' + (req.user._filter ? req.user._filter : '').toString();
208 query.filters = [];
209
210 console.log(req.params);
211
212 //console.log(query);
213 console.log(query.filtertext);
214
215 var filterSplit = query.filtertext.split('[AND]');
216 _.each(filterSplit, function (filter) {
217 var _filter = {};
218 var parts = filter.split('*=');
219 if (parts.length > 1) {
220 _filter.dimension = parts[0].trim();
221 _filter.operator = '*=';
222 _filter.value = parts[1].trim();
223 }
224 else {
225 parts = filter.split('=');
226 if (parts.length > 0) {
227 _filter.dimension = parts[0];
228 _filter.operator = '=';
229 _filter.value = parts[1]
230 }
231 }
232 if (_filter.dimension) {
233 var _fdimension = _dimensions.getByName(_filter.dimension);
234 if (!_fdimension)
235 _fdimension = _dimensions.get(_filter.dimension);
236 if (_fdimension) {
237 _filter.dimension = _fdimension;
238 query.filters.push(_filter);
239 }
240 }
241 });
242
243 query.sortKey = req.params.sortKey;
244 if (query.sortKey) {
245 query.sortDir = req.params.sortDir;
246
247 var _sortKey = _dimensions.get(query.sortKey);
248 if (_sortKey) {
249 if (_sortKey.class == 'calculated') {
250 _sortKey.name = _sortKey.column;
251 query.sortKey = _sortKey;
252 }
253 else
254 query.sortKey = _sortKey;
255 }
256 else {
257 _sortKey = _metrics.get(query.sortKey);
258 if (_sortKey) {
259 query.sortKey = _sortKey;
260 }
261 else
262 query.sortKey = null;
263 }
264 }
265
266 query.resolution = req.params.resolution.toLowerCase() || 'day';
267
268 //if (!cachedResult) {
269 joola.logger.debug('Compiling list of participating collections...');
270 var collections = [];
271 _.each(query.metrics, function (m) {
272
273 if (m.class != 'calculated') {
274 if (!m.datatable)
275 m.datatable = _datatables.get(m.datatableid);
276 var _table = _.find(collections, function (c) {
277 if (c)
278 return c.id == m.datatable.id;
279 else
280 return false;
281 });
282 if (!_table) {
283
284 pushU(collections, ce.clone(m.datatable));
285 }
286 }
287 else {
288
289 }
290 });
291
292 var calls = [];
293 var subcalls = [];
294 var results = [];
295
296 _.each(collections, function (c) {
297 var call = function (query, c, callback) {
298 query.datatable = ce.clone(c);
299 var dlist = '';
300 var mlist = '';
301 _.each(query.dimensions, function (_d) {
302 dlist += _d.id;
303 });
304
305 _.each(query.metrics, function (_m) {
306 mlist += _m.id;
307 });
308
309 var sortKey = (sortKey ? sortKey.name : '');
310 var resultKey = c.id + '_' + query.startdate + '_' + query.enddate + '_' + dlist + '_' + mlist + '_' + query.resolution + '_' + query.filtertext + '_' + sortKey + '_' + query.sortDir;
311 resultKey = resultKey.clean();
312 var cachedResult = false;
313
314 joola.cache.load('results', resultKey, function (err, value) {
315 if (err) {
316 cachedResult = false;
317 }
318 else {
319 joola.logger.info('Found cached result [' + resultKey + ']');
320 //cachedResult = true;
321 }
322 if (!cachedResult) {
323 manager.fetch(ce.clone(query), function (err, result, query) {
324 joola.logger.debug('Received result from fetch [' + query.datatable.id + '], rows: ' + result.length);
325 results.push({source: query.datatable.id, data: result});
326 joola.cache.save('results', resultKey, result, function (err) {
327 callback(err);
328 });
329 });
330 }
331 else {
332 results.push({source: query.datatable.id, data: value});
333 callback();
334 }
335 });
336
337 };
338 calls.push(function (callback) {
339 call(ce.clone(query), ce.clone(c), function () {
340 callback();
341 });
342 });
343 });
344
345 var calcMetrics = _.find(metricsForCount, function (m) {
346 return m.class == 'calculated' && m.aggregation == 'count';
347 });
348
349 if (calcMetrics && !Array.isArray(calcMetrics)) {
350 var _calcMetrics = [];
351 _calcMetrics.push(calcMetrics);
352 calcMetrics = _calcMetrics;
353 }
354
355 _.each(calcMetrics, function (m) {
356 var call = function (query, callback) {
357 var _query = ce.clone(query);
358
359 _query.type = 'distinct';
360 var _metric = _metrics.get(m.metric);
361 _metric.datatable = _datatables.get(_metric.datatableid);
362 _query.datatable = ce.clone(_metric.datatable);
363 _query.distinctCount = true;
364
365 var _dimension = _dimensions.get(m.dimension);
366 _query.dimensions.push(_dimension);
367 _query.metrics = [m];
368
369 _query.sortKey = query.sortKey;
370 _query.sortDir = query.sortDir;
371
372 var dlist = '';
373 var mlist = '';
374 _.each(_query.dimensions, function (_d) {
375 dlist += _d.id;
376 });
377
378 _.each(_query.metrics, function (_m) {
379 mlist += _m.id;
380 });
381
382 var sortKey = (sortKey ? sortKey.name : '');
383 var resultKey = 'distinct_' + m.name + '_' + _query.startdate + '_' + _query.enddate + '_' + dlist + '_' + mlist + '_' + _query.resolution + '_' + _query.filtertext + '_' + sortKey + '_' + _query.sortDir;
384 resultKey = resultKey.clean();
385 var cachedResult = false;
386 joola.cache.load('results', resultKey, function (err, value) {
387 if (err) {
388 cachedResult = false;
389 }
390 else {
391 joola.logger.info('Found cached result [' + resultKey + ']');
392 //cachedResult = true;
393 }
394 if (!cachedResult) {
395 manager.fetch(ce.clone(_query), function (err, result, query) {
396 joola.logger.debug('Received result from fetch [' + query.datatable.id + '], rows: ' + result.length);
397 results.push({source: 'distinct_' + m.name, data: result, dimension: _dimension});
398
399
400 joola.cache.save('results', resultKey, result, function (err) {
401 callback(err);
402 });
403 });
404 }
405 else {
406 results.push({source: 'distinct_' + m.name, data: value, dimension: _dimension});
407 callback();
408 }
409 });
410 };
411 subcalls.push(function (callback) {
412 call(ce.clone(query), callback);
413 });
414 });
415
416 /*
417 if (calls.length == 0) {
418 calls.push(function (callback) {
419 callback();
420 })
421 }*/
422
423 joola.logger.info('Fetching collections [' + calls.length + ',' + subcalls.length + ']...');
424 fork(calls, function () {
425 fork(subcalls, function () {
426 try {
427 calls = [];
428 _.each(results, function (result) {
429 if (result.data) {
430
431
432 var exist = _.find(dimensions, function (d) {
433 return d.id == 'date.date'
434 });
435 if (exist) {
436 var dt = _datatables.get(result.source);
437 var itr = moment.twix(query.startdate, query.enddate).iterate("days");
438 while (itr.hasNext()) {
439 var _d = itr.next();
440 const formattedDate = _d.format('YYYY-M-D');
441
442 var dateExist = _.find(result.data, function (document) {
443 //noinspection JSReferencingMutableVariableFromClosure
444 return new Date(document._id['date']).format('yyyy-m-d') == formattedDate;
445 });
446
447 if (!dateExist && _d._d.fixDate(true, true) < query.enddate) {
448 var structure = {_id: {}};
449 var document = result.data[0];
450 _.each(document, function (value, key) {
451 if (typeof(value) == 'object') {
452 structure[key] = {};
453 _.each(value, function (_value, _key) {
454 structure[key][_key] = 0;
455 });
456 }
457 else
458 structure[key] = 0;
459 });
460
461 structure._id['date'] = _d._d;
462
463 if (dt) {
464 var ds = _datasources.get(dt.datasourceid);
465 if (ds.GMTbased)
466 structure._id['date'] = structure._id['date'].fixDate(true, false, true);//.format('yyyy-mm-ddThh:nn:ss.fffZ');
467 else
468 structure._id['date'] = structure._id['date'].fixDate(true, false, false).format('yyyy-mm-ddThh:nn:ss.fffZ');
469 }
470 else
471 structure._id['date'] = structure._id['date'].fixDate(true, false, false).format('yyyy-mm-ddThh:nn:ss.fffZ');
472 result.data.push(structure);
473 }
474 }
475
476 result.data = _.sortBy(result.data, function (document) {
477 return new Date(document._id['date']);
478 });
479 }
480
481 var call = function (result, callback) {
482 //var pool = new functionpool.Pool({size: 20}, function (callback) {
483 var dt = _datatables.get(result.source);
484
485 if (!dt && result.source.indexOf('distinct_') > -1) {
486 var d = result.dimension;
487 _.each(result.data, function (document) {
488 delete document._id[d.name];
489 });
490 }
491 var allmetrics = _metrics.list();
492
493 var filteredDimensions = _.filter(dimensions, function (d) {
494 return d.class == 'calculated';
495 });
496
497 _.each(query.filters, function (f) {
498 if (f.dimension.type == 'date') {
499 var _d = f.dimension;
500 _d._name = _d.name;
501 filteredDimensions.push(_d);
502
503 }
504 });
505
506 _.each(result.data, function (document) {
507 var id = document._id;
508
509 var distinctKeys = {};
510 var rowcount = document.rowcount;
511
512 _.each(metricsForCount, function (metric) {
513 if (metric.formula)
514 document[metric.name] = -1;
515 });
516
517 if (document._id.date)
518 document._id.date = new Date(document._id.date);
519
520 _.each(filteredDimensions, function (d) {
521 if (d.class == 'calculated') {
522 if (d.type == 'date') {
523 switch (d._name.toLowerCase()) {
524 case 'date':
525 document._id[d._name] = document._id.date;
526 break;
527 case 'day of week':
528 document._id[d._name] = document._id.date.dayName();
529 break;
530 case 'month':
531 document._id[d._name] = utils.formatDate(document._id.date, 'mmm yyyy');
532 break;
533 case 'quarter':
534 document._id[d._name] = 'Q' + document._id.date.quarterNumber();
535 break;
536 case 'week':
537 document._id[d._name] = 'Week ' + document._id.date.weekNumber()[1];
538 break;
539 case 'year':
540 document._id[d._name] = utils.formatDate(document._id.date, 'yyyy');
541 break;
542 default:
543 document._id[d._name] = document.date;
544 break;
545 }
546 }
547 }
548 });
549 delete document._id.date;
550
551 var exist = _.find(merged, function (merge) {
552 return JSON.stringify(merge.dimensions) == JSON.stringify(id);
553 });
554 if (exist) {
555 row = exist;
556 }
557 else {
558 joola.logger.silly('Pushing new dimensions key to merged collection [' + id + '].');
559 var row = {};
560 row.dimensions = id;
561 _.each(document._id, function (value, key) {
562 row[key] = value;
563 });
564 merged.push(row);
565 }
566
567 _.each(document, function (value, key) {
568 var exist;
569 if (dt) {
570 exist = _.find(dt.metrics, function (metric) {
571 return metric.name == key || '_' + metric.name == key;
572 });
573 }
574
575 if (!exist) {
576 exist = _.find(metricsForCount, function (metric) {
577 return metric.name == key || '_' + metric.name == key;
578 });
579 }
580
581 if (exist) {
582 if (exist.aggregation == 'min') {
583 if (parseFloat(value) < parseFloat(row[key])) {
584 row[key] = value;
585 }
586 }
587 else if (exist.aggregation == 'max') {
588 if (parseFloat(value) > parseFloat(row[key])) {
589 row[key] = value;
590 }
591 }
592 else if (exist.aggregation == 'avg') {
593 value = (parseFloat(value) / parseFloat(rowcount)).toFixed(2);
594 row[key] = parseFloat(value);
595 }
596
597 else if (exist.aggregation == 'count') {
598 row[key] = value;
599 }
600 else if (exist.aggregation == 'sum') {
601 row[key] = value;
602 }
603 else
604 row[key] = value;
605 }
606 else {
607 if (!row[key]) {
608 exist = _.find(allmetrics, function (metric) {
609 return metric.name == key || '_' + metric.name == key;
610 });
611 if (exist)
612 row[key] = 0;
613 }
614 }
615 });
616
617 });
618 callback();
619 };
620
621 calls.push(function (callback) {
622 call(result, callback);
623 });
624 }
625 }
626 );
627
628 fork(calls, function () {
629 try {
630 _.each(metricsForCount, function (m) {
631 if (m.formula) {
632 var left = _metrics.get(m.formula.left);
633 var right = _metrics.get(m.formula.right);
634
635 _.each(merged, function (row) {
636 var left_value = parseFloat(row[left.name]);
637 var right_value = parseFloat(row[right.name]);
638
639 var value = eval(left_value + m.formula.function + right_value + (m.formula.post ? m.formula.post : ''));
640 row[m.name] = parseFloat(value.toFixed(2));
641 if (m.remove && m.remove == true) {
642 //delete row[m.name];
643 }
644
645 _.each(metrics, function (_m) {
646 if (_m.remove == true && (_m.id == left.id || _m.id == right.id)) {
647 }
648 //delete row[_m.name];
649 });
650 _.each(metricsForCount, function (_m) {
651 if (_m.remove == true && (_m.id == left.id || _m.id == right.id)) {
652 }
653 // delete row[_m.name];
654 });
655 });
656 }
657 });
658
659 _.each(dimensions, function (d) {
660 if (d._name) {
661 d.name = d._name;
662 delete d._name;
663 }
664 });
665
666 if (calcMetrics && calcMetrics.length > 0)
667 metrics = metrics.concat(calcMetrics);
668 if (metricsForCount && metricsForCount.length > 0)
669 metrics = metrics.concat(metricsForCount);
670
671 var __metrics = [];
672 _.each(metrics, function (m) {
673 if (m.remove != true) {
674 pushU(__metrics, m);
675 }
676 });
677 metrics = __metrics;
678
679 //organize keys
680 var _merged = [];
681 var _out_dimensions = [];
682 var _out_metrics = [];
683 splits = req.params.dimensions.split(',');
684
685 _.each(splits, function (d) {
686 d = _dimensions.get(d.trim());
687 if (d)
688 _out_dimensions.push(d);
689 });
690 splits = req.params.metrics.split(',');
691 _.each(splits, function (m) {
692 m = _metrics.get(m.trim());
693 if (m)
694 _out_metrics.push(m);
695 });
696
697 _.each(merged, function (row) {
698 var _row = {};
699
700 _.each(_out_dimensions, function (d) {
701 if (row[d.name])
702 _row[d.name] = row[d.name];
703 });
704
705 _.each(_out_metrics, function (m) {
706 if (row[m.name])
707 _row[m.name] = row[m.name];
708 else
709 _row[m.name] = 0;
710 });
711
712 _.each(query.filters, function (filter) {
713 if (filter.dimension.type == 'date') {
714 var bfound = true;
715
716 if (filter.dimension.id == 'date.date') {
717
718 bfound = row[filter.dimension.name].getTime() == new Date(filter.value).getTime();
719 }
720 else if (filter.dimension.type == 'date') {
721
722 bfound = row[filter.dimension.name] == filter.value;
723 }
724 if (!bfound) {
725 _.each(_out_metrics, function (m) {
726 _row[m.name] = 0;
727 });
728 }
729 }
730 });
731
732 _merged.push(_row);
733 });
734 merged = _merged;
735
736 sendResponse(merged, _out_dimensions, _out_metrics);
737 }
738 catch (ex) {
739 joola.logger.error('Query action error (1): ' + ex.message);
740 console.log(ex.stack);
741 }
742 });
743 } catch (ex) {
744 joola.logger.error('Query action error (2): ' + ex.message);
745 console.log(ex.stack);
746 }
747 })
748 });
749 });
750 }
751 catch (ex) {
752 //joola.logger.error('Query action failed (3): ' + ex.message);
753 throw ex;
754 }
755 }
756};
\No newline at end of file