<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.menu',
  name: 'DAOMenu2',
  documentation: `
    A DAOMenu which can accept a DAOControllerConfig and uses
    the v2 DAOController
  `,

  extends: 'foam.nanos.menu.AbstractMenu',
  requires: [
    'foam.comics.v2.DAOControllerConfig'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config',
      factory: function() {
        return this.DAOControllerConfig.create();
      }
    }
  ],
  methods: [
    function createView(X) {
      return {
        class: 'foam.comics.v2.DAOBrowseControllerView',
        data: this.config.dao,
        config: this.config
      };
    }
  ]
});
<commit_msg>Formatting<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.menu',
  name: 'DAOMenu2',
  extends: 'foam.nanos.menu.AbstractMenu',

  documentation: `
    A DAOMenu which can accept a DAOControllerConfig and uses
    the v2 DAOController
  `,

  requires: [
    'foam.comics.v2.DAOControllerConfig'
  ],

  properties: [
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config',
      factory: function() {
        return this.DAOControllerConfig.create();
      }
    }
  ],

  methods: [
    function createView(X) {
      return {
        class: 'foam.comics.v2.DAOBrowseControllerView',
        data: this.config.dao,
        config: this.config
      };
    }
  ]
});
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var async = require('async');

/*
 * @param {Number} ttl - how long to store a value. null will store forever
 *        default: null
 * @param {String} dir - Where to store data
 */
function Cache(ttl, dir) {
    // This cache will live forever if ttl is null
  if (typeof ttl !== 'number') {
    ttl = null;
  }
  this.ttl = ttl;
  this.dir = dir;
}

Cache.prototype = {
    // Creates a md5 hash based on the stringified arg
  _makeFilepath: function(arg) {
    var hash = crypto.createHash('md5')
            .update(JSON.stringify(arg))
            .digest('hex');
    return path.join(this.dir, hash);
  },

    /*
     * @visibility private
     * @param {Mixed} key - A JSON stringifiable value
     * @param {Mixed} val - a JSON stringifiable value
     * @param {Boolean} isSync - specifies which fs call to use
     * @param {Function} fn - A function called only if the value is async
     */
  _put: function(key, val, isSync, fn) {
    var filepath = this._makeFilepath(key);

        // Create cache key
    var cache = {
      ttl: new Date(Date.now() + this.ttl),
      data: val
    };

    var data = JSON.stringify(cache);
    var  method = isSync ? 'writeFileSync' : 'writeFile';
    var args = [filepath, data, 'UTF8', isSync ? null : fn];

    return fs[method].apply(fs, args);
  },

  put: function(key, val, fn) {
    return this._put(key, val, false, fn);
  },
  putSync: function(key, val) {
    return this._put(key, val, true, null);
  },

    /*
     * @param {Mixed} key - A JSON stringifiable key that is used to lookup
     *                      up a cached file
     */
  get: function(key, fn) {
    var self = this;
    var filepath = this._makeFilepath(key);

    fs.exists(filepath, function(exists) {
      if (!exists) {
        return fn();
      }

      fs.readFile(filepath, 'UTF8', function(err, data) {
        if (err) {
          return fn(err);
        }

        var cache = JSON.parse(data);

        if (!self._cacheIsValid(cache)) {
          return self.del(key, fn);
        }
        fn(null, cache.data);
      });

    });
  },

    /*
     * @desc - Synchronous get
     */
  getSync: function(key) {
    var filepath = this._makeFilepath(key);
    if (!fs.existsSync(filepath)) {
      return;
    }

    var cache = JSON.parse(fs.readFileSync(filepath), 'UTF8');

    if (!this._cacheIsValid(cache))      {
      return this.delSync(key);
    }

    return cache.data;
  },

  _cacheIsValid: function(cache) {
        // A cache of null lives forever
    if (cache.ttl == null) {
      return true;
    }
    if (new Date(cache.ttl) - new Date() > 0) {
      return true;
    }
    return false;
  },

    /*
     * @param {Mixed} key - A JSON stringifiable key that is used to lookup
     *                      up a cached file
     */
  del: function(key, fn) {
    var filepath = this._makeFilepath(key);
    fs.exists(filepath, function(exists) {
      if (!exists) {
        return fn();
      }
      fs.unlink(filepath, fn);
    });
  },

    /*
     * @desc - Synchronous del
     */
  delSync: function(key) {
    var filepath = this._makeFilepath(key);
    if (!fs.existsSync(filepath)) {
      return;
    }
    fs.unlinkSync(filepath);

  },

    /*
     * @desc Clears all cached values
     */
  clean: function(fn) {
        // Cleans all cache files
    var self = this;
    fs.readdir(this.dir, function(err, files) {
      if (err) {
        return fn(err);
      }
      var methods = files.map(function(file) {
        return function(_fn) {
          fs.unlink(path.join(self.dir, file), function(err) {
            _fn(err);
          });
        };
      });
      async.parallel(methods, function(err) {
        fn(err);
      });
    });
  },

    /*
     * Synchronous clean
     */
  cleanSync: function() {
        // Cleans all cache files
    if (fs.existsSync(this.dir)) {
      var files = fs.readdirSync(this.dir);
      files.forEach(function(file) {
        fs.unlinkSync(path.join(this.dir, file));
      }.bind(this));
    }
  }
};

exports.Cache = Cache;

/*
 * @param {Number} ttl
 * @param {String} dir - Directory to store cached data
 */
exports.create = function(ttl, dir, fn) {
  fs.stat(dir, function(err, stats) {
    if (!err && stats.isDirectory()) {
      return fn(null, new Cache(ttl, dir));
    }
    fs.mkdir(dir, function(err) {
      if (err) {
        return fn(err);
      }
      return fn(null, new Cache(ttl, dir));
    });
  });
};

exports.createSync = function(ttl, dir) {
  if (!(fs.existsSync(dir) && fs.statSync(dir).isDirectory())) {
    fs.mkdirSync(dir);
  }
  return new Cache(ttl, dir);
};
<commit_msg>fix formatting<commit_after>'use strict';

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var async = require('async');

/*
* @param {Number} ttl - how long to store a value. null will store forever
*        default: null
* @param {String} dir - Where to store data
*/
function Cache(ttl, dir) {
    // This cache will live forever if ttl is null
  if (typeof ttl !== 'number') {
    ttl = null;
  }
  this.ttl = ttl;
  this.dir = dir;
}

Cache.prototype = {
    // Creates a md5 hash based on the stringified arg
  _makeFilepath: function(arg) {
    var hash = crypto.createHash('md5')
            .update(JSON.stringify(arg))
            .digest('hex');
    return path.join(this.dir, hash);
  },

  /*
  * @visibility private
  * @param {Mixed} key - A JSON stringifiable value
  * @param {Mixed} val - a JSON stringifiable value
  * @param {Boolean} isSync - specifies which fs call to use
  * @param {Function} [fn] - A function called only if the value is async
  */
  _put: function(key, val, isSync, fn) {
    var filepath = this._makeFilepath(key);

        // Create cache key
    var cache = {
      ttl: new Date(Date.now() + this.ttl),
      data: val
    };

    var data = JSON.stringify(cache);
    var  method = isSync ? 'writeFileSync' : 'writeFile';
    var args = [filepath, data, 'UTF8', isSync ? null : fn];

    return fs[method].apply(fs, args);
  },

  put: function(key, val, fn) {
    return this._put(key, val, false, fn);
  },

  putSync: function(key, val) {
    return this._put(key, val, true, null);
  },

  /*
  * @param {Mixed} key - A JSON stringifiable key that is used to lookup
  *                      up a cached file
  */
  get: function(key, fn) {
    var self = this;
    var filepath = this._makeFilepath(key);

    fs.exists(filepath, function(exists) {
      if (!exists) {
        return fn();
      }

      fs.readFile(filepath, 'UTF8', function(err, data) {
        if (err) {
          return fn(err);
        }

        var cache = JSON.parse(data);

        if (!self._cacheIsValid(cache)) {
          return self.del(key, fn);
        }
        fn(null, cache.data);
      });

    });
  },

  /*
  * @desc - Synchronous get
  */
  getSync: function(key) {
    var filepath = this._makeFilepath(key);
    if (!fs.existsSync(filepath)) {
      return;
    }

    var cache = JSON.parse(fs.readFileSync(filepath), 'UTF8');

    if (!this._cacheIsValid(cache)) {
      return this.delSync(key);
    }

    return cache.data;
  },

  _cacheIsValid: function(cache) {
        // A cache of null lives forever
    if (cache.ttl == null) {
      return true;
    }
    if (new Date(cache.ttl) - new Date() > 0) {
      return true;
    }
    return false;
  },

  /*
  * @param {Mixed} key - A JSON stringifiable key that is used to lookup
  *                      up a cached file
  */
  del: function(key, fn) {
    var filepath = this._makeFilepath(key);

    fs.exists(filepath, function(exists) {
      if (!exists) {
        return fn();
      }

      fs.unlink(filepath, fn);
    });
  },

  /*
  * @desc - Synchronous del
  */
  delSync: function(key) {
    var filepath = this._makeFilepath(key);

    if (!fs.existsSync(filepath)) {
      return;
    }

    fs.unlinkSync(filepath);
  },

  /*
  * @desc Clears all cached values
  */
  clean: function(fn) {
    // Cleans all cache files
    var self = this;

    fs.readdir(this.dir, function(err, files) {
      if (err) {
        return fn(err);
      }

      var methods = files.map(function(file) {
        return function(_fn) {
          fs.unlink(path.join(self.dir, file), function(err) {
            _fn(err);
          });
        };
      });

      async.parallel(methods, function(err) {
        fn(err);
      });
    });
  },

  /*
  * Synchronous clean
  */
  cleanSync: function() {
    // Cleans all cache files
    if (fs.existsSync(this.dir)) {
      var files = fs.readdirSync(this.dir);

      files.forEach(function(file) {
        fs.unlinkSync(path.join(this.dir, file));
      }.bind(this));
    }
  }
};

exports.Cache = Cache;

/*
* @param {Number} ttl
* @param {String} dir - Directory to store cached data
*/
exports.create = function(ttl, dir, fn) {
  fs.stat(dir, function(err, stats) {
    if (!err && stats.isDirectory()) {
      return fn(null, new Cache(ttl, dir));
    }

    fs.mkdir(dir, function(err) {
      if (err) {
        return fn(err);
      }
      return fn(null, new Cache(ttl, dir));
    });
  });
};

exports.createSync = function(ttl, dir) {
  if (!(fs.existsSync(dir) && fs.statSync(dir).isDirectory())) {
    fs.mkdirSync(dir);
  }

  return new Cache(ttl, dir);
};
<|endoftext|>


<commit_before>const baseUrl = 'http://m.dev.kaze-d.fr';

export class Config {
    static get baseUrl() {
        return baseUrl;
    }
}
<commit_msg>base url updated<commit_after>const baseUrl = 'https://m.dev.kaze-d.fr';

export class Config {
    static get baseUrl() {
        return baseUrl;
    }
}
<|endoftext|>


<commit_before>import * as api from '../helpers/api';

export const PAGE_SIZE = 16;

const fieldMap = {
  firstName: 'First Name',
  lastName: 'Last Name',
  emailAddress: 'Email Address',
  profilePictureURI: 'Avatar URL',
  vanityName: 'Vanity Name',
  bio: 'Bio',
  websiteURI: 'Website URL',
  blogURI: 'Blog Website URL',
  blogFeedURI: 'Blog Feed URL',
  cvURI: 'CV URL',
  linkedInURI: 'LinkedIn URL',
  githubUsername: 'GitHub Name',
  twitterUsername: 'Twitter Name'
};

function assignFriendlyFieldNames(fields) {
  const friendlyFields = {};
  const keys = Object.keys(fields);

  for (let i = 0; i < keys.length; ++i) {
    const key = keys[i];

    friendlyFields[key] = {};
    friendlyFields[key].name = fieldMap[key];
    friendlyFields[key].message = fields[key];
  }

  return friendlyFields;
}

export function getAllBloggers(pageNumber) {
  return api.get('user', {
    page: pageNumber - 1,
    page_size: PAGE_SIZE
  });
}

export function getSingleBlogger(vanityName) {
  return api.get('user', {
    vanity_name: vanityName
  });
}

export function getLoggedInBlogger(token) {
  return api.getAuth('user/me', token);
}

export function registerUser(user, token) {
  return api.postAuth('user', user, token)
    .then(data => {
      if (data.status === 201) {
        return data;
      }

      return {
        status: data.status,
        errors: assignFriendlyFieldNames(data.validationErrors)
      };
    });
}

export function updateUser(user, token) {
  return api.putAuth('user/me', user, token)
    .then(data => {
      if (data.status === 200) {
        return data;
      }

      const error = data;

      if (error.status === 422 && error.validationErrors.vanityName) {
        error.validationErrors.vanityName =
          `should only contain lowercase a-z, 0-9, and hyphens
           (no more than one hyphen consecutively) and must not
           start or end with a hyphen`;
      }

      return {
        status: error.status,
        errors: assignFriendlyFieldNames(error.validationErrors)
      };
    });
}

export function deleteUser(token) {
  return api.deleteAuth('user/me', null, token);
}
<commit_msg>Show correct error message for vanity name uniqueness<commit_after>import * as api from '../helpers/api';

export const PAGE_SIZE = 16;

const fieldMap = {
  firstName: 'First Name',
  lastName: 'Last Name',
  emailAddress: 'Email Address',
  profilePictureURI: 'Avatar URL',
  vanityName: 'Vanity Name',
  bio: 'Bio',
  websiteURI: 'Website URL',
  blogURI: 'Blog Website URL',
  blogFeedURI: 'Blog Feed URL',
  cvURI: 'CV URL',
  linkedInURI: 'LinkedIn URL',
  githubUsername: 'GitHub Name',
  twitterUsername: 'Twitter Name'
};

function assignFriendlyFieldNames(fields) {
  const friendlyFields = {};
  const keys = Object.keys(fields);

  for (let i = 0; i < keys.length; ++i) {
    const key = keys[i];

    friendlyFields[key] = {};
    friendlyFields[key].name = fieldMap[key];
    friendlyFields[key].message = fields[key];
  }

  return friendlyFields;
}

export function getAllBloggers(pageNumber) {
  return api.get('user', {
    page: pageNumber - 1,
    page_size: PAGE_SIZE
  });
}

export function getSingleBlogger(vanityName) {
  return api.get('user', {
    vanity_name: vanityName
  });
}

export function getLoggedInBlogger(token) {
  return api.getAuth('user/me', token);
}

export function registerUser(user, token) {
  return api.postAuth('user', user, token)
    .then(data => {
      if (data.status === 201) {
        return data;
      }

      return {
        status: data.status,
        errors: assignFriendlyFieldNames(data.validationErrors)
      };
    });
}

export function updateUser(user, token) {
  return api.putAuth('user/me', user, token)
    .then(data => {
      if (data.status === 200) {
        return data;
      }

      const error = data;

      if (error.name !== 'UniqueConstraintError' && error.validationErrors.vanityName) {
        error.validationErrors.vanityName =
          `should only contain lowercase a-z, 0-9, and hyphens
           (no more than one hyphen consecutively) and must not
           start or end with a hyphen`;
      }

      return {
        status: error.status,
        errors: assignFriendlyFieldNames(error.validationErrors)
      };
    });
}

export function deleteUser(token) {
  return api.deleteAuth('user/me', null, token);
}
<|endoftext|>


<commit_before>/**
 * Separate processing code commit data for the product widget
 */
(function () {
    'use strict';

    angular
        .module(HygieiaConfig.module)
        .factory('productCommitData', function () {
            return {
                process: process
            }
        });

    function process(dependencies) {
        // unwrap dependencies
        var db = dependencies.db,
            configuredTeam = dependencies.configuredTeam,
            $q = dependencies.$q,
            $timeout = dependencies.$timeout,
            isReload = dependencies.isReload,
            pipelineData = dependencies.pipelineData,
            nowTimestamp = dependencies.nowTimestamp,
            ctrlStages = dependencies.ctrlStages,
            prodStageValue = dependencies.prodStageValue;

        var prodStage = prodStageValue;

        // timestamps
        var now = moment(),
            dateEnds = now.valueOf(),
            ninetyDaysAgo = now.add(-90, 'days').valueOf(),
            dateBegins = ninetyDaysAgo;

        // querying pipeline commits by date will only return production commits that have
        // moved to prod since that last request. this way we can avoid sending 90 days
        // of production commit data with each request. all other environments will show
        // a current snapshot of data
        var collectorItemId = configuredTeam.collectorItemId;

        // get our pipeline commit data. start by seeing if we've already run this request
        db.lastRequest.where('[type+id]').equals(['pipeline-commit', collectorItemId]).first().then(processLastRequestResponse);

        function processLastRequestResponse(lastRequest) {
            // if we already have made a request, just get the delta
            pipelineData
                .commits(dateBegins, nowTimestamp, collectorItemId)
                .then(function (response) {
                    return processPipelineCommitResponse(response, lastRequest);
                })
                .then(processPipelineCommitData)
                .finally(function () {
                    dependencies.cleanseData(db.prodCommit, ninetyDaysAgo);
                });
        }

        function processPipelineCommitResponse(response, lastRequest) {
            if (!response.length) {
                return $q.reject('No response found');
            }

            // we only requested one team so it's safe to assume
            // that it's in the first position
            response = response[0];

            // don't continue saving local data
            if (HygieiaConfig.local && isReload) {
                return response;
            }

            // save the request object so we can get the delta next time as well
            if (lastRequest) {
                lastRequest.timestamp = dateEnds;
                lastRequest.save();
            }
            // need to add a new item
            else {
                db.lastRequest.add({
                    id: collectorItemId,
                    type: 'pipeline-commit',
                    timestamp: dateEnds
                });
            }

            // put all results in the database
            _(response.stages[prodStage]).forEach(function (commit) {
                // extend the commit object with fields we need
                // to search the db
                commit.collectorItemId = collectorItemId;
                commit.timestamp = commit.processedTimestamps[prodStage];

                db.prodCommit.add(commit);
            });

            return response;
        }

        function processPipelineCommitData(team) {
            db.prodCommit.where('[collectorItemId+timestamp]').between([collectorItemId, ninetyDaysAgo], [collectorItemId, dateEnds]).toArray(function (rows) {
                var uniqueRows = _.uniqBy(rows,'scmRevisionNumber');
                team.stages[prodStage] = _(uniqueRows).sortBy('timestamp').reverse().value();

                var teamStageData = {},
                    stageDurations = {},
                    stages = [].concat(ctrlStages); // create a local copy so it doesn't get overwritten

                // go backward through the stages and define commit data.
                // reverse should make it easier to calculate time in the previous stage
                _(stages).reverse().forEach(function (currentStageName) {
                    var commits = [], // store our new commit object
                        localStages = [].concat(ctrlStages), // create a copy of the stages
                        previousStages = _(localStages.splice(0, localStages.indexOf(currentStageName))).reverse().value(); // only look for stages before this one

                    // loop through each commit and create our own custom commit object
                    _(team.stages[currentStageName]).forEach(function (commitObj) {
                        var commit = {
                            author: commitObj.scmAuthor || 'NA',
                            message: commitObj.scmCommitLog || 'No message',
                            id: commitObj.scmRevisionNumber,
                            timestamp: commitObj.scmCommitTimestamp,
                            in: {} //placeholder for stage duration data per commit
                        };

                        // make sure this stage exists to track durations
                        if (!stageDurations[currentStageName]) {
                            stageDurations[currentStageName] = [];
                        }

                        // use this commit to calculate time in the current stage
                        var currentStageTimestampCompare = commit.timestamp;
                        if (commitObj.processedTimestamps[currentStageName]) {
                            currentStageTimestampCompare = commitObj.processedTimestamps[currentStageName];
                        }

                        // use this time in our metric calculations
                        var timeInCurrentStage = nowTimestamp - currentStageTimestampCompare;
                        stageDurations[currentStageName].push(timeInCurrentStage);

                        // make sure current stage is set
                        commit.in[currentStageName] = timeInCurrentStage;

                        // on each commit, set data for how long it was in each stage by looping
                        // through any previous stage and subtracting its timestamp from the next stage
                        var currentStageTimestamp = commitObj.processedTimestamps[currentStageName];
                        _(previousStages).forEach(function (previousStage) {
                            if (!commitObj.processedTimestamps[previousStage] || isNaN(currentStageTimestamp)) {
                                return;
                            }

                            var previousStageTimestamp = commitObj.processedTimestamps[previousStage],
                                timeInPreviousStage = currentStageTimestamp - previousStageTimestamp;

                            // it is possible that a hot-fix or some other change was made which caused
                            // the commit to skip an earlier environment. In this case just set that
                            // time to 0 so it's considered in the calculation, but does not negatively
                            // take away from the average
                            timeInPreviousStage = Math.max(timeInPreviousStage, 0);

                            // add how long it was in the previous stage
                            commit.in[previousStage] = timeInPreviousStage;

                            // add this number to the stage duration array so it can be used
                            // to calculate each stages average duration individually
                            if (!stageDurations[previousStage]) {
                                stageDurations[previousStage] = [];
                            }

                            // add this time to our duration list
                            stageDurations[previousStage].push(timeInPreviousStage);

                            // now use this as our new current timestamp
                            currentStageTimestamp = previousStageTimestamp;
                        });

                        // add our commit object back
                        commits.push(commit);
                    });

                    // make sure commits are always set
                    teamStageData[currentStageName] = {
                        commits: commits
                    }
                });

                // now that we've added all the duration data for all commits in each stage
                // we can calculate the averages and std deviation and put the data on the stage
                _(stageDurations).forEach(function (durationArray, currentStageName) {
                    if (!teamStageData[currentStageName]) {
                        teamStageData[currentStageName] = {};
                    }

                    var stats = getStageDurationStats(durationArray);
                    angular.extend(teamStageData[currentStageName], {
                        stageAverageTime: stats.mean,
                        stageStdDeviation: stats.deviation
                    })
                });

                // now that we have average and std deviation we can determine if a commit
                // has been in the environment for longer than 2 std deviations in which case
                // it should be marked as a failure
                _(teamStageData).forEach(function (data, stage) {

                    if (!data.stageStdDeviation || !data.commits) {
                        return;
                    }

                    _(data.commits).forEach(function (commit) {
                        // use the time it's been in the existing environment to compare
                        var timeInStage = commit.in[stage];

                        commit.errorState = timeInStage > 2 * data.stageStdDeviation;
                    });
                });

                // create some summary data used in each stage's cell
                _(teamStageData).forEach(function (stageData, stageName) {
                    stageData.summary = {
                        // helper for determining whether this stage has current commits
                        hasCommits: stageData.commits && stageData.commits.length ? true : false,

                        // green block count
                        commitsInsideTimeframe: _(stageData.commits).filter(function (c) {
                            return !c.errorState;
                        }).value().length,

                        // red block count
                        commitsOutsideTimeframe: _(stageData.commits).filter({errorState: true}).value().length,

                        // stage last updated text
                        lastUpdated: (function (stageData) {
                            if (!stageData.commits || !stageData.commits.length) {
                                return false;
                            }

                            // try to get the last commit to enter this stage by evaluating the duration
                            // for this current stage, otherwise use the commit timestamp
                            var lastUpdatedDuration = _(stageData.commits).map(function (commit) {
                                    return commit.in[stageName] || moment().valueOf() - commit.timestamp;
                                }).min(),
                                lastUpdated = moment().add(-1 * lastUpdatedDuration, 'milliseconds');

                            return {
                                longDisplay: lastUpdated.format('MMMM Do YYYY, h:mm:ss a'),
                                shortDisplay: lastUpdated.dash('ago')
                            }
                        })(stageData),

                        // stage deviation
                        deviation: (function (stageData) {
                            if (stageData.stageStdDeviation == undefined) {
                                return false;
                            }

                            // determine how to display the standard deviation
                            var number = moment.duration(2 * stageData.stageStdDeviation).minutes(),
                                desc = 'min';

                            if (number > 60 * 24) {
                                desc = 'day';
                                number = Math.round(number / 24 / 60);
                            }
                            else if (number > 60) {
                                desc = 'hour';
                                number = Math.round(number / 60);
                            }

                            return {
                                number: number,
                                descriptor: desc
                            }
                        })(stageData),

                        average: (function (stageData) {
                            // determine how to display the average time
                            if (!stageData.stageAverageTime) {
                                return false;
                            }

                            var average = moment.duration(stageData.stageAverageTime);

                            return {
                                days: Math.floor(average.asDays()),
                                hours: average.hours(),
                                minutes: average.minutes()
                            }
                        })(stageData)
                    };
                });

                // calculate info used in prod cell
                var commitTimeToProd;
                var commitArray = _(team.stages)
                // limit to prod
                    .filter(function (val, key) {
                        return key == prodStage
                    })
                    // make all commits a single array
                    .reduce(function (num, commits) {
                        return num + commits;
                    });
                if(!angular.isUndefined(commitArray)){
                    // calculate their time to prod
                    commitTimeToProd = commitArray.map(function (commit) {
                            return {
                                duration: commit.processedTimestamps[prodStage] - commit.scmCommitTimestamp,
                                commitTimestamp: commit.scmCommitTimestamp
                            };
                        });
                }

                var teamProdData = {
                    averageDays: '--',
                    totalCommits: 0
                },commitTimeToProd;

                teamProdData.totalCommits = !angular.isUndefined(commitTimeToProd)?commitTimeToProd.length:0;

                if (!angular.isUndefined(commitTimeToProd)?commitTimeToProd.length:0 > 1) {
                    var averageDuration = _(commitTimeToProd).map('duration').reduce(function (a, b) {
                            return a + b;
                        }) / commitTimeToProd.length;

                    teamProdData.averageDays = Math.floor(moment.duration(averageDuration).asDays());

                    var plotData = _(commitTimeToProd).map(function (ttp) {
                        var daysAgo = -1 * moment.duration(moment().diff(ttp.commitTimestamp)).asDays();
                        return [daysAgo, ttp.duration];
                    }).value();

                    var averageToProdResult = regression('linear', plotData);
                    teamProdData.trendUp = averageToProdResult.equation[0] > 0;
                }

                // handle the api telling us which stages need configuration
                if (team.unmappedStages) {
                    for (var stageName in teamStageData) {
                        teamStageData[stageName].needsConfiguration = team.unmappedStages.indexOf(stageName) != -1;
                    }
                }

                $timeout(function() {
                    dependencies.setTeamData(team.collectorItemId, {
                    stages: teamStageData,
                    prod: teamProdData,
                    prodStage:prodStage
                    });
                });
            });
        }

        function getStageDurationStats(a) {
            var r = {mean: 0, variance: 0, deviation: 0}, t = a.length;
            for (var m, s = 0, l = t; l--; s += a[l]);
            for (m = r.mean = s / t, l = t, s = 0; l--; s += Math.pow(a[l] - m, 2));
            return r.deviation = Math.sqrt(r.variance = s / t), r;
        }
    }
})();<commit_msg>Added restriction to push time (#2760)<commit_after>/**
 * Separate processing code commit data for the product widget
 */
(function () {
    'use strict';

    angular
        .module(HygieiaConfig.module)
        .factory('productCommitData', function () {
            return {
                process: process
            }
        });

    function process(dependencies) {
        // unwrap dependencies
        var db = dependencies.db,
            configuredTeam = dependencies.configuredTeam,
            $q = dependencies.$q,
            $timeout = dependencies.$timeout,
            isReload = dependencies.isReload,
            pipelineData = dependencies.pipelineData,
            nowTimestamp = dependencies.nowTimestamp,
            ctrlStages = dependencies.ctrlStages,
            prodStageValue = dependencies.prodStageValue;

        var prodStage = prodStageValue;

        // timestamps
        var now = moment(),
            dateEnds = now.valueOf(),
            ninetyDaysAgo = now.add(-90, 'days').valueOf(),
            dateBegins = ninetyDaysAgo;

        // querying pipeline commits by date will only return production commits that have
        // moved to prod since that last request. this way we can avoid sending 90 days
        // of production commit data with each request. all other environments will show
        // a current snapshot of data
        var collectorItemId = configuredTeam.collectorItemId;

        // get our pipeline commit data. start by seeing if we've already run this request
        db.lastRequest.where('[type+id]').equals(['pipeline-commit', collectorItemId]).first().then(processLastRequestResponse);

        function processLastRequestResponse(lastRequest) {
            // if we already have made a request, just get the delta
            pipelineData
                .commits(dateBegins, nowTimestamp, collectorItemId)
                .then(function (response) {
                    return processPipelineCommitResponse(response, lastRequest);
                })
                .then(processPipelineCommitData)
                .finally(function () {
                    dependencies.cleanseData(db.prodCommit, ninetyDaysAgo);
                });
        }

        function processPipelineCommitResponse(response, lastRequest) {
            if (!response.length) {
                return $q.reject('No response found');
            }

            // we only requested one team so it's safe to assume
            // that it's in the first position
            response = response[0];

            // don't continue saving local data
            if (HygieiaConfig.local && isReload) {
                return response;
            }

            // save the request object so we can get the delta next time as well
            if (lastRequest) {
                lastRequest.timestamp = dateEnds;
                lastRequest.save();
            }
            // need to add a new item
            else {
                db.lastRequest.add({
                    id: collectorItemId,
                    type: 'pipeline-commit',
                    timestamp: dateEnds
                });
            }

            // put all results in the database
            _(response.stages[prodStage]).forEach(function (commit) {
                // extend the commit object with fields we need
                // to search the db
                commit.collectorItemId = collectorItemId;
                commit.timestamp = commit.processedTimestamps[prodStage];

                db.prodCommit.add(commit);
            });

            return response;
        }

        function processPipelineCommitData(team) {
            db.prodCommit.where('[collectorItemId+timestamp]').between([collectorItemId, ninetyDaysAgo], [collectorItemId, dateEnds]).toArray(function (rows) {
                var uniqueRows = _.uniqBy(rows,'scmRevisionNumber');
                team.stages[prodStage] = _(uniqueRows).sortBy('timestamp').reverse().value();

                var teamStageData = {},
                    stageDurations = {},
                    stages = [].concat(ctrlStages); // create a local copy so it doesn't get overwritten

                // go backward through the stages and define commit data.
                // reverse should make it easier to calculate time in the previous stage
                var nextStageName = ''
                _(stages).reverse().forEach(function (currentStageName) {
                    var commits = [], // store our new commit object
                        localStages = [].concat(ctrlStages), // create a copy of the stages
                        previousStages = _(localStages.splice(0, localStages.indexOf(currentStageName))).reverse().value(); // only look for stages before this one

                    // loop through each commit and create our own custom commit object
                    _(team.stages[currentStageName]).forEach(function (commitObj) {
                        var commit = {
                            author: commitObj.scmAuthor || 'NA',
                            message: commitObj.scmCommitLog || 'No message',
                            id: commitObj.scmRevisionNumber,
                            timestamp: commitObj.scmCommitTimestamp,
                            in: {} //placeholder for stage duration data per commit
                        };

                        // make sure this stage exists to track durations
                        if (!stageDurations[currentStageName]) {
                            stageDurations[currentStageName] = [];
                        }

                        // use this commit to calculate time in the current stage
                        var currentStageTimestampCompare = commit.timestamp;
                        if (commitObj.processedTimestamps[currentStageName]) {
                            currentStageTimestampCompare = commitObj.processedTimestamps[currentStageName];
                        }

                        // use this time in our metric calculations
                        var timeInCurrentStage = nowTimestamp - currentStageTimestampCompare;
                        if (nextStageName != '' && team.stages[nextStageName].length == 0) {
                             stageDurations[currentStageName].push(timeInCurrentStage);
                        }

                        // make sure current stage is set
                        commit.in[currentStageName] = timeInCurrentStage;

                        // on each commit, set data for how long it was in each stage by looping
                        // through any previous stage and subtracting its timestamp from the next stage
                        var currentStageTimestamp = commitObj.processedTimestamps[currentStageName];
                        _(previousStages).forEach(function (previousStage) {
                            if (!commitObj.processedTimestamps[previousStage] || isNaN(currentStageTimestamp)) {
                                return;
                            }

                            var previousStageTimestamp = commitObj.processedTimestamps[previousStage],
                                timeInPreviousStage = currentStageTimestamp - previousStageTimestamp;

                            // it is possible that a hot-fix or some other change was made which caused
                            // the commit to skip an earlier environment. In this case just set that
                            // time to 0 so it's considered in the calculation, but does not negatively
                            // take away from the average
                            timeInPreviousStage = Math.max(timeInPreviousStage, 0);

                            // add how long it was in the previous stage
                            commit.in[previousStage] = timeInPreviousStage;

                            // add this number to the stage duration array so it can be used
                            // to calculate each stages average duration individually
                            if (!stageDurations[previousStage]) {
                                stageDurations[previousStage] = [];
                            }

                            // add this time to our duration list
                            stageDurations[previousStage].push(timeInPreviousStage);

                            // now use this as our new current timestamp
                            currentStageTimestamp = previousStageTimestamp;
                        });

                        // add our commit object back
                        commits.push(commit);
                    });

                    // make sure commits are always set
                    teamStageData[currentStageName] = {
                        commits: commits
                    }
                    nextStageName = currentStageName;
                });

                // now that we've added all the duration data for all commits in each stage
                // we can calculate the averages and std deviation and put the data on the stage
                _(stageDurations).forEach(function (durationArray, currentStageName) {
                    if (!teamStageData[currentStageName]) {
                        teamStageData[currentStageName] = {};
                    }

                    var stats = getStageDurationStats(durationArray);
                    angular.extend(teamStageData[currentStageName], {
                        stageAverageTime: stats.mean,
                        stageStdDeviation: stats.deviation
                    })
                });

                // now that we have average and std deviation we can determine if a commit
                // has been in the environment for longer than 2 std deviations in which case
                // it should be marked as a failure
                _(teamStageData).forEach(function (data, stage) {

                    if (!data.stageStdDeviation || !data.commits) {
                        return;
                    }

                    _(data.commits).forEach(function (commit) {
                        // use the time it's been in the existing environment to compare
                        var timeInStage = commit.in[stage];

                        commit.errorState = timeInStage > 2 * data.stageStdDeviation;
                    });
                });

                // create some summary data used in each stage's cell
                _(teamStageData).forEach(function (stageData, stageName) {
                    stageData.summary = {
                        // helper for determining whether this stage has current commits
                        hasCommits: stageData.commits && stageData.commits.length ? true : false,

                        // green block count
                        commitsInsideTimeframe: _(stageData.commits).filter(function (c) {
                            return !c.errorState;
                        }).value().length,

                        // red block count
                        commitsOutsideTimeframe: _(stageData.commits).filter({errorState: true}).value().length,

                        // stage last updated text
                        lastUpdated: (function (stageData) {
                            if (!stageData.commits || !stageData.commits.length) {
                                return false;
                            }

                            // try to get the last commit to enter this stage by evaluating the duration
                            // for this current stage, otherwise use the commit timestamp
                            var lastUpdatedDuration = _(stageData.commits).map(function (commit) {
                                    return commit.in[stageName] || moment().valueOf() - commit.timestamp;
                                }).min(),
                                lastUpdated = moment().add(-1 * lastUpdatedDuration, 'milliseconds');

                            return {
                                longDisplay: lastUpdated.format('MMMM Do YYYY, h:mm:ss a'),
                                shortDisplay: lastUpdated.dash('ago')
                            }
                        })(stageData),

                        // stage deviation
                        deviation: (function (stageData) {
                            if (stageData.stageStdDeviation == undefined) {
                                return false;
                            }

                            // determine how to display the standard deviation
                            var number = moment.duration(2 * stageData.stageStdDeviation).minutes(),
                                desc = 'min';

                            if (number > 60 * 24) {
                                desc = 'day';
                                number = Math.round(number / 24 / 60);
                            }
                            else if (number > 60) {
                                desc = 'hour';
                                number = Math.round(number / 60);
                            }

                            return {
                                number: number,
                                descriptor: desc
                            }
                        })(stageData),

                        average: (function (stageData) {
                            // determine how to display the average time
                            if (!stageData.stageAverageTime) {
                                return false;
                            }

                            var average = moment.duration(stageData.stageAverageTime);

                            return {
                                days: Math.floor(average.asDays()),
                                hours: average.hours(),
                                minutes: average.minutes()
                            }
                        })(stageData)
                    };
                });

                // calculate info used in prod cell
                var commitTimeToProd;
                var commitArray = _(team.stages)
                // limit to prod
                    .filter(function (val, key) {
                        return key == prodStage
                    })
                    // make all commits a single array
                    .reduce(function (num, commits) {
                        return num + commits;
                    });
                if(!angular.isUndefined(commitArray)){
                    // calculate their time to prod
                    commitTimeToProd = commitArray.map(function (commit) {
                            return {
                                duration: commit.processedTimestamps[prodStage] - commit.scmCommitTimestamp,
                                commitTimestamp: commit.scmCommitTimestamp
                            };
                        });
                }

                var teamProdData = {
                    averageDays: '--',
                    totalCommits: 0
                },commitTimeToProd;

                teamProdData.totalCommits = !angular.isUndefined(commitTimeToProd)?commitTimeToProd.length:0;

                if (!angular.isUndefined(commitTimeToProd)?commitTimeToProd.length:0 > 1) {
                    var averageDuration = _(commitTimeToProd).map('duration').reduce(function (a, b) {
                            return a + b;
                        }) / commitTimeToProd.length;

                    teamProdData.averageDays = Math.floor(moment.duration(averageDuration).asDays());

                    var plotData = _(commitTimeToProd).map(function (ttp) {
                        var daysAgo = -1 * moment.duration(moment().diff(ttp.commitTimestamp)).asDays();
                        return [daysAgo, ttp.duration];
                    }).value();

                    var averageToProdResult = regression('linear', plotData);
                    teamProdData.trendUp = averageToProdResult.equation[0] > 0;
                }

                // handle the api telling us which stages need configuration
                if (team.unmappedStages) {
                    for (var stageName in teamStageData) {
                        teamStageData[stageName].needsConfiguration = team.unmappedStages.indexOf(stageName) != -1;
                    }
                }

                $timeout(function() {
                    dependencies.setTeamData(team.collectorItemId, {
                    stages: teamStageData,
                    prod: teamProdData,
                    prodStage:prodStage
                    });
                });
            });
        }

        function getStageDurationStats(a) {
            var r = {mean: 0, variance: 0, deviation: 0}, t = a.length;
            for (var m, s = 0, l = t; l--; s += a[l]);
            for (m = r.mean = s / t, l = t, s = 0; l--; s += Math.pow(a[l] - m, 2));
            return r.deviation = Math.sqrt(r.variance = s / t), r;
        }
    }
})();
<|endoftext|>


<commit_before>'use strict';

/**
 * This contains all helper methods for the services install
 */

module.exports = function(kbox) {

  // Native modules
  var fs = require('fs');
  var path = require('path');

  // Npm modules
  var _ = require('lodash');

  // Kalabox modules
  var meta = require('./meta.js')(kbox);
  var provider = kbox.engine.provider;
  var Promise = kbox.Promise;
  var config = kbox.core.deps.get('globalConfig');

  // Set Kalabox DNS constantsz
  // @todo: stronger test
  var KALABOX_WIN32_DNS = '10.13.37.42';

  /*
   * Return some info about the current state of the kalabox installation
   */
  var getCurrentInstall = function(installDetails) {

    // This is where our current install file should live
    var cIF = path.join(config.sysConfRoot, 'installed.json');

    // If the file exists use that if not empty object
    var currentInstall = (fs.existsSync(cIF)) ? require(cIF) : {};

    return currentInstall;

  };

  /*
   * Helper function to grab and compare a meta prop
   */
  var getProUp = function(prop) {

    // Get details about the state of the current installation
    var currentInstall = getCurrentInstall();

    // This is the first time we've installed so we def need
    if (_.isEmpty(currentInstall) || !currentInstall[prop]) {
      return true;
    }

    // We have a syncversion to compare
    // @todo: is diffence a strong enough check?
    var nV = meta[prop];
    if (currentInstall[prop] && (currentInstall[prop] !== nV)) {
      return true;
    }

    // Hohum i guess we're ok
    return false;

  };

  /*
   * Helper function to assess whether we need new service images
   */
  var needsImages = function() {
    return getProUp('SERVICE_IMAGES_VERSION');
  };

  /*
   * Helper function to determine whether we need to run darwin DNS commands
   */
  var needsDarwinDNS = function() {
    var dnsPath = path.join(meta.dns.darwin.path, meta.dns.darwin.file);
    return !fs.existsSync(dnsPath);
  };

  /*
   * Return linux dns information.
   * @todo: clean this up
   */
  var getLinuxDnsServers = _.once(function() {

    // Get linux flavor.
    var flavor = kbox.install.linuxOsInfo.getFlavor();

    // Get linux flavor distribution.
    var distro = (flavor === 'debian') ? flavor : 'linux';

    // Build path to dns file.
    var dnsFilepath = path.join(
      meta.dns.linux[distro].path,
      meta.dns.linux[distro].file
    );

    // Read dns file.
    return Promise.fromNode(function(cb) {
      fs.readFile(dnsFilepath, {encoding: 'utf8'}, cb);
    })
    // If file does not exist return false otherwise throw error.
    .catch(function(err) {
      if (err.code === 'ENOENT') {
        return '';
      } else {
        throw err;
      }
    })
    // Split data read from file into lines.
    .then(function(data) {
      return data.split('\n');
    })
    // Filter out uninteresting lines.
    .filter(function(line) {
      var isComment = _.startsWith(line, '#');
      var hasSpaces = _.contains(line, ' ');
      // Filter out comments, and only include lines which contain spaces.
      return !isComment && hasSpaces;
    })
    // Reduce remaining lines into an array of name servers.
    .reduce(function(dnsServers, line) {
      // Split each line, trim the parts, and remove double quotes.
      var parts = _.chain(line.split(' '))
        .map(_.trim)
        .map(function(part) {
          return part.replace(/"/g, '');
        })
        .value();
      // If parts array starts with nameserver, the second element is a
      // dns nameserver, so add it to the list of dns servers.
      if (parts[0] === 'nameserver') {
        dnsServers.push(parts[1]);
      }
      return dnsServers;
    }, []);
  });

  /*
   * Helper function to determine whether we need to run linux DNS commands
   */
  var needsLinuxDNS = function() {

    // Use for later
    var ips = [];

    // Grab list of server IPs
    return provider().call('getServerIps')

    // Add dns servers to list of dns options.
    // Get list of dns server configured on this linux host.
    .then(function(result) {
      ips = result;
      return getLinuxDnsServers();
    })

    // If we are missing DNS servers add them
    .then(function(dnsServers) {

      // Get what DNS servers we should consider adding
      var diff = _.difference(ips, dnsServers);
      return !_.isEmpty(diff);
    });

  };

  /*
   * Returns what we need to add for DNS to linux
   */
  var getLinuxDNStoAdd = function() {

    // Use this later
    var serverIps = [];

    // Grab list of server IPs
    return provider().call('getServerIps')

    // Add dns servers to list of dns options.
    // Get list of dns server configured on this linux host.
    .then(function(result) {
      serverIps = result;
      return getLinuxDnsServers();
    })

    // If we are missing DNS servers add them
    .then(function(dnsServers) {

      // If list of configured dns servers doesn't include this
      // IP then we need to run things
      var ipsToAdd = [];
      _.each(serverIps, function(ip) {
        if (!_.contains(dnsServers, ip)) {
          ipsToAdd.push(['nameserver', ip].join(' '));
        }
      });

      // Debug
      kbox.core.log.debug('DNS2ADD4U => ' + JSON.stringify(ipsToAdd));

      // Return what we need to add
      return ipsToAdd;

    });

  };

  /*
   * Returns whether you need to add in some packages or not during the install
   * step
   * @todo: as of now this targets basically just linux
   */
  var needsDNSAdmin = function() {

    // Get shell library.
    var shell = kbox.core.deps.get('shell');
    var flavor = kbox.install.linuxOsInfo.getFlavor();

    // Command to run
    // @todo: other flavors
    if (flavor !== 'debian') {
      return Promise.resolve(false);
    }
    // Check to see if this package is already installed
    else {

      // Cmd to run
      var cmd = 'dpkg-query -f \'${binary:Package}\n\' -W | grep resolvconf';

      // Get network information from virtual box.
      return Promise.fromNode(function(cb) {
        shell.exec(cmd, cb);
      })

      // Parse the output
      .then(function(output) {

        // Debug log output
        kbox.core.log.debug('DNS PKG => ' + JSON.stringify(output.trim()));

        return !_.contains(output.trim(), 'resolvconf');

      });
    }

  };

  /*
   * Get the correct windows network adapter
   */
  var getWindowsAdapter = function() {

    // Get shell library.
    var shell = kbox.core.deps.get('shell');

    // Command to run
    var cmd = [
      '"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe"',
      'showvminfo "Kalabox2" | findstr "Host-only"'
    ];

    // Get network information from virtual box.
    return Promise.fromNode(function(cb) {
      shell.exec(cmd.join(' '), cb);
    })

    // Parse the output
    .then(function(output) {

      // Debug log output
      kbox.core.log.debug('ADAPTER INFO => ' + JSON.stringify(output));

      // Parse output to get network adapter information.
      var start = output.indexOf('\'');
      var last = output.lastIndexOf('\'');

      // Get the adapter
      var adapter = [
        output.slice(start + 1, last).replace('Ethernet Adapter', 'Network')
      ];

      // debug
      kbox.core.log.debug('WINDOWS ADAPTER => ' + JSON.stringify(adapter));

      // Return
      return adapter;
    });

  };

  /*
   * Helper function to determine whether we need to run win32 DNS commands
   * @todo: Need to expand this to handle both nameservers
   */
  var needsWin32DNS = function() {

    // Get shell library.
    var shell = kbox.core.deps.get('shell');

    // @todo: Need a stronger check than this eventually
    var ip = KALABOX_WIN32_DNS;

    // Grab the host only adapter so we can be SUPER PRECISE!
    return getWindowsAdapter()

    // Get network information from virtual box.
    .then(function(adapter) {

      var adp = adapter;

      // Command to run
      var cmd = 'netsh interface ipv4 show dnsservers';

      // Execute promisified shell
      return Promise.fromNode(function(cb) {
        shell.exec(cmd, cb);
      })

      // Need to catch findstr null reporting as error
      .catch(function(err) {
        // @todo: something more precise here
      })

      .then(function(output) {

        // Truncate the string for just data on what we need
        // This elminates the possibility that another adapter has our
        // setup. Although, to be fair, if another adapter does then
        // we are probably SOL anyway.

        // Trim the left
        var leftTrim = 'Configuration for interface "' + adp + '"';
        var truncLeft = output.indexOf(leftTrim);
        var left = output.slice(truncLeft);

        // Trim the right
        var rightTrim = 'Register with which suffix:';
        var truncRight = left.indexOf(rightTrim);
        var adapterConfig = left.slice(0, truncRight);

        // Get the primary DNS IP
        var aSplit = adapterConfig.split(':');
        var adapter = _.trim(aSplit[1]);

        // Return precise
        return adapter;
      });
    })

    // Parse the output
    .then(function(output) {

      // Parse output
      var needDns = !_.contains(output, ip);

      // Debug log output
      kbox.core.log.debug('DNS SET CORRECTLY => ' + JSON.stringify(!needDns));

      // Return
      return needDns;
    });
  };

  /*
   * Validate admin commands
   */
  var validateCmds = function(cmds) {

    // Check if this is an array
    if (!Array.isArray(cmds)) {
      return 'Invalid adminCommands: ' + cmds;
    }

    // Check if each cmd is a string
    cmds.forEach(function(cmd, index) {
      if (typeof cmd !== 'string' || cmd.length < 1) {
        return 'Invalid cmd index: ' + index + ' cmd: ' + cmd;
      }
    });

    // Looks like we good!
    return true;

  };

  /*
   * Run the admin commands
   */
  var runCmds = function(adminCommands, state, callback) {

    // Validate the admin commands
    if (validateCmds(adminCommands) !== true) {
      callback(new Error(validateCmds(adminCommands)));
    }

    // Process admin commands.
    var child = kbox.install.cmd.runCmdsAsync(adminCommands, state);

    // Events
    // Output data
    child.stdout.on('data', function(data) {
      state.log.info(data);
    });
    // Callback when done
    child.stdout.on('end', function() {
      callback();
    });
    // Output stderr data.
    child.stderr.on('data', function(data) {
      state.log.info(data);
    });
    // Fail the installer if we get an error
    child.stderr.on('error', function(err) {
      state.fail(state, err);
    });

  };

  /*
   * Helper to set up darwin DNS
   */
  var setupDarwinDNS = function(state) {

    // Start up a collector
    var dnsCmds = [];

    // Get list of server ips.
    return provider().call('getServerIps')

    // Add dns setup command.
    .then(function(ips) {

      // Build DNS command
      if (needsDarwinDNS()) {
        var dnsFile = [meta.dns.darwin.path, meta.dns.darwin.file];
        var ipCmds = kbox.install.cmd.buildDnsCmd(ips, dnsFile);
        var cmd = ipCmds.join(' && ');
        dnsCmds.push(cmd);
      }

      // Debug
      kbox.core.log.debug('DNS CMDS => ' + JSON.stringify(dnsCmds));

      // Try to install DNS if we have commands to run
      return Promise.fromNode(function(cb) {
        if (!_.isEmpty(dnsCmds)) {
          runCmds(dnsCmds, state, cb);
        }
        else {
          cb();
        }
      });

    });

  };

  /*
   * Helper to set up windows DNS
   */
  var setupWindowsDNS = function(state) {

    // Start up a collector
    var dnsCmds = [];

    return needsWin32DNS()

    .then(function(needsDns) {
      if (needsDns !== KALABOX_WIN32_DNS) {
        // Grab the appropriate windows network adapter
        return getWindowsAdapter()
        // Generate teh DNS commands and run it
        .then(function(adapter) {

          // Get list of server IPs.
          return provider().call('getServerIps')

          .then(function(ips) {

            // Build DNS commands
            dnsCmds = (kbox.install.cmd.buildDnsCmd(ips, adapter));

            // Debug
            state.log.debug('DNS CMDS => ' + JSON.stringify(dnsCmds));

            // Run each command through elevation
            // @todo: doesn't seem like node-windows can
            // handle combining via & or && so do this for now
            return Promise.each(dnsCmds, function(cmd) {
              return kbox.util.shell.execElevated(cmd);
            });
          });

        });
      }
    });

  };

  /*
   * Helper to set up linux DNS
   */
  var setupLinuxDNS = function(state) {

    // Start a collection
    var dnsCmds = [];
    // Grab linux flavor
    var flavor = kbox.install.linuxOsInfo.getFlavor();
    // Grab a generic dis
    var dis = (flavor === 'debian') ? flavor : 'linux';

    // Check out what we need to add
    return getLinuxDNStoAdd()

    // Put together some commands if we need to
    .then(function(ips) {

      // Check if we need linux DNS
      return needsLinuxDNS()

      // Create DNS command
      .then(function(needIt) {
        if (needIt) {
          var dnsFile = [meta.dns.linux[dis].path, meta.dns.linux[dis].file];
          dnsCmds = kbox.install.cmd.buildDnsCmd(ips, dnsFile);
          dnsCmds.push('resolvconf -u');
        }
      })

      // Run commands if needed
      .then(function() {
        // Debug
        state.log.debug('DNS CMDS => ' + JSON.stringify(dnsCmds));

        // Try to install DNS if we have commands to run
        return Promise.fromNode(function(cb) {
          if (!_.isEmpty(dnsCmds)) {
            runCmds(dnsCmds, state, cb);
          }
          else {
            cb();
          }
        });
      });

    });

  };

  return {
    needsImages: needsImages,
    needsDNSAdmin: needsDNSAdmin,
    setupDarwinDNS: setupDarwinDNS,
    setupWindowsDNS: setupWindowsDNS,
    setupLinuxDNS: setupLinuxDNS
  };

};
<commit_msg>#720: More precise needsDNS func on Win32<commit_after>'use strict';

/**
 * This contains all helper methods for the services install
 */

module.exports = function(kbox) {

  // Native modules
  var fs = require('fs');
  var path = require('path');

  // Npm modules
  var _ = require('lodash');

  // Kalabox modules
  var meta = require('./meta.js')(kbox);
  var provider = kbox.engine.provider;
  var Promise = kbox.Promise;
  var config = kbox.core.deps.get('globalConfig');

  // Set Kalabox DNS constantsz
  // @todo: stronger test
  var KALABOX_WIN32_DNS = '10.13.37.42';

  /*
   * Return some info about the current state of the kalabox installation
   */
  var getCurrentInstall = function(installDetails) {

    // This is where our current install file should live
    var cIF = path.join(config.sysConfRoot, 'installed.json');

    // If the file exists use that if not empty object
    var currentInstall = (fs.existsSync(cIF)) ? require(cIF) : {};

    return currentInstall;

  };

  /*
   * Helper function to grab and compare a meta prop
   */
  var getProUp = function(prop) {

    // Get details about the state of the current installation
    var currentInstall = getCurrentInstall();

    // This is the first time we've installed so we def need
    if (_.isEmpty(currentInstall) || !currentInstall[prop]) {
      return true;
    }

    // We have a syncversion to compare
    // @todo: is diffence a strong enough check?
    var nV = meta[prop];
    if (currentInstall[prop] && (currentInstall[prop] !== nV)) {
      return true;
    }

    // Hohum i guess we're ok
    return false;

  };

  /*
   * Helper function to assess whether we need new service images
   */
  var needsImages = function() {
    return getProUp('SERVICE_IMAGES_VERSION');
  };

  /*
   * Helper function to determine whether we need to run darwin DNS commands
   */
  var needsDarwinDNS = function() {
    var dnsPath = path.join(meta.dns.darwin.path, meta.dns.darwin.file);
    return !fs.existsSync(dnsPath);
  };

  /*
   * Return linux dns information.
   * @todo: clean this up
   */
  var getLinuxDnsServers = _.once(function() {

    // Get linux flavor.
    var flavor = kbox.install.linuxOsInfo.getFlavor();

    // Get linux flavor distribution.
    var distro = (flavor === 'debian') ? flavor : 'linux';

    // Build path to dns file.
    var dnsFilepath = path.join(
      meta.dns.linux[distro].path,
      meta.dns.linux[distro].file
    );

    // Read dns file.
    return Promise.fromNode(function(cb) {
      fs.readFile(dnsFilepath, {encoding: 'utf8'}, cb);
    })
    // If file does not exist return false otherwise throw error.
    .catch(function(err) {
      if (err.code === 'ENOENT') {
        return '';
      } else {
        throw err;
      }
    })
    // Split data read from file into lines.
    .then(function(data) {
      return data.split('\n');
    })
    // Filter out uninteresting lines.
    .filter(function(line) {
      var isComment = _.startsWith(line, '#');
      var hasSpaces = _.contains(line, ' ');
      // Filter out comments, and only include lines which contain spaces.
      return !isComment && hasSpaces;
    })
    // Reduce remaining lines into an array of name servers.
    .reduce(function(dnsServers, line) {
      // Split each line, trim the parts, and remove double quotes.
      var parts = _.chain(line.split(' '))
        .map(_.trim)
        .map(function(part) {
          return part.replace(/"/g, '');
        })
        .value();
      // If parts array starts with nameserver, the second element is a
      // dns nameserver, so add it to the list of dns servers.
      if (parts[0] === 'nameserver') {
        dnsServers.push(parts[1]);
      }
      return dnsServers;
    }, []);
  });

  /*
   * Helper function to determine whether we need to run linux DNS commands
   */
  var needsLinuxDNS = function() {

    // Use for later
    var ips = [];

    // Grab list of server IPs
    return provider().call('getServerIps')

    // Add dns servers to list of dns options.
    // Get list of dns server configured on this linux host.
    .then(function(result) {
      ips = result;
      return getLinuxDnsServers();
    })

    // If we are missing DNS servers add them
    .then(function(dnsServers) {

      // Get what DNS servers we should consider adding
      var diff = _.difference(ips, dnsServers);
      return !_.isEmpty(diff);
    });

  };

  /*
   * Returns what we need to add for DNS to linux
   */
  var getLinuxDNStoAdd = function() {

    // Use this later
    var serverIps = [];

    // Grab list of server IPs
    return provider().call('getServerIps')

    // Add dns servers to list of dns options.
    // Get list of dns server configured on this linux host.
    .then(function(result) {
      serverIps = result;
      return getLinuxDnsServers();
    })

    // If we are missing DNS servers add them
    .then(function(dnsServers) {

      // If list of configured dns servers doesn't include this
      // IP then we need to run things
      var ipsToAdd = [];
      _.each(serverIps, function(ip) {
        if (!_.contains(dnsServers, ip)) {
          ipsToAdd.push(['nameserver', ip].join(' '));
        }
      });

      // Debug
      kbox.core.log.debug('DNS2ADD4U => ' + JSON.stringify(ipsToAdd));

      // Return what we need to add
      return ipsToAdd;

    });

  };

  /*
   * Returns whether you need to add in some packages or not during the install
   * step
   * @todo: as of now this targets basically just linux
   */
  var needsDNSAdmin = function() {

    // Get shell library.
    var shell = kbox.core.deps.get('shell');
    var flavor = kbox.install.linuxOsInfo.getFlavor();

    // Command to run
    // @todo: other flavors
    if (flavor !== 'debian') {
      return Promise.resolve(false);
    }
    // Check to see if this package is already installed
    else {

      // Cmd to run
      var cmd = 'dpkg-query -f \'${binary:Package}\n\' -W | grep resolvconf';

      // Get network information from virtual box.
      return Promise.fromNode(function(cb) {
        shell.exec(cmd, cb);
      })

      // Parse the output
      .then(function(output) {

        // Debug log output
        kbox.core.log.debug('DNS PKG => ' + JSON.stringify(output.trim()));

        return !_.contains(output.trim(), 'resolvconf');

      });
    }

  };

  /*
   * Get the correct windows network adapter
   */
  var getWindowsAdapter = function() {

    // Get shell library.
    var shell = kbox.core.deps.get('shell');

    // Command to run
    var cmd = [
      '"C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe"',
      'showvminfo "Kalabox2" | findstr "Host-only"'
    ];

    // Get network information from virtual box.
    return Promise.fromNode(function(cb) {
      shell.exec(cmd.join(' '), cb);
    })

    // Parse the output
    .then(function(output) {

      // Debug log output
      kbox.core.log.debug('ADAPTER INFO => ' + JSON.stringify(output));

      // Parse output to get network adapter information.
      var start = output.indexOf('\'');
      var last = output.lastIndexOf('\'');

      // Get the adapter
      var adapter = [
        output.slice(start + 1, last).replace('Ethernet Adapter', 'Network')
      ];

      // debug
      kbox.core.log.debug('WINDOWS ADAPTER => ' + JSON.stringify(adapter));

      // Return
      return adapter;
    });

  };

  /*
   * Helper function to determine whether we need to run win32 DNS commands
   * @todo: Need to expand this to handle both nameservers
   */
  var needsWin32DNS = function() {

    // Get shell library.
    var shell = kbox.core.deps.get('shell');

    // @todo: Need a stronger check than this eventually
    var ip = KALABOX_WIN32_DNS;

    // Grab the host only adapter so we can be SUPER PRECISE!
    return getWindowsAdapter()

    // Get network information from virtual box.
    .then(function(adapter) {

      var adp = adapter;

      // Command to run
      var cmd = 'netsh interface ipv4 show dnsservers';

      // Execute promisified shell
      return Promise.fromNode(function(cb) {
        shell.exec(cmd, cb);
      })

      // Need to catch findstr null reporting as error
      .catch(function(err) {
        // @todo: something more precise here
      })

      .then(function(output) {

        // Truncate the string for just data on what we need
        // This elminates the possibility that another adapter has our
        // setup. Although, to be fair, if another adapter does then
        // we are probably SOL anyway.

        // Trim the left
        var leftTrim = 'Configuration for interface "' + adp + '"';
        var truncLeft = output.indexOf(leftTrim);
        var left = output.slice(truncLeft);

        // Trim the right
        var rightTrim = 'Register with which suffix:';
        var truncRight = left.indexOf(rightTrim);
        var adapterConfig = left.slice(0, truncRight);

        // Get the raw DNS IPs
        var aSplit = adapterConfig.split(':');
        var rawAdapters = _.trim(aSplit[1]);

        // Map to array of IPs
        var adapters = _.map(rawAdapters.split('\r\n'), function(rawAdapter) {
          return _.trim(rawAdapter);
        });

        // Return precise
        var needDns = adapters[0] !== KALABOX_WIN32_DNS;
        kbox.core.log.debug('DNS SET CORRECTLY => ' + JSON.stringify(needDns));
        return needDns;
      });
    });

  };

  /*
   * Validate admin commands
   */
  var validateCmds = function(cmds) {

    // Check if this is an array
    if (!Array.isArray(cmds)) {
      return 'Invalid adminCommands: ' + cmds;
    }

    // Check if each cmd is a string
    cmds.forEach(function(cmd, index) {
      if (typeof cmd !== 'string' || cmd.length < 1) {
        return 'Invalid cmd index: ' + index + ' cmd: ' + cmd;
      }
    });

    // Looks like we good!
    return true;

  };

  /*
   * Run the admin commands
   */
  var runCmds = function(adminCommands, state, callback) {

    // Validate the admin commands
    if (validateCmds(adminCommands) !== true) {
      callback(new Error(validateCmds(adminCommands)));
    }

    // Process admin commands.
    var child = kbox.install.cmd.runCmdsAsync(adminCommands, state);

    // Events
    // Output data
    child.stdout.on('data', function(data) {
      state.log.info(data);
    });
    // Callback when done
    child.stdout.on('end', function() {
      callback();
    });
    // Output stderr data.
    child.stderr.on('data', function(data) {
      state.log.info(data);
    });
    // Fail the installer if we get an error
    child.stderr.on('error', function(err) {
      state.fail(state, err);
    });

  };

  /*
   * Helper to set up darwin DNS
   */
  var setupDarwinDNS = function(state) {

    // Start up a collector
    var dnsCmds = [];

    // Get list of server ips.
    return provider().call('getServerIps')

    // Add dns setup command.
    .then(function(ips) {

      // Build DNS command
      if (needsDarwinDNS()) {
        var dnsFile = [meta.dns.darwin.path, meta.dns.darwin.file];
        var ipCmds = kbox.install.cmd.buildDnsCmd(ips, dnsFile);
        var cmd = ipCmds.join(' && ');
        dnsCmds.push(cmd);
      }

      // Debug
      kbox.core.log.debug('DNS CMDS => ' + JSON.stringify(dnsCmds));

      // Try to install DNS if we have commands to run
      return Promise.fromNode(function(cb) {
        if (!_.isEmpty(dnsCmds)) {
          runCmds(dnsCmds, state, cb);
        }
        else {
          cb();
        }
      });

    });

  };

  /*
   * Helper to set up windows DNS
   */
  var setupWindowsDNS = function(state) {

    // Start up a collector
    var dnsCmds = [];

    return needsWin32DNS()

    .then(function(needsDns) {
      if (needsDns) {
        // Grab the appropriate windows network adapter
        return getWindowsAdapter()
        // Generate teh DNS commands and run it
        .then(function(adapter) {

          // Get list of server IPs.
          return provider().call('getServerIps')

          .then(function(ips) {

            // Build DNS commands
            dnsCmds = (kbox.install.cmd.buildDnsCmd(ips, adapter));

            // Debug
            state.log.debug('DNS CMDS => ' + JSON.stringify(dnsCmds));

            // Run each command through elevation
            // @todo: doesn't seem like node-windows can
            // handle combining via & or && so do this for now
            return Promise.each(dnsCmds, function(cmd) {
              return kbox.util.shell.execElevated(cmd);
            });
          });

        });
      }
    });

  };

  /*
   * Helper to set up linux DNS
   */
  var setupLinuxDNS = function(state) {

    // Start a collection
    var dnsCmds = [];
    // Grab linux flavor
    var flavor = kbox.install.linuxOsInfo.getFlavor();
    // Grab a generic dis
    var dis = (flavor === 'debian') ? flavor : 'linux';

    // Check out what we need to add
    return getLinuxDNStoAdd()

    // Put together some commands if we need to
    .then(function(ips) {

      // Check if we need linux DNS
      return needsLinuxDNS()

      // Create DNS command
      .then(function(needIt) {
        if (needIt) {
          var dnsFile = [meta.dns.linux[dis].path, meta.dns.linux[dis].file];
          dnsCmds = kbox.install.cmd.buildDnsCmd(ips, dnsFile);
          dnsCmds.push('resolvconf -u');
        }
      })

      // Run commands if needed
      .then(function() {
        // Debug
        state.log.debug('DNS CMDS => ' + JSON.stringify(dnsCmds));

        // Try to install DNS if we have commands to run
        return Promise.fromNode(function(cb) {
          if (!_.isEmpty(dnsCmds)) {
            runCmds(dnsCmds, state, cb);
          }
          else {
            cb();
          }
        });
      });

    });

  };

  return {
    needsImages: needsImages,
    needsDNSAdmin: needsDNSAdmin,
    setupDarwinDNS: setupDarwinDNS,
    setupWindowsDNS: setupWindowsDNS,
    setupLinuxDNS: setupLinuxDNS
  };

};
<|endoftext|>


<commit_before>import {Transform} from 'vega-dataflow';
import {Bounds, boundStroke} from 'vega-scenegraph';
import {inherits} from 'vega-util';

var Fit = 'fit',
    Pad = 'pad',
    None = 'none';

var AxisRole = 'axis',
    FrameRole = 'frame',
    LegendRole = 'legend',
    ScopeRole = 'scope';

/**
 * Layout view elements such as axes and legends.
 * Also performs size adjustments.
 * @constructor
 * @param {object} params - The parameters for this operator.
 * @param {object} params.mark - Scenegraph mark of groups to layout.
 */
export default function ViewLayout(params) {
  Transform.call(this, null, params);
}

var prototype = inherits(ViewLayout, Transform);

prototype.transform = function(_, pulse) {
  // TODO incremental update, output?
  var view = pulse.dataflow;
  _.mark.items.forEach(function(group) {
    layoutGroup(view, group, _);
  });
  return pulse;
};

function layoutGroup(view, group, _) {
  var items = group.items,
      width = Math.max(0, group.width || 0),
      height = Math.max(0, group.height || 0),
      viewBounds = new Bounds().set(0, 0, width, height),
      markBounds = viewBounds.clone(),
      axisBounds = markBounds.clone(),
      legends = [],
      mark, flow, b, i, n;

  // layout axes, gather legends, collect bounds
  for (i=0, n=items.length; i<n; ++i) {
    mark = items[i];
    switch (mark.role) {
      case AxisRole:
        axisBounds.union(layoutAxis(mark, width, height));
        break;
      case LegendRole:
        legends.push(mark); break;
      case FrameRole:
      case ScopeRole:
        viewBounds.union(mark.bounds); // break omitted
      default:
        markBounds.union(mark.bounds);
    }
  }
  viewBounds.union(axisBounds);

  // layout legends, extending viewBounds
  if (legends.length) {
    flow = {left: 0, right: 0, margin: _.legendMargin || 8};
    axisBounds.union(viewBounds); // see vega/vega#694

    for (i=0, n=legends.length; i<n; ++i) {
      b = layoutLegend(legends[i], flow, axisBounds, width, height);
      (_.autosize === Fit)
        ? viewBounds.add(b.x1, 0).add(b.x2, 0)
        : viewBounds.union(b);
    }
  }

  // perform size adjustment
  layoutSize(view, group, markBounds, viewBounds.union(markBounds), _);
}

function axisIndices(datum) {
  var index = +datum.grid;
  return [
    datum.tick ? index++ : -1,  // tick index
    datum.label ? index++ : -1, // label index
    index + (+datum.domain)     // title index
  ];
}

function layoutAxis(axis, width, height) {
  var item = axis.items[0],
      datum = item.datum,
      orient = datum.orient,
      indices = axisIndices(datum),
      range = item.range,
      offset = item.offset,
      position = item.position,
      minExtent = item.minExtent,
      maxExtent = item.maxExtent,
      title = datum.title && item.items[indices[2]].items[0],
      titlePadding = item.titlePadding,
      titleSize = title ? title.fontSize + titlePadding : 0,
      bounds = item.bounds,
      x = 0, y = 0, i, s;

  bounds.clear();
  if ((i=indices[0]) > -1) bounds.union(item.items[i].bounds);
  if ((i=indices[1]) > -1) bounds.union(item.items[i].bounds);

  // position axis group and title
  switch (orient) {
    case 'top': {
      x = position || 0;
      y = -offset;
      s = Math.max(minExtent, Math.min(maxExtent, -bounds.y1));
      if (title) title.auto
        ? (title.y = -(titlePadding + s), s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(0, -s).add(range, 0);
      break;
    }
    case 'left': {
      x = -offset;
      y = position || 0;
      s = Math.max(minExtent, Math.min(maxExtent, -bounds.x1));
      if (title) title.auto
        ? (title.x = -(titlePadding + s), s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(-s, 0).add(0, range);
      break;
    }
    case 'right': {
      x = width + offset;
      y = position || 0;
      s = Math.max(minExtent, Math.min(maxExtent, bounds.x2));
      if (title) title.auto
        ? (title.x = titlePadding + s, s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(0, 0).add(s, range);
      break;
    }
    case 'bottom': {
      x = position || 0;
      y = height + offset;
      s = Math.max(minExtent, Math.min(maxExtent, bounds.y2));
      if (title) title.auto
        ? (title.y = titlePadding + s, s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(0, 0).add(range, s);
      break;
    }
  }

  item.x = x + 0.5;
  item.y = y + 0.5;

  // update bounds
  boundStroke(bounds.translate(x, y), item);
  item.mark.bounds.clear().union(bounds);
  return bounds;
}

function layoutLegend(legend, flow, axisBounds, width, height) {
  var item = legend.items[0],
      datum = item.datum,
      orient = datum.orient,
      offset = item.offset,
      bounds = item.bounds.clear(),
      x = 0,
      y = (flow[orient] || 0),
      w, h;

  // aggregate bounds to determine size
  // shave off 1 pixel because it looks better...
  item.items.forEach(function(_) { bounds.union(_.bounds); });
  w = Math.round(bounds.width()) + 2 * item.padding - 1;
  h = Math.round(bounds.height()) + 2 * item.padding - 1;

  switch (orient) {
    case 'left':
      x -= w + offset - Math.floor(axisBounds.x1);
      flow.left += h + flow.margin;
      break;
    case 'right':
      x += offset + Math.ceil(axisBounds.x2);
      flow.right += h + flow.margin;
      break;
    case 'top-left':
      x += offset;
      y += offset;
      break;
    case 'top-right':
      x += width - w - offset;
      y += offset;
      break;
    case 'bottom-left':
      x += offset;
      y += height - h - offset;
      break;
    case 'bottom-right':
      x += width - w - offset;
      y += height - h - offset;
      break;
  }

  // update legend layout
  item.x = x;
  item.y = y;
  item.width = w;
  item.height = h;

  // update bounds
  boundStroke(bounds.set(x, y, x + w, y + h), item);
  item.mark.bounds.clear().union(bounds);
  return bounds;
}

function layoutSize(view, group, markBounds, viewBounds, _) {
  var type = _.autosize,
      viewWidth = view._width,
      viewHeight = view._height;

  if (view._autosize < 1 || !type) return;

  var width  = Math.max(0, group.width || 0),
      left   = Math.max(0, Math.ceil(-viewBounds.x1)),
      right  = Math.max(0, Math.ceil(viewBounds.x2 - width)),
      height = Math.max(0, group.height || 0),
      top    = Math.max(0, Math.ceil(-viewBounds.y1)),
      bottom = Math.max(0, Math.ceil(viewBounds.y2 - height));

  if (type === None) {
    viewWidth = width;
    viewHeight = height;
    left = 0;
    top = 0;
  }

  else if (type === Fit) {
    width = Math.max(0, viewWidth - left - right);
    height = Math.max(0, viewHeight - top - bottom);
  }

  else if (type === Pad) {
    viewWidth = width + left + right;
    viewHeight = height + top + bottom;
    if (group.width  < 0) width = markBounds.width();
    if (group.height < 0) height = markBounds.height();
  }

  view.autosize(viewWidth, viewHeight, width, height, [left, top]);
}
<commit_msg>Add support for axis/legend orient none.<commit_after>import {Transform} from 'vega-dataflow';
import {Bounds, boundStroke} from 'vega-scenegraph';
import {inherits} from 'vega-util';

var Fit = 'fit',
    Pad = 'pad',
    None = 'none';

var AxisRole = 'axis',
    FrameRole = 'frame',
    LegendRole = 'legend',
    ScopeRole = 'scope';

/**
 * Layout view elements such as axes and legends.
 * Also performs size adjustments.
 * @constructor
 * @param {object} params - The parameters for this operator.
 * @param {object} params.mark - Scenegraph mark of groups to layout.
 */
export default function ViewLayout(params) {
  Transform.call(this, null, params);
}

var prototype = inherits(ViewLayout, Transform);

prototype.transform = function(_, pulse) {
  // TODO incremental update, output?
  var view = pulse.dataflow;
  _.mark.items.forEach(function(group) {
    layoutGroup(view, group, _);
  });
  return pulse;
};

function layoutGroup(view, group, _) {
  var items = group.items,
      width = Math.max(0, group.width || 0),
      height = Math.max(0, group.height || 0),
      viewBounds = new Bounds().set(0, 0, width, height),
      markBounds = viewBounds.clone(),
      axisBounds = markBounds.clone(),
      legends = [],
      mark, flow, b, i, n;

  // layout axes, gather legends, collect bounds
  for (i=0, n=items.length; i<n; ++i) {
    mark = items[i];
    switch (mark.role) {
      case AxisRole:
        axisBounds.union(layoutAxis(mark, width, height));
        break;
      case LegendRole:
        legends.push(mark); break;
      case FrameRole:
      case ScopeRole:
        viewBounds.union(mark.bounds); // break omitted
      default:
        markBounds.union(mark.bounds);
    }
  }
  viewBounds.union(axisBounds);

  // layout legends, extending viewBounds
  if (legends.length) {
    flow = {left: 0, right: 0, margin: _.legendMargin || 8};
    axisBounds.union(viewBounds); // see vega/vega#694

    for (i=0, n=legends.length; i<n; ++i) {
      b = layoutLegend(legends[i], flow, axisBounds, width, height);
      (_.autosize === Fit)
        ? viewBounds.add(b.x1, 0).add(b.x2, 0)
        : viewBounds.union(b);
    }
  }

  // perform size adjustment
  layoutSize(view, group, markBounds, viewBounds.union(markBounds), _);
}

function axisIndices(datum) {
  var index = +datum.grid;
  return [
    datum.tick ? index++ : -1,  // tick index
    datum.label ? index++ : -1, // label index
    index + (+datum.domain)     // title index
  ];
}

function layoutAxis(axis, width, height) {
  var item = axis.items[0],
      datum = item.datum,
      orient = datum.orient,
      indices = axisIndices(datum),
      range = item.range,
      offset = item.offset,
      position = item.position,
      minExtent = item.minExtent,
      maxExtent = item.maxExtent,
      title = datum.title && item.items[indices[2]].items[0],
      titlePadding = item.titlePadding,
      titleSize = title ? title.fontSize + titlePadding : 0,
      bounds = item.bounds,
      x = 0, y = 0, i, s;

  bounds.clear();
  if ((i=indices[0]) > -1) bounds.union(item.items[i].bounds);
  if ((i=indices[1]) > -1) bounds.union(item.items[i].bounds);

  // position axis group and title
  switch (orient) {
    case 'top':
      x = position || 0;
      y = -offset;
      s = Math.max(minExtent, Math.min(maxExtent, -bounds.y1));
      if (title) title.auto
        ? (title.y = -(titlePadding + s), s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(0, -s).add(range, 0);
      break;
    case 'left':
      x = -offset;
      y = position || 0;
      s = Math.max(minExtent, Math.min(maxExtent, -bounds.x1));
      if (title) title.auto
        ? (title.x = -(titlePadding + s), s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(-s, 0).add(0, range);
      break;
    case 'right':
      x = width + offset;
      y = position || 0;
      s = Math.max(minExtent, Math.min(maxExtent, bounds.x2));
      if (title) title.auto
        ? (title.x = titlePadding + s, s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(0, 0).add(s, range);
      break;
    case 'bottom':
      x = position || 0;
      y = height + offset;
      s = Math.max(minExtent, Math.min(maxExtent, bounds.y2));
      if (title) title.auto
        ? (title.y = titlePadding + s, s += titleSize)
        : bounds.union(title.bounds);
      bounds.add(0, 0).add(range, s);
      break;
    default:
      x = item.x;
      y = item.y;
  }

  item.x = x + 0.5;
  item.y = y + 0.5;

  // update bounds
  boundStroke(bounds.translate(x, y), item);
  item.mark.bounds.clear().union(bounds);
  return bounds;
}

function layoutLegend(legend, flow, axisBounds, width, height) {
  var item = legend.items[0],
      datum = item.datum,
      orient = datum.orient,
      offset = item.offset,
      bounds = item.bounds.clear(),
      x = 0,
      y = (flow[orient] || 0),
      w, h;

  // aggregate bounds to determine size
  // shave off 1 pixel because it looks better...
  item.items.forEach(function(_) { bounds.union(_.bounds); });
  w = Math.round(bounds.width()) + 2 * item.padding - 1;
  h = Math.round(bounds.height()) + 2 * item.padding - 1;

  switch (orient) {
    case 'left':
      x -= w + offset - Math.floor(axisBounds.x1);
      flow.left += h + flow.margin;
      break;
    case 'right':
      x += offset + Math.ceil(axisBounds.x2);
      flow.right += h + flow.margin;
      break;
    case 'top-left':
      x += offset;
      y += offset;
      break;
    case 'top-right':
      x += width - w - offset;
      y += offset;
      break;
    case 'bottom-left':
      x += offset;
      y += height - h - offset;
      break;
    case 'bottom-right':
      x += width - w - offset;
      y += height - h - offset;
      break;
    default:
      x = item.x;
      y = item.y;
  }

  // update legend layout
  item.x = x;
  item.y = y;
  item.width = w;
  item.height = h;

  // update bounds
  boundStroke(bounds.set(x, y, x + w, y + h), item);
  item.mark.bounds.clear().union(bounds);
  return bounds;
}

function layoutSize(view, group, markBounds, viewBounds, _) {
  var type = _.autosize,
      viewWidth = view._width,
      viewHeight = view._height;

  if (view._autosize < 1 || !type) return;

  var width  = Math.max(0, group.width || 0),
      left   = Math.max(0, Math.ceil(-viewBounds.x1)),
      right  = Math.max(0, Math.ceil(viewBounds.x2 - width)),
      height = Math.max(0, group.height || 0),
      top    = Math.max(0, Math.ceil(-viewBounds.y1)),
      bottom = Math.max(0, Math.ceil(viewBounds.y2 - height));

  if (type === None) {
    viewWidth = width;
    viewHeight = height;
    left = 0;
    top = 0;
  }

  else if (type === Fit) {
    width = Math.max(0, viewWidth - left - right);
    height = Math.max(0, viewHeight - top - bottom);
  }

  else if (type === Pad) {
    viewWidth = width + left + right;
    viewHeight = height + top + bottom;
    if (group.width  < 0) width = markBounds.width();
    if (group.height < 0) height = markBounds.height();
  }

  view.autosize(viewWidth, viewHeight, width, height, [left, top]);
}
<|endoftext|>


<commit_before>var config = require('../config');

module.exports = function(){
	return function(req,res,next){
	    if(config.testing){
	        res.cache = function(){
	            return this;
	        }
	        return next();
	    }

	    var redis = require('./redis');

	    var end = res.end,
	        write = res.write,
	        cache = [],
	        key = req.originalUrl;

	    redis.get(key,function(error,reply){
	        if(!error && reply){
	        	res.set('Content-Type', 'application/json');
	            res.end(reply);
	        }else{
	            res.cache = function(timeout) {

	                if(typeof timeout === 'undefined')
	                    timeout = 21600; //Default timeout is 6 hours

	                console.log('INSIDE CACHE')
	                res.write = function(chunk, encoding){
	                    cache.push(chunk)
	                    write.call(res,chunk,encoding);
	                }

	                res.end = function(chunk, encoding){
	                    if(chunk) this.write(chunk,encoding);
	                    //console.log('Chunk norris:', chunk.toString())

	                    console.log('Expiration is :',timeout);

	                    redis.setex(key, timeout , cache.join(''));

	                    end.call(res);
	                };
	                return this;
	            };
	            return next();
	        }
	    })
	}
}<commit_msg>Improved caching layer in case of a redis failure<commit_after>var config = require('../config');

module.exports = function(){
	return function(req,res,next){
	    if(config.testing){
	        res.cache = function(){
	            return this;
	        }
	        return next();
	    }

	    var redis = require('./redis');

	    var end = res.end,
	        write = res.write,
	        cache = [],
	        key = req.originalUrl;

	    redis.get(key,function(error,reply){
	        if(error){
	        	console.log('Error in caching layer:',error);
	        	return next();
	        }else if(reply){
	        	res.set('Content-Type', 'application/json');
	            res.end(reply);
	        }else{
	            res.cache = function(timeout) {

	                if(typeof timeout === 'undefined')
	                    timeout = 21600; //Default timeout is 6 hours

	                console.log('INSIDE CACHE')
	                res.write = function(chunk, encoding){
	                    cache.push(chunk)
	                    write.call(res,chunk,encoding);
	                }

	                res.end = function(chunk, encoding){
	                    if(chunk) this.write(chunk,encoding);
	                    //console.log('Chunk norris:', chunk.toString())

	                    console.log('Expiration is :',timeout);

	                    redis.setex(key, timeout , cache.join(''));

	                    end.call(res);
	                };
	                return this;
	            };
	            return next();
	        }
	    })
	}
}<|endoftext|>