Skip to content

Commit 9c9867d

Browse files
authored
Merge pull request #2 from Countly/next
Next 23 Jan
2 parents 0541f27 + edc4d02 commit 9c9867d

File tree

8 files changed

+243
-33
lines changed

8 files changed

+243
-33
lines changed

api/parts/data/fetch.js

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,16 @@ fetch.fetchTop = function(params) {
503503
const metrics = fetch.metricToCollection(params.qstring.metric);
504504
if (metrics[0]) {
505505
fetchTimeObj(metrics[0], params, false, function(data) {
506-
var model = metrics[2] || countlyModel.load(metrics[0]);
506+
var model;
507+
if (metrics[2] && typeof metrics[2] === "object") {
508+
model = metrics[2];
509+
}
510+
else if (typeof metrics[2] === "string" && metrics[2].length) {
511+
model = countlyModel.load(metrics[2]);
512+
}
513+
else {
514+
model = countlyModel.load(metrics[0]);
515+
}
507516
countlyCommon.setTimezone(params.appTimezone);
508517
model.setDb(data || {});
509518
common.returnOutput(params, model.getBars(metrics[1] || metrics[0]));
@@ -529,7 +538,16 @@ fetch.fetchTop = function(params) {
529538
var metrics = fetch.metricToCollection(metric);
530539
if (metrics[0]) {
531540
fetchTimeObj(metrics[0], params, false, function(db) {
532-
var model = metrics[2] || countlyModel.load(metrics[0]);
541+
var model;
542+
if (metrics[2] && typeof metrics[2] === "object") {
543+
model = metrics[2];
544+
}
545+
else if (typeof metrics[2] === "string" && metrics[2].length) {
546+
model = countlyModel.load(metrics[2]);
547+
}
548+
else {
549+
model = countlyModel.load(metrics[0]);
550+
}
533551
countlyCommon.setTimezone(params.appTimezone);
534552
model.setDb(db || {});
535553
data[metric] = model.getBars(metrics[1] || metrics[0]);
@@ -848,7 +866,9 @@ fetch.metricToCollection = function(metric) {
848866
case 'cities':
849867
return ["cities", "cities"];
850868
default:
851-
return [metric, null];
869+
var data = {metric: metric, data: [metric, null]};
870+
plugins.dispatch("/metric/collection", data);
871+
return data.data;
852872
}
853873
};
854874

@@ -1120,10 +1140,15 @@ fetch.getTotalUsersObjWithOptions = function(metric, params, options, callback)
11201140
if (metric === "cities") {
11211141
match.cc = params.app_cc;
11221142
}
1123-
1143+
11241144
if (groupBy === "users") {
11251145
options.db.collection("app_users" + params.app_id).count(match, function(error, appUsersDbResult) {
1126-
callback(appUsersDbResult || 0);
1146+
if (!error && appUsersDbResult) {
1147+
callback([{"_id": "users", "u": appUsersDbResult}]);
1148+
}
1149+
else {
1150+
callback([]);
1151+
}
11271152
});
11281153
}
11291154
else {
@@ -1137,13 +1162,13 @@ fetch.getTotalUsersObjWithOptions = function(metric, params, options, callback)
11371162
}
11381163
}
11391164
], { allowDiskUse: true }, function(error, appUsersDbResult) {
1140-
1165+
11411166
if (plugins.getConfig("api", params.app && params.app.plugins, true).metric_changes && shortcodesForMetrics[metric]) {
1142-
1167+
11431168
var metricChangesMatch = {ts: countlyCommon.getTimestampRangeQuery(params, true)};
1144-
1169+
11451170
metricChangesMatch[shortcodesForMetrics[metric] + ".o"] = { "$exists": true };
1146-
1171+
11471172
/*
11481173
We track changes to metrics such as app version in metric_changesAPPID collection;
11491174
{ "uid" : "2", "ts" : 1462028715, "av" : { "o" : "1:0:1", "n" : "1:1" } }
@@ -1167,13 +1192,13 @@ fetch.getTotalUsersObjWithOptions = function(metric, params, options, callback)
11671192
}
11681193
}
11691194
], { allowDiskUse: true }, function(err, metricChangesDbResult) {
1170-
1195+
11711196
if (metricChangesDbResult) {
11721197
var appUsersDbResultIndex = _.pluck(appUsersDbResult, '_id');
1173-
1198+
11741199
for (let i = 0; i < metricChangesDbResult.length; i++) {
11751200
var itemIndex = appUsersDbResultIndex.indexOf(metricChangesDbResult[i]._id);
1176-
1201+
11771202
if (itemIndex === -1) {
11781203
appUsersDbResult.push(metricChangesDbResult[i]);
11791204
}
@@ -1375,7 +1400,6 @@ function fetchTimeObj(collection, params, isCustomEvent, options, callback) {
13751400
}
13761401
}
13771402
}
1378-
13791403
options.db.collection(collection).find({'_id': {$in: documents}}, {}).toArray(function(err, dataObjects) {
13801404
callback(getMergedObj(dataObjects, false, options.levels));
13811405
});

api/parts/mgmt/app_users.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -985,13 +985,24 @@ usersApi.loyalty = function(params) {
985985
const rangeLabels = ["1", "2", "3-5", "6-9", "10-19", "20-49", "50-99", "100-499", "> 500"];
986986
const ranges = [[1], [2], [3, 5], [6, 9], [10, 19], [20, 49], [40, 99], [100, 499], [500] ];
987987
const collectionName = 'app_users' + params.qstring.app_id;
988+
let query = params.qstring.query || {};
989+
990+
if (typeof query === "string") {
991+
try {
992+
query = JSON.parse(query);
993+
}
994+
catch (error) {
995+
query = {};
996+
}
997+
}
988998

989999
// Time
9901000
const ts = (new Date()).getTime();
9911001
const sevenDays = 7 * 24 * 60 * 60 * 1000;
9921002
const thirtyDays = 30 * 24 * 60 * 60 * 1000;
9931003

9941004
// Aggregations
1005+
const matchQuery = { '$match': query};
9951006
const sevenDaysMatch = { '$match': {ls: { '$gte': (ts - sevenDays) / 1000}}};
9961007
const thirtyDaysMatch = { '$match': {ls: { '$gte': (ts - thirtyDays) / 1000}}};
9971008
const rangeProject = { '$project': { 'range': { '$concat': [] }}};
@@ -1016,11 +1027,10 @@ usersApi.loyalty = function(params) {
10161027
{ '$cond': [{'$eq': ['$_id', label]}, index.toString(), '']}
10171028
));
10181029

1019-
10201030
// Promises
1021-
const allDataPromise = getAggregatedAppUsers(collectionName, [rangeProject, groupBy, indexProject, sort]);
1022-
const sevenDaysPromise = getAggregatedAppUsers(collectionName, [sevenDaysMatch, rangeProject, groupBy, indexProject, sort]);
1023-
const thirtyDaysPromise = getAggregatedAppUsers(collectionName, [thirtyDaysMatch, rangeProject, groupBy, indexProject, sort]);
1031+
const allDataPromise = getAggregatedAppUsers(collectionName, [matchQuery, rangeProject, groupBy, indexProject, sort]);
1032+
const sevenDaysPromise = getAggregatedAppUsers(collectionName, [sevenDaysMatch, matchQuery, rangeProject, groupBy, indexProject, sort]);
1033+
const thirtyDaysPromise = getAggregatedAppUsers(collectionName, [thirtyDaysMatch, matchQuery, rangeProject, groupBy, indexProject, sort]);
10241034

10251035
Promise.all([allDataPromise, sevenDaysPromise, thirtyDaysPromise]).then(promiseResults => {
10261036
common.returnOutput(params, {

frontend/express/public/javascripts/countly/countly.views.js

Lines changed: 163 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global countlyView, countlySession, countlyTotalUsers, countlyCommon, app, CountlyHelpers, countlyGlobal, store, Handlebars, countlyCity, countlyLocation, countlyDevice, countlyDeviceDetails, countlyAppVersion, countlyCarrier, _, countlyEvent, countlyTaskManager, countlyVersionHistoryManager, countlyTokenManager, SessionView, UserView, LoyaltyView, CountriesView, FrequencyView, DeviceView, PlatformView, AppVersionView, CarrierView, ResolutionView, DurationView, ManageAppsView, ManageUsersView, EventsView, DashboardView, EventsBlueprintView, EventsOverviewView, LongTaskView, DownloadView, TokenManagerView, VersionHistoryView, Backbone, pathsToSectionNames, moment, sdks, jstz, getUrls, T, jQuery, $*/
1+
/* global countlyView, countlySession, countlyTotalUsers, countlyCommon, app, CountlyHelpers, countlyGlobal, store, Handlebars, countlyCity, countlyLocation, countlyDevice, countlyDeviceDetails, countlyAppVersion, countlyCarrier, _, countlyEvent, countlyTaskManager, countlyVersionHistoryManager, countlyTokenManager, SessionView, UserView, LoyaltyView, CountriesView, FrequencyView, DeviceView, PlatformView, AppVersionView, CarrierView, ResolutionView, DurationView, ManageAppsView, ManageUsersView, EventsView, DashboardView, EventsBlueprintView, EventsOverviewView, LongTaskView, DownloadView, TokenManagerView, VersionHistoryView, Backbone, pathsToSectionNames, moment, sdks, jstz, getUrls, T, jQuery, $, extendViewWithFilter*/
22
window.SessionView = countlyView.extend({
33
beforeRender: function() {
44
return $.when(countlySession.initialize(), countlyTotalUsers.initialize("users")).then(function() {});
@@ -192,12 +192,14 @@ window.LoyaltyView = countlyView.extend({
192192
},
193193
getLoyaltyData: function() {
194194
var self = this;
195+
var query = this._query;
195196
return $.ajax({
196197
type: "GET",
197198
url: countlyCommon.API_PARTS.data.r + '/app_users/loyalty',
198199
data: {
199200
app_id: countlyCommon.ACTIVE_APP_ID,
200-
api_key: countlyGlobal.member.api_key
201+
api_key: countlyGlobal.member.api_key,
202+
query: JSON.stringify(query)
201203
},
202204
dataType: "json",
203205
success: function(result) {
@@ -292,36 +294,43 @@ window.LoyaltyView = countlyView.extend({
292294
};
293295
},
294296
renderCommon: function(isRefresh) {
295-
var chartData = this.fetchResult();
296-
297297
this.templateData = {
298298
"page-title": jQuery.i18n.map["user-loyalty.title"],
299299
"logo-class": "loyalty",
300300
"chart-helper": "loyalty.chart",
301-
"table-helper": "loyalty.table"
301+
"table-helper": "loyalty.table",
302+
"drill-filter": countlyGlobal.plugins.indexOf('drill') >= 0
302303
};
303304

304305
if (!isRefresh) {
305306
var self = this;
307+
var chartData = this.fetchResult();
306308

307309
$(this.el).html(this.template(this.templateData));
308310
$('#date-selector').hide();
309311

310312
var labelsHtml = $('<div id="label-container"><div class="labels"></div></div>');
311313
var onLabelClick = function() {
314+
chartData = self.fetchResult();
312315
$(this).toggleClass("hidden");
313316
countlyCommon.drawGraph(self.getActiveLabelData(chartData.chartDP), "#dashboard-graph", "bar", { legend: { show: false }});
314317
};
315-
for (var i = 0; i < chartData.chartDP.dp.length; i++) {
316-
var data = chartData.chartDP.dp[i];
317-
var labelDOM = $("<div class='label' style='max-width:250px'><div class='color' style='background-color:" + countlyCommon.GRAPH_COLORS[i] + "'></div><div style='max-width:200px' class='text' title='" + data.label + "'>" + data.label + "</div></div>");
318+
for (var iChart = 0; iChart < chartData.chartDP.dp.length; iChart++) {
319+
var data = chartData.chartDP.dp[iChart];
320+
var labelDOM = $("<div class='label' style='max-width:250px'><div class='color' style='background-color:" + countlyCommon.GRAPH_COLORS[iChart] + "'></div><div style='max-width:200px' class='text' title='" + data.label + "'>" + data.label + "</div></div>");
318321
labelDOM.on('click', onLabelClick.bind(labelDOM, data));
319322
labelsHtml.find('.labels').append(labelDOM);
320323
}
321324

322325
$('.widget-content').css('height', '350px');
323326
$('#dashboard-graph').css("height", "85%");
324327
$('#dashboard-graph').after(labelsHtml);
328+
if (chartData.chartData.length > 0) {
329+
$('#label-container').show();
330+
}
331+
else {
332+
$('#label-container').hide();
333+
}
325334

326335
countlyCommon.drawGraph(this.getActiveLabelData(chartData.chartDP), "#dashboard-graph", "bar", { legend: { show: false }});
327336
this.dtable = $('.d-table').dataTable($.extend({}, $.fn.dataTable.defaults, {
@@ -335,10 +344,145 @@ window.LoyaltyView = countlyView.extend({
335344
}));
336345

337346
$(".d-table").stickyTableHeaders();
347+
348+
this.byDisabled = true;
349+
if (typeof extendViewWithFilter === "function") {
350+
extendViewWithFilter(this);
351+
this.initDrill();
352+
353+
setTimeout(function() {
354+
self.filterBlockClone = $("#filter-view").clone(true);
355+
if (self._query) {
356+
if ($(".filter-view-container").is(":visible")) {
357+
$("#filter-view").hide();
358+
$(".filter-view-container").hide();
359+
}
360+
else {
361+
$("#filter-view").show();
362+
$(".filter-view-container").show();
363+
self.adjustFilters();
364+
}
365+
366+
$(".flot-text").hide().show(0);
367+
var filter = self._query;
368+
var inputs = [];
369+
var subs = {};
370+
for (var i in filter) {
371+
inputs.push(i);
372+
subs[i] = [];
373+
for (var j in filter[i]) {
374+
if (filter[i][j].length) {
375+
for (var k = 0; k < filter[i][j].length; k++) {
376+
subs[i].push([j, filter[i][j][k]]);
377+
}
378+
}
379+
else {
380+
subs[i].push([j, filter[i][j]]);
381+
}
382+
}
383+
}
384+
self.setInput(inputs, subs, 0, 0, 1);
385+
}
386+
}, 500);
387+
}
338388
}
339389
},
390+
setInput: function(inputs, subs, cur, sub, total) {
391+
var self = this;
392+
sub = sub || 0;
393+
if (inputs[cur]) {
394+
var filterType = subs[inputs[cur]][sub][0];
395+
396+
if (filterType === "$in") {
397+
filterType = "=";
398+
}
399+
else if (filterType === "$nin") {
400+
filterType = "!=";
401+
}
402+
var val = subs[inputs[cur]][sub][1];
403+
var el = $(".query:nth-child(" + (total) + ")");
404+
$(el).data("query_value", val + ""); //saves value as attribute for selected query
405+
el.find(".filter-name").trigger("click");
406+
el.find(".filter-type").trigger("click");
407+
408+
409+
if (inputs[cur].indexOf("chr.") === 0) {
410+
el.find(".filter-name").find(".select-items .item[data-value='chr']").trigger("click");
411+
if (val === "t") {
412+
el.find(".filter-type").find(".select-items .item[data-value='=']").trigger("click");
413+
}
414+
else {
415+
el.find(".filter-type").find(".select-items .item[data-value='!=']").trigger("click");
416+
}
417+
val = inputs[cur].split(".")[1];
418+
subs[inputs[cur]] = ["true"];
419+
}
420+
else if (inputs[cur] === "did" || inputs[cur] === "chr" || inputs[cur].indexOf(".") > -1) {
421+
el.find(".filter-name").find(".select-items .item[data-value='" + inputs[cur] + "']").trigger("click");
422+
}
423+
else {
424+
el.find(".filter-name").find(".select-items .item[data-value='up." + inputs[cur] + "']").trigger("click");
425+
}
426+
427+
el.find(".filter-type").find(".select-items .item[data-value='" + filterType + "']").trigger("click");
428+
setTimeout(function() {
429+
el.find(".filter-value").not(".hidden").trigger("click");
430+
if (el.find(".filter-value").not(".hidden").find(".select-items .item[data-value='" + val + "']").length) {
431+
el.find(".filter-value").not(".hidden").find(".select-items .item[data-value='" + val + "']").trigger("click");
432+
}
433+
else if (_.isNumber(val) && (val + "").length === 10) {
434+
el.find(".filter-value.date").find("input").val(countlyCommon.formatDate(moment(val * 1000), "DD MMMM, YYYY"));
435+
el.find(".filter-value.date").find("input").data("timestamp", val);
436+
}
437+
else {
438+
el.find(".filter-value").not(".hidden").find("input").val(val);
439+
}
440+
441+
if (subs[inputs[cur]].length === sub + 1) {
442+
cur++;
443+
sub = 0;
444+
}
445+
else {
446+
sub++;
447+
}
448+
total++;
449+
if (inputs[cur]) {
450+
$("#filter-add-container").trigger("click");
451+
if (sub > 0) {
452+
setTimeout(function() {
453+
var elChild = $(".query:nth-child(" + (total) + ")");
454+
elChild.find(".and-or").find(".select-items .item[data-value='OR']").trigger("click");
455+
self.setInput(inputs, subs, cur, sub, total);
456+
}, 500);
457+
}
458+
else {
459+
self.setInput(inputs, subs, cur, sub, total);
460+
}
461+
}
462+
else {
463+
setTimeout(function() {
464+
$("#apply-filter").removeClass("disabled");
465+
$("#no-filter").hide();
466+
var filterData = self.getFilterObjAndByVal();
467+
$("#current-filter").show().find(".text").text(filterData.bookmarkText);
468+
$("#connector-container").show();
469+
}, 500);
470+
}
471+
}, 500);
472+
}
473+
},
474+
loadAndRefresh: function() {
475+
var filter = {};
476+
for (var i in this.filterObj) {
477+
filter[i.replace("up.", "")] = this.filterObj[i];
478+
}
479+
this._query = filter;
480+
app.navigate("/analytics/loyalty/" + JSON.stringify(filter), false);
481+
this.refresh();
482+
},
340483
refresh: function() {
341484
var self = this;
485+
342486
$.when(this.getLoyaltyData()).then(function() {
343487
if (app.activeView !== self) {
344488
return false;
@@ -347,6 +491,12 @@ window.LoyaltyView = countlyView.extend({
347491
var chartData = self.fetchResult();
348492
countlyCommon.drawGraph(self.getActiveLabelData(chartData.chartDP), "#dashboard-graph", "bar", { legend: { show: false }});
349493
CountlyHelpers.refreshTable(self.dtable, chartData.chartData);
494+
if (chartData.chartData.length > 0) {
495+
$('#label-container').show();
496+
}
497+
else {
498+
$('#label-container').hide();
499+
}
350500
});
351501
},
352502
getActiveLabelData: function(data) {
@@ -5590,6 +5740,11 @@ app.route("/analytics/users", "users", function() {
55905740
this.renderWhenReady(this.userView);
55915741
});
55925742
app.route("/analytics/loyalty", "loyalty", function() {
5743+
this.loyaltyView._query = undefined;
5744+
this.renderWhenReady(this.loyaltyView);
5745+
});
5746+
app.route("/analytics/loyalty/*query", "loyalty_query", function(query) {
5747+
this.loyaltyView._query = query && CountlyHelpers.isJSON(query) ? JSON.parse(query) : undefined;
55935748
this.renderWhenReady(this.loyaltyView);
55945749
});
55955750
app.route("/analytics/countries", "countries", function() {

0 commit comments

Comments
 (0)