parts/data/stats.js

/**
* This module retrieves some stats from server
* @module "api/parts/data/stats"
*/

/** @lends module:api/parts/data/stats */
var stats = {},
    async = require('async'),
    common = require("../../utils/common.js"),
    { getUserApps } = require('../../utils/rights.js');

var countlyDb;
/**
* Get overal server data
* @param {object} db - database connection
* @param {function} callback - function to call when done
**/
stats.getOverall = function(db, callback) {
    countlyDb = db;
    getTotalUsers(function(totalUsers, totalApps) {
        getTotalEvents(function(totalEvents) {
            getTotalMsgUsers(function(totalMsgUsers) {
                getTotalMsgCreated(function(totalMsgCreated) {
                    getTotalMsgSent(function(totalMsgSent) {
                        callback({
                            "total-users": totalUsers,
                            "total-apps": totalApps,
                            "total-events": totalEvents,
                            "total-msg-users": totalMsgUsers,
                            "total-msg-created": totalMsgCreated,
                            "total-msg-sent": totalMsgSent
                        });
                    });
                });
            });
        });
    });
};

/**
* Get minimal server data
* @param {object} db - database connection
* @param {function} callback - function to call when done
**/
stats.getServer = function(db, callback) {
    countlyDb = db;
    getTotalUsers(function(totalAppUsers, totalApps) {
        getDashboardUsers(function(totalUsers) {
            callback({
                "app_users": totalAppUsers,
                "apps": totalApps,
                "users": totalUsers
            });
        });
    });
};

/**
* Get overal user data
* @param {object} db - database connection
* @param {object} user - members document from db
* @param {function} callback - function to call when done
**/
stats.getUser = function(db, user, callback) {
    countlyDb = db;
    var apps;

    if (!user.global_admin) {
        apps = getUserApps(user) || [];
    }

    getTotalEvents(function(totalEvents) {
        getTotalMsgSent(function(totalMsgSent) {
            getCrashGroups(function(totalCrashgroups) {
                getAllPlatforms(function(platforms) {
                    getTotalUsers(function(userCount) {
                        callback({
                            "total-events": totalEvents,
                            "total-msg-sent": totalMsgSent,
                            "total-crash-groups": totalCrashgroups,
                            "total-platforms": platforms,
                            "total-users": userCount
                        });
                    }, apps);
                }, apps);
            }, apps);
        }, apps);
    }, apps);
};

/**
* Get total users for all apps
* @param {function} callback - function to call when done
* @param {array=} apps - provide array of apps to fetch data for, else will fetch data for all apps
**/
function getTotalUsers(callback, apps) {
    /**
     *  Process app result
     *  @param {Error} err - database error
     *  @param {Array} allApps - array of apps
     */
    function processApps(err, allApps) {
        if (err || !allApps) {
            callback(0, 0);
        }
        else {
            async.map(allApps, getUserCountForApp, function(err2, results) {
                if (err2) {
                    callback(0, 0);
                }

                var userCount = 0;

                for (let i = 0; i < results.length; i++) {
                    userCount += results[i] || 0;
                }

                callback(userCount, allApps.length);
            });
        }
    }
    if (typeof apps !== "undefined") {
        async.map(apps, function(app, done) {
            getUserCountForApp({_id: app}, done);
        }, function(err, results) {
            if (err) {
                callback(0, 0);
            }

            var userCount = 0;

            for (let i = 0; i < results.length; i++) {
                userCount += results[i] || 0;
            }

            callback(userCount, apps.length);
        });
    }
    else {
        if (common.readBatcher) {
            common.readBatcher.getMany("apps", {}, {_id: 1}, processApps);
        }
        else {
            countlyDb.collection("apps").find({}, {_id: 1}).toArray(processApps);
        }
    }
}

/**
* Get total events for all apps
* @param {function} callback - function to call when done
* @param {array=} apps - provide array of apps to fetch data for, else will fetch data for all apps
**/
function getTotalEvents(callback, apps) {
    var query = {};
    if (typeof apps !== "undefined") {
        var inarray = [];
        for (let i = 0; i < apps.length; i++) {
            if (apps[i] && apps[i].length) {
                inarray.push(countlyDb.ObjectID(apps[i]));
            }
        }
        query._id = {$in: inarray};
    }
    countlyDb.collection("events").aggregate([{$match: query}, {$project: {len: {$size: '$list'}}}, {$group: {_id: 'count', len: {$sum: '$len'}}}], function(err, count) {
        callback(count && count[0] && count[0].len || 0);
    });
}

/**
* Get total messaging users for all apps
* @param {function} callback - function to call when done
**/
function getTotalMsgUsers(callback) {
    countlyDb.collection("users").find({_id: {"$regex": ".*:0.*"}}, {"d.m": 1}).toArray(function(err, msgUsers) {
        if (err || !msgUsers) {
            callback(0);
        }
        else {
            var msgUserCount = 0;

            for (let i = 0; i < msgUsers.length; i++) {
                if (msgUsers[i] && msgUsers[i].d && msgUsers[i].d.m) {
                    msgUserCount += msgUsers[i].d.m;
                }
            }

            callback(msgUserCount);
        }
    });
}

/**
* Get total messages for all apps
* @param {function} callback - function to call when done
**/
function getTotalMsgCreated(callback) {
    countlyDb.collection("messages").estimatedDocumentCount(function(err, msgCreated) {
        if (err || !msgCreated) {
            callback(0);
        }
        else {
            callback(msgCreated);
        }
    });
}

/**
* Get total messagess sent for all apps
* @param {function} callback - function to call when done
* @param {array=} apps - provide array of apps to fetch data for, else will fetch data for all apps
**/
function getTotalMsgSent(callback, apps) {
    var query = {};
    if (typeof apps !== "undefined") {
        var inarray = [];
        for (let i = 0; i < apps.length; i++) {
            if (apps[i] && apps[i].length) {
                inarray.push(countlyDb.ObjectID(apps[i]));
            }
        }
        query.apps = {$in: inarray};
    }
    countlyDb.collection("messages").aggregate([{$match: query}, {$group: {_id: 'count', sent: {$sum: '$result.sent'}}}], function(err, count) {
        callback(count && count[0] && count[0].sent || 0);
    });
}

/**
* Get total user count for app
* @param {object} app - app document from db
* @param {function} callback - function to call when done
**/
function getUserCountForApp(app, callback) {
    countlyDb.collection("app_users" + app._id).estimatedDocumentCount(function(err, count) {
        if (err || !count) {
            callback(null, 0);
        }
        else {
            callback(null, count);
        }
    });
}

/**
* Get dashboard user count
* @param {function} callback - function to call when done
**/
function getDashboardUsers(callback) {
    countlyDb.collection("members").estimatedDocumentCount(function(err, count) {
        if (err || !count) {
            callback(0);
        }
        else {
            callback(count);
        }
    });
}

/**
* Get total crash count for app
* @param {object} app - app document from db
* @param {function} callback - function to call when done
**/
function getCrashGroupsForApp(app, callback) {
    countlyDb.collection("app_crashgroups" + app).estimatedDocumentCount(function(err, count) {
        if (err || !count) {
            callback(null, 0);
        }
        else {
            callback(null, count);
        }
    });
}

/**
* Get total unique crashes count for app
* @param {function} callback - function to call when done
* @param {array=} apps - provide array of apps to fetch data for, else will fetch data for all apps
**/
function getCrashGroups(callback, apps) {
    if (typeof apps !== "undefined") {
        async.map(apps, getCrashGroupsForApp, function(err, results) {
            if (err) {
                callback(0, 0);
            }

            var userCount = 0;

            for (let i = 0; i < results.length; i++) {
                userCount += results[i];
            }

            callback(userCount, apps.length);
        });
    }
    else {
        countlyDb.collection("apps").find({}, {_id: 1}).toArray(function(err, allApps) {
            if (err || !allApps) {
                callback(0, 0);
            }
            else {
                async.map(allApps, getCrashGroupsForApp, function(err2, results) {
                    if (err2) {
                        callback(0, 0);
                    }

                    var userCount = 0;

                    for (let i = 0; i < results.length; i++) {
                        userCount += results[i];
                    }

                    callback(userCount, allApps.length);
                });
            }
        });
    }
}

/**
* Get all platforms for apps
* @param {function} callback - function to call when done
* @param {array=} apps - provide array of apps to fetch data for, else will fetch data for all apps
**/
function getAllPlatforms(callback, apps) {
    countlyDb.collection("device_details").find({_id: {"$regex": ".*:0.*"}}, {
        "a": 1,
        "meta": 1
    }).toArray(function(err, arr) {
        if (err || !arr) {
            callback(0);
        }
        else {
            var platforms = {};

            for (let i = 0; i < arr.length; i++) {
                if (arr[i] && arr[i].meta && arr[i].meta.os && (typeof apps === "undefined" || apps.indexOf(arr[i].a) > -1)) {
                    for (let j = 0; j < arr[i].meta.os.length; j++) {
                        platforms[arr[i].meta.os[j]] = true;
                    }
                }
            }

            callback(Object.keys(platforms));
        }
    });
}

module.exports = stats;