<commit_before>var Q           = require('q'),
    COLORS      = require('colors'),
    BENCHMARK   = require('benchmark'),
    FS          = require('fs'),
    VM          = require('vm'),
    PATH        = require('path'),
    CP          = require('child_process'),
    INHERIT     = require('inherit'),
    LOGGER      = require('./logger'),
    LEVEL       = require('./level'),
    U           = require('./util'),

    Table       = require('cli-table');

/**
 * Benchmark - testing on BEMHTML templates performance between revisions
 */
var Benchmark = INHERIT({

    __constructor: function(opts, args){

        this.pathTmp        = '.bem/cache/bench/';
        this.pathSelf       = 'current_state';
        this.pathBenchmarks = 'benchmark.bundles';
        this.treeishList    = args['treeish-list'];
        this.withoutCurrent = opts['no-wc'];
        this.noMake         = opts['no-make'];
        this.benchmarks     = opts.benchmarks;

        this.DELAY             = opts['delay'] ? opts['delay'] : 20;
        this.WARMIN_CYCLE      = 100;
        this.ALLOWABLE_PERCENT = opts['delta-rme'] ? opts['delta-rme'] : 5;

    },

    /**
     * Extract one revision to pathTmp folder
     *
     * @param  {String} treeish  Hash, HEAD, tag...
     * @return {Promise * String}  Path to temporary revision
     */
    exportGitRevision: function(treeish) {

        var def = Q.defer(),
            label = 'Export git revision [' + treeish.green + ']',
            archiveErr = '',
            extractErr = '',
            extract,
            archive;

        LOGGER.time(label);

        archive = CP.spawn('git', [
            'archive',
            '--format=tar',
            '--prefix=' + this.pathTmp + '/' + treeish + '/',
            treeish
        ]);
        extract = CP.spawn('tar', ['-x']);

        archive.stdout.on('data', function(data) {
            extract.stdin.write(data);
        });

        archive.stderr.on('data', function(err) {
            archiveErr += err.toString().replace(/(\n)$/, '');
        });

        archive.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + archiveErr);
                def.reject(new Error(archiveErr));
            } else {
                LOGGER.timeEnd(label);
                def.resolve(treeish);
            }
            extract.stdin.end();
        });

        extract.stderr.on('data', function(err) {
            extractErr += err.toString().replace(/(\n)$/, '');
        });

        extract.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + extractErr);
                def.reject(new Error(extractErr));
            }
        });

        return def.promise;

    },

    /**
     * Copy current files state with exclude
     *
     * @return {Promise * String}  Path to files
     */
    exportWorkingCopy: function() {

        var self = this,
            label = 'Export working copy [' + self.pathSelf.green + ']',
            cmd = [
                'rsync',
                '-av',
                '--exclude=node_modules',
                '--exclude=bench',
                '--exclude=bem-bl',
                '--exclude=.git',
                '.',
                self.pathTmp + self.pathSelf
            ].join(' ');

        LOGGER.time(label);

        return U.exec(cmd)
            .fail(function(err) {
                LOGGER.error('exportWorkingCopy - '.blue + err);
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
                return self.pathSelf;
            });

    },

    /**
     *  Getting all paths to .bemjson.js file on needed level
     *
     *  @param  {String} treeishPath  Checked out treeish path
     *  @param  {String} levelPath  level path
     *  @return {Array}  Array of links
     */
    getBenchmarksPaths: function(treeishPath, levelPath) {

        var self = this,
            level =  LEVEL.createLevel(PATH.join(treeishPath, levelPath)),
            benchmarks;

        if (self.benchmarks && self.benchmarks.length) {
            // if -b flag detected then compare not all benchmarks
            benchmarks = U.arrayUnique(self.benchmarks)
                .map(function(b) {
                    return U.bemParseKey(b);
                });
        }
        else {
            // if -b flag not detected then all benchmarks
            benchmarks = level.getItemsByIntrospection()
                .filter(function(item) {
                    return item.tech == 'bemjson.js';
                });
        }

        return benchmarks
            .map(function(benchmark) {
                return level.getPathByObj(benchmark, 'bemjson.js');
            });

    },

    /**
     * Make one target and return all links on blocks
     *
     * @param  {String} target  Path to target
     * @return {Array}  All links of target
     */
    makeTarget: function(target) {

        var self = this,
            targets = [self.pathBenchmarks],
            label = '[' + target.green + '] has been assembled';

        LOGGER.time(label);

        if (self.benchmarks && self.benchmarks.length) {
            targets = self.benchmarks
                .map(function(b) {
                    return PATH.join(self.pathBenchmarks, b);
                });
        }

        return U.exec('cd ' + PATH.join(self.pathTmp, target) + ' && bem make ' + targets.join(' '))
            .fail(function(err) {
                LOGGER.error(target.red + ' not maked');
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Make all targets and run benchmarks for each target
     *
     * @param  {Array} targets  List of targets
     * @return {Promise * Undefined}  Object with tests results
     */
    make: function(targets) {
        return Q.all(targets.map(this.makeTarget.bind(this)))
            .then(function() {});
    },

    /**
     * Active waiting
     *
     * @param  {Number} seconds  Time in seconds to wait
     * @return {Promise * Undefined}
     */
    activeWait: function(seconds) {

        if (seconds == 0) return;

        var dt = new Date().getTime(),
            title = seconds + 'sec';

        LOGGER.info('Delay ' + title.red);
        while (dt + (seconds * 1000) > new Date().getTime()) {}

    },

    /**
     * Run all benchmarks of the single target
     *
     * @param  {Array} links  Links to source bemjson files
     * @param  {String} target  Revision name
     * @return {Promise * Object}  Object with tests results
     */
    bench: function(links, target) {

        var self = this;

        return Q.all(links.map(function(link) {

            var def = Q.defer(),
                bemjson = FS.readFileSync(link , 'UTF-8'),
                bemhtml = require(link.replace(/.bemjson.js/, '.bemhtml.js')),
                context = VM.createContext({}),
                res = VM.runInContext(bemjson, context, link),
                name = link.match(/([^\/]+)$/)[0],
                name_short = name.replace('.bemjson.js',''),
                suite = new BENCHMARK.Suite(),

                label = '[' + target.green + ' => ' +name_short.blue+ '] has been tested';

            LOGGER.time(label);

            // Warming-up for the best results
            (function() {
                var i = self.WARMIN_CYCLE;
                while (i--) bemhtml.BEMHTML.apply(res);
            })();

            suite
                .add(name, function() {
                    bemhtml.BEMHTML.apply(res);
                })
                .on('complete', function(event) {
                    LOGGER.timeEnd(label);
                    def.resolve({
                        'name' : String(event.target.name),
                        'hz'   : Number(Math.round(event.target.hz) / 1000).toFixed(1),
                        'rme'  : Number(event.target.stats.rme).toFixed(1),
                        'runs' : event.target.stats.sample.length
                    });
                })
                .run({ 'async': false });

            return def.promise;

        }))
        .then(function(res) {
            return [target].concat(res);
        });

    },

    /**
     * Extract all revisions and mark that it exist
     *
     * @return {Promise * Undefined}
     */
    exportGitRevisions: function() {

        var self = this;

        return self.treeishList
            .map(function(treeish) {
                return self.exportGitRevision(treeish);
            });

    },

    /**
     * Copy reference benchmarks from specified source into every checked out revision
     *
     * @param  {String[]} targets  Array of target names
     * @param  {String} source  Path to benchmarks source
     * @return {Promise * Undefined}
     */
    cloneBenchmarks: function(source, targets) {

        targets = [].concat(targets);

        var self = this,
            search = targets.indexOf(source),

            onFail = function(err) {
                LOGGER.error('cloneBenchmarks - '.blue + err);
                return Q.reject(err);
            };

        if (search !== -1) {
            targets.splice(search, 1);
        }

        return Q.all(targets.map(function(target) {

            var cmd = 'rm -rf ' + PATH.join(self.pathTmp, target, self.pathBenchmarks);

            return U.exec(cmd)
                .fail(onFail)
                .then(function() {

                    var cmd = [
                        'cp',
                        '-R',
                        PATH.join(self.pathTmp, source, self.pathBenchmarks + '*'),
                        PATH.join(self.pathTmp, target, self.pathBenchmarks)
                    ].join(' ');

                    return U.exec(cmd)
                        .fail(onFail);

                });

        }));

    },

    /**
     * Return latest treeish compared at date
     *
     * @return {String}  Latest treeish
     */
    getLatestTreeish: function() {

        var self = this;

        if (!self.withoutCurrent) {
            LOGGER.info('Latest revision is - ', self.pathSelf.magenta);
            return Q.resolve(self.pathSelf);
        }

        return Q.all(self.treeishList
            .map(function(treeish) {

                var cmd = "git show " + treeish + " | grep Date | awk -F':   ' '{print $2}'";

                return U.exec(cmd, null, true)
                    .then(function(output) {
                        return {
                            treeish: treeish,
                            date: output.replace('\n', '')
                        };
                    });

            })
        )
        .then(function(dates) {

            var maxTime = 0,
                lastTreeish;

            dates.forEach(function(dt) {
                var time = new Date(dt.date).getTime();
                if (time > maxTime) {
                    maxTime = time;
                    lastTreeish = dt.treeish;
                }
            });

            LOGGER.info('Latest revision is - ' + lastTreeish.magenta);

            return lastTreeish;

        });

    },

    /**
     * Sort results by command line order
     *
     * @param  {Object} res  Results data
     * @param  {String[]} targets  Array of target names
     * @return {Array}  Sorted results data
     */
    sortResult: function(res, targets) {
        var sorted = [];

        for (var i = 0; i < res.length; i++) {
            for (var j = 0; j < res.length; j++) {
                if (res[j][0] === targets[i]) {
                    sorted.push(res[j]);
                    break;
                }
            }
        }

        return sorted;
    },

    /**
     * Construct cli table object with benchmarks results
     *
     * @param  {Object} results  Object with test results
     * @param  {String[]} targets  Array of target names
     * @return {Object}  cli table object
     */
    getResultsTable: function(results, targets) {

        var sortedRes = this.sortResult(results, targets),
            header = [],
            data = [],
            rme = [],
            name,
            stat,
            perc,
            t;

        header.push(
            '№'.magenta,
            'benchmark'.yellow.underline);

        sortedRes.forEach(function(item) {
            header.push(item[0].blue + '(hz/rme)'.cyan);
        });

        for (var i = 1; i < sortedRes[0].length; i += 1) {
            name = undefined;
            t = [];
            rme = [];

            for (var j = 0; j < sortedRes.length; j++) {
                if (!name) name = sortedRes[j][i].name;

                if (sortedRes[j][i]) {
                    t.push('[' + sortedRes[j][i].hz.green + '] ±' + sortedRes[j][i].rme.magenta + '%');
                    rme.push(Number(sortedRes[j][i].rme));
                } else {
                    t.push('none');
                }
            }

            var max = Math.max.apply(null, rme),
                min = Math.min.apply(null, rme);

            if ((max - min) < this.ALLOWABLE_PERCENT) {
                perc = 'stable'.yellow.underline;
            } else {
                perc = 'unstable'.red.underline;
            }

            data.push([i, name.replace('.bemjson.js', '')]
                .concat(t)
                .concat(perc));
        }

        header.push('RME stat'.magenta);

        var table = new Table({
            head: header,
            style: {
                compact: true,
                'padding-left': 1,
                'padding-right': 1
            }
        });

        data.forEach(function(row) {
            table.push(row);
        });

        return table;

    },

    /**
     * Cleaning all repo from temporary folder
     *
     * @return {Promise * Undefined}
     */
    cleanTempDir: function() {

        var cmd = 'rm -rf ./' + this.pathTmp + '*',
            label = 'TMP folder has been cleaned';

        LOGGER.info('Cleaning a TMP folder');
        LOGGER.time(label);

        return U.exec(cmd)
            .fin(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Main flow
     *
     * @return {Promise}  Promise of benchmarks finish
     */
    start: function() {

        var self = this,
            bcsInclude = 'Include ' + String(this.pathSelf).magenta,
            bcsExclude = 'Exclude ' + String(this.pathSelf).magenta,
            targets = [].concat(self.treeishList);

        LOGGER.time('All time');

        if (!self.withoutCurrent) {
            LOGGER.info(bcsInclude);
            targets.push(self.pathSelf);
        } else {
            LOGGER.info(bcsExclude);
        }

        return Q.resolve()
            .then(function() {

                if (self.noMake) {

                    // without make
                    LOGGER.info('[NO MAKE]'.magenta + ' mode');
                    return Q.resolve();

                }
                else {

                    // export and make revisions
                    return U.mkdirp(self.pathTmp)
                        .then(function() {
                            return self.cleanTempDir();
                        })
                        .then(function() {

                            var exports = self.exportGitRevisions();
                            if (!self.withoutCurrent) {
                                exports.push(self.exportWorkingCopy());
                            }

                            return Q.all(exports)
                                .then(function() {
                                    return self.getLatestTreeish()
                                        .then(function(rev) {
                                            return self.cloneBenchmarks(rev, targets);
                                        });
                                })
                                .then(function() {
                                    LOGGER.info('Make...');
                                    return self.make(targets);
                                });

                        });

                }

            })
            .then(function() {
                LOGGER.info('Benchmark...');

                return Q.all(targets
                    .map(function(target) {

                        var links = self.getBenchmarksPaths(
                            PATH.join(self.pathTmp, target),
                            self.pathBenchmarks);

                        self.activeWait(self.DELAY);
                        return self.bench(links, target);

                    })
                );

            })
            .then(function(res) {
                LOGGER.timeEnd('All time');
                return self.getResultsTable(res, targets);
            });

    }

});

/**
 * Create instance on Benchmark and share it
 *
 * @param  {Array} opts Options from COA
 * @param  {Array} args Arguments from COA
 * @return {Object} Benchmark instance
 */
module.exports = function(opts, args) {
    return new Benchmark(opts, args);
};
<commit_msg>Rename methods - 'make()' to 'makeAll()', 'bench()' to 'runBenchmark()'<commit_after>var Q           = require('q'),
    COLORS      = require('colors'),
    BENCHMARK   = require('benchmark'),
    FS          = require('fs'),
    VM          = require('vm'),
    PATH        = require('path'),
    CP          = require('child_process'),
    INHERIT     = require('inherit'),
    LOGGER      = require('./logger'),
    LEVEL       = require('./level'),
    U           = require('./util'),

    Table       = require('cli-table');

/**
 * Benchmark - testing on BEMHTML templates performance between revisions
 */
var Benchmark = INHERIT({

    __constructor: function(opts, args){

        this.pathTmp        = '.bem/cache/bench/';
        this.pathSelf       = 'current_state';
        this.pathBenchmarks = 'benchmark.bundles';
        this.treeishList    = args['treeish-list'];
        this.withoutCurrent = opts['no-wc'];
        this.noMake         = opts['no-make'];
        this.benchmarks     = opts.benchmarks;

        this.DELAY             = opts['delay'] ? opts['delay'] : 20;
        this.WARMIN_CYCLE      = 100;
        this.ALLOWABLE_PERCENT = opts['delta-rme'] ? opts['delta-rme'] : 5;

    },

    /**
     * Extract one revision to pathTmp folder
     *
     * @param  {String} treeish  Hash, HEAD, tag...
     * @return {Promise * String}  Path to temporary revision
     */
    exportGitRevision: function(treeish) {

        var def = Q.defer(),
            label = 'Export git revision [' + treeish.green + ']',
            archiveErr = '',
            extractErr = '',
            extract,
            archive;

        LOGGER.time(label);

        archive = CP.spawn('git', [
            'archive',
            '--format=tar',
            '--prefix=' + this.pathTmp + '/' + treeish + '/',
            treeish
        ]);
        extract = CP.spawn('tar', ['-x']);

        archive.stdout.on('data', function(data) {
            extract.stdin.write(data);
        });

        archive.stderr.on('data', function(err) {
            archiveErr += err.toString().replace(/(\n)$/, '');
        });

        archive.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + archiveErr);
                def.reject(new Error(archiveErr));
            } else {
                LOGGER.timeEnd(label);
                def.resolve(treeish);
            }
            extract.stdin.end();
        });

        extract.stderr.on('data', function(err) {
            extractErr += err.toString().replace(/(\n)$/, '');
        });

        extract.on('close', function(code) {
            if (code !== 0) {
                LOGGER.error('exportGitRevision - '.blue + '[' + treeish.green + '] ' + extractErr);
                def.reject(new Error(extractErr));
            }
        });

        return def.promise;

    },

    /**
     * Copy current files state with exclude
     *
     * @return {Promise * String}  Path to files
     */
    exportWorkingCopy: function() {

        var self = this,
            label = 'Export working copy [' + self.pathSelf.green + ']',
            cmd = [
                'rsync',
                '-av',
                '--exclude=node_modules',
                '--exclude=bench',
                '--exclude=bem-bl',
                '--exclude=.git',
                '.',
                self.pathTmp + self.pathSelf
            ].join(' ');

        LOGGER.time(label);

        return U.exec(cmd)
            .fail(function(err) {
                LOGGER.error('exportWorkingCopy - '.blue + err);
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
                return self.pathSelf;
            });

    },

    /**
     *  Getting all paths to .bemjson.js file on needed level
     *
     *  @param  {String} treeishPath  Checked out treeish path
     *  @param  {String} levelPath  level path
     *  @return {Array}  Array of links
     */
    getBenchmarksPaths: function(treeishPath, levelPath) {

        var self = this,
            level =  LEVEL.createLevel(PATH.join(treeishPath, levelPath)),
            benchmarks;

        if (self.benchmarks && self.benchmarks.length) {
            // if -b flag detected then compare not all benchmarks
            benchmarks = U.arrayUnique(self.benchmarks)
                .map(function(b) {
                    return U.bemParseKey(b);
                });
        }
        else {
            // if -b flag not detected then all benchmarks
            benchmarks = level.getItemsByIntrospection()
                .filter(function(item) {
                    return item.tech == 'bemjson.js';
                });
        }

        return benchmarks
            .map(function(benchmark) {
                return level.getPathByObj(benchmark, 'bemjson.js');
            });

    },

    /**
     * Make one target and return all links on blocks
     *
     * @param  {String} target  Path to target
     * @return {Array}  All links of target
     */
    makeTarget: function(target) {

        var self = this,
            targets = [self.pathBenchmarks],
            label = '[' + target.green + '] has been assembled';

        LOGGER.time(label);

        if (self.benchmarks && self.benchmarks.length) {
            targets = self.benchmarks
                .map(function(b) {
                    return PATH.join(self.pathBenchmarks, b);
                });
        }

        return U.exec('cd ' + PATH.join(self.pathTmp, target) + ' && bem make ' + targets.join(' '))
            .fail(function(err) {
                LOGGER.error(target.red + ' not make');
                return Q.reject(err);
            })
            .then(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Make all targets and run benchmarks for each target
     *
     * @param  {Array} targets  List of targets
     * @return {Promise * Undefined}  Object with tests results
     */
    makeAll: function(targets) {
        return Q.all(targets.map(this.makeTarget.bind(this)))
            .then(function() {});
    },

    /**
     * Active waiting
     *
     * @param  {Number} seconds  Time in seconds to wait
     * @return {Promise * Undefined}
     */
    activeWait: function(seconds) {

        if (seconds == 0) return;

        var dt = new Date().getTime(),
            title = seconds + 'sec';

        LOGGER.info('Delay ' + title.red);
        while (dt + (seconds * 1000) > new Date().getTime()) {}

    },

    /**
     * Run all benchmarks of the single target
     *
     * @param  {Array} links  Links to source bemjson files
     * @param  {String} target  Revision name
     * @return {Promise * Object}  Object with tests results
     */
    runBenchmark: function(links, target) {

        var self = this;

        return Q.all(links.map(function(link) {

            var def = Q.defer(),
                bemjson = FS.readFileSync(link , 'UTF-8'),
                bemhtml = require(link.replace(/.bemjson.js/, '.bemhtml.js')),
                context = VM.createContext({}),
                res = VM.runInContext(bemjson, context, link),
                name = link.match(/([^\/]+)$/)[0],
                name_short = name.replace('.bemjson.js',''),
                suite = new BENCHMARK.Suite(),

                label = '[' + target.green + ' => ' +name_short.blue+ '] has been tested';

            LOGGER.time(label);

            // Warming-up for the best results
            (function() {
                var i = self.WARMIN_CYCLE;
                while (i--) bemhtml.BEMHTML.apply(res);
            })();

            suite
                .add(name, function() {
                    bemhtml.BEMHTML.apply(res);
                })
                .on('complete', function(event) {
                    LOGGER.timeEnd(label);
                    def.resolve({
                        'name' : String(event.target.name),
                        'hz'   : Number(Math.round(event.target.hz) / 1000).toFixed(1),
                        'rme'  : Number(event.target.stats.rme).toFixed(1),
                        'runs' : event.target.stats.sample.length
                    });
                })
                .run({ 'async': false });

            return def.promise;

        }))
        .then(function(res) {
            return [target].concat(res);
        });

    },

    /**
     * Extract all revisions and mark that it exist
     *
     * @return {Promise * Undefined}
     */
    exportGitRevisions: function() {

        var self = this;

        return self.treeishList
            .map(function(treeish) {
                return self.exportGitRevision(treeish);
            });

    },

    /**
     * Copy reference benchmarks from specified source into every checked out revision
     *
     * @param  {String[]} targets  Array of target names
     * @param  {String} source  Path to benchmarks source
     * @return {Promise * Undefined}
     */
    cloneBenchmarks: function(source, targets) {

        targets = [].concat(targets);

        var self = this,
            search = targets.indexOf(source),

            onFail = function(err) {
                LOGGER.error('cloneBenchmarks - '.blue + err);
                return Q.reject(err);
            };

        if (search !== -1) {
            targets.splice(search, 1);
        }

        return Q.all(targets.map(function(target) {

            var cmd = 'rm -rf ' + PATH.join(self.pathTmp, target, self.pathBenchmarks);

            return U.exec(cmd)
                .fail(onFail)
                .then(function() {

                    var cmd = [
                        'cp',
                        '-R',
                        PATH.join(self.pathTmp, source, self.pathBenchmarks + '*'),
                        PATH.join(self.pathTmp, target, self.pathBenchmarks)
                    ].join(' ');

                    return U.exec(cmd)
                        .fail(onFail);

                });

        }));

    },

    /**
     * Return latest treeish compared at date
     *
     * @return {String}  Latest treeish
     */
    getLatestTreeish: function() {

        var self = this;

        if (!self.withoutCurrent) {
            LOGGER.info('Latest revision is - ', self.pathSelf.magenta);
            return Q.resolve(self.pathSelf);
        }

        return Q.all(self.treeishList
            .map(function(treeish) {

                var cmd = "git show " + treeish + " | grep Date | awk -F':   ' '{print $2}'";

                return U.exec(cmd, null, true)
                    .then(function(output) {
                        return {
                            treeish: treeish,
                            date: output.replace('\n', '')
                        };
                    });

            })
        )
        .then(function(dates) {

            var maxTime = 0,
                lastTreeish;

            dates.forEach(function(dt) {
                var time = new Date(dt.date).getTime();
                if (time > maxTime) {
                    maxTime = time;
                    lastTreeish = dt.treeish;
                }
            });

            LOGGER.info('Latest revision is - ' + lastTreeish.magenta);

            return lastTreeish;

        });

    },

    /**
     * Sort results by command line order
     *
     * @param  {Object} res  Results data
     * @param  {String[]} targets  Array of target names
     * @return {Array}  Sorted results data
     */
    sortResult: function(res, targets) {
        var sorted = [];

        for (var i = 0; i < res.length; i++) {
            for (var j = 0; j < res.length; j++) {
                if (res[j][0] === targets[i]) {
                    sorted.push(res[j]);
                    break;
                }
            }
        }

        return sorted;
    },

    /**
     * Construct cli table object with benchmarks results
     *
     * @param  {Object} results  Object with test results
     * @param  {String[]} targets  Array of target names
     * @return {Object}  cli table object
     */
    getResultsTable: function(results, targets) {

        var sortedRes = this.sortResult(results, targets),
            header = [],
            data = [],
            rme = [],
            name,
            stat,
            perc,
            t;

        header.push(
            '№'.magenta,
            'benchmark'.yellow.underline);

        sortedRes.forEach(function(item) {
            header.push(item[0].blue + '(hz/rme)'.cyan);
        });

        for (var i = 1; i < sortedRes[0].length; i += 1) {
            name = undefined;
            t = [];
            rme = [];

            for (var j = 0; j < sortedRes.length; j++) {
                if (!name) name = sortedRes[j][i].name;

                if (sortedRes[j][i]) {
                    t.push('[' + sortedRes[j][i].hz.green + '] ±' + sortedRes[j][i].rme.magenta + '%');
                    rme.push(Number(sortedRes[j][i].rme));
                } else {
                    t.push('none');
                }
            }

            var max = Math.max.apply(null, rme),
                min = Math.min.apply(null, rme);

            if ((max - min) < this.ALLOWABLE_PERCENT) {
                perc = 'stable'.yellow.underline;
            } else {
                perc = 'unstable'.red.underline;
            }

            data.push([i, name.replace('.bemjson.js', '')]
                .concat(t)
                .concat(perc));
        }

        header.push('RME stat'.magenta);

        var table = new Table({
            head: header,
            style: {
                compact: true,
                'padding-left': 1,
                'padding-right': 1
            }
        });

        data.forEach(function(row) {
            table.push(row);
        });

        return table;

    },

    /**
     * Cleaning all repo from temporary folder
     *
     * @return {Promise * Undefined}
     */
    cleanTempDir: function() {

        var cmd = 'rm -rf ./' + this.pathTmp + '*',
            label = 'TMP folder has been cleaned';

        LOGGER.info('Cleaning a TMP folder');
        LOGGER.time(label);

        return U.exec(cmd)
            .fin(function() {
                LOGGER.timeEnd(label);
            });

    },

    /**
     * Main flow
     *
     * @return {Promise}  Promise of benchmarks finish
     */
    start: function() {

        var self = this,
            bcsInclude = 'Include ' + String(this.pathSelf).magenta,
            bcsExclude = 'Exclude ' + String(this.pathSelf).magenta,
            targets = [].concat(self.treeishList);

        LOGGER.time('All time');

        if (!self.withoutCurrent) {
            LOGGER.info(bcsInclude);
            targets.push(self.pathSelf);
        } else {
            LOGGER.info(bcsExclude);
        }

        return Q.resolve()
            .then(function() {

                if (self.noMake) {

                    // without make
                    LOGGER.info('[NO MAKE]'.magenta + ' mode');
                    return Q.resolve();

                }
                else {

                    // export and make revisions
                    return U.mkdirp(self.pathTmp)
                        .then(function() {
                            return self.cleanTempDir();
                        })
                        .then(function() {

                            var exports = self.exportGitRevisions();
                            if (!self.withoutCurrent) {
                                exports.push(self.exportWorkingCopy());
                            }

                            return Q.all(exports)
                                .then(function() {
                                    return self.getLatestTreeish()
                                        .then(function(rev) {
                                            return self.cloneBenchmarks(rev, targets);
                                        });
                                })
                                .then(function() {
                                    LOGGER.info('Make...');
                                    return self.makeAll(targets);
                                });

                        });

                }

            })
            .then(function() {
                LOGGER.info('Benchmark...');

                return Q.all(targets
                    .map(function(target) {

                        var links = self.getBenchmarksPaths(
                            PATH.join(self.pathTmp, target),
                            self.pathBenchmarks);

                        self.activeWait(self.DELAY);
                        return self.runBenchmark(links, target);

                    })
                );

            })
            .then(function(res) {
                LOGGER.timeEnd('All time');
                return self.getResultsTable(res, targets);
            });

    }

});

/**
 * Create instance on Benchmark and share it
 *
 * @param  {Array} opts Options from COA
 * @param  {Array} args Arguments from COA
 * @return {Object} Benchmark instance
 */
module.exports = function(opts, args) {
    return new Benchmark(opts, args);
};
<|endoftext|>


<commit_before>/**
 * The PlaceService class manages places in the application.
 *
 * A place is just the current state of the application which can be
 * represented as an Object or a URL. For example, the place associated with:
 *
 *   http://localhost:5000/map/6/2/17/ALL/terrain/loss
 *
 * Can also be represented like this:
 *
 *  zoom - 6
 *  lat - 2
 *  lng - 17
 *  iso - ALL
 *  maptype - terrain
 *  baselayers - loss
 *
 * The PlaceService class handles the following use cases:
 *
 * 1) New route updates views
 *
 *   The Router receives a new URL and all application views need to be updated
 *   with the state encoded in the URL.
 *
 *   Here the router publishes the "Place/update" event passing in the route
 *   name and route parameters. The PlaceService handles the event by
 *   standardizing the route parameters and publishing them in a "Place/go"
 *   event. Any presenters listening to the event receives the updated
 *   application state and can update their views.
 *
 * 2) Updated view updates URL
 *
 *   A View state changes (e.g., a new map zoom) and the URL needs to be
 *   updated, not only with its new state, but from the state of all views in
 *   the application that provide state for URLs.
 *
 *   Here presenters publishe the "Place/register" event passing in a
 *   reference to themselves. The PlaceService subscribes to the
 *   "Place/register" event so that it can keep references to all presenters
 *   that provide state. Then the view publishes the "Place/update" event
 *   passing in a "go" parameter. If "go" is false, the PlaceService will
 *   update the URL. Otherwise it will publish the "Place/go" event which will
 *   notify all subscribed presenters.
 *
 * @return {PlaceService} The PlaceService class
 */
define([
  'underscore',
  'mps',
  'uri',
  'map/presenters/PresenterClass',
  'map/services/LayerSpecService'
], function (_, mps, UriTemplate, PresenterClass, layerSpecService) {

  'use strict';

  var urlDefaultsParams = {
    baselayers: 'loss,forestgain',
    zoom: 3,
    lat: 15,
    lng: 27,
    maptype: 'grayscale',
    iso: 'ALL'
  };

  var PlaceService = PresenterClass.extend({

    _uriTemplate: '{name}{/zoom}{/lat}{/lng}{/iso}{/maptype}{/baselayers}{/sublayers}{?geojson,wdpaid,begin,end,threshold,dont_analyze}',

    /**
     * Create new PlaceService with supplied Backbone.Router.
     *
     * @param  {Backbond.Router} router Instance of Backbone.Router
     */
    init: function(router) {
      this.router = router;
      this._presenters = [];
      this._name = null;
      this._presenters.push(layerSpecService); // this makes the test fail
      this._super();
    },

    /**
     * Subscribe to application events.
     */
    _subscriptions: [{
      'Place/register': function(presenter) {
        this._presenters = _.union(this._presenters, [presenter]);
      }
    }, {
      'Place/update': function() {
        this._updatePlace();
      }
    }],

    /**
     * Init by the router to set the name
     * and publish the first place.
     *
     * @param  {String} name   Place name
     * @param  {Object} params Url params
     */
    initPlace: function(name, params) {
      this._name = name;
      this._newPlace(params);
    },

    /**
     * Silently updates the url from the presenter params.
     */
    _updatePlace: function() {
      var route, params;
      params = this._destandardizeParams(
        this._getPresenterParams(this._presenters));

      route = this._getRoute(params);
      console.log(this.router);
      console.log(route);
      this.router.navigateTo(route, {silent: true});
    },

    /**
     * Handles a new place.
     *
     * @param  {Object}  params The place parameters
     */
    _newPlace: function(params) {
      var where, place = {};

      place.params = this._standardizeParams(params);

      where = _.union(place.params.baselayers,
        place.params.sublayers);

      layerSpecService.toggle(
        where,
        _.bind(function(layerSpec) {
          place.layerSpec = layerSpec;
          mps.publish('Place/go', [place]);
        }, this)
      );
    },

    /**
     * Return route URL for supplied route name and route params.
     *
     * @param  {Object} params The route params
     * @return {string} The route URL
     */
    _getRoute: function(param) {
      var url = new UriTemplate(this._uriTemplate).fillFromObject(param);
      return decodeURIComponent(url);
    },

    /**
     * Return standardized representation of supplied params object.
     *
     * @param  {Object} params The params to standardize
     * @return {Object} The standardized params.
     */
    _standardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      p.name = this._name;

      p.baselayers = _.map(p.baselayers.split(','), function(slug) {
        return {slug: slug};
      });

      p.sublayers = p.sublayers ? _.map(p.sublayers.split(','), function(id) {
        return {id: _.toNumber(id)};
      }) : [];

      p.zoom = _.toNumber(p.zoom);
      p.lat = _.toNumber(p.lat);
      p.lng = _.toNumber(p.lng);
      p.iso = _.object(['country', 'region'], p.iso.split('-'));
      p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
      p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
      p.geojson = p.geojson ? JSON.parse(decodeURIComponent(p.geojson)) : null;
      p.wdpaid = p.wdpaid ? _.toNumber(p.wdpaid) : null;
      p.threshold = p.threshold ? _.toNumber(p.threshold) : null;
      p.subscribe_alerts = (p.subscribe_alerts === 'subscribe') ? true : null;
      p.referral = p.referral;
      return p;
    },

    /**
     * Return formated URL representation of supplied params object based on
     * a route name.
     *
     * @param  {Object} params Place to standardize
     * @return {Object} Params ready for URL
     */
    _destandardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      var baselayers = _.pluck(p.baselayers, 'slug');
      p.name = this._name;
      p.baselayers = (baselayers.length > 0) ? baselayers : 'none';
      p.sublayers = p.sublayers ? p.sublayers.join(',') : null;
      p.zoom = String(p.zoom);
      p.lat = p.lat.toFixed(2);
      p.lng = p.lng.toFixed(2);
      p.iso = _.compact(_.values(p.iso)).join('-') || 'ALL';
      p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
      p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
      p.geojson = p.geojson ? encodeURIComponent(p.geojson) : null;
      p.wdpaid = p.wdpaid ? String(p.wdpaid) : null;
      p.threshold = p.threshold ? String(p.threshold) : null;
      return p;
    },

    /**
     * Return param object representing state from all registered presenters
     * that implement getPlaceParams().
     *
     * @param  {Array} presenters The registered presenters
     * @return {Object} Params representing state from all presenters
     */
    _getPresenterParams: function(presenters) {
      var p = {};

      _.each(presenters, function(presenter) {
        _.extend(p, presenter.getPlaceParams());
      }, this);

      return p;
    }

  });

  return PlaceService;
});
<commit_msg>cleaning<commit_after>/**
 * The PlaceService class manages places in the application.
 *
 * A place is just the current state of the application which can be
 * represented as an Object or a URL. For example, the place associated with:
 *
 *   http://localhost:5000/map/6/2/17/ALL/terrain/loss
 *
 * Can also be represented like this:
 *
 *  zoom - 6
 *  lat - 2
 *  lng - 17
 *  iso - ALL
 *  maptype - terrain
 *  baselayers - loss
 *
 * The PlaceService class handles the following use cases:
 *
 * 1) New route updates views
 *
 *   The Router receives a new URL and all application views need to be updated
 *   with the state encoded in the URL.
 *
 *   Here the router publishes the "Place/update" event passing in the route
 *   name and route parameters. The PlaceService handles the event by
 *   standardizing the route parameters and publishing them in a "Place/go"
 *   event. Any presenters listening to the event receives the updated
 *   application state and can update their views.
 *
 * 2) Updated view updates URL
 *
 *   A View state changes (e.g., a new map zoom) and the URL needs to be
 *   updated, not only with its new state, but from the state of all views in
 *   the application that provide state for URLs.
 *
 *   Here presenters publishe the "Place/register" event passing in a
 *   reference to themselves. The PlaceService subscribes to the
 *   "Place/register" event so that it can keep references to all presenters
 *   that provide state. Then the view publishes the "Place/update" event
 *   passing in a "go" parameter. If "go" is false, the PlaceService will
 *   update the URL. Otherwise it will publish the "Place/go" event which will
 *   notify all subscribed presenters.
 *
 * @return {PlaceService} The PlaceService class
 */
define([
  'underscore',
  'mps',
  'uri',
  'map/presenters/PresenterClass',
  'map/services/LayerSpecService'
], function (_, mps, UriTemplate, PresenterClass, layerSpecService) {

  'use strict';

  var urlDefaultsParams = {
    baselayers: 'loss,forestgain',
    zoom: 3,
    lat: 15,
    lng: 27,
    maptype: 'grayscale',
    iso: 'ALL'
  };

  var PlaceService = PresenterClass.extend({

    _uriTemplate: '{name}{/zoom}{/lat}{/lng}{/iso}{/maptype}{/baselayers}{/sublayers}{?geojson,wdpaid,begin,end,threshold,dont_analyze}',

    /**
     * Create new PlaceService with supplied Backbone.Router.
     *
     * @param  {Backbond.Router} router Instance of Backbone.Router
     */
    init: function(router) {
      this.router = router;
      this._presenters = [];
      this._name = null;
      this._presenters.push(layerSpecService); // this makes the test fail
      this._super();
    },

    /**
     * Subscribe to application events.
     */
    _subscriptions: [{
      'Place/register': function(presenter) {
        this._presenters = _.union(this._presenters, [presenter]);
      }
    }, {
      'Place/update': function() {
        this._updatePlace();
      }
    }],

    /**
     * Init by the router to set the name
     * and publish the first place.
     *
     * @param  {String} name   Place name
     * @param  {Object} params Url params
     */
    initPlace: function(name, params) {
      this._name = name;
      this._newPlace(params);
    },

    /**
     * Silently updates the url from the presenter params.
     */
    _updatePlace: function() {
      var route, params;
      params = this._destandardizeParams(
        this._getPresenterParams(this._presenters));

      route = this._getRoute(params);
      this.router.navigateTo(route, {silent: true});
    },

    /**
     * Handles a new place.
     *
     * @param  {Object}  params The place parameters
     */
    _newPlace: function(params) {
      var where, place = {};

      place.params = this._standardizeParams(params);

      where = _.union(place.params.baselayers,
        place.params.sublayers);

      layerSpecService.toggle(
        where,
        _.bind(function(layerSpec) {
          place.layerSpec = layerSpec;
          mps.publish('Place/go', [place]);
        }, this)
      );
    },

    /**
     * Return route URL for supplied route name and route params.
     *
     * @param  {Object} params The route params
     * @return {string} The route URL
     */
    _getRoute: function(param) {
      var url = new UriTemplate(this._uriTemplate).fillFromObject(param);
      return decodeURIComponent(url);
    },

    /**
     * Return standardized representation of supplied params object.
     *
     * @param  {Object} params The params to standardize
     * @return {Object} The standardized params.
     */
    _standardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      p.name = this._name;

      p.baselayers = _.map(p.baselayers.split(','), function(slug) {
        return {slug: slug};
      });

      p.sublayers = p.sublayers ? _.map(p.sublayers.split(','), function(id) {
        return {id: _.toNumber(id)};
      }) : [];

      p.zoom = _.toNumber(p.zoom);
      p.lat = _.toNumber(p.lat);
      p.lng = _.toNumber(p.lng);
      p.iso = _.object(['country', 'region'], p.iso.split('-'));
      p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
      p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
      p.geojson = p.geojson ? JSON.parse(decodeURIComponent(p.geojson)) : null;
      p.wdpaid = p.wdpaid ? _.toNumber(p.wdpaid) : null;
      p.threshold = p.threshold ? _.toNumber(p.threshold) : null;
      p.subscribe_alerts = (p.subscribe_alerts === 'subscribe') ? true : null;
      p.referral = p.referral;
      return p;
    },

    /**
     * Return formated URL representation of supplied params object based on
     * a route name.
     *
     * @param  {Object} params Place to standardize
     * @return {Object} Params ready for URL
     */
    _destandardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      var baselayers = _.pluck(p.baselayers, 'slug');
      p.name = this._name;
      p.baselayers = (baselayers.length > 0) ? baselayers : 'none';
      p.sublayers = p.sublayers ? p.sublayers.join(',') : null;
      p.zoom = String(p.zoom);
      p.lat = p.lat.toFixed(2);
      p.lng = p.lng.toFixed(2);
      p.iso = _.compact(_.values(p.iso)).join('-') || 'ALL';
      p.begin = p.begin ? p.begin.format('YYYY-MM-DD') : null;
      p.end = p.end ? p.end.format('YYYY-MM-DD') : null;
      p.geojson = p.geojson ? encodeURIComponent(p.geojson) : null;
      p.wdpaid = p.wdpaid ? String(p.wdpaid) : null;
      p.threshold = p.threshold ? String(p.threshold) : null;
      return p;
    },

    /**
     * Return param object representing state from all registered presenters
     * that implement getPlaceParams().
     *
     * @param  {Array} presenters The registered presenters
     * @return {Object} Params representing state from all presenters
     */
    _getPresenterParams: function(presenters) {
      var p = {};

      _.each(presenters, function(presenter) {
        _.extend(p, presenter.getPlaceParams());
      }, this);

      return p;
    }

  });

  return PlaceService;
});
<|endoftext|>


<commit_before>import apiClient from 'panoptes-client/lib/api-client';
import { config } from 'constants/config';
import * as type from 'constants/actions';

function workflowsRequested() {
  return { type: type.WORKFLOWS_REQUESTED };
}

function workflowsReceived(json) {
  return { type: type.WORKFLOWS_RECEIVED, json };
}

export function fetchWorkflows() {
  return dispatch => {
    dispatch(workflowsRequested());
    apiClient.type('workflows').get({
      project_id: config.projectId,
      fields: 'active,completeness,display_name,finished_at',
    }).then(json => dispatch(workflowsReceived(json)));
  };
}
<commit_msg>increase workflow call page_size<commit_after>import apiClient from 'panoptes-client/lib/api-client';
import { config } from 'constants/config';
import * as type from 'constants/actions';

function workflowsRequested() {
  return { type: type.WORKFLOWS_REQUESTED };
}

function workflowsReceived(json) {
  return { type: type.WORKFLOWS_RECEIVED, json };
}

export function fetchWorkflows() {
  return dispatch => {
    dispatch(workflowsRequested());
    apiClient.type('workflows').get({
      project_id: config.projectId,
      fields: 'active,completeness,display_name,finished_at',
      page_size: 50,
    }).then(json => dispatch(workflowsReceived(json)));
  };
}
<|endoftext|>


<commit_before>import React, { forwardRef, useRef, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Box from '../box';
import theme from './theme.css';
import uiUtilities from '@teamleader/ui-utilities';

const Link = forwardRef(({ children, className, element, onMouseUp, onMouseLeave, ...others }, ref) => {
  const linkRef = useRef();
  useImperativeHandle(ref, () => linkRef.current);

  const blur = () => {
    const currentLinkRef = linkRef.current;
    if (currentLinkRef.blur) {
      currentLinkRef.blur();
    }
  };

  const handleMouseUp = (event) => {
    blur();
    onMouseUp && onMouseUp(event);
  };

  const handleMouseLeave = (event) => {
    blur();
    onMouseLeave && onMouseLeave(event);
  };

  const classNames = cx(uiUtilities['reset-font-smoothing'], theme['link'], className);

  return (
    <Box
      element={element}
      ref={linkRef}
      className={classNames}
      data-teamleader-ui="marketing-link"
      onMouseUp={handleMouseUp}
      onMouseLeave={handleMouseLeave}
      {...others}
    >
      {children}
    </Box>
  );
});

Link.propTypes = {
  /** The content to display inside the link. */
  children: PropTypes.any.isRequired,
  /** A class name for the link to give custom styles. */
  className: PropTypes.string,
  /** A custom element to be rendered */
  element: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
  /** Callback function that is fired when mouse leaves the component. */
  onMouseLeave: PropTypes.func,
  /** Callback function that is fired when the mouse button is released. */
  onMouseUp: PropTypes.func,
};

Link.defaultProps = {
  className: '',
  element: 'a',
};

Link.displayName = 'MarketingLink';

export default Link;
<commit_msg>:recycle: rename internal component<commit_after>import React, { forwardRef, useRef, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Box from '../box';
import theme from './theme.css';
import uiUtilities from '@teamleader/ui-utilities';

const MarketingLink = forwardRef(({ children, className, element, onMouseUp, onMouseLeave, ...others }, ref) => {
  const linkRef = useRef();
  useImperativeHandle(ref, () => linkRef.current);

  const blur = () => {
    const currentLinkRef = linkRef.current;
    if (currentLinkRef.blur) {
      currentLinkRef.blur();
    }
  };

  const handleMouseUp = (event) => {
    blur();
    onMouseUp && onMouseUp(event);
  };

  const handleMouseLeave = (event) => {
    blur();
    onMouseLeave && onMouseLeave(event);
  };

  const classNames = cx(uiUtilities['reset-font-smoothing'], theme['link'], className);

  return (
    <Box
      element={element}
      ref={linkRef}
      className={classNames}
      data-teamleader-ui="marketing-link"
      onMouseUp={handleMouseUp}
      onMouseLeave={handleMouseLeave}
      {...others}
    >
      {children}
    </Box>
  );
});

MarketingLink.propTypes = {
  /** The content to display inside the link. */
  children: PropTypes.any.isRequired,
  /** A class name for the link to give custom styles. */
  className: PropTypes.string,
  /** A custom element to be rendered */
  element: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
  /** Callback function that is fired when mouse leaves the component. */
  onMouseLeave: PropTypes.func,
  /** Callback function that is fired when the mouse button is released. */
  onMouseUp: PropTypes.func,
};

MarketingLink.defaultProps = {
  className: '',
  element: 'a',
};

MarketingLink.displayName = 'MarketingLink';

export default MarketingLink;
<|endoftext|>


<commit_before>angular
  .module("animeHub")
  .controller("usersController", usersController);

usersController.$inject = ['User', 'TokenService'];
function usersController(User, TokenService){

  // object saved as self
  var self = this;

  // method to login
  self.login = function() {
    User.login(
      self.user,
      function(res) {
        var userToken = res.token;
        TokenService.saveUserToken(userToken);
        self.loginMessage = res.message;
        self.user = {};
      }, function(err) {
        self.loginMessage = err.data.message;
      }
    );
  };

  // method to signup
  self.signup = function() {
    User.signup(
      self.user,
      function(res) {
        self.signupMessage = res.message;
        self.user = {};
      }, function(err) {
        self.signupMessage = err.data.message;
      }
    );
  };

  // method to logout
  self.logout = function() {
    TokenService.removeUserToken();
  };

  // user is logged in
  self.loggedIn = function() {
    return !!TokenService.getUserToken();
  };

}<commit_msg>injected window<commit_after>angular
  .module("animeHub")
  .controller("usersController", usersController);

usersController.$inject = ['User', 'TokenService', '$window'];
function usersController(User, TokenService, $window){

  // object saved as self
  var self = this;

  // method to login
  self.login = function() {
    User.login(
      self.user,
      function(res) {
        var userToken = res.token;
        TokenService.saveUserToken(userToken);
        self.loginMessage = res.message;
        self.user = {};
      }, function(err) {
        self.loginMessage = err.data.message;
      }
    );
  };

  // method to signup
  self.signup = function() {
    User.signup(
      self.user,
      function(res) {
        self.signupMessage = res.message;
        self.user = {};
      }, function(err) {
        self.signupMessage = err.data.message;
      }
    );
  };

  // method to logout
  self.logout = function() {
    TokenService.removeUserToken();
  };

  // user is logged in
  self.loggedIn = function() {
    return !!TokenService.getUserToken();
  };

}<|endoftext|>


<commit_before>/**
 * Retrieve the point id from the SSL certificate fingerprint
 * @param {Request}  req  Express request
 * @param {Response} res  Express response
 * @param {Function} next Next middleware
 */
export default (req, res, next) => {
    let fingerprint = req.connection.getPeerCertificate().fingerprint.replace(/:/g, '').trim();

    let Device = req.app.locals.models.Device;
    let Period = req.app.locals.models.Period;

    let device;
    let chosenPoint = null;

    Device
        .getAll(fingerprint, {
            index: 'fingerprint'
        })
        .getJoin({
            periodPoints: true
        })
        .run()
        .then(devices => {
            if (devices.length === 0) {
                // TODO : throw
                return;
            }

            device = devices[0];

            let periodPoints = device.periodPoints;

            let minPeriod = Infinity;

            let promises = periodPoints.map(periodPoint =>
                Period.get(periodPoint.periodId).run().then(period => {
                    let diff = period.end - period.start;

                    if (diff < minPeriod) {
                        chosenPoint = periodPoint.pointId;
                        console.log('I choose point', chosenPoint);
                        minPeriod   = diff;
                    }
                })
            );

            return Promise.all(promises);
        })
        .then(() => {
            req.pointId = chosenPoint;

            res.header('point', req.pointId);
            res.header('device', device.id);

            return next();
        });
};
<commit_msg>Fail when device is not found<commit_after>import APIError from '../APIError';

/**
 * Retrieve the point id from the SSL certificate fingerprint
 * @param {Request}  req  Express request
 * @param {Response} res  Express response
 * @param {Function} next Next middleware
 */
export default (req, res, next) => {
    let fingerprint = req.connection.getPeerCertificate().fingerprint.replace(/:/g, '').trim();

    let Device = req.app.locals.models.Device;
    let Period = req.app.locals.models.Period;

    let device;
    let chosenPoint = null;

    Device
        .getAll(fingerprint, {
            index: 'fingerprint'
        })
        .getJoin({
            periodPoints: true
        })
        .run()
        .then(devices => {
            if (devices.length === 0) {
                return false;
            }

            device = devices[0];

            let periodPoints = device.periodPoints;

            let minPeriod = Infinity;

            let promises = periodPoints.map(periodPoint =>
                Period.get(periodPoint.periodId).run().then(period => {
                    let diff = period.end - period.start;

                    if (diff < minPeriod) {
                        chosenPoint = periodPoint.pointId;
                        minPeriod   = diff;
                    }
                })
            );

            return Promise.all(promises);
        })
        .then(ok => {
            if (!ok) {
                return next(new APIError(404, 'Device not found', fingerprint));
            }

            req.pointId = chosenPoint;

            res.header('point', req.pointId);
            res.header('device', device.id);

            return next();
        });
};
<|endoftext|>


<commit_before>'use strict';

angular.module('frontendApp')
  .controller('ProfilesCtrl', ['$scope', 'AreaSelection', 'CensusAPI', 'Indicators', '$timeout', function($scope, AreaSelection, CensusAPI, Indicators, $timeout) {
    $scope.selection = AreaSelection.getModel();

    // Storage for svg and chart elements
    // {elemId: {svg: svgElement, chart: chartElement}, ...}
    $scope._charts = {};

    // Because each column is unique to a table, we don't have to filter on table
    var baseQuery = {
      column: [
        // Age
        'l6_male',
        'm6_female',
        // Ethnicity
        'tab0_male',
        'tab0_female',
        // Language
        'e18_both',
        // Occupation
        'l81_male',
        'm81_female',
        // Industry
        'l95_male',
        'm95_female',
        // Individual monthly income
        'c77_male',
        'd77_female'
      ],
      projector: ['value', 'row', 'column'],
      return: ['data', 'options']
    };

    $scope.$watch('selection', function() {
      if($scope.selection.selectedAreas().length == 0) {
        return;
      }

      var query = new CensusAPI.Query(baseQuery);

      var areas = $scope.selection.selectedAreas();

      // Get the name of the area to display
      if (areas.length > 1) {
        // Selected a district
        var districtCode = areas[0][0];
        $scope.selectionName = i18n.t('district.' + districtCode);
      } else {
        // Selected an area
        $scope.selectionName = i18n.t('area.' + areas[0]);
      }

      // Add the selected area to the filter
      query.addParam('area', areas);

      query.fetch().then(function(res) {
        $scope._rawResponse = res;
        $scope._queryData = CensusAPI.joinData(res.data);
        $scope.redrawCharts();
      });

      // Scroll to the results section
      // Must delay this so that the section can show
      $timeout(function() {$('body').animate({scrollTop: $('#profile-charts').offset().top}, 'slow')}, 250);

    }, true);

    $scope.redrawCharts = function() {
      $scope._drawAge();
      $scope._drawEthnicity();
      $scope._drawLanguage();
      $scope._drawOccupation();
      $scope._drawIndustry();
      $scope._drawIncome();
    };

    var _clearChart = function(selector) {
      if (!_.isUndefined($scope._charts[selector])) {
        $scope._charts[selector].svg.remove();
      }
    };

    var _addChartToCache = function(selector, svg, chart) {
      $scope._charts[selector] = {svg: svg, chart: chart};
    };

    $scope._drawAge = function() {
      var elemSelector = "#profile-age";
      _clearChart(elemSelector);

      var filters = ['l6_male', 'm6_female'];
      var filtered = _.filter($scope._queryData, function(val) {return (filters.indexOf(val.column) > -1);});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);  //  Need this to handle district aggregates

      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({"Age Group": i18n.t('row.' + split[0]),
          Gender: i18n.t('column.' + split[1]),
          Population: split[1] === 'l6_male' ? (-1 * val) : val});
      });

      // Make the chart
      var svg = dimple.newSvg(elemSelector, undefined, 300);
      var chart = new dimple.chart(svg, data);
      chart.setBounds("13%", 0, "85%", "85%");
      chart.addMeasureAxis('x', 'Population');
      var y = chart.addCategoryAxis('y', 'Age Group');
      y.addOrderRule(_.map(Indicators.ordering.ageGroup, function(k) {return i18n.t('row.' + k);}), true);
      chart.addSeries('Gender', dimple.plot.bar);
      chart.addLegend("75%", "77%", "30%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawEthnicity = function() {
      var elemSelector = "#profile-ethnicity";
      _clearChart(elemSelector);

      // Filter out the data we want
      var filters = ['tab0_male', 'tab0_female'];
      var filtered = _.filter($scope._queryData, function(val) {return (filters.indexOf(val.column) > -1);});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);  //  Need this to handle district aggregates
      // Grouped is {row,column: value}
      // Reshape the grouped aggregate to a data array
      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Ethnicity: i18n.t('row.' + split[0]), Gender: i18n.t('column.' + split[1]), Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 300);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 15, "70%", "81%");
      chart.addCategoryAxis('x', ['Gender']);
      chart.addPctAxis('y', 'Population');
      chart.addSeries('Ethnicity', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawLanguage = function() {
      var elemSelector = "#profile-language";
      _clearChart(elemSelector);

      var filtered = _.filter($scope._queryData, function(val) {return val.column === "e18_both";});
      var grouped = CensusAPI.sumBy(filtered, ['row']);
      var data = [];
      _.forOwn(grouped, function(val, key) {
        data.push({Language: i18n.t('row.' + key), 'All people': '', Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 300);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 15, "70%", "81%");
      chart.addCategoryAxis('x', 'All People');
      chart.addPctAxis('y', 'Population');
      chart.addSeries('Language', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawOccupation = function() {
      var elemSelector = "#profile-occupation";
       _clearChart(elemSelector);

      var filters = [
        'l81_male',
        'm81_female',
      ];
      var filtered = _.filter($scope._queryData, function(val) {return filters.indexOf(val.column) > -1;});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);
      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Occupation: i18n.t('row.' + split[0]), 'Gender': i18n.t('column.' + split[1]), Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 150);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 10, 490, 90);
      chart.addCategoryAxis('y', 'Gender');
      chart.addPctAxis('x', 'Population');
      chart.addSeries('Occupation', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", 20);
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawIndustry = function() {
      var elemSelector = "#profile-industry";
      _clearChart(elemSelector);

      var filters = [
        'l95_male',
        'm95_female',
      ];
      var filtered = _.filter($scope._queryData, function(val) {return filters.indexOf(val.column) > -1;});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);
      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Industry: i18n.t('row.' + split[0]), 'Gender': i18n.t('column.' + split[1]), Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 150);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 10, 490, 90);
      chart.addCategoryAxis('y', 'Gender');
      chart.addPctAxis('x', 'Population');
      chart.addSeries('Industry', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", 20);
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawIncome = function() {
      var elemSelector = "#profile-income";
      _clearChart(elemSelector);

      var filters = [
        'c77_male',
        'd77_female'
      ];
      var filtered = _.filter($scope._queryData, function(val) {return (filters.indexOf(val.column) > -1);});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);  //  Need this to handle district aggregates

      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Income: i18n.t('row.' + split[0]),
          Gender: i18n.t('column.' + split[1]),
          Population: split[1] === 'c77_male' ? (-1 * val) : val});
      });

      // Make the chart
      var svg = dimple.newSvg(elemSelector, undefined, 350);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(120, 0, 400, "85%");
      chart.addMeasureAxis('x', 'Population');
      var y = chart.addCategoryAxis('y', 'Income');
      y.addOrderRule(_.map(Indicators.ordering.income, function(k) {return i18n.t('row.' + k);}), true);
      chart.addSeries('Gender', dimple.plot.bar);
      chart.addLegend("75%", 10, "30%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

}]);<commit_msg>tweak animation<commit_after>'use strict';

angular.module('frontendApp')
  .controller('ProfilesCtrl', ['$scope', 'AreaSelection', 'CensusAPI', 'Indicators', '$timeout', function($scope, AreaSelection, CensusAPI, Indicators, $timeout) {
    $scope.selection = AreaSelection.getModel();

    // Storage for svg and chart elements
    // {elemId: {svg: svgElement, chart: chartElement}, ...}
    $scope._charts = {};

    // Because each column is unique to a table, we don't have to filter on table
    var baseQuery = {
      column: [
        // Age
        'l6_male',
        'm6_female',
        // Ethnicity
        'tab0_male',
        'tab0_female',
        // Language
        'e18_both',
        // Occupation
        'l81_male',
        'm81_female',
        // Industry
        'l95_male',
        'm95_female',
        // Individual monthly income
        'c77_male',
        'd77_female'
      ],
      projector: ['value', 'row', 'column'],
      return: ['data', 'options']
    };

    $scope.$watch('selection', function() {
      if($scope.selection.selectedAreas().length == 0) {
        return;
      }

      var query = new CensusAPI.Query(baseQuery);

      var areas = $scope.selection.selectedAreas();

      // Get the name of the area to display
      if (areas.length > 1) {
        // Selected a district
        var districtCode = areas[0][0];
        $scope.selectionName = i18n.t('district.' + districtCode);
      } else {
        // Selected an area
        $scope.selectionName = i18n.t('area.' + areas[0]);
      }

      // Add the selected area to the filter
      query.addParam('area', areas);

      // Delay fetching so that the scrolling finishes first
      // Hurts response time on first query, but helps animation on later queries
      $timeout(function() {
        query.fetch().then(function(res) {
          $scope._rawResponse = res;
          $scope._queryData = CensusAPI.joinData(res.data);
          $scope.redrawCharts();
        });
      }, 300);

      // Scroll to the results section
      // Must delay this so that the section can show
      $timeout(function() {$('body').animate({scrollTop: $('#profile-charts').offset().top}, 'slow')}, 100);

    }, true);

    $scope.redrawCharts = function() {
      $scope._drawAge();
      $scope._drawEthnicity();
      $scope._drawLanguage();
      $scope._drawOccupation();
      $scope._drawIndustry();
      $scope._drawIncome();
    };

    var _clearChart = function(selector) {
      if (!_.isUndefined($scope._charts[selector])) {
        $scope._charts[selector].svg.remove();
      }
    };

    var _addChartToCache = function(selector, svg, chart) {
      $scope._charts[selector] = {svg: svg, chart: chart};
    };

    $scope._drawAge = function() {
      var elemSelector = "#profile-age";
      _clearChart(elemSelector);

      var filters = ['l6_male', 'm6_female'];
      var filtered = _.filter($scope._queryData, function(val) {return (filters.indexOf(val.column) > -1);});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);  //  Need this to handle district aggregates

      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({"Age Group": i18n.t('row.' + split[0]),
          Gender: i18n.t('column.' + split[1]),
          Population: split[1] === 'l6_male' ? (-1 * val) : val});
      });

      // Make the chart
      var svg = dimple.newSvg(elemSelector, undefined, 300);
      var chart = new dimple.chart(svg, data);
      chart.setBounds("13%", 0, "85%", "85%");
      chart.addMeasureAxis('x', 'Population');
      var y = chart.addCategoryAxis('y', 'Age Group');
      y.addOrderRule(_.map(Indicators.ordering.ageGroup, function(k) {return i18n.t('row.' + k);}), true);
      chart.addSeries('Gender', dimple.plot.bar);
      chart.addLegend("75%", "77%", "30%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawEthnicity = function() {
      var elemSelector = "#profile-ethnicity";
      _clearChart(elemSelector);

      // Filter out the data we want
      var filters = ['tab0_male', 'tab0_female'];
      var filtered = _.filter($scope._queryData, function(val) {return (filters.indexOf(val.column) > -1);});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);  //  Need this to handle district aggregates
      // Grouped is {row,column: value}
      // Reshape the grouped aggregate to a data array
      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Ethnicity: i18n.t('row.' + split[0]), Gender: i18n.t('column.' + split[1]), Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 300);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 15, "70%", "81%");
      chart.addCategoryAxis('x', ['Gender']);
      chart.addPctAxis('y', 'Population');
      chart.addSeries('Ethnicity', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawLanguage = function() {
      var elemSelector = "#profile-language";
      _clearChart(elemSelector);

      var filtered = _.filter($scope._queryData, function(val) {return val.column === "e18_both";});
      var grouped = CensusAPI.sumBy(filtered, ['row']);
      var data = [];
      _.forOwn(grouped, function(val, key) {
        data.push({Language: i18n.t('row.' + key), 'All people': '', Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 300);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 15, "70%", "81%");
      chart.addCategoryAxis('x', 'All People');
      chart.addPctAxis('y', 'Population');
      chart.addSeries('Language', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawOccupation = function() {
      var elemSelector = "#profile-occupation";
       _clearChart(elemSelector);

      var filters = [
        'l81_male',
        'm81_female',
      ];
      var filtered = _.filter($scope._queryData, function(val) {return filters.indexOf(val.column) > -1;});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);
      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Occupation: i18n.t('row.' + split[0]), 'Gender': i18n.t('column.' + split[1]), Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 150);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 10, 490, 90);
      chart.addCategoryAxis('y', 'Gender');
      chart.addPctAxis('x', 'Population');
      chart.addSeries('Occupation', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", 20);
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawIndustry = function() {
      var elemSelector = "#profile-industry";
      _clearChart(elemSelector);

      var filters = [
        'l95_male',
        'm95_female',
      ];
      var filtered = _.filter($scope._queryData, function(val) {return filters.indexOf(val.column) > -1;});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);
      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Industry: i18n.t('row.' + split[0]), 'Gender': i18n.t('column.' + split[1]), Population: val});
      });

      var svg = dimple.newSvg(elemSelector, undefined, 150);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(65, 10, 490, 90);
      chart.addCategoryAxis('y', 'Gender');
      chart.addPctAxis('x', 'Population');
      chart.addSeries('Industry', dimple.plot.bar);
//      chart.addLegend(0, 0, "100%", 20);
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

    $scope._drawIncome = function() {
      var elemSelector = "#profile-income";
      _clearChart(elemSelector);

      var filters = [
        'c77_male',
        'd77_female'
      ];
      var filtered = _.filter($scope._queryData, function(val) {return (filters.indexOf(val.column) > -1);});
      var grouped = CensusAPI.sumBy(filtered, ['row', 'column']);  //  Need this to handle district aggregates

      var data = [];
      _.forOwn(grouped, function(val, key) {
        var split = key.split(',');
        data.push({Income: i18n.t('row.' + split[0]),
          Gender: i18n.t('column.' + split[1]),
          Population: split[1] === 'c77_male' ? (-1 * val) : val});
      });

      // Make the chart
      var svg = dimple.newSvg(elemSelector, undefined, 350);
      var chart = new dimple.chart(svg, data);
      chart.setBounds(120, 0, 400, "85%");
      chart.addMeasureAxis('x', 'Population');
      var y = chart.addCategoryAxis('y', 'Income');
      y.addOrderRule(_.map(Indicators.ordering.income, function(k) {return i18n.t('row.' + k);}), true);
      chart.addSeries('Gender', dimple.plot.bar);
      chart.addLegend("75%", 10, "30%", "10%");
      chart.draw(1000);
      _addChartToCache(elemSelector, svg, chart);
    };

}]);<|endoftext|>


<commit_before>import classNames from 'classnames';
import mixin from 'reactjs-mixin';
/* eslint-disable no-unused-vars */
import React from 'react';
/* eslint-enable no-unused-vars */
import {StoreMixin} from 'mesosphere-shared-reactjs';

import DescriptionList from './DescriptionList';
import MarathonStore from '../stores/MarathonStore';
import MesosStateStore from '../stores/MesosStateStore';
import MesosSummaryStore from '../stores/MesosSummaryStore';
import ResourceTypes from '../constants/ResourceTypes';
import RequestErrorMsg from './RequestErrorMsg';
import ServicesBreadcrumb from './ServicesBreadcrumb';
import TaskDebugView from './TaskDebugView';
import TaskDirectoryView from './TaskDirectoryView';
import TaskDirectoryStore from '../stores/TaskDirectoryStore';
import TaskStates from '../constants/TaskStates';
import TaskUtil from '../utils/TaskUtil';
import Units from '../utils/Units';

import InternalStorageMixin from '../mixins/InternalStorageMixin';
import TabsMixin from '../mixins/TabsMixin';

const TABS = {
  files: 'Files',
  details: 'Details',
  debug: 'Log Viewer'
};

const METHODS_TO_BIND = [
  'onTaskDirectoryStoreError',
  'onTaskDirectoryStoreSuccess'
];

class TaskDetail extends mixin(InternalStorageMixin, TabsMixin, StoreMixin) {
  constructor() {
    super(...arguments);

    this.tabs_tabs = Object.assign({}, TABS);

    this.state = {
      currentTab: 'details',
      directory: null,
      expandClass: 'large',
      showExpandButton: false,
      selectedLogFile: null,
      taskDirectoryErrorCount: 0
    };

    this.store_listeners = [
      {name: 'state', events: ['success'], listenAlways: false},
      {name: 'summary', events: ['success'], listenAlways: false},
      {name: 'taskDirectory', events: ['error', 'success']}
    ];

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentDidMount() {
    super.componentDidMount(...arguments);
    this.store_removeEventListenerForStoreID('summary', 'success');
  }

  onStateStoreSuccess() {
    let task = MesosStateStore.getTaskFromTaskID(this.props.params.taskID);
    TaskDirectoryStore.getDirectory(task);
  }

  onTaskDirectoryStoreError() {
    this.setState({
      taskDirectoryErrorCount: this.state.taskDirectoryErrorCount + 1
    });
  }

  onTaskDirectoryStoreSuccess() {
    this.setState({
      directory: TaskDirectoryStore.get('directory'),
      taskDirectoryErrorCount: 0
    });
  }

  hasLoadingError() {
    return this.state.taskDirectoryErrorCount >= 3;
  }

  getErrorScreen() {
    return (

        <RequestErrorMsg />

    );
  }

  getLoadingScreen() {
    return (

    );
  }

  handleOpenLogClick(selectedLogFile) {
    this.setState({selectedLogFile, currentTab: 'debug'});
  }

  getResources(task) {
    if (task.resources == null) {
      return null;
    }

    let resources = Object.keys(task.resources);

    return resources.map(function (resource) {
      if (resource === 'ports') {
        return null;
      }

      let colorIndex = ResourceTypes[resource].colorIndex;
      let resourceLabel = ResourceTypes[resource].label;
      let resourceIconClasses = `icon icon-sprite icon-sprite-medium
        icon-sprite-medium-color icon-resources-${resourceLabel.toLowerCase()}`;
      let resourceValue = Units.formatResource(
        resource, task.resources[resource]
      );
      return (

                  {resourceValue}

                  {resourceLabel.toUpperCase()}

      );
    });
  }

  getBasicInfo(task, node) {
    // Hide when no task or when we are viewing debug tab
    if (task == null || this.state.currentTab === 'debug') {
      return null;
    }

    let statusIcon = TaskUtil.getTaskStatusIcon(task);
    let statusClassName = `${TaskUtil.getTaskStatusClassName(task)} side-panel-subheader`;

    return (

          {task.id}

              {statusIcon}

                {TaskStates[task.state].displayName}

                {node.hostname}

    );
  }

  renderDetailsTabView() {
    let task = MesosStateStore.getTaskFromTaskID(this.props.params.taskID);

    if (task == null || !MesosSummaryStore.get('statesProcessed')) {
      return null;
    }

    let node = MesosStateStore.getNodeFromID(task.slave_id);
    let services = MesosSummaryStore.get('states')
      .lastSuccessful()
      .getServiceList();
    let service = services.filter({ids: [task.framework_id]}).last();

    let headerValueMapping = {
      ID: task.id,
      Service: `${service.name} (${service.id})`,
      Node: `${node.hostname} (${node.id})`
    };

    let sandBoxPath = TaskDirectoryStore.get('sandBoxPath');
    if (sandBoxPath) {
      headerValueMapping['Sandbox Path'] = sandBoxPath;
    }

    let labelMapping = {};

    if (task.labels) {
      task.labels.forEach(function (label) {
        labelMapping[label.key] = label.value;
      });
    }

    return (

          {this.getResources(task)}

        <DescriptionList
          className="container container-fluid flush container-pod container-pod-super-short flush-top"
          hash={headerValueMapping}
          headline="Configuration" />
        <DescriptionList
          className="container container-fluid flush container-pod container-pod-super-short flush-top"
          hash={labelMapping}
          headline="Labels" />

    );
  }

  renderFilesTabView() {
    let {state, props} = this;
    let task = MesosStateStore.getTaskFromTaskID(props.params.taskID);
    if (this.hasLoadingError()) {
      this.getErrorScreen();
    }
    if (!state.directory || !task) {
      return this.getLoadingScreen();
    }

    return (

        <TaskDirectoryView
          directory={state.directory}
          task={task}
          onOpenLogClick={this.handleOpenLogClick.bind(this)} />

    );
  }

  renderLogViewerTabView() {
    let {state, props} = this;
    let task = MesosStateStore.getTaskFromTaskID(props.params.taskID);
    if (this.hasLoadingError()) {
      this.getErrorScreen();
    }
    if (!state.directory || !task) {
      return this.getLoadingScreen();
    }

    return (

        <TaskDebugView
          selectedLogFile={state.selectedLogFile}
          showExpandButton={this.showExpandButton}
          directory={state.directory}
          task={task} />

    );
  }

  tabs_handleTabClick() {
    this.setState({selectedLogFile: null});

    // Only call super after we are done removing/adding listeners
    super.tabs_handleTabClick(...arguments);
  }

  getNotFound(itemType) {
    return (

          {`Error finding ${itemType}`}

          {`Did not find a ${itemType} by the id "${this.props.params.taskID}"`}

    );
  }

  getExpandButton() {
    if (!this.state.showExpandButton) {
      return null;
    }

    return (
      <button
        className="button button-stroke button-expand"
        onClick={this.handleExpand}>
      </button>
    );
  }

  render() {
    if (MesosStateStore.get('lastMesosState').slaves == null) {
      return null;
    }

    let task = MesosStateStore.getTaskFromTaskID(this.props.params.taskID);
    let service = MarathonStore.getServiceFromTaskID(this.props.params.taskID);

    if (task == null) {
      return this.getNotFound('task');
    }

    let node = MesosStateStore.getNodeFromID(task.slave_id);
    let panelClasses = classNames({
      'container container-fluid container-pod container-pod-short container-pod-super-short-top flush': true,
      'container-pod-short': this.state.currentTab === 'debug'
    });

    return (

        <ServicesBreadcrumb serviceTreeItem={service} />
        {this.getExpandButton()}

          {this.getBasicInfo(task, node)}

            {this.tabs_getUnroutedTabs()}

        {this.tabs_getTabView()}

    );
  }
}

module.exports = TaskDetail;
<commit_msg>Use PageHeader in task details<commit_after>import classNames from 'classnames';
import mixin from 'reactjs-mixin';
/* eslint-disable no-unused-vars */
import React from 'react';
/* eslint-enable no-unused-vars */
import {StoreMixin} from 'mesosphere-shared-reactjs';

import DescriptionList from './DescriptionList';
import MarathonStore from '../stores/MarathonStore';
import MesosStateStore from '../stores/MesosStateStore';
import MesosSummaryStore from '../stores/MesosSummaryStore';
import ResourceTypes from '../constants/ResourceTypes';
import RequestErrorMsg from './RequestErrorMsg';
import ServicesBreadcrumb from './ServicesBreadcrumb';
import PageHeader from './PageHeader';
import TaskDebugView from './TaskDebugView';
import TaskDirectoryView from './TaskDirectoryView';
import TaskDirectoryStore from '../stores/TaskDirectoryStore';
import TaskStates from '../constants/TaskStates';
import TaskUtil from '../utils/TaskUtil';
import Units from '../utils/Units';

import InternalStorageMixin from '../mixins/InternalStorageMixin';
import TabsMixin from '../mixins/TabsMixin';

const TABS = {
  files: 'Files',
  details: 'Details',
  debug: 'Log Viewer'
};

const METHODS_TO_BIND = [
  'onTaskDirectoryStoreError',
  'onTaskDirectoryStoreSuccess'
];

class TaskDetail extends mixin(InternalStorageMixin, TabsMixin, StoreMixin) {
  constructor() {
    super(...arguments);

    this.tabs_tabs = Object.assign({}, TABS);

    this.state = {
      currentTab: 'details',
      directory: null,
      expandClass: 'large',
      showExpandButton: false,
      selectedLogFile: null,
      taskDirectoryErrorCount: 0
    };

    this.store_listeners = [
      {name: 'state', events: ['success'], listenAlways: false},
      {name: 'summary', events: ['success'], listenAlways: false},
      {name: 'taskDirectory', events: ['error', 'success']}
    ];

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentDidMount() {
    super.componentDidMount(...arguments);
    this.store_removeEventListenerForStoreID('summary', 'success');
  }

  onStateStoreSuccess() {
    let task = MesosStateStore.getTaskFromTaskID(this.props.params.taskID);
    TaskDirectoryStore.getDirectory(task);
  }

  onTaskDirectoryStoreError() {
    this.setState({
      taskDirectoryErrorCount: this.state.taskDirectoryErrorCount + 1
    });
  }

  onTaskDirectoryStoreSuccess() {
    this.setState({
      directory: TaskDirectoryStore.get('directory'),
      taskDirectoryErrorCount: 0
    });
  }

  hasLoadingError() {
    return this.state.taskDirectoryErrorCount >= 3;
  }

  getErrorScreen() {
    return (

        <RequestErrorMsg />

    );
  }

  getLoadingScreen() {
    return (

    );
  }

  handleOpenLogClick(selectedLogFile) {
    this.setState({selectedLogFile, currentTab: 'debug'});
  }

  getResources(task) {
    if (task.resources == null) {
      return null;
    }

    let resources = Object.keys(task.resources);

    return resources.map(function (resource) {
      if (resource === 'ports') {
        return null;
      }

      let colorIndex = ResourceTypes[resource].colorIndex;
      let resourceLabel = ResourceTypes[resource].label;
      let resourceIconClasses = `icon icon-sprite icon-sprite-medium
        icon-sprite-medium-color icon-resources-${resourceLabel.toLowerCase()}`;
      let resourceValue = Units.formatResource(
        resource, task.resources[resource]
      );
      return (

                  {resourceValue}

                  {resourceLabel.toUpperCase()}

      );
    });
  }

  getBasicInfo(task) {
    // Hide when no task or when we are viewing debug tab
    if (task == null || this.state.currentTab === 'debug') {
      return null;
    }

    let taskIcon = (

    );

    let tabs = (

        {this.tabs_getUnroutedTabs()}

    );

    return (
      <PageHeader
        icon={taskIcon}
        subTitle={TaskStates[task.state].displayName}
        navigationTabs={tabs}
        mediaWrapperClassName="media-object-spacing-wrapper"
        title={task.getId()} />
    );
  }

  renderDetailsTabView() {
    let task = MesosStateStore.getTaskFromTaskID(this.props.params.taskID);

    if (task == null || !MesosSummaryStore.get('statesProcessed')) {
      return null;
    }

    let node = MesosStateStore.getNodeFromID(task.slave_id);
    let services = MesosSummaryStore.get('states')
      .lastSuccessful()
      .getServiceList();
    let service = services.filter({ids: [task.framework_id]}).last();

    let headerValueMapping = {
      ID: task.id,
      Service: `${service.name} (${service.id})`,
      Node: `${node.hostname} (${node.id})`
    };

    let sandBoxPath = TaskDirectoryStore.get('sandBoxPath');
    if (sandBoxPath) {
      headerValueMapping['Sandbox Path'] = sandBoxPath;
    }

    let labelMapping = {};

    if (task.labels) {
      task.labels.forEach(function (label) {
        labelMapping[label.key] = label.value;
      });
    }

    return (

          {this.getResources(task)}

        <DescriptionList
          className="container container-fluid flush container-pod container-pod-super-short flush-top"
          hash={headerValueMapping}
          headline="Configuration" />
        <DescriptionList
          className="container container-fluid flush container-pod container-pod-super-short flush-top"
          hash={labelMapping}
          headline="Labels" />

    );
  }

  renderFilesTabView() {
    let {state, props} = this;
    let task = MesosStateStore.getTaskFromTaskID(props.params.taskID);
    if (this.hasLoadingError()) {
      this.getErrorScreen();
    }
    if (!state.directory || !task) {
      return this.getLoadingScreen();
    }

    return (

        <TaskDirectoryView
          directory={state.directory}
          task={task}
          onOpenLogClick={this.handleOpenLogClick.bind(this)} />

    );
  }

  renderLogViewerTabView() {
    let {state, props} = this;
    let task = MesosStateStore.getTaskFromTaskID(props.params.taskID);
    if (this.hasLoadingError()) {
      this.getErrorScreen();
    }
    if (!state.directory || !task) {
      return this.getLoadingScreen();
    }

    return (

        <TaskDebugView
          selectedLogFile={state.selectedLogFile}
          showExpandButton={this.showExpandButton}
          directory={state.directory}
          task={task} />

    );
  }

  tabs_handleTabClick() {
    this.setState({selectedLogFile: null});

    // Only call super after we are done removing/adding listeners
    super.tabs_handleTabClick(...arguments);
  }

  getNotFound(itemType) {
    return (

          {`Error finding ${itemType}`}

          {`Did not find a ${itemType} by the id "${this.props.params.taskID}"`}

    );
  }

  getExpandButton() {
    if (!this.state.showExpandButton) {
      return null;
    }

    return (
      <button
        className="button button-stroke button-expand"
        onClick={this.handleExpand}>
      </button>
    );
  }

  render() {
    if (MesosStateStore.get('lastMesosState').slaves == null) {
      return null;
    }

    let task = MesosStateStore.getTaskFromTaskID(this.props.params.taskID);
    let service = MarathonStore.getServiceFromTaskID(this.props.params.taskID);

    if (task == null) {
      return this.getNotFound('task');
    }

    let node = MesosStateStore.getNodeFromID(task.slave_id);
    let panelClasses = classNames({
      'container container-fluid container-pod container-pod-short container-pod-super-short-top flush': true,
      'container-pod-short': this.state.currentTab === 'debug'
    });

    return (

        <ServicesBreadcrumb serviceTreeItem={service} />
        {this.getExpandButton()}

          {this.getBasicInfo(task, node)}

            {this.tabs_getUnroutedTabs()}

        {this.tabs_getTabView()}

    );
  }
}

module.exports = TaskDetail;
<|endoftext|>


<commit_before>const fs = require("fs");
const path = require("path");
const matter = require("gray-matter");
const toml = require("@iarna/toml");
const markdoc = require("@urbit/markdoc");

const options = {
  engines: {
    toml: toml.parse.bind(toml),
  },
  language: "toml",
  delimiters: "+++",
};

const index = [];

function buildSearchIndex(dir) {
  const dirName = dir.split("/").slice(-1).join("");
  let metadata = {
    data: {
      title: dirName.charAt(0).toUpperCase() + dirName.slice(1),
    },
  };

  try {
    metadata = matter(fs.readFileSync(path.join(dir, "_index.md")), options);
  } catch (err) {}

  const children = fs.readdirSync(dir, { withFileTypes: true });
  const pages = children.filter((f) => f.isFile() && f.name !== "_index.md");
  const subdirs = children.filter(
    (f) => f.isDirectory() && f.name !== "ids" && f.name !== "glossary"
  );

  index.push(
    ...pages.map((page) => {
      const { data: pageData, content: pageContent } = matter(
        fs.readFileSync(path.join(dir, page.name)),
        options
      );
      const parsed = markdoc.parse(pageContent);
      const transform = markdoc.transform(parsed);
      return {
        title: pageData.title,
        base: page.name,
        slug: path.join(
          dir.substr(dir.indexOf("content") + 7),
          "/",
          page.name.replace(/.md$/, "")
        ),
        content: pageData.description || "",
        headings: transform.children
          .filter((e) => e.name === "h2" || e.name === "h3")
          .map((e) => e.children)
          .flat()
          .filter((e) => typeof e === "string"),
        parent: metadata.data.title,
      };
    })
  );

  Object.fromEntries(
    subdirs.map((subdir) => [
      subdir.name,
      buildSearchIndex(path.join(dir, subdir.name)),
    ])
  );
}

buildSearchIndex(path.join(process.cwd(), "content"));

const fileContents = `export const index = ${JSON.stringify(index)}`;

try {
  fs.readdirSync("cache");
} catch (err) {
  fs.mkdirSync("cache");
}

fs.writeFile("cache/data.js", fileContents, function (err) {
  if (err) return console.error(err);
  console.log("Site index created.");
});
<commit_msg>cache: fix grant permalink navigation on searches<commit_after>const fs = require("fs");
const path = require("path");
const matter = require("gray-matter");
const toml = require("@iarna/toml");
const markdoc = require("@urbit/markdoc");

const options = {
  engines: {
    toml: toml.parse.bind(toml),
  },
  language: "toml",
  delimiters: "+++",
};

const index = [];

function buildSearchIndex(dir) {
  const dirName = dir.split("/").slice(-1).join("");
  let metadata = {
    data: {
      title: dirName.charAt(0).toUpperCase() + dirName.slice(1),
    },
  };

  try {
    metadata = matter(fs.readFileSync(path.join(dir, "_index.md")), options);
  } catch (err) { }

  const children = fs.readdirSync(dir, { withFileTypes: true });
  const pages = children.filter((f) => f.isFile() && f.name !== "_index.md");
  const subdirs = children.filter(
    (f) => f.isDirectory() && f.name !== "ids" && f.name !== "glossary"
  );

  index.push(
    ...pages.map((page) => {
      const { data: pageData, content: pageContent } = matter(
        fs.readFileSync(path.join(dir, page.name)),
        options
      );
      const parsed = markdoc.parse(pageContent);
      const transform = markdoc.transform(parsed);
      const slug = dir.includes("/grants/")
        ? `/grants/${page.name.replace(/.md$/, "")}`
        : path.join(
          dir.substr(dir.indexOf("content") + 7),
          "/",
          page.name.replace(/.md$/, "")
        );
      return {
        title: pageData.title,
        base: page.name,
        slug,
        content: pageData.description || "",
        headings: transform.children
          .filter((e) => e.name === "h2" || e.name === "h3")
          .map((e) => e.children)
          .flat()
          .filter((e) => typeof e === "string"),
        parent: metadata.data.title,
      };
    })
  );

  Object.fromEntries(
    subdirs.map((subdir) => [
      subdir.name,
      buildSearchIndex(path.join(dir, subdir.name)),
    ])
  );
}

buildSearchIndex(path.join(process.cwd(), "content"));

const fileContents = `export const index = ${JSON.stringify(index)}`;

try {
  fs.readdirSync("cache");
} catch (err) {
  fs.mkdirSync("cache");
}

fs.writeFile("cache/data.js", fileContents, function (err) {
  if (err) return console.error(err);
  console.log("Site index created.");
});
<|endoftext|>