<commit_before>var step = require('step');
var assert = require('assert');
var dot = require('dot');
dot.templateSettings.strip = false;
var PSQL = require('cartodb-psql');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');

function AnalysesController(prepareContext) {
    this.prepareContext = prepareContext;
}

module.exports = AnalysesController;

AnalysesController.prototype.register = function(app) {
    app.get(
        app.base_url_mapconfig + '/analyses/catalog',
        cors(),
        userMiddleware,
        this.prepareContext,
        this.catalog.bind(this)
    );
};

AnalysesController.prototype.sendResponse = function(req, res, body) {
    res.set('Cache-Control', 'public,max-age=10,must-revalidate');

    res.status(200);

    if (req.query && req.query.callback) {
        res.jsonp(body);
    } else {
        res.json(body);
    }
};

AnalysesController.prototype.catalog = function (req, res, next) {
    var self = this;
    var username = res.locals.user;

    step(
        function catalogQuery() {
            var pg = new PSQL(dbParamsFromReqParams(res.locals));
            getMetadata(username, pg, this);
        },
        function prepareResponse(err, results) {
            assert.ifError(err);

            var analysisIdToTable = results.tables.reduce(function(analysisIdToTable, table) {
                var analysisId = table.relname.split('_')[2];
                if (analysisId && analysisId.length === 40) {
                    analysisIdToTable[analysisId] = table;
                }
                return analysisIdToTable;
            }, {});

            var catalogWithTables = results.catalog.map(function(analysis) {
                if (analysisIdToTable.hasOwnProperty(analysis.node_id)) {
                    analysis.table = analysisIdToTable[analysis.node_id];
                }
                return analysis;
            });

            return catalogWithTables.sort(function(analysisA, analysisB) {
                if (!!analysisA.table && !!analysisB.table) {
                    return analysisB.table.size - analysisA.table.size;
                }

                if (!!analysisA.table) {
                    return -1;
                }

                if (!!analysisB.table) {
                    return 1;
                }

                return -1;
            });
        },
        function sendResponse(err, catalogWithTables) {
            if (err) {
                if (err.message.match(/permission\sdenied/)) {
                    err = new Error('Unauthorized');
                    err.http_status = 401;
                }

                next(err);
            } else {
                self.sendResponse(req, res, { catalog: catalogWithTables });
            }
        }
    );
};

var catalogQueryTpl = dot.template(
    'SELECT analysis_def->>\'type\' as type, * FROM cartodb.cdb_analysis_catalog WHERE username = \'{{=it._username}}\''
);

var tablesQueryTpl = dot.template([
    "WITH analysis_tables AS (",
    "    SELECT",
    "        n.nspname AS nspname,",
    "        c.relname AS relname,",
    "        pg_total_relation_size(",
    "            format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))",
    "        ) AS size,",
    "        format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name",
    "    FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n",
    "    WHERE c.relnamespace = n.oid",
    "    AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'",
    "    AND n.nspname IN ('{{=it._username}}', 'public')",
    ")",
    "SELECT *, pg_size_pretty(size) as size_pretty",
    "FROM analysis_tables",
    "ORDER BY size DESC"
].join('\n'));

function getMetadata(username, pg, callback) {
    var results = {
        catalog: [],
        tables: []
    };
    step(
        function getCatalog() {
            pg.query(catalogQueryTpl({_username: username}), this, true); // use read-only transaction
        },
        function handleCatalog(err, resultSet) {
            assert.ifError(err);
            resultSet = resultSet || {};
            results.catalog = resultSet.rows || [];
            this();
        },
        function getTables(err) {
            assert.ifError(err);
            pg.query(tablesQueryTpl({_username: username}), this, true); // use read-only transaction
        },
        function handleTables(err, resultSet) {
            assert.ifError(err);
            resultSet = resultSet || {};
            results.tables = resultSet.rows || [];
            this();
        },
        function finish(err) {
            if (err) {
                return callback(err);
            }

            return callback(null, results);
        }
    );
}

function dbParamsFromReqParams(params) {
    var dbParams = {};
    if ( params.dbuser ) {
        dbParams.user = params.dbuser;
    }
    if ( params.dbpassword ) {
        dbParams.pass = params.dbpassword;
    }
    if ( params.dbhost ) {
        dbParams.host = params.dbhost;
    }
    if ( params.dbport ) {
        dbParams.port = params.dbport;
    }
    if ( params.dbname ) {
        dbParams.dbname = params.dbname;
    }
    return dbParams;
}
<commit_msg>Remove schema from analyses catalog query perfoming that query in the same way that we do for pooling the node status in analyses backend<commit_after>var step = require('step');
var assert = require('assert');
var dot = require('dot');
dot.templateSettings.strip = false;
var PSQL = require('cartodb-psql');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');

function AnalysesController(prepareContext) {
    this.prepareContext = prepareContext;
}

module.exports = AnalysesController;

AnalysesController.prototype.register = function(app) {
    app.get(
        app.base_url_mapconfig + '/analyses/catalog',
        cors(),
        userMiddleware,
        this.prepareContext,
        this.catalog.bind(this)
    );
};

AnalysesController.prototype.sendResponse = function(req, res, body) {
    res.set('Cache-Control', 'public,max-age=10,must-revalidate');

    res.status(200);

    if (req.query && req.query.callback) {
        res.jsonp(body);
    } else {
        res.json(body);
    }
};

AnalysesController.prototype.catalog = function (req, res, next) {
    var self = this;
    var username = res.locals.user;

    step(
        function catalogQuery() {
            var pg = new PSQL(dbParamsFromReqParams(res.locals));
            getMetadata(username, pg, this);
        },
        function prepareResponse(err, results) {
            assert.ifError(err);

            var analysisIdToTable = results.tables.reduce(function(analysisIdToTable, table) {
                var analysisId = table.relname.split('_')[2];
                if (analysisId && analysisId.length === 40) {
                    analysisIdToTable[analysisId] = table;
                }
                return analysisIdToTable;
            }, {});

            var catalogWithTables = results.catalog.map(function(analysis) {
                if (analysisIdToTable.hasOwnProperty(analysis.node_id)) {
                    analysis.table = analysisIdToTable[analysis.node_id];
                }
                return analysis;
            });

            return catalogWithTables.sort(function(analysisA, analysisB) {
                if (!!analysisA.table && !!analysisB.table) {
                    return analysisB.table.size - analysisA.table.size;
                }

                if (!!analysisA.table) {
                    return -1;
                }

                if (!!analysisB.table) {
                    return 1;
                }

                return -1;
            });
        },
        function sendResponse(err, catalogWithTables) {
            if (err) {
                if (err.message.match(/permission\sdenied/)) {
                    err = new Error('Unauthorized');
                    err.http_status = 401;
                }

                next(err);
            } else {
                self.sendResponse(req, res, { catalog: catalogWithTables });
            }
        }
    );
};

var catalogQueryTpl = dot.template(
    'SELECT analysis_def->>\'type\' as type, * FROM cdb_analysis_catalog WHERE username = \'{{=it._username}}\''
);

var tablesQueryTpl = dot.template([
    "WITH analysis_tables AS (",
    "    SELECT",
    "        n.nspname AS nspname,",
    "        c.relname AS relname,",
    "        pg_total_relation_size(",
    "            format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))",
    "        ) AS size,",
    "        format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name",
    "    FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n",
    "    WHERE c.relnamespace = n.oid",
    "    AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'",
    "    AND n.nspname IN ('{{=it._username}}', 'public')",
    ")",
    "SELECT *, pg_size_pretty(size) as size_pretty",
    "FROM analysis_tables",
    "ORDER BY size DESC"
].join('\n'));

function getMetadata(username, pg, callback) {
    var results = {
        catalog: [],
        tables: []
    };
    step(
        function getCatalog() {
            pg.query(catalogQueryTpl({_username: username}), this, true); // use read-only transaction
        },
        function handleCatalog(err, resultSet) {
            assert.ifError(err);
            resultSet = resultSet || {};
            results.catalog = resultSet.rows || [];
            this();
        },
        function getTables(err) {
            assert.ifError(err);
            pg.query(tablesQueryTpl({_username: username}), this, true); // use read-only transaction
        },
        function handleTables(err, resultSet) {
            assert.ifError(err);
            resultSet = resultSet || {};
            results.tables = resultSet.rows || [];
            this();
        },
        function finish(err) {
            if (err) {
                return callback(err);
            }

            return callback(null, results);
        }
    );
}

function dbParamsFromReqParams(params) {
    var dbParams = {};
    if ( params.dbuser ) {
        dbParams.user = params.dbuser;
    }
    if ( params.dbpassword ) {
        dbParams.pass = params.dbpassword;
    }
    if ( params.dbhost ) {
        dbParams.host = params.dbhost;
    }
    if ( params.dbport ) {
        dbParams.port = params.dbport;
    }
    if ( params.dbname ) {
        dbParams.dbname = params.dbname;
    }
    return dbParams;
}
<|endoftext|>


<commit_before>"use strict";

/**
 * Prometheus-style /metrics gathering and exporting.
 * This class provides a central location to register gauge and counter metrics
 * used to generate the /metrics page.
 *
 * This class depends on having prome-client installed. It
 * will attempt to load this module when the constructor is invoked.
 *
 * @example
A simple metric that counts the keys in an object:

 *   var metrics = new PrometheusMetrics();
 *
 *   var orange = {};
 *   metrics.addGauge({
 *       name: "oranges",
 *       help: "current number of oranges",
 *       refresh: (gauge) => {
 *           gauge.set({}, Object.keys(oranges).length);
 *       },
 *   });
 *
 * @example
Generating values for multiple gauges in a single collector
 * function.

 *   var metrics = new PrometheusMetrics();
 *
 *   var oranges_gauge = metrics.addGauge({
 *       name: "oranges",
 *       help: "current number of oranges",
 *   });
 *   var apples_gauge = metrics.addGauge({
 *       name: "apples",
 *       help: "current number of apples",
 *   });
 *
 *   metrics.addCollector(() => {
 *       var counts = this._countFruit();
 *       oranges_gauge.set({}, counts.oranges);
 *       apples_gauge.set({}, counts.apples);
 *   });
 *
 * @example
Using counters

 *   var metrics = new PrometheusMetrics();
 *
 *   metrics.addCollector({
 *       name: "things_made",
 *       help: "count of things that we have made",
 *   });
 *
 *   function makeThing() {
 *       metrics.incCounter("things_made");
 *       return new Thing();
 *   }
 *
 * @constructor
 */
function PrometheusMetrics() {
    // Only attempt to load these dependencies if metrics are enabled
    var client = this._client = require("prom-client");

    this._collectors = []; // executed in order
    this._counters = {}; // counter metrics keyed by name
}

/**
 * Registers some exported metrics that relate to operations of the embedded
 * matrix-js-sdk. In particular, a metric is added that counts the number of
 * calls to client API endpoints made by the client library.
 */
PrometheusMetrics.prototype.registerMatrixSdkMetrics = function() {
    var callCounts = this.addCounter({
        name: "matrix_api_calls",
        help: "Count of the number of Matrix client API calls made",
        labels: ["method"],
    });

    /*
     * We'll now annotate a bunch of the methods in MatrixClient to keep counts
     * of every time they're called. This seems to be neater than trying to
     * intercept all HTTP requests and try to intuit what internal method was
     * invoked based on the HTTP URL.
     * It's kindof messy to do this because we have to maintain a list of
     * client SDK method names, but the only other alternative is to hook the
     * 'request' function and attempt to parse methods out by inspecting the
     * underlying client API HTTP URLs, and that is even messier. So this is
     * the lesser of two evils.
     */

    var matrixClientPrototype = require("matrix-js-sdk").MatrixClient.prototype;

    var CLIENT_METHODS = [
        "ban",
        "createAlias",
        "createRoom",
        "getProfileInfo",
        "getStateEvent",
        "invite",
        "joinRoom",
        "kick",
        "leave",
        "register",
        "roomState",
        "sendEvent",
        "sendReceipt",
        "sendStateEvent",
        "sendTyping",
        "setAvatarUrl",
        "setDisplayName",
        "setPowerLevel",
        "setPresence",
        "setProfileInfo",
        "unban",
        "uploadContent",
    ];

    CLIENT_METHODS.forEach(function(method) {
        callCounts.inc({method: method}, 0); // initialise the count to zero

        var orig = matrixClientPrototype[method];
        matrixClientPrototype[method] = function() {
            callCounts.inc({method: method});
            return orig.apply(this, arguments);
        }
    });
};

/**
 * Registers some exported metrics that expose counts of various kinds of
 * objects within the bridge.
 * @param {BridgeGaugesCallback} counterFunc A function that when invoked
 * returns the current counts of various items in the bridge.
 */
PrometheusMetrics.prototype.registerBridgeGauges = function(counterFunc) {
    var matrixRoomsGauge = this.addGauge({
        name: "matrix_configured_rooms",
        help: "Current count of configured rooms by matrix room ID",
    });
    var remoteRoomsGauge = this.addGauge({
        name: "remote_configured_rooms",
        help: "Current count of configured rooms by remote room ID",
    });

    var matrixGhostsGauge = this.addGauge({
        name: "matrix_ghosts",
        help: "Current count of matrix-side ghost users",
    });
    var remoteGhostsGauge = this.addGauge({
        name: "remote_ghosts",
        help: "Current count of remote-side ghost users",
    });

    var matrixRoomsByAgeGauge = this.addGauge({
        name: "matrix_rooms_by_age",
        help: "Current count of matrix rooms partitioned by activity age",
        labels: ["age"],
    });
    var remoteRoomsByAgeGauge = this.addGauge({
        name: "remote_rooms_by_age",
        help: "Current count of remote rooms partitioned by activity age",
        labels: ["age"],
    });

    var matrixUsersByAgeGauge = this.addGauge({
        name: "matrix_users_by_age",
        help: "Current count of matrix users partitioned by activity age",
        labels: ["age"],
    });
    var remoteUsersByAgeGauge = this.addGauge({
        name: "remote_users_by_age",
        help: "Current count of remote users partitioned by activity age",
        labels: ["age"],
    });

    this.addCollector(function () {
        var counts = counterFunc();

        matrixRoomsGauge.set(counts.matrixRoomConfigs);
        remoteRoomsGauge.set(counts.remoteRoomConfigs);

        matrixGhostsGauge.set(counts.matrixGhosts);
        remoteGhostsGauge.set(counts.remoteGhosts);

        counts.matrixRoomsByAge.setGauge(matrixRoomsByAgeGauge);
        counts.remoteRoomsByAge.setGauge(remoteRoomsByAgeGauge);

        counts.matrixUsersByAge.setGauge(matrixUsersByAgeGauge);
        counts.remoteUsersByAge.setGauge(remoteUsersByAgeGauge);
    });
};

PrometheusMetrics.prototype.refresh = function() {
    this._collectors.forEach(function(f) { f(); });
};

/**
 * Adds a new collector function. These collector functions are run whenever
 * the /metrics page is about to be generated, allowing code to update values
 * of gauges.
 * @param {Function} func A new collector function.
 * This function is passed no arguments and is not expected to return anything.
 * It runs purely to have a side-effect on previously registered gauges.
 */
PrometheusMetrics.prototype.addCollector = function(func) {
    this._collectors.push(func);
};

/**
 * Adds a new gauge metric.
 * @param {Object} opts Options
 * @param {string=} opts.namespace An optional toplevel namespace name for the
 * new metric. Default: "bridge".
 * @param {string} opts.name The variable name for the new metric.
 * @param {string} opts.help Descriptive help text for the new metric.
 * @param {[string]=} opts.labels An optional list of string label names
 * @param {Function=} opts.refresh An optional function to invoke to generate a
 * new value for the gauge.
 * If a refresh function is provided, it is invoked with the gauge as its only
 * parameter. The function should call the set() method on this
 * gauge in order to provide a new value for it.
 * @return {Gauge} A gauge metric.
 */
PrometheusMetrics.prototype.addGauge = function(opts) {
    var refresh = opts.refresh;
    var name = [opts.namespace || "bridge", opts.name].join("_");

    var gauge = new this._client.Gauge(name, opts.help, opts.labels || []);

    if (opts.refresh) {
        this._collectors.push(function() { refresh(gauge); });
    }

    return gauge;
};

/**
 * Adds a new counter metric
 * @param {Object} opts Options
 * @param {string} opts.namespace An optional toplevel namespace name for the
 * new metric. Default: "bridge".
 * @param {string} opts.name The variable name for the new metric.
 * @param {string} opts.help Descriptive help text for the new metric.
 * Once created, the value of this metric can be incremented with the
 * incCounter method.
 * @param {[string]=} opts.labels An optional list of string label names
 * @return {Counter} A counter metric.
 */
PrometheusMetrics.prototype.addCounter = function(opts) {
    var name = [opts.namespace || "bridge", opts.name].join("_");

    var counter = this._counters[opts.name] =
        new this._client.Counter(name, opts.help, opts.labels || []);

    return counter;
};

/**
 * Increments the value of a counter metric
 * @param{string} name The name the metric was previously registered as.
 * @param{Object} labels Optional object containing additional label values.
 */
PrometheusMetrics.prototype.incCounter = function(name, labels) {
    if (!this._counters[name]) {
        throw new Error("Unrecognised counter metric name '" + name + "'");
    }

    this._counters[name].inc(labels);
};

/**
 * Registers the /metrics page generating function with the
 * containing Express app.
 * @param {Bridge} bridge The containing Bridge instance.
 */
PrometheusMetrics.prototype.addAppServicePath = function(bridge) {
    var register = this._client.register;

    bridge.addAppServicePath({
        method: "GET",
        path: "/metrics",
        handler: function(req, res) {
            this.refresh();

            try {
                var exposition = register.metrics();

                res.set("Content-Type", "text/plain");
                res.send(exposition);
            }
            catch (e) {
                res.status(500);

                res.set("Content-Type", "text/plain");
                res.send(e.toString());
            }
        }.bind(this),
    });
};

/**
 * Invoked at metrics export time to count items in the bridge.
 * @callback BridgeGaugesCallback
 * @return {BridgeGaugesCounts} An object containing counts of items in the
 * bridge.
 */

/**
 * @typedef BridgeGaugesCounts
 * @type {Object}
 * @param {number} matrixRoomConfigs The number of distinct matrix room IDs
 * known in the configuration.
 * @param {number} remoteRoomConfigs The number of distinct remote rooms known
 * in the configuration.
 * @param {number} matrixGhosts The number of matrix-side ghost users that
 * currently exist.
 * @param {number} remoteGhosts The number of remote-side ghost users that
 * currently exist.
 * @param {AgeCounters} matrixRoomsByAge The distribution of distinct matrix
 * room IDs by age of the most recently-seen message from them,
 * @param {AgeCounters} remoteRoomsByAge The distribution of distinct remote
 * rooms by age of the most recently-seen message from them.
 * @param {AgeCounters} matrixUsersByAge The distribution of distinct matrix
 * users by age of the most recently-seen message from them.
 * @param {AgeCounters} remoteUsersByAge The distribution of distinct remote
 * users by age of the most recently-seen message from them.
 */

module.exports = PrometheusMetrics;
<commit_msg>Initial hack at supporting Prometheus Histogram metrics (called 'timer' here)<commit_after>"use strict";

/**
 * Prometheus-style /metrics gathering and exporting.
 * This class provides a central location to register gauge and counter metrics
 * used to generate the /metrics page.
 *
 * This class depends on having prome-client installed. It
 * will attempt to load this module when the constructor is invoked.
 *
 * @example
A simple metric that counts the keys in an object:

 *   var metrics = new PrometheusMetrics();
 *
 *   var orange = {};
 *   metrics.addGauge({
 *       name: "oranges",
 *       help: "current number of oranges",
 *       refresh: (gauge) => {
 *           gauge.set({}, Object.keys(oranges).length);
 *       },
 *   });
 *
 * @example
Generating values for multiple gauges in a single collector
 * function.

 *   var metrics = new PrometheusMetrics();
 *
 *   var oranges_gauge = metrics.addGauge({
 *       name: "oranges",
 *       help: "current number of oranges",
 *   });
 *   var apples_gauge = metrics.addGauge({
 *       name: "apples",
 *       help: "current number of apples",
 *   });
 *
 *   metrics.addCollector(() => {
 *       var counts = this._countFruit();
 *       oranges_gauge.set({}, counts.oranges);
 *       apples_gauge.set({}, counts.apples);
 *   });
 *
 * @example
Using counters

 *   var metrics = new PrometheusMetrics();
 *
 *   metrics.addCollector({
 *       name: "things_made",
 *       help: "count of things that we have made",
 *   });
 *
 *   function makeThing() {
 *       metrics.incCounter("things_made");
 *       return new Thing();
 *   }
 *
 * @constructor
 */
function PrometheusMetrics() {
    // Only attempt to load these dependencies if metrics are enabled
    var client = this._client = require("prom-client");

    this._collectors = []; // executed in order
    this._counters = {}; // counter metrics keyed by name
    this._timers = {}; // timer metrics (Histograms) keyed by name
}

/**
 * Registers some exported metrics that relate to operations of the embedded
 * matrix-js-sdk. In particular, a metric is added that counts the number of
 * calls to client API endpoints made by the client library.
 */
PrometheusMetrics.prototype.registerMatrixSdkMetrics = function() {
    var callCounts = this.addCounter({
        name: "matrix_api_calls",
        help: "Count of the number of Matrix client API calls made",
        labels: ["method"],
    });

    /*
     * We'll now annotate a bunch of the methods in MatrixClient to keep counts
     * of every time they're called. This seems to be neater than trying to
     * intercept all HTTP requests and try to intuit what internal method was
     * invoked based on the HTTP URL.
     * It's kindof messy to do this because we have to maintain a list of
     * client SDK method names, but the only other alternative is to hook the
     * 'request' function and attempt to parse methods out by inspecting the
     * underlying client API HTTP URLs, and that is even messier. So this is
     * the lesser of two evils.
     */

    var matrixClientPrototype = require("matrix-js-sdk").MatrixClient.prototype;

    var CLIENT_METHODS = [
        "ban",
        "createAlias",
        "createRoom",
        "getProfileInfo",
        "getStateEvent",
        "invite",
        "joinRoom",
        "kick",
        "leave",
        "register",
        "roomState",
        "sendEvent",
        "sendReceipt",
        "sendStateEvent",
        "sendTyping",
        "setAvatarUrl",
        "setDisplayName",
        "setPowerLevel",
        "setPresence",
        "setProfileInfo",
        "unban",
        "uploadContent",
    ];

    CLIENT_METHODS.forEach(function(method) {
        callCounts.inc({method: method}, 0); // initialise the count to zero

        var orig = matrixClientPrototype[method];
        matrixClientPrototype[method] = function() {
            callCounts.inc({method: method});
            return orig.apply(this, arguments);
        }
    });
};

/**
 * Registers some exported metrics that expose counts of various kinds of
 * objects within the bridge.
 * @param {BridgeGaugesCallback} counterFunc A function that when invoked
 * returns the current counts of various items in the bridge.
 */
PrometheusMetrics.prototype.registerBridgeGauges = function(counterFunc) {
    var matrixRoomsGauge = this.addGauge({
        name: "matrix_configured_rooms",
        help: "Current count of configured rooms by matrix room ID",
    });
    var remoteRoomsGauge = this.addGauge({
        name: "remote_configured_rooms",
        help: "Current count of configured rooms by remote room ID",
    });

    var matrixGhostsGauge = this.addGauge({
        name: "matrix_ghosts",
        help: "Current count of matrix-side ghost users",
    });
    var remoteGhostsGauge = this.addGauge({
        name: "remote_ghosts",
        help: "Current count of remote-side ghost users",
    });

    var matrixRoomsByAgeGauge = this.addGauge({
        name: "matrix_rooms_by_age",
        help: "Current count of matrix rooms partitioned by activity age",
        labels: ["age"],
    });
    var remoteRoomsByAgeGauge = this.addGauge({
        name: "remote_rooms_by_age",
        help: "Current count of remote rooms partitioned by activity age",
        labels: ["age"],
    });

    var matrixUsersByAgeGauge = this.addGauge({
        name: "matrix_users_by_age",
        help: "Current count of matrix users partitioned by activity age",
        labels: ["age"],
    });
    var remoteUsersByAgeGauge = this.addGauge({
        name: "remote_users_by_age",
        help: "Current count of remote users partitioned by activity age",
        labels: ["age"],
    });

    this.addCollector(function () {
        var counts = counterFunc();

        matrixRoomsGauge.set(counts.matrixRoomConfigs);
        remoteRoomsGauge.set(counts.remoteRoomConfigs);

        matrixGhostsGauge.set(counts.matrixGhosts);
        remoteGhostsGauge.set(counts.remoteGhosts);

        counts.matrixRoomsByAge.setGauge(matrixRoomsByAgeGauge);
        counts.remoteRoomsByAge.setGauge(remoteRoomsByAgeGauge);

        counts.matrixUsersByAge.setGauge(matrixUsersByAgeGauge);
        counts.remoteUsersByAge.setGauge(remoteUsersByAgeGauge);
    });
};

PrometheusMetrics.prototype.refresh = function() {
    this._collectors.forEach(function(f) { f(); });
};

/**
 * Adds a new collector function. These collector functions are run whenever
 * the /metrics page is about to be generated, allowing code to update values
 * of gauges.
 * @param {Function} func A new collector function.
 * This function is passed no arguments and is not expected to return anything.
 * It runs purely to have a side-effect on previously registered gauges.
 */
PrometheusMetrics.prototype.addCollector = function(func) {
    this._collectors.push(func);
};

/**
 * Adds a new gauge metric.
 * @param {Object} opts Options
 * @param {string=} opts.namespace An optional toplevel namespace name for the
 * new metric. Default: "bridge".
 * @param {string} opts.name The variable name for the new metric.
 * @param {string} opts.help Descriptive help text for the new metric.
 * @param {[string]=} opts.labels An optional list of string label names
 * @param {Function=} opts.refresh An optional function to invoke to generate a
 * new value for the gauge.
 * If a refresh function is provided, it is invoked with the gauge as its only
 * parameter. The function should call the set() method on this
 * gauge in order to provide a new value for it.
 * @return {Gauge} A gauge metric.
 */
PrometheusMetrics.prototype.addGauge = function(opts) {
    var refresh = opts.refresh;
    var name = [opts.namespace || "bridge", opts.name].join("_");

    var gauge = new this._client.Gauge(name, opts.help, opts.labels || []);

    if (opts.refresh) {
        this._collectors.push(function() { refresh(gauge); });
    }

    return gauge;
};

/**
 * Adds a new counter metric
 * @param {Object} opts Options
 * @param {string} opts.namespace An optional toplevel namespace name for the
 * new metric. Default: "bridge".
 * @param {string} opts.name The variable name for the new metric.
 * @param {string} opts.help Descriptive help text for the new metric.
 * Once created, the value of this metric can be incremented with the
 * incCounter method.
 * @param {[string]=} opts.labels An optional list of string label names
 * @return {Counter} A counter metric.
 */
PrometheusMetrics.prototype.addCounter = function(opts) {
    var name = [opts.namespace || "bridge", opts.name].join("_");

    var counter = this._counters[opts.name] =
        new this._client.Counter(name, opts.help, opts.labels || []);

    return counter;
};

/**
 * Increments the value of a counter metric
 * @param{string} name The name the metric was previously registered as.
 * @param{Object} labels Optional object containing additional label values.
 */
PrometheusMetrics.prototype.incCounter = function(name, labels) {
    if (!this._counters[name]) {
        throw new Error("Unrecognised counter metric name '" + name + "'");
    }

    this._counters[name].inc(labels);
};

/**
 * Adds a new timer metric, represented by a prometheus Histogram.
 * @param {Object} opts Options
 * @param {string} opts.namespace An optional toplevel namespace name for the
 * new metric. Default: "bridge".
 * @param {string} opts.name The variable name for the new metric.
 * @param {string} opts.help Descriptive help text for the new metric.
 * @param {[string]=} opts.labels An optional list of string label names
 * @return {Histogram} A histogram metric.
 * Once created, the value of this metric can be incremented with the
 * startTimer method.
 */
PrometheusMetrics.prototype.addTimer = function(opts) {
    var name = [opts.namespace || "bridge", opts.name].join("_");

    var timer = this._timers[opts.name] =
        new this._client.Histogram(name, opts.help, opts.labels || []);

    return timer;
};

/**
 * Begins a new timer observation for a timer metric.
 * @param{string} name The name the metric was previously registered as.
 * @param{Object} labels Optional object containing additional label values.
 * @return {function} A function to be called to end the timer and report the
 * observation.
 */
PrometheusMetrics.prototype.startTimer = function(name, labels) {
    if (!this._timers[name]) {
        throw new Error("Unrecognised timer metric name '" + name + "'");
    }

    return this._timers[name].startTimer(labels);
};

/**
 * Registers the /metrics page generating function with the
 * containing Express app.
 * @param {Bridge} bridge The containing Bridge instance.
 */
PrometheusMetrics.prototype.addAppServicePath = function(bridge) {
    var register = this._client.register;

    bridge.addAppServicePath({
        method: "GET",
        path: "/metrics",
        handler: function(req, res) {
            this.refresh();

            try {
                var exposition = register.metrics();

                res.set("Content-Type", "text/plain");
                res.send(exposition);
            }
            catch (e) {
                res.status(500);

                res.set("Content-Type", "text/plain");
                res.send(e.toString());
            }
        }.bind(this),
    });
};

/**
 * Invoked at metrics export time to count items in the bridge.
 * @callback BridgeGaugesCallback
 * @return {BridgeGaugesCounts} An object containing counts of items in the
 * bridge.
 */

/**
 * @typedef BridgeGaugesCounts
 * @type {Object}
 * @param {number} matrixRoomConfigs The number of distinct matrix room IDs
 * known in the configuration.
 * @param {number} remoteRoomConfigs The number of distinct remote rooms known
 * in the configuration.
 * @param {number} matrixGhosts The number of matrix-side ghost users that
 * currently exist.
 * @param {number} remoteGhosts The number of remote-side ghost users that
 * currently exist.
 * @param {AgeCounters} matrixRoomsByAge The distribution of distinct matrix
 * room IDs by age of the most recently-seen message from them,
 * @param {AgeCounters} remoteRoomsByAge The distribution of distinct remote
 * rooms by age of the most recently-seen message from them.
 * @param {AgeCounters} matrixUsersByAge The distribution of distinct matrix
 * users by age of the most recently-seen message from them.
 * @param {AgeCounters} remoteUsersByAge The distribution of distinct remote
 * users by age of the most recently-seen message from them.
 */

module.exports = PrometheusMetrics;
<|endoftext|>


<commit_before>/* ***********************************************************************************************

    Unify Project

    Homepage: unify-project.org
    License: MIT + Apache (V2)
    Copyright: 2009-2010 Deutsche Telekom AG, Germany, http://telekom.com

*********************************************************************************************** */
/* ************************************************************************

#require(unify.event.handler.Android)

************************************************************************ */

/**
 * Manager for navigation of typical iPhone-like applications.
 *
 * * Integrates with browser's history managment
 * * Structures the location using "/" as divider for views and ":" for separating parameters.
 * * Supports multiple ways of controlling the location.
 * * Support for TabView like navigation with deep inner navigation
 */
qx.Class.define("unify.view.mobile.Navigation",
{
  extend : qx.core.Object,
  type : "singleton",

  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function()
  {
    this.base(arguments);

    this.__viewManagers = {};

    // Initialize switch data
    this.__switchData = {};

    // FIXME
    // TODO: Variant for android
    // if (unify.bom.client.System.ANDROID) {
    //  qx.event.Registration.addListener(document, "backKeyDown", this.__onAndroidBackButton, this);
    //}
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    add : function(viewManager)
    {
      this.debug("Register ViewManager: " + viewManager.getId());
      this.__viewManagers[viewManager.getId()] = viewManager;
    },

    // ***********************************************************************************

    __switchData : null,

    /*
    ---------------------------------------------------------------------------
      USER API
    ---------------------------------------------------------------------------
    */

    /**
     * Returns the navigation path object
     *
     * @return {unify.view.mobile.navigation.Path} The path object
     */
    getPath : function() {
      return this.__path || null;
    },

    /**
     * Initialized previous state from history or client-side storage.
     *
     * Should be called by the application as soon as all views are registered.
     */
    init : function()
    {
      // Connect to history managment
      var History = unify.bom.History.getInstance();
      History.addListener("change", this.__onHistoryChange, this);

      // Restore path from storage or use default view
      var path;
      if (window.localStorage) {
        path = localStorage["unify/navigationpath"];
      }

      if (!path) {
        path = this.__defaultView;
      }

      History.init(path);
    },

    /**
     * Follows a HTML link or any element with goto attribute
     *
     * *General*
     *
     * Navigation is possible using the attribute goto and rel. Executing
     * methods on the current view is possible using the exec. These two special attributes
     * are only allowed on non-link elements.
     *
     * To execute commands just define the name of the method to execute as the value
     * of the exec attribute. The function to execute needs to be public on the
     * current view and will be executed in context of the view, but without any parameters.
     *
     * These are the supported "rel" attributes:
     *
     *

     *
param: Replaces the param of the current view with the value from goto.

     *
up: Move one level up in structure. No goto supported.

     *
parent: Move into the given view (+param) but on the same parent (replacing the current view in structure).

     *
root: The given goto is processed in a way that it works as an absolute destination, completely ignoring the current position.

     *
swap: Swaps to the root view given by the goto attribute. Stores and restores position inside of this root view.

     *

     *
     * The goto attribute might contain a name of a view,
     * a value or a complete path depending on the value of the
     * rel attribute.
     *
     * If no rel attribute is given, the view (+param) is appended to the current
     * path which results into a navigation deeper inside the structure.
     *
     * *Links*
     *
     * For typical HTML links only a subset of aboves features is available:
     *
     *

     *
open the link in a new window

     *
enter a child view

     *

     *
     * The behavior depends on whether the link begins with a "#" or not. The hash
     * symbol marks internal references like in normal HTML. A link to "#details" results
     * into a jump into the child view "details". A link to "http://www.google.com"
     * opens a new window/tab to the given external URL.
     *
     * @param link {Element} DOM element to follow
     */
    follow : function(link)
    {
      // Debug link processing time
      var start = +new Date;

      try
      {
        // Process simple links
        var href = link.getAttribute("href");
        if (href)
        {
          // this.debug("Follow HREF: " + href);

          if (href.indexOf("#") == 0) {
            this.enter(href.substring(1));
          } else {
            window.open(href);
          }
        }
        else
        {
          // Execute view commands
          var exec = link.getAttribute("exec");
          if (exec)
          {
            // this.debug("Follow EXEC: " + exec);
            this.exec(exec, link);
          }
          else
          {
            // Do advanced processing
            var path = link.getAttribute("goto");
            var rel = link.getAttribute("rel");

            // this.debug("Follow GOTO: " + path);

            if (rel == null)
            {
              this.enter(path);
            }
            else
            {
              var method = this.__followMapper[rel];
              if (method) {
                this[method](path);
              } else if (qx.core.Variant.isSet("qx.debug", "on")) {
                this.error("Unsupported 'rel' attribute: " + rel);
              }
            }
          }
        }
      }
      catch(ex)
      {
        this.error("Could not follow link " + (href||exec||rel));
        this.error(ex.message);
      }

      this.debug("Executed in: " + (new Date - start) + "ms");
    },

    /** {Map} Internal helper to map rel attributes to method names to execute */
    __followMapper :
    {
      param : "parametize",
      up : "up",
      parent : "replace",
      root : "jump",
      swap : "swap",
      segment : "segment"
    },

    /*
    ---------------------------------------------------------------------------
      USER API: LINK SUPPORT
    ---------------------------------------------------------------------------
    */

    /**
     * Jumps to the specified path without respecting current position.
     *
     * @param path {String} Any valid path
     */
    jump : function(path) {
      unify.bom.History.getInstance().jump(path);
    },

    /**
     * Executes the given function on the current view
     *
     * @param func {String} Name of function to execute (needs to be a public function!)
     * @param target {Element} DOM element which was clicked onto
     * @return {var} Return value of function to execute
     */
    exec : function(func, target)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (func.indexOf("_") == 0) {
          throw new Error("Could not execute protected or private methods: " + func);
        }
      }

      if (view && view[func]) {
        return view[func](target);
      } else if (qx.core.Variant.isSet("qx.debug", "on")) {
        throw new Error("Executing " + func + "() not supported by current view!");
      }
    },

    /**
     * Enters the specified view (+param). This leads deeper into the navigation
     * structure e.g. "root" + "page1" => "root/page1"
     *
     * @param view {String} View to enter (with optional param)
     */
    enter : function(view)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (view.indexOf("/") != -1) {
          throw new Error("Invalid path to dive into: " + view);
        }
      }

      unify.bom.History.getInstance().jump(this.__path + "/" + view);
    },

    /**
     * Switches in the current view to the given segment
     *
     * @param segment {String} Name of segment
     */
    segment : function(segment)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (segment.indexOf("/") != -1 || segment.indexOf(":") != -1 || segment.indexOf(".") != -1) {
          throw new Error("Invalid param for segment(): " + segment);
        }
      }

      // Create and configure clone
      var clone = new unify.view.mobile.navigation.Path;
      clone.setLocation(this.__path.getLocation());
      clone.setSegment(segment);

      // Jump in history
      unify.bom.History.getInstance().jump(clone.toString());
    },

    /**
     * Paremetize the current view with the given parameter. This leads to a refreshment
     * of the current view.
     *
     * @param param {String|Number} Any valid param
     */
    parametize : function(param)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (param.indexOf("/") != -1 || param.indexOf(":") != -1 || param.indexOf(".") != -1) {
          throw new Error("Invalid param for parametize(): " + param);
        }
      }

      // Create and configure clone
      var clone = new unify.view.mobile.navigation.Path;
      clone.setLocation(this.__path.getLocation());
      clone.setParam(param);

      // Jump in history
      unify.bom.History.getInstance().jump(clone.toString());
    },

    /**
     * Replaces current view with the given view (+param) on the same parent
     *
     * @param view {String} Any valid view (with optional params)
     */
    replace : function(view)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (view.indexOf("/") != -1) {
          throw new Error("Invalid view for replace(): " + view);
        }
      }

      var current = this.__path.toString();
      var pos = current.lastIndexOf("/");
      if (pos > 0) {
        view = current.substring(0, pos+1) + view;
      }

      unify.bom.History.getInstance().jump(view);
    },

    /**
     * Jumps up a segment in the history structure
     */
    up : function()
    {
      var current = this.__path.toString();
      var pos = current.lastIndexOf("/");
      if (pos > 0) {
        unify.bom.History.getInstance().jump(current.substring(0, pos));
      } else if (qx.core.Variant.isSet("qx.debug", "on")) {
        throw new Error("Could not go up further!");
      }
    },

    /**
     * Event listener for Android back button
     *
     * @param e {qx.event.type.Event} Event object
     */
    __onAndroidBackButton : function(e)
    {
      var current = this.__path.toString();
      var pos = current.lastIndexOf("/");
      if (pos > 0)
      {
        // Go up in unify hierarchy
        this.up();
      }
      else
      {
        // Finish (native) activity
        var DroidGap = window.DroidGap;
        if (DroidGap) {
          DroidGap.nativeBackButton();
        }
      }
    },

    /**
     * Switches the complete path to another path, but keeping the
     * original path in a cache to jump back to the complete structure.
     *
     * This feature is especially useful for TabViews where we have
     * some kind of sub-navigation in each Tab.
     *
     * @param view {String} Indentifier of view to switch to e.g. the name of the Tab
     */
    swap : function(view)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (view.indexOf("/") != -1 || view.indexOf(":") != -1 || view.indexOf(".") != -1) {
          throw new Error("Invalid view for swap(): " + view);
        }
      }

      var switchData = this.__switchData;

      // Store current location
      var oldPath = this.__path;
      var oldRootView = oldPath.getView(0);
      switchData[oldRootView] = oldPath.toString();

      // Jump to new location or to root, defending on current path
      var History = unify.bom.History.getInstance();
      var restore = switchData[view];
      if (restore && view != oldRootView) {
        History.jump(restore);
      } else {
        History.jump(view);
      }
    },

    /*
    ---------------------------------------------------------------------------
      INTERNALS
    ---------------------------------------------------------------------------
    */

    __path : null,

    /**
     * Computes the differences between to {@link Path} and returns
     * a value describing these differences.
     *
     * * equal: both path are equal
     * * reset: current path is null
     * * initial: previous path is null
     * * swap: root view was changed via e.g. TabBar
     * * in: new path is deeper in hierarchy than previous one
     * * out: new path is lower in hiearchy than previous one
     * * replace: view was replaced with other view with same parent
     * * segment: only the segment in the view was replaced
     * * param: only the parameter of view was replaced
     * * null: other unknown change
     *
     * @param value {Path} Current path
     * @param old {Path} Previous path
     * @return {String} The computed mode
     */
    __computeMode : function(value, old)
    {
      if (value == old) {
        return "equal";
      }

      if (!value || !old)
      {
        if (value) {
          return "initial";
        } else {
          return "reset";
        }
      }

      if (value.toString() === old.toString()) {
        return "equal";
      } else if (value.getView(0) != old.getView(0)) {
        return "swap";
      } else if (value.getSize() != old.getSize()) {
        return value.getSize() < old.getSize() ? "out" : "in";
      } else if (value.getView() != old.getView()) {
        return "replace";
      } else if (value.getSegment() != old.getSegment()) {
        return "segment";
      } else if (value.getParam() != old.getParam()) {
        return "param";
      } else {
        return null;
      }
    },

    __viewManagers : null,
    __defaultView : null,
    __location : null,

    __urlMatcher : /^([a-z-]+)(\.([a-z-]+))?(\:([a-zA-Z0-9_-]+))?$/,

    /**
     * Reacts on changes of the browser history.
     *
     * @param e {unify.event.type.History} History event
     */
    __onHistoryChange : function(e)
    {
      var History = e.getTarget();

      // Quick check: Don't allow empty paths
      var currentLocation = decodeURI(e.getLocation());
      if (currentLocation == "")
      {
        this.warn("Ignoring empty path");
        return;
      }

      var oldLocation = this.__location || "";
      if (currentLocation == oldLocation) {
        return;
      }

      if (window.localStorage)
      {
        try
        {
          // deleting first is required on iPad with OS 3.2
          delete localStorage["unify/navigationpath"];
          localStorage["unify/navigationpath"] = decodeURI(currentLocation);
        }
        catch(ex) {
          this.warn("Could not store path: " + ex);
        }
      }

      this.debug("History Change: \"" + oldLocation + "\" => \"" + currentLocation + "\"");

      var currentSplits = currentLocation == "" ? [] : currentLocation.split("/");
      var oldSplits = oldLocation == "" ? [] : oldLocation.split("/");

      // Create new data structure mapping managers to their individual structure
      var managers = this.__viewManagers;
      var structure = {};
      for (var id in managers) {
        structure[id] = [];
      }

      // Process current path
      for (var i=0, l=currentSplits.length; i<l; i++)
      {
        var match = this.__urlMatcher.exec(currentSplits[i]);
        var view = RegExp.$1;
        var segment = RegExp.$3;
        var param = RegExp.$5;

        for (var id in managers)
        {
          var viewClass = managers[id].getById(view);
          if (viewClass)
          {
            structure[id].push({
              view : viewClass,
              segment : segment,
              param : param
            });

            view = null;
            break;
          }
        }

        if (view) {
          throw new Error("Unknown view: " + view);
        }
      }

      // Update managers
      for (var id in structure) {
        managers[id].update(structure[id]);
      }
    }
  },

  /*
  *****************************************************************************
     DESTRUCTOR
  *****************************************************************************
  */

  destruct : function()
  {
    var History = unify.bom.History.getInstance();
    History.removeListener("change", this.__onHistoryChange, this);
  }
});
<commit_msg>Minor fixes<commit_after>/* ***********************************************************************************************

    Unify Project

    Homepage: unify-project.org
    License: MIT + Apache (V2)
    Copyright: 2009-2010 Deutsche Telekom AG, Germany, http://telekom.com

*********************************************************************************************** */
/* ************************************************************************

#require(unify.event.handler.Android)

************************************************************************ */

/**
 * Manager for navigation of typical iPhone-like applications.
 *
 * * Integrates with browser's history managment
 * * Structures the location using "/" as divider for views and ":" for separating parameters.
 * * Supports multiple ways of controlling the location.
 * * Support for TabView like navigation with deep inner navigation
 */
qx.Class.define("unify.view.mobile.Navigation",
{
  extend : qx.core.Object,
  type : "singleton",

  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function()
  {
    this.base(arguments);

    this.__viewManagers = {};

    // Initialize switch data
    this.__switchData = {};

    // FIXME
    // TODO: Variant for android
    // if (unify.bom.client.System.ANDROID) {
    //  qx.event.Registration.addListener(document, "backKeyDown", this.__onAndroidBackButton, this);
    //}
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    add : function(viewManager)
    {
      this.debug("Register ViewManager: " + viewManager.getId());
      this.__viewManagers[viewManager.getId()] = viewManager;
    },

    // ***********************************************************************************

    __switchData : null,

    /*
    ---------------------------------------------------------------------------
      USER API
    ---------------------------------------------------------------------------
    */

    /**
     * Returns the navigation path object
     *
     * @return {unify.view.mobile.navigation.Path} The path object
     */
    getPath : function() {
      return this.__path || null;
    },

    /**
     * Initialized previous state from history or client-side storage.
     *
     * Should be called by the application as soon as all views are registered.
     */
    init : function()
    {
      // Connect to history managment
      var History = unify.bom.History.getInstance();
      History.addListener("change", this.__onHistoryChange, this);

      // Restore path from storage or use default view
      var path;
      if (window.localStorage) {
        path = localStorage["unify/navigationpath"];
      }

      if (!path) {
        path = this.__defaultView;
      }

      History.init(path);
    },

    /**
     * Follows a HTML link or any element with goto attribute
     *
     * *General*
     *
     * Navigation is possible using the attribute goto and rel. Executing
     * methods on the current view is possible using the exec. These two special attributes
     * are only allowed on non-link elements.
     *
     * To execute commands just define the name of the method to execute as the value
     * of the exec attribute. The function to execute needs to be public on the
     * current view and will be executed in context of the view, but without any parameters.
     *
     * These are the supported "rel" attributes:
     *
     *

     *
param: Replaces the param of the current view with the value from goto.

     *
up: Move one level up in structure. No goto supported.

     *
parent: Move into the given view (+param) but on the same parent (replacing the current view in structure).

     *
root: The given goto is processed in a way that it works as an absolute destination, completely ignoring the current position.

     *
swap: Swaps to the root view given by the goto attribute. Stores and restores position inside of this root view.

     *

     *
     * The goto attribute might contain a name of a view,
     * a value or a complete path depending on the value of the
     * rel attribute.
     *
     * If no rel attribute is given, the view (+param) is appended to the current
     * path which results into a navigation deeper inside the structure.
     *
     * *Links*
     *
     * For typical HTML links only a subset of aboves features is available:
     *
     *

     *
open the link in a new window

     *
enter a child view

     *

     *
     * The behavior depends on whether the link begins with a "#" or not. The hash
     * symbol marks internal references like in normal HTML. A link to "#details" results
     * into a jump into the child view "details". A link to "http://www.google.com"
     * opens a new window/tab to the given external URL.
     *
     * @param link {Element} DOM element to follow
     */
    follow : function(link)
    {
      // Debug link processing time
      var start = +new Date;

      try
      {
        // Process simple links
        var href = link.getAttribute("href");
        if (href)
        {
          // this.debug("Follow HREF: " + href);

          if (href.indexOf("#") == 0) {
            this.enter(href.substring(1));
          } else {
            window.open(href);
          }
        }
        else
        {
          // Execute view commands
          var exec = link.getAttribute("exec");
          if (exec)
          {
            // this.debug("Follow EXEC: " + exec);
            this.exec(exec, link);
          }
          else
          {
            // Do advanced processing
            var path = link.getAttribute("goto");
            var rel = link.getAttribute("rel");

            // this.debug("Follow GOTO: " + path);

            if (rel == null)
            {
              this.enter(path);
            }
            else
            {
              var method = this.__followMapper[rel];
              if (method) {
                this[method](path);
              } else if (qx.core.Variant.isSet("qx.debug", "on")) {
                this.error("Unsupported 'rel' attribute: " + rel);
              }
            }
          }
        }
      }
      catch(ex)
      {
        this.error("Could not follow link " + (href||exec||rel));
        this.error(ex.message);
      }

      this.debug("Executed in: " + (new Date - start) + "ms");
    },

    /** {Map} Internal helper to map rel attributes to method names to execute */
    __followMapper :
    {
      param : "parametize",
      up : "up",
      parent : "replace",
      root : "jump",
      swap : "swap",
      segment : "segment"
    },

    /*
    ---------------------------------------------------------------------------
      USER API: LINK SUPPORT
    ---------------------------------------------------------------------------
    */

    /**
     * Jumps to the specified path without respecting current position.
     *
     * @param path {String} Any valid path
     */
    jump : function(path) {
      unify.bom.History.getInstance().jump(path);
    },

    /**
     * Executes the given function on the current view
     *
     * @param func {String} Name of function to execute (needs to be a public function!)
     * @param target {Element} DOM element which was clicked onto
     * @return {var} Return value of function to execute
     */
    exec : function(func, target)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (func.indexOf("_") == 0) {
          throw new Error("Could not execute protected or private methods: " + func);
        }
      }

      if (view && view[func]) {
        return view[func](target);
      } else if (qx.core.Variant.isSet("qx.debug", "on")) {
        throw new Error("Executing " + func + "() not supported by current view!");
      }
    },

    /**
     * Enters the specified view (+param). This leads deeper into the navigation
     * structure e.g. "root" + "page1" => "root/page1"
     *
     * @param view {String} View to enter (with optional param)
     */
    enter : function(view)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (view.indexOf("/") != -1) {
          throw new Error("Invalid path to dive into: " + view);
        }
      }

      unify.bom.History.getInstance().jump(this.__location + "/" + view);
    },

    /**
     * Switches in the current view to the given segment
     *
     * @param segment {String} Name of segment
     */
    segment : function(segment)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (segment.indexOf("/") != -1 || segment.indexOf(":") != -1 || segment.indexOf(".") != -1) {
          throw new Error("Invalid param for segment(): " + segment);
        }
      }

      // Create and configure clone
      var clone = new unify.view.mobile.navigation.Path;
      clone.setLocation(this.__path.getLocation());
      clone.setSegment(segment);

      // Jump in history
      unify.bom.History.getInstance().jump(clone.toString());
    },

    /**
     * Paremetize the current view with the given parameter. This leads to a refreshment
     * of the current view.
     *
     * @param param {String|Number} Any valid param
     */
    parametize : function(param)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (param.indexOf("/") != -1 || param.indexOf(":") != -1 || param.indexOf(".") != -1) {
          throw new Error("Invalid param for parametize(): " + param);
        }
      }

      // Create and configure clone
      var clone = new unify.view.mobile.navigation.Path;
      clone.setLocation(this.__path.getLocation());
      clone.setParam(param);

      // Jump in history
      unify.bom.History.getInstance().jump(clone.toString());
    },

    /**
     * Replaces current view with the given view (+param) on the same parent
     *
     * @param view {String} Any valid view (with optional params)
     */
    replace : function(view)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (view.indexOf("/") != -1) {
          throw new Error("Invalid view for replace(): " + view);
        }
      }

      var current = this.__path.toString();
      var pos = current.lastIndexOf("/");
      if (pos > 0) {
        view = current.substring(0, pos+1) + view;
      }

      unify.bom.History.getInstance().jump(view);
    },

    /**
     * Jumps up a segment in the history structure
     */
    up : function()
    {
      var current = this.__path.toString();
      var pos = current.lastIndexOf("/");
      if (pos > 0) {
        unify.bom.History.getInstance().jump(current.substring(0, pos));
      } else if (qx.core.Variant.isSet("qx.debug", "on")) {
        throw new Error("Could not go up further!");
      }
    },

    /**
     * Event listener for Android back button
     *
     * @param e {qx.event.type.Event} Event object
     */
    __onAndroidBackButton : function(e)
    {
      var current = this.__path.toString();
      var pos = current.lastIndexOf("/");
      if (pos > 0)
      {
        // Go up in unify hierarchy
        this.up();
      }
      else
      {
        // Finish (native) activity
        var DroidGap = window.DroidGap;
        if (DroidGap) {
          DroidGap.nativeBackButton();
        }
      }
    },

    /**
     * Switches the complete path to another path, but keeping the
     * original path in a cache to jump back to the complete structure.
     *
     * This feature is especially useful for TabViews where we have
     * some kind of sub-navigation in each Tab.
     *
     * @param view {String} Indentifier of view to switch to e.g. the name of the Tab
     */
    swap : function(view)
    {
      if (qx.core.Variant.isSet("qx.debug", "on"))
      {
        if (view.indexOf("/") != -1 || view.indexOf(":") != -1 || view.indexOf(".") != -1) {
          throw new Error("Invalid view for swap(): " + view);
        }
      }

      var switchData = this.__switchData;

      // Store current location
      var oldPath = this.__path;
      var oldRootView = oldPath.getView(0);
      switchData[oldRootView] = oldPath.toString();

      // Jump to new location or to root, defending on current path
      var History = unify.bom.History.getInstance();
      var restore = switchData[view];
      if (restore && view != oldRootView) {
        History.jump(restore);
      } else {
        History.jump(view);
      }
    },

    /*
    ---------------------------------------------------------------------------
      INTERNALS
    ---------------------------------------------------------------------------
    */

    __path : null,

    /**
     * Computes the differences between to {@link Path} and returns
     * a value describing these differences.
     *
     * * equal: both path are equal
     * * reset: current path is null
     * * initial: previous path is null
     * * swap: root view was changed via e.g. TabBar
     * * in: new path is deeper in hierarchy than previous one
     * * out: new path is lower in hiearchy than previous one
     * * replace: view was replaced with other view with same parent
     * * segment: only the segment in the view was replaced
     * * param: only the parameter of view was replaced
     * * null: other unknown change
     *
     * @param value {Path} Current path
     * @param old {Path} Previous path
     * @return {String} The computed mode
     */
    __computeMode : function(value, old)
    {
      if (value == old) {
        return "equal";
      }

      if (!value || !old)
      {
        if (value) {
          return "initial";
        } else {
          return "reset";
        }
      }

      if (value.toString() === old.toString()) {
        return "equal";
      } else if (value.getView(0) != old.getView(0)) {
        return "swap";
      } else if (value.getSize() != old.getSize()) {
        return value.getSize() < old.getSize() ? "out" : "in";
      } else if (value.getView() != old.getView()) {
        return "replace";
      } else if (value.getSegment() != old.getSegment()) {
        return "segment";
      } else if (value.getParam() != old.getParam()) {
        return "param";
      } else {
        return null;
      }
    },

    __viewManagers : null,
    __defaultView : null,
    __location : null,

    __urlMatcher : /^([a-z-]+)(\.([a-z-]+))?(\:([a-zA-Z0-9_-]+))?$/,

    /**
     * Reacts on changes of the browser history.
     *
     * @param e {unify.event.type.History} History event
     */
    __onHistoryChange : function(e)
    {
      var History = e.getTarget();

      // Quick check: Don't allow empty paths
      var currentLocation = decodeURI(e.getLocation());
      if (currentLocation == "")
      {
        this.warn("Ignoring empty path");
        return;
      }

      var oldLocation = this.__location || "";
      if (currentLocation == oldLocation) {
        return;
      }
      this.__location = currentLocation;

      if (window.localStorage)
      {
        try
        {
          // deleting first is required on iPad with OS 3.2
          delete localStorage["unify/navigationpath"];
          localStorage["unify/navigationpath"] = decodeURI(currentLocation);
        }
        catch(ex) {
          this.warn("Could not store path: " + ex);
        }
      }

      this.debug("History Change: \"" + oldLocation + "\" => \"" + currentLocation + "\"");

      var currentSplits = currentLocation == "" ? [] : currentLocation.split("/");
      var oldSplits = oldLocation == "" ? [] : oldLocation.split("/");

      // Create new data structure mapping managers to their individual structure
      var managers = this.__viewManagers;
      var structure = {};
      for (var id in managers) {
        structure[id] = [];
      }

      // Process current path
      for (var i=0, l=currentSplits.length; i<l; i++)
      {
        var match = this.__urlMatcher.exec(currentSplits[i]);
        var view = RegExp.$1;
        var segment = RegExp.$3;
        var param = RegExp.$5;

        for (var id in managers)
        {
          var viewClass = managers[id].getById(view);
          if (viewClass)
          {
            structure[id].push({
              view : viewClass,
              segment : segment,
              param : param
            });

            view = null;
            break;
          }
        }

        if (view) {
          throw new Error("Unknown view: " + view);
        }
      }

      // Update managers
      for (var id in structure) {
        managers[id].update(structure[id]);
      }
    }
  },

  /*
  *****************************************************************************
     DESTRUCTOR
  *****************************************************************************
  */

  destruct : function()
  {
    var History = unify.bom.History.getInstance();
    History.removeListener("change", this.__onHistoryChange, this);
  }
});
<|endoftext|>


<commit_before>/*

PROJECT: Object Oriented Office (OOO)
SCRIPT: ooo_chair.js
VERSION: 0.02
FUNCTION: Operating code, via the Johhny-Five framework,
  for collecting data from embedded sensors in an office
  chair, and determining whether the chair is occupied and
  how it is oriented.
SENSORS: FSR (force-sensitive resistor), Magnetometer (compass)

ISSUES:
  (1) Determine Threshold Values
  (2) Working Magnetometer
*/

var five = require("johnny-five"),
    board, fsr1, fsr2, fsr3, fsr4, mag;
    //led;

//array of output buffers
var buffers = [];
//array of sensors
var fsrs = [];
//array of thresholds values
var thresholds = [];
//length of buffer array
var MAX_BUFFER_LENGTH = 5;

//function for grabbing minimum value from buffer array
function getMin( buf ){
  var rtn = buf[0];
  for(var i = 1; i < buf.length; i++){
    rtn = Math.min( rtn, buf[i] );
  }
  return rtn;
}

//function for grabbing maximum value from buffer array
function getMax( buf ){
  var rtn = buf[0];
  for(var i = 1; i < buf.length; i++){
    rtn = Math.max( rtn, buf[i] );
  }
  return rtn;
}

//function for determining occupancy through thresholds
function occupied( fsr_index, value ){
  //if buffer is full, push out last value (first in first out)
  if(buffers[ fsr_index ].length > MAX_BUFFER_LENGTH){
    buffers[ fsr_index ].shift();
  }
  //add current value to buffer
  buffers[ fsr_index ].push( value );

  //grab minimum value
  var min;
  if(buffers[ fsr_index ].length > 0){
    min = getMin( buffers[ fsr_index ] );
  }else{
    min = value;
  }

  //grab maximum value
  var max;
  if(buffers[ fsr_index ].length > 0){
    max = getMax( buffers[ fsr_index ] );
  }else{
    max = value;
  }

  //determine value range
  var delta = max - min;

  //console.log(delta);

  //check if value range is within occupancy threshold
  if(delta > thresholds[ fsr_index ]){
    return true;
  }else{
    return false;
  }
}

(new five.Board()).on("ready", function() {

  //FSR 00
  fsrs[0] = new five.Sensor({
    pin: "A0",
    freq: 250
  });

  buffers[0] = [];
  thresholds[0] = 70;

  fsrs[0].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);
    var oc = occupied( 0, this.value );
    if(oc == true){
      console.log( 'occupied fsr 0' );
    }
  });

  //FSR 01
  fsrs[1] = new five.Sensor({
    pin: "A1",
    freq: 250
  });

  buffers[1] = [];
  thresholds[1] = 80;

  fsrs[1].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);
    var oc = occupied( 1, this.value );
    if(oc == true){
      console.log( 'occupied fsr 1' );
    }
  });

  /*
  //FSR 02
  fsrs[2] = new five.Sensor({
    pin: "A3",
    freq: 100
  });

  buffers[2] = [];
  thresholds[2] = 80;

  fsrs[2].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);
    var oc = occupied( 1, this.value );
    if(oc == true){
      console.log( 'occupied fsr 2' );
    }
  });

  //FSR 03
  fsrs[3] = new five.Sensor({
    pin: "A4",
    freq: 100
  });

  buffers[3] = [];
  thresholds[3] = 80;

  fsrs[3].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);

    var oc = occupied( 1, this.value );

    if(oc == true){
      console.log( 'occupied fsr 2' );
    }
  });
  */

});<commit_msg>changed read rate on FSR sensors<commit_after>/*

PROJECT: Object Oriented Office (OOO)
SCRIPT: ooo_chair.js
VERSION: 0.02
FUNCTION: Operating code, via the Johhny-Five framework,
  for collecting data from embedded sensors in an office
  chair, and determining whether the chair is occupied and
  how it is oriented.
SENSORS: FSR (force-sensitive resistor), Magnetometer (compass)

ISSUES:
  (1) Determine Threshold Values
  (2) Working Magnetometer
*/

var five = require("johnny-five"),
    board, fsr1, fsr2, fsr3, fsr4, mag;
    //led;

//array of output buffers
var buffers = [];
//array of sensors
var fsrs = [];
//array of thresholds values
var thresholds = [];
//length of buffer array
var MAX_BUFFER_LENGTH = 5;

//function for grabbing minimum value from buffer array
function getMin( buf ){
  var rtn = buf[0];
  for(var i = 1; i < buf.length; i++){
    rtn = Math.min( rtn, buf[i] );
  }
  return rtn;
}

//function for grabbing maximum value from buffer array
function getMax( buf ){
  var rtn = buf[0];
  for(var i = 1; i < buf.length; i++){
    rtn = Math.max( rtn, buf[i] );
  }
  return rtn;
}

//function for determining occupancy through thresholds
function occupied( fsr_index, value ){
  //if buffer is full, push out last value (first in first out)
  if(buffers[ fsr_index ].length > MAX_BUFFER_LENGTH){
    buffers[ fsr_index ].shift();
  }
  //add current value to buffer
  buffers[ fsr_index ].push( value );

  //grab minimum value
  var min;
  if(buffers[ fsr_index ].length > 0){
    min = getMin( buffers[ fsr_index ] );
  }else{
    min = value;
  }

  //grab maximum value
  var max;
  if(buffers[ fsr_index ].length > 0){
    max = getMax( buffers[ fsr_index ] );
  }else{
    max = value;
  }

  //determine value range
  var delta = max - min;

  //console.log(delta);

  //check if value range is within occupancy threshold
  if(delta > thresholds[ fsr_index ]){
    return true;
  }else{
    return false;
  }
}

(new five.Board()).on("ready", function() {

  //FSR 00
  fsrs[0] = new five.Sensor({
    pin: "A0",
    freq: 100
  });

  buffers[0] = [];
  thresholds[0] = 70;

  fsrs[0].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);
    var oc = occupied( 0, this.value );
    if(oc == true){
      console.log( 'occupied fsr 0' );
    }
  });

  //FSR 01
  fsrs[1] = new five.Sensor({
    pin: "A1",
    freq: 100
  });

  buffers[1] = [];
  thresholds[1] = 80;

  fsrs[1].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);
    var oc = occupied( 1, this.value );
    if(oc == true){
      console.log( 'occupied fsr 1' );
    }
  });

  /*
  //FSR 02
  fsrs[2] = new five.Sensor({
    pin: "A3",
    freq: 100
  });

  buffers[2] = [];
  thresholds[2] = 80;

  fsrs[2].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);
    var oc = occupied( 1, this.value );
    if(oc == true){
      console.log( 'occupied fsr 2' );
    }
  });

  //FSR 03
  fsrs[3] = new five.Sensor({
    pin: "A4",
    freq: 100
  });

  buffers[3] = [];
  thresholds[3] = 80;

  fsrs[3].scale([ 0, 100 ]).on("data",function(){
    //console.log("seat_left: " + this.value);

    var oc = occupied( 1, this.value );

    if(oc == true){
      console.log( 'occupied fsr 2' );
    }
  });
  */

});<|endoftext|>


<commit_before>import debounce from 'lodash.debounce';
import forEach from 'lodash.foreach';
import hoistStatics from 'hoist-non-react-statics';
import isEqual from 'lodash.isequal';
import warning from 'warning';
import Q from 'q';
import React, {Component, PropTypes} from 'react';
import {mergeFragments, buildQueryAndVariables} from 'relax-fragments';

import queryAction from '../actions/query';

export default function rootDataConnect (config) {
  return function wrapWithDataConnect (WrappedComponent) {
    class RootConnectData extends Component {
      static contextTypes = {
        store: PropTypes.any.isRequired,
        relate_ssr: PropTypes.func
      };

      static childContextTypes = {
        fetchData: PropTypes.func.isRequired
      };

      static relateIdentifier = 'ROOT_DATA_CONNECT';

      constructor (props, context) {
        super(props, context);
        this.bundle = {};
        this.childFetchDataBind = ::this.childFetchData;
        this.fetchDebounce = debounce(::this.fetchData, 10);
        this.scopeID = 0;
      }

      getChildContext () {
        return {
          fetchData: this.childFetchDataBind
        };
      }

      componentDidMount () {
        this.mounted = true;
        if (this.bundle && this.bundle.fragments) {
          this.fetchData();
        }
      }

      childFetchData ({fragments, variables = {}, ID, mutations, loadMore = false}) {
        // Check for same query with different variables
        const scopes = {};
        const resultFragments = Object.assign({}, fragments || {});
        const resultVariables = Object.assign({}, variables || {});

        if (this.bundle.fragments) {
          forEach(fragments, (fragment, queryName) => {
            if (this.bundle.fragments[queryName]) {
              // Same query name detected, will have to check if variables are the same
              const sameVariables = isEqual(
                variables && variables[queryName],
                this.bundle.variables && this.bundle.variables[queryName]
              );

              if (!sameVariables) {
                // Will have to scope it
                const scope = `relate_${this.scopeID++}`;
                const newQueryName = `${scope}: ${queryName}`;
                resultFragments[newQueryName] = Object.assign({}, fragments[queryName]);
                scopes[scope] = queryName;
                delete resultFragments[queryName];

                if (resultVariables[queryName]) {
                  resultVariables[newQueryName] = Object.assign({}, variables[queryName]);
                  delete resultVariables[queryName];
                }
              }
            }
          });
        }

        this.bundle = {
          fragments: mergeFragments(this.bundle.fragments || {}, resultFragments),
          variables: Object.assign(this.bundle.variables || {}, resultVariables),
          connectors: Object.assign(this.bundle.connectors || {}, {
            [ID]: {fragments, variables, mutations, scopes, loadMore}
          }),
          scopes: Object.assign(this.bundle.scopes || {}, scopes)
        };

        let result = null;
        if (!this.context || !this.context.relate_ssr) {
          if (this.fetchingData && this.deferred) {
            if (!this.nextDeferred) {
              this.nextDeferred = Q.defer();

              this.deferred.promise.fin(() => {
                this.deferred = this.nextDeferred;
                this.nextDeferred = null;
                this.fetchData();
              });
            }

            result = this.nextDeferred.promise;
          } else {
            if (this.mounted) {
              this.fetchDebounce();
            }

            this.deferred = this.deferred || Q.defer();
            result = this.deferred.promise;
          }
        }

        return result;
      }

      fetchData () {
        const {store, relate_ssr} = this.context;
        const {dispatch} = store;
        const {fragments, variables, connectors, scopes} = this.bundle;
        this.bundle = {};
        this.deferred = this.deferred || Q.defer();

        if (fragments && Object.keys(fragments).length) {
          this.fetchingData = true;
          dispatch(
            queryAction(
              buildQueryAndVariables(fragments, variables),
              fragments,
              connectors,
              scopes,
              config,
              relate_ssr
            )
          ).then((data) => {
            this.deferred.resolve(data);
          }).catch((err) => {
            warning(false, err);
            this.deferred.reject();
          }).fin(() => {
            this.deferred = null;
            this.fetchingData = false;
          });
        } else {
          this.deferred.resolve();
          this.fetchingData = false;
        }

        return this.deferred.promise;
      }

      render () {
        return <WrappedComponent {...this.props} />;
      }
    }

    return hoistStatics(RootConnectData, WrappedComponent, {
      relateIdentifier: true
    });
  };
}
<commit_msg>root data connect config function option<commit_after>import debounce from 'lodash.debounce';
import forEach from 'lodash.foreach';
import hoistStatics from 'hoist-non-react-statics';
import isEqual from 'lodash.isequal';
import warning from 'warning';
import Q from 'q';
import React, {Component, PropTypes} from 'react';
import {mergeFragments, buildQueryAndVariables} from 'relax-fragments';

import queryAction from '../actions/query';

export default function rootDataConnect (config) {
  return function wrapWithDataConnect (WrappedComponent) {
    class RootConnectData extends Component {
      static contextTypes = {
        store: PropTypes.any.isRequired,
        relate_ssr: PropTypes.func
      };

      static childContextTypes = {
        fetchData: PropTypes.func.isRequired
      };

      static relateIdentifier = 'ROOT_DATA_CONNECT';

      constructor (props, context) {
        super(props, context);
        this.bundle = {};
        this.childFetchDataBind = ::this.childFetchData;
        this.fetchDebounce = debounce(::this.fetchData, 10);
        this.scopeID = 0;
      }

      getChildContext () {
        return {
          fetchData: this.childFetchDataBind
        };
      }

      componentDidMount () {
        this.mounted = true;
        if (this.bundle && this.bundle.fragments) {
          this.fetchData();
        }
      }

      childFetchData ({fragments, variables = {}, ID, mutations, loadMore = false}) {
        // Check for same query with different variables
        const scopes = {};
        const resultFragments = Object.assign({}, fragments || {});
        const resultVariables = Object.assign({}, variables || {});

        if (this.bundle.fragments) {
          forEach(fragments, (fragment, queryName) => {
            if (this.bundle.fragments[queryName]) {
              // Same query name detected, will have to check if variables are the same
              const sameVariables = isEqual(
                variables && variables[queryName],
                this.bundle.variables && this.bundle.variables[queryName]
              );

              if (!sameVariables) {
                // Will have to scope it
                const scope = `relate_${this.scopeID++}`;
                const newQueryName = `${scope}: ${queryName}`;
                resultFragments[newQueryName] = Object.assign({}, fragments[queryName]);
                scopes[scope] = queryName;
                delete resultFragments[queryName];

                if (resultVariables[queryName]) {
                  resultVariables[newQueryName] = Object.assign({}, variables[queryName]);
                  delete resultVariables[queryName];
                }
              }
            }
          });
        }

        this.bundle = {
          fragments: mergeFragments(this.bundle.fragments || {}, resultFragments),
          variables: Object.assign(this.bundle.variables || {}, resultVariables),
          connectors: Object.assign(this.bundle.connectors || {}, {
            [ID]: {fragments, variables, mutations, scopes, loadMore}
          }),
          scopes: Object.assign(this.bundle.scopes || {}, scopes)
        };

        let result = null;
        if (!this.context || !this.context.relate_ssr) {
          if (this.fetchingData && this.deferred) {
            if (!this.nextDeferred) {
              this.nextDeferred = Q.defer();

              this.deferred.promise.fin(() => {
                this.deferred = this.nextDeferred;
                this.nextDeferred = null;
                this.fetchData();
              });
            }

            result = this.nextDeferred.promise;
          } else {
            if (this.mounted) {
              this.fetchDebounce();
            }

            this.deferred = this.deferred || Q.defer();
            result = this.deferred.promise;
          }
        }

        return result;
      }

      fetchData () {
        const {store, relate_ssr} = this.context;
        const {dispatch} = store;
        const {fragments, variables, connectors, scopes} = this.bundle;
        this.bundle = {};
        this.deferred = this.deferred || Q.defer();

        if (fragments && Object.keys(fragments).length) {
          this.fetchingData = true;
          dispatch(
            queryAction(
              buildQueryAndVariables(fragments, variables),
              fragments,
              connectors,
              scopes,
              typeof config === 'function' ? config(this.props) : config,
              relate_ssr
            )
          ).then((data) => {
            this.deferred.resolve(data);
          }).catch((err) => {
            warning(false, err);
            this.deferred.reject();
          }).fin(() => {
            this.deferred = null;
            this.fetchingData = false;
          });
        } else {
          this.deferred.resolve();
          this.fetchingData = false;
        }

        return this.deferred.promise;
      }

      render () {
        return <WrappedComponent {...this.props} />;
      }
    }

    return hoistStatics(RootConnectData, WrappedComponent, {
      relateIdentifier: true
    });
  };
}
<|endoftext|>


<commit_before>(function () {
  'use strict';

var express = require('express');
var controller = require('./user.ctrl');
var config = require('../../config/environment');
var auth = require('../../auth/auth.service');

var router = express.Router();

//think of '/api/user' +'xxx'

router.get('/', auth.hasRole('admin'), controller.index);
router.delete('/:id', auth.hasRole('admin'), controller.destroy);
router.get('/me', auth.isAuthenticated(), controller.me);
router.put('/:id/password', auth.isAuthenticated(), controller.changePassword);
router.get('/:id', auth.isAuthenticated(), controller.show);
router.post('/', controller.create);

//Mike
// router.get('/', controller.getAllUsers);
// router.get('/:memberId', controller.findByID);
router.get('-details/:memberId', controller.getUserDetails);
router.get('-logos/:memberId', controller.getUserLogos);
router.get('-pathos/:memberId', controller.getUserPathos);
router.get('-ethos/:memberId', controller.getUserEthos);

// router.post('-add', controller.addUser);
router.put('-update', controller.updateUser);
router.put('-update-isadmin');
router.put('-update-isactive' );

module.exports = router;

}());
<commit_msg>added user api route<commit_after>(function () {
  'use strict';

var express = require('express');
var controller = require('./user.ctrl');
var config = require('../../config/environment');
var auth = require('../../auth/auth.service');

var router = express.Router();

//think of '/api/user' +'xxx'

router.get('/', auth.hasRole('admin'), controller.index);
router.delete('/:id', auth.hasRole('admin'), controller.destroy);
router.get('/me', auth.isAuthenticated(), controller.me);
router.put('/:id/password', auth.isAuthenticated(), controller.changePassword);
router.get('/:id', auth.isAuthenticated(), controller.show);
router.post('/', controller.create);

//Mike
// router.get('/', controller.getAllUsers);
// router.get('/:memberId', controller.findByID);
router.get('-details/:memberId', controller.getUserDetails);
router.get('-logos/:memberId', controller.getUserLogos);
router.get('-logos/physical', controller.getUserLogos);
router.get('-pathos/:memberId', controller.getUserPathos);
router.get('-ethos/:memberId', controller.getUserEthos);

// router.post('-add', controller.addUser);
router.put('-update', controller.updateUser);
router.put('-update-isadmin');
router.put('-update-isactive' );

module.exports = router;

}());
<|endoftext|>