<commit_before>// TODO: add GC statistics

const SDebug = require('sdebug')
const client = require('prom-client')
const debug = new SDebug('prometheus')
const exposition = require('exposition')
const redis = require('redis')
const underscore = require('underscore')

const Prometheus = function (config, runtime) {
  if (!(this instanceof Prometheus)) return new Prometheus(config, runtime)

  this.metrics = {}

  if (!config.prometheus) return

  this.label = config.prometheus.label
  this.runtime = runtime
  this.local = {}
  this.global = {}

  if (config.prometheus.redis) this.publisher = redis.createClient(config.prometheus.redis)
  this.msgno = 0

  setInterval(this.maintenance.bind(this), 10 * 1000)
}

Prometheus.prototype.plugin = function () {
  const self = this

  const registry = client.register

  const plugin = {
    register: (server, o, done) => {
      server.route({
        method: 'GET',
        path: '/metrics',
        handler: (req, reply) => { reply(exposition.stringify(underscore.values(self.global))).type('text/plain') }
      })

      server.route({
        method: 'GET',
        path: '/metrics-internal',
        handler: (req, reply) => { reply(client.register.metrics()).type('text/plain') }
      })

      server.ext('onRequest', (request, reply) => {
        request.prometheus = { start: process.hrtime() }
        reply.continue()
      })

      server.on('response', (response) => {
        const analysis = response._route._analysis
        const statusCode = response.response.statusCode
        let cardinality, diff, duration, method, metric, params, path

        diff = process.hrtime(response.prometheus.start)
        duration = Math.round((diff[0] * 1e9 + diff[1]) / 1000000)

        method = response.method.toLowerCase()
        params = underscore.clone(analysis.params)
        cardinality = params.length ? 'many' : 'one'
        path = analysis.fingerprint.split('/')
        for (let i = 0; i < path.length; i++) { if (path[i] === '?') path[i] = '{' + (params.shift() || '?') + '}' }
        path = path.join('/')

        metric = registry._metrics['http_request_buckets_milliseconds']
        if (!metric) {
          metric = new client.Summary({
            name: 'http_request_duration_milliseconds',
            help: 'request duration in milliseconds',
            labelNames: ['method', 'path', 'cardinality', 'status']
          })
        }
        metric.labels(method, path, cardinality, statusCode).observe(duration)

        metric = registry._metrics['http_request_buckets_milliseconds']
        if (!metric) {
          metric = new client.Histogram({
            name: 'http_request_buckets_milliseconds',
            help: 'request duration buckets for 500 and 2000 milliseconds',
            labelNames: ['method', 'path', 'cardinality', 'status'],
            buckets: [ 500, 2000 ]
          })
        }
        metric.labels(method, path, cardinality, statusCode).observe(duration)
      })

      self.subscribeP = true

      return done()
    }
  }

  plugin.register.attributes = {
    name: 'runtime-prometheus',
    version: '1.0.0'
  }

  return plugin
}

Prometheus.prototype.maintenance = function () {
  const self = this

  const entries = exposition.parse(client.register.metrics())
  let updates

  if (!self.subscribeP) client.collectDefaultMetrics()

  const merge = (source) => {
    source.forEach((update) => {
      const name = update.name
      let entry

      if (!(update.metrics && update.metrics.length)) return

      entry = self.global[name]
      if (!entry) {
        self.global[name] = update
        return
      }

      update.metrics.forEach((metric) => {
        let offset, tag

        if (metric.buckets) {
          tag = underscore.first(underscore.keys(metric.buckets))

          offset = underscore.findIndex(entry.metrics, (value) => {
            return (underscore.keys(value.buckets || {}).indexOf(tag) !== -1)
          })
        } else {
          offset = underscore.findIndex(entry.metrics, (value) => {
            return underscore.isEqual(metric.labels, value.labels)
          })
        }

        if (offset < 0) entry.metrics.push(metric)
        else entry.metrics.splice(offset, 1, metric)
      })
    })
  }

  updates = []
  entries.forEach((entry) => {
    const metrics = []
    const name = entry.name

    // no metrics(?) or no change
    if ((!entry.metrics) || (underscore.isEqual(self.local[name] || {}, entry))) return

    self.local[name] = entry
    entry.metrics.forEach((metric) => {
      const buckets = {}

      if (metric.buckets) {
        for (let bucket in metric.buckets) {
          const kvs = bucket.split(',')

          kvs.splice(1, 0, 'instance="' + self.label + '"')
          buckets[kvs.join(',')] = metric.buckets[bucket]
        }
        metric.buckets = buckets
      } else {
        metric.labels = underscore.extend(metric.labels || {}, { instance: self.label })
      }
      metrics.push(metric)
    })
    entry.metrics = metrics
    updates.push(entry)
  })
  if (!updates.length) return

  merge(updates)

  if (!self.publisher) {
    self.publisher = (self.runtime.cache && self.runtime.cache.cache) || self.runtime.queue.config.client
    if (!self.publisher) return
  }

  self.publisher.publish('prometheus:' + process.env.SERVICE, JSON.stringify({
    label: self.label,
    msgno: self.msgno++,
    updates: updates
  }))
  if (self.label.indexOf('.worker.') !== -1) return

  if ((self.subscriber) || (!self.subscribeP)) return

  self.subscriber = self.publisher.duplicate().on('subscribe', (channel, count) => {
    debug('subscribe', { channel: channel, count: count })
  }).on('message', (channel, message) => {
    let packet

    try {
      packet = JSON.parse(message)
    } catch (ex) {
      return debug('message', { error: ex.toString() })
    }

    if (packet.label === self.label) return

    if (packet.msgno === 0) {
      self.publisher.publish('prometheus:' + process.env.SERVICE, JSON.stringify({
        label: self.label,
        msgno: self.msgno++,
        updates: underscore.values(self.global || {})
      }))
    }

    merge(packet.updates)
  })

  self.subscriber.subscribe('prometheus:' + process.env.SERVICE)
}

Prometheus.prototype.setCounter = async function (name, help, value) {
  if (!this.metrics[name]) this.metrics[name] = new client.Counter({ name: name, help: help })

  this.metrics[name].reset()
  this.metrics[name].inc(value)
}

Prometheus.prototype.incrCounter = async function (name, help, delta) {
  if (!this.metrics[name]) this.metrics[name] = new client.Counter({ name: name, help: help })

  this.metrics[name].inc(delta)
}

Prometheus.prototype.setGauge = async function (name, help, value) {
  if (!this.metrics[name]) this.metrics[name] = new client.Gauge({ name: name, help: help })

  this.metrics[name].set(value)
}

// NB: not doing histograms (yet!)

module.exports = Prometheus
<commit_msg>expanded buckets (#592)<commit_after>// TODO: add GC statistics

const SDebug = require('sdebug')
const client = require('prom-client')
const debug = new SDebug('prometheus')
const exposition = require('exposition')
const redis = require('redis')
const underscore = require('underscore')

const Prometheus = function (config, runtime) {
  if (!(this instanceof Prometheus)) return new Prometheus(config, runtime)

  this.metrics = {}

  if (!config.prometheus) return

  this.label = config.prometheus.label
  this.runtime = runtime
  this.local = {}
  this.global = {}

  if (config.prometheus.redis) this.publisher = redis.createClient(config.prometheus.redis)
  this.msgno = 0

  setInterval(this.maintenance.bind(this), 10 * 1000)
}

Prometheus.prototype.plugin = function () {
  const self = this

  const registry = client.register

  const plugin = {
    register: (server, o, done) => {
      server.route({
        method: 'GET',
        path: '/metrics',
        handler: (req, reply) => { reply(exposition.stringify(underscore.values(self.global))).type('text/plain') }
      })

      server.route({
        method: 'GET',
        path: '/metrics-internal',
        handler: (req, reply) => { reply(client.register.metrics()).type('text/plain') }
      })

      server.ext('onRequest', (request, reply) => {
        request.prometheus = { start: process.hrtime() }
        reply.continue()
      })

      server.on('response', (response) => {
        const analysis = response._route._analysis
        const statusCode = response.response.statusCode
        let cardinality, diff, duration, method, metric, params, path

        diff = process.hrtime(response.prometheus.start)
        duration = Math.round((diff[0] * 1e9 + diff[1]) / 1000000)

        method = response.method.toLowerCase()
        params = underscore.clone(analysis.params)
        cardinality = params.length ? 'many' : 'one'
        path = analysis.fingerprint.split('/')
        for (let i = 0; i < path.length; i++) { if (path[i] === '?') path[i] = '{' + (params.shift() || '?') + '}' }
        path = path.join('/')

        metric = registry._metrics['http_request_buckets_milliseconds']
        if (!metric) {
          metric = new client.Summary({
            name: 'http_request_duration_milliseconds',
            help: 'request duration in milliseconds',
            labelNames: ['method', 'path', 'cardinality', 'status']
          })
        }
        metric.labels(method, path, cardinality, statusCode).observe(duration)

        metric = registry._metrics['http_request_buckets_milliseconds']
        if (!metric) {
          metric = new client.Histogram({
            name: 'http_request_buckets_milliseconds',
            help: 'request duration buckets in milliseconds',
            labelNames: ['method', 'path', 'cardinality', 'status'],
            buckets: [ 125, 250, 500, 1000, 2000, 4000, 8000, 16000 ]
          })
        }
        metric.labels(method, path, cardinality, statusCode).observe(duration)
      })

      self.subscribeP = true

      return done()
    }
  }

  plugin.register.attributes = {
    name: 'runtime-prometheus',
    version: '1.0.0'
  }

  return plugin
}

Prometheus.prototype.maintenance = function () {
  const self = this

  const entries = exposition.parse(client.register.metrics())
  let updates

  if (!self.subscribeP) client.collectDefaultMetrics()

  const merge = (source) => {
    source.forEach((update) => {
      const name = update.name
      let entry

      if (!(update.metrics && update.metrics.length)) return

      entry = self.global[name]
      if (!entry) {
        self.global[name] = update
        return
      }

      update.metrics.forEach((metric) => {
        let offset, tag

        if (metric.buckets) {
          tag = underscore.first(underscore.keys(metric.buckets))

          offset = underscore.findIndex(entry.metrics, (value) => {
            return (underscore.keys(value.buckets || {}).indexOf(tag) !== -1)
          })
        } else {
          offset = underscore.findIndex(entry.metrics, (value) => {
            return underscore.isEqual(metric.labels, value.labels)
          })
        }

        if (offset < 0) entry.metrics.push(metric)
        else entry.metrics.splice(offset, 1, metric)
      })
    })
  }

  updates = []
  entries.forEach((entry) => {
    const metrics = []
    const name = entry.name

    // no metrics(?) or no change
    if ((!entry.metrics) || (underscore.isEqual(self.local[name] || {}, entry))) return

    self.local[name] = entry
    entry.metrics.forEach((metric) => {
      const buckets = {}

      if (metric.buckets) {
        for (let bucket in metric.buckets) {
          const kvs = bucket.split(',')

          kvs.splice(1, 0, 'instance="' + self.label + '"')
          buckets[kvs.join(',')] = metric.buckets[bucket]
        }
        metric.buckets = buckets
      } else {
        metric.labels = underscore.extend(metric.labels || {}, { instance: self.label })
      }
      metrics.push(metric)
    })
    entry.metrics = metrics
    updates.push(entry)
  })
  if (!updates.length) return

  merge(updates)

  if (!self.publisher) {
    self.publisher = (self.runtime.cache && self.runtime.cache.cache) || self.runtime.queue.config.client
    if (!self.publisher) return
  }

  self.publisher.publish('prometheus:' + process.env.SERVICE, JSON.stringify({
    label: self.label,
    msgno: self.msgno++,
    updates: updates
  }))
  if (self.label.indexOf('.worker.') !== -1) return

  if ((self.subscriber) || (!self.subscribeP)) return

  self.subscriber = self.publisher.duplicate().on('subscribe', (channel, count) => {
    debug('subscribe', { channel: channel, count: count })
  }).on('message', (channel, message) => {
    let packet

    try {
      packet = JSON.parse(message)
    } catch (ex) {
      return debug('message', { error: ex.toString() })
    }

    if (packet.label === self.label) return

    if (packet.msgno === 0) {
      self.publisher.publish('prometheus:' + process.env.SERVICE, JSON.stringify({
        label: self.label,
        msgno: self.msgno++,
        updates: underscore.values(self.global || {})
      }))
    }

    merge(packet.updates)
  })

  self.subscriber.subscribe('prometheus:' + process.env.SERVICE)
}

Prometheus.prototype.setCounter = async function (name, help, value) {
  if (!this.metrics[name]) this.metrics[name] = new client.Counter({ name: name, help: help })

  this.metrics[name].reset()
  this.metrics[name].inc(value)
}

Prometheus.prototype.incrCounter = async function (name, help, delta) {
  if (!this.metrics[name]) this.metrics[name] = new client.Counter({ name: name, help: help })

  this.metrics[name].inc(delta)
}

Prometheus.prototype.setGauge = async function (name, help, value) {
  if (!this.metrics[name]) this.metrics[name] = new client.Gauge({ name: name, help: help })

  this.metrics[name].set(value)
}

// NB: not doing histograms (yet!)

module.exports = Prometheus
<|endoftext|>


<commit_before>var _ = require('underscore');
var $ = require('jquery');
var cdb = require('cartodb.js');
var d3 = require('d3');
var WidgetHistogramChart = require('../../../src/widgets/histogram/chart');

describe('widgets/histogram/chart', function () {
  var onWindowResizeReal;
  var onWindowResizeSpy;

  afterEach(function () {
    $('.js-chart').remove();
  });

  beforeEach(function () {
    d3.select('body').append('svg').attr('class', 'js-chart');

    this.width = 300;
    this.height = 100;

    d3.select($('.js-chart')[0])
      .attr('width', this.width)
      .attr('height', this.height)
      .append('g')
      .attr('class', 'Canvas');

    this.data = genHistogramData(20);
    this.margin = {
      top: 4,
      right: 4,
      bottom: 4,
      left: 4
    };

    this.originalData = genHistogramData(20);
    this.originalModel = new cdb.core.Model({
      data: this.originalData,
      start: this.originalData[0].start,
      end: this.originalData[this.originalData.length - 1].end,
      bins: 20
    });
    this.originalModel.getData = function () {
      return this.originalData;
    }.bind(this);

    // override default behavior of debounce, to be able to control callback
    onWindowResizeSpy = jasmine.createSpy('_onWindowResize');
    spyOn(_, 'debounce').and.callFake(function (cb) {
      onWindowResizeReal = cb;
      return onWindowResizeSpy;
    });

    this.view = new WidgetHistogramChart(({
      el: $('.js-chart'),
      margin: this.margin,
      hasHandles: true,
      height: 100,
      data: this.data,
      originalData: this.originalModel,
      displayShadowBars: true,
      xAxisTickFormat: function (d, i) {
        return d;
      }
    }));

    var parentSpy = jasmine.createSpyObj('view.$el.parent()', ['width']);
    parentSpy.width.and.returnValue(this.width);
    spyOn(this.view.$el, 'parent');
    this.view.$el.parent.and.returnValue(parentSpy);

    spyOn(this.view, 'refresh').and.callThrough();

    this.view.render();
  });

  it('should be hidden initially', function () {
    expect(this.view.$el.attr('style')).toMatch('none');
  });

  describe('shadow bars', function () {

    it('should not show shadow bars', function () {
      this.view.options.displayShadowBars = false;
      this.view.model.set('show_shadow_bars', false);
      expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0);
      this.originalModel.trigger('change:data');
      expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0);
      this.view.showShadowBars();
      expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0);
    });

    it('should remove and generate shadow bars when original data chagnes', function () {
      spyOn(this.view, '_removeShadowBars');
      spyOn(this.view, '_generateShadowBars');
      this.originalModel.trigger('change:data');
      expect(this.view._removeShadowBars).toHaveBeenCalled();
      expect(this.view._generateShadowBars).toHaveBeenCalled();
    });
  });

  describe('when view is resized but set to not be shown just yet', function () {
    beforeEach(function () {
      expect(this.view.options.showOnWidthChange).toBe(true); // assert default value, in case it's changed
      this.view.options.showOnWidthChange = false;
    });

    it('should not show view', function () {
      expect(this.view.$el.attr('style')).toMatch('none');
    });
  });

  describe('when view is resized', function () {
    beforeEach(function () {
      onWindowResizeReal.call(this);
      expect(this.view.$el.parent).toHaveBeenCalled();
    });

    it('should render the view', function () {
      expect(this.view.$el.attr('style')).not.toMatch('none');
    });

    it('should calculate the width of the bars', function () {
      expect(this.view.barWidth).toBe((this.width - this.margin.left - this.margin.right) / this.data.length);
    });

    it('should draw the bars', function () {
      expect(this.view.$el.find('.CDB-Chart-bar').size()).toBe(this.data.length);
    });

    it('should draw the axis', function () {
      expect(this.view.$el.find('.CDB-Chart-axis').size()).toBe(1);
    });

    it('should draw the handles', function () {
      expect(this.view.$el.find('.CDB-Chart-handle').size()).toBe(2);
    });

    it('should hide the chart', function () {
      expect(this.view.$el.css('display')).toBe('inline');

      this.view.hide();

      expect(this.view.isHidden()).toBe(true);
      expect(this.view.model.get('display')).toBe(false);
      expect(this.view.$el.css('display')).toBe('none');
    });

    it('should show the chart', function () {
      this.view.hide();
      this.view.show();
      expect(this.view.$el.css('display')).toBe('inline');
    });

    it('should set the parent width', function () {
      this.view.show();
      this.view._resizeToParentElement();
      expect(this.view.model.get('width')).toBe(this.width);
    });

    it('should generate the shadow bars', function () {
      expect(this.view.$el.find('.CDB-Chart-shadowBars').length).toBe(1);
    });

    it('should hide the shadow bars', function () {
      this.view.removeShadowBars();
      expect(this.view.$el.find('.CDB-Chart-shadowBars').length).toBe(0);
    });

    it('should maintain the visibility after calling _resizeToParentElement', function () {
      this.view.show();
      this.view.$el.parent().width();
      this.view._resizeToParentElement();
      expect(this.view.$el.css('display')).toBe('inline');
      expect(this.view.model.get('display')).toBe(true);

      this.view.hide();
      this.view._resizeToParentElement();
      expect(this.view.$el.css('display')).toBe('none');
      expect(this.view.model.get('display')).toBe(false);
    });

    it('should calculate the scales', function () {
      expect(this.view.xScale(0)).toBe(0);
      expect(this.view.xScale(100)).toMatch(/\d+/);
      expect(this.view.xScale(100)).toEqual(this.view.chartWidth());
      expect(this.view.yScale(0)).toMatch(/\d+/);
      expect(this.view.yScale(0)).toEqual(this.view.chartHeight());
    });

    it('should refresh the data', function () {
      this.view.show();
      this.view.model.set({ data: genHistogramData(20) });
      expect(this.view.refresh).toHaveBeenCalled();
    });

    describe('should allow to manage the y scale', function () {
      beforeEach(function () {
        this.originalScale = this.view.yScale;
        this.view.model.set('data', genHistogramData(10));
      });

      it('should calculate the y scale on request', function () {
        expect(this.view.yScale).toEqual(this.originalScale);

        this.view.updateYScale();
        expect(this.view.yScale).not.toEqual(this.originalScale);
      });

      it('should restore the y scale on request', function () {
        this.view.resetYScale();
        expect(this.view.yScale).toEqual(this.originalScale);
      });
    });
  });

  describe('._getDataForScales', function () {
    it('should calculate (x|y)Scale depending originalData if it is provided and view is not bounded', function () {
      var data = this.view._getDataForScales();
      expect(data).toBe(this.originalData);
      expect(data).not.toBe(this.data);
    });

    it('should calculate (x|y)Scale depending current data if view is bounded', function () {
      this.view.model.set('bounded', true);
      var data = this.view._getDataForScales();
      expect(data).not.toBe(this.originalData);
      expect(data).toBe(this.data);
    });

    it('should calculate (x|y)Scale depending current data if originalData is not defined', function () {
      delete this.view._originalData;
      var data = this.view._getDataForScales();
      expect(data).not.toBe(this.originalData);
      expect(data).toBe(this.data);
    });
  });
});

function genHistogramData (n) {
  n = n || 1;
  var arr = [];
  _.times(n, function (i) {
    var start = (100 * i) + Math.round(Math.random() * 1000);
    var end = start + 100;
    var obj = {
      bin: i,
      freq: Math.round(Math.random() * 10),
      start: start,
      end: end,
      max: end,
      min: start
    };
    arr.push(obj);
  });
  return arr;
}
<commit_msg>linter companing<commit_after>var _ = require('underscore');
var $ = require('jquery');
var cdb = require('cartodb.js');
var d3 = require('d3');
var WidgetHistogramChart = require('../../../src/widgets/histogram/chart');

describe('widgets/histogram/chart', function () {
  var onWindowResizeReal;
  var onWindowResizeSpy;

  afterEach(function () {
    $('.js-chart').remove();
  });

  beforeEach(function () {
    d3.select('body').append('svg').attr('class', 'js-chart');

    this.width = 300;
    this.height = 100;

    d3.select($('.js-chart')[0])
      .attr('width', this.width)
      .attr('height', this.height)
      .append('g')
      .attr('class', 'Canvas');

    this.data = genHistogramData(20);
    this.margin = {
      top: 4,
      right: 4,
      bottom: 4,
      left: 4
    };

    this.originalData = genHistogramData(20);
    this.originalModel = new cdb.core.Model({
      data: this.originalData,
      start: this.originalData[0].start,
      end: this.originalData[this.originalData.length - 1].end,
      bins: 20
    });
    this.originalModel.getData = function () {
      return this.originalData;
    }.bind(this);

    // override default behavior of debounce, to be able to control callback
    onWindowResizeSpy = jasmine.createSpy('_onWindowResize');
    spyOn(_, 'debounce').and.callFake(function (cb) {
      onWindowResizeReal = cb;
      return onWindowResizeSpy;
    });

    this.view = new WidgetHistogramChart(({
      el: $('.js-chart'),
      margin: this.margin,
      hasHandles: true,
      height: 100,
      data: this.data,
      originalData: this.originalModel,
      displayShadowBars: true,
      xAxisTickFormat: function (d, i) {
        return d;
      }
    }));

    var parentSpy = jasmine.createSpyObj('view.$el.parent()', ['width']);
    parentSpy.width.and.returnValue(this.width);
    spyOn(this.view.$el, 'parent');
    this.view.$el.parent.and.returnValue(parentSpy);

    spyOn(this.view, 'refresh').and.callThrough();

    this.view.render();
  });

  it('should be hidden initially', function () {
    expect(this.view.$el.attr('style')).toMatch('none');
  });

  describe('shadow bars', function () {
    it('should not show shadow bars', function () {
      this.view.options.displayShadowBars = false;
      this.view.model.set('show_shadow_bars', false);
      expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0);
      this.originalModel.trigger('change:data');
      expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0);
      this.view.showShadowBars();
      expect(this.view.$('.CDB-Chart-shadowBars').length).toBe(0);
    });

    it('should remove and generate shadow bars when original data chagnes', function () {
      spyOn(this.view, '_removeShadowBars');
      spyOn(this.view, '_generateShadowBars');
      this.originalModel.trigger('change:data');
      expect(this.view._removeShadowBars).toHaveBeenCalled();
      expect(this.view._generateShadowBars).toHaveBeenCalled();
    });
  });

  describe('when view is resized but set to not be shown just yet', function () {
    beforeEach(function () {
      expect(this.view.options.showOnWidthChange).toBe(true); // assert default value, in case it's changed
      this.view.options.showOnWidthChange = false;
    });

    it('should not show view', function () {
      expect(this.view.$el.attr('style')).toMatch('none');
    });
  });

  describe('when view is resized', function () {
    beforeEach(function () {
      onWindowResizeReal.call(this);
      expect(this.view.$el.parent).toHaveBeenCalled();
    });

    it('should render the view', function () {
      expect(this.view.$el.attr('style')).not.toMatch('none');
    });

    it('should calculate the width of the bars', function () {
      expect(this.view.barWidth).toBe((this.width - this.margin.left - this.margin.right) / this.data.length);
    });

    it('should draw the bars', function () {
      expect(this.view.$el.find('.CDB-Chart-bar').size()).toBe(this.data.length);
    });

    it('should draw the axis', function () {
      expect(this.view.$el.find('.CDB-Chart-axis').size()).toBe(1);
    });

    it('should draw the handles', function () {
      expect(this.view.$el.find('.CDB-Chart-handle').size()).toBe(2);
    });

    it('should hide the chart', function () {
      expect(this.view.$el.css('display')).toBe('inline');

      this.view.hide();

      expect(this.view.isHidden()).toBe(true);
      expect(this.view.model.get('display')).toBe(false);
      expect(this.view.$el.css('display')).toBe('none');
    });

    it('should show the chart', function () {
      this.view.hide();
      this.view.show();
      expect(this.view.$el.css('display')).toBe('inline');
    });

    it('should set the parent width', function () {
      this.view.show();
      this.view._resizeToParentElement();
      expect(this.view.model.get('width')).toBe(this.width);
    });

    it('should generate the shadow bars', function () {
      expect(this.view.$el.find('.CDB-Chart-shadowBars').length).toBe(1);
    });

    it('should hide the shadow bars', function () {
      this.view.removeShadowBars();
      expect(this.view.$el.find('.CDB-Chart-shadowBars').length).toBe(0);
    });

    it('should maintain the visibility after calling _resizeToParentElement', function () {
      this.view.show();
      this.view.$el.parent().width();
      this.view._resizeToParentElement();
      expect(this.view.$el.css('display')).toBe('inline');
      expect(this.view.model.get('display')).toBe(true);

      this.view.hide();
      this.view._resizeToParentElement();
      expect(this.view.$el.css('display')).toBe('none');
      expect(this.view.model.get('display')).toBe(false);
    });

    it('should calculate the scales', function () {
      expect(this.view.xScale(0)).toBe(0);
      expect(this.view.xScale(100)).toMatch(/\d+/);
      expect(this.view.xScale(100)).toEqual(this.view.chartWidth());
      expect(this.view.yScale(0)).toMatch(/\d+/);
      expect(this.view.yScale(0)).toEqual(this.view.chartHeight());
    });

    it('should refresh the data', function () {
      this.view.show();
      this.view.model.set({ data: genHistogramData(20) });
      expect(this.view.refresh).toHaveBeenCalled();
    });

    describe('should allow to manage the y scale', function () {
      beforeEach(function () {
        this.originalScale = this.view.yScale;
        this.view.model.set('data', genHistogramData(10));
      });

      it('should calculate the y scale on request', function () {
        expect(this.view.yScale).toEqual(this.originalScale);

        this.view.updateYScale();
        expect(this.view.yScale).not.toEqual(this.originalScale);
      });

      it('should restore the y scale on request', function () {
        this.view.resetYScale();
        expect(this.view.yScale).toEqual(this.originalScale);
      });
    });
  });

  describe('._getDataForScales', function () {
    it('should calculate (x|y)Scale depending originalData if it is provided and view is not bounded', function () {
      var data = this.view._getDataForScales();
      expect(data).toBe(this.originalData);
      expect(data).not.toBe(this.data);
    });

    it('should calculate (x|y)Scale depending current data if view is bounded', function () {
      this.view.model.set('bounded', true);
      var data = this.view._getDataForScales();
      expect(data).not.toBe(this.originalData);
      expect(data).toBe(this.data);
    });

    it('should calculate (x|y)Scale depending current data if originalData is not defined', function () {
      delete this.view._originalData;
      var data = this.view._getDataForScales();
      expect(data).not.toBe(this.originalData);
      expect(data).toBe(this.data);
    });
  });
});

function genHistogramData (n) {
  n = n || 1;
  var arr = [];
  _.times(n, function (i) {
    var start = (100 * i) + Math.round(Math.random() * 1000);
    var end = start + 100;
    var obj = {
      bin: i,
      freq: Math.round(Math.random() * 10),
      start: start,
      end: end,
      max: end,
      min: start
    };
    arr.push(obj);
  });
  return arr;
}
<|endoftext|>


<commit_before>function showTree(url, container) {
  'use strict';

  function eachNode(nodes, iterator) {
    var length = nodes.length, i;

    for (i = 0; i < length; i++) {
      iterator.call(this, nodes[i]);

      if (nodes[i]['children'] && nodes[i]['children'].length) {
        eachNode(nodes[i]['children'], iterator);
      }
    }
  }

  d3.json(url, function(error, treeData) {
    if (error) {
      var errorData = JSON.parse(error.response),
      errorEl   = $('

'),
      messageEl = $('

');

      $('.loading').remove();

      $(container).append(
        errorEl.append(messageEl.text(errorData.error))
      );

      if (errorData.message) {
        errorEl.append(
          $('

').text(errorData.message)
        );
      }

      if (errorData.backtrace) {
        errorEl.append(
          $('

')
          .html(errorData.backtrace.join('
'))
        );
      }

      return false;
    }

    // Calculate total nodes, max label length
    var maxLabelLength = 0,
        maxViewerHeight = 500,
        // Misc. variables
        nodeIds = 0,
        duration = 0,
        ease = 'cubic-out',
        root,

        // D3 components.
        dragListener, zoomListener, node, baseSvg,

        // Size of the diagram. Height is just a placeholder since we'll change it
        // depending on the contents.
        viewerWidth = $(container).width(),
        viewerHeight = 500,

        // The diagram.
        tree = d3.layout.tree().size([viewerHeight, viewerWidth]),

        // Define a d3 diagonal projection for use by the node paths later on.
        diagonal = d3.svg.diagonal()
          .projection(function(d) { return [d.y, d.x]; }),

        // Keep track of "storage on" data.
        storageShown = false,
        storageLoads = false,

        // Most recently clicked node, so that we can update the load chart if
        // the user opts to show storage.
        lastClicked;

        treeData = treeData.graph;

    // A recursive helper function for performing some setup by walking through
    // all nodes.
    function visit(parent, visitFn, childrenFn) {
      if (!parent) return;

      var children, count, i;

      visitFn(parent);
      children = childrenFn(parent);

      if (children) {
        count = children.length;

        for (i = 0; i < count; i++) {
          visit(children[i], visitFn, childrenFn);
        }
      }
    };

    // Call visit function to establish maxLabelLength
    visit(treeData, function(d) {
      maxLabelLength = Math.max(d.name.length, maxLabelLength);

      if (d.children && d.children.length === 0) {
        d.children = null;
      }
    }, function(d) {
      return d.children && d.children.length > 0 ? d.children : null;
    });

    visit(treeData, toggleChildren, function(n) { return n._children });

    // Sort the tree according to the node names
    tree.sort(function(a, b) {
      return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
    });

    // Define the zoomListener which calls the zoom function on the "zoom"
    // event constrained within the scaleExtents
    zoomListener = d3.behavior.zoom()
    .scaleExtent([0.1, 3]).on('zoom', function () {
      svgGroup.attr('transform',
        'translate(' + d3.event.translate + ')' +
        'scale(' + d3.event.scale + ')');
    });

    // Define the baseSvg, attaching a class for styling and the zoomListener
    baseSvg = d3.select(container).append('svg')
      .attr('width', viewerWidth)
      .attr('height', viewerHeight)
      .attr('class', 'overlay')
      .call(zoomListener)
          // Disable double-click causing a zoom-in.
          .on('wheel.zoom', null)
          .on('dblclick.zoom', null);

    // Listener which prevents the drag movement of the diagram when clicking on
    // a node.
    dragListener = d3.behavior.drag()
      .on('dragstart', function(d) {
        d3.event.sourceEvent.stopPropagation();
      });

    // Function to center node when clicked/dropped so node doesn't get lost
    // when collapsing/moving with large amount of children.
    function centerNode(source, xOffset) {
      xOffset = xOffset || 0;

      var height = Math.min(maxViewerHeight, viewerHeight);
      var scale = zoomListener.scale(),
      x = -source.y0 - xOffset,
      y = -source.x0;

      x = x * scale + viewerWidth  / 2;
      y = y * scale + height / 2;

      d3.select('g').transition()
      .duration(duration).ease(ease)
      .attr('transform', 'translate(' + x + ',' + y + ')' +
       'scale(' + scale + ')');

      zoomListener.scale(scale);
      zoomListener.translate([x, y]);
    };

    // Shows or hides children depending on the current state.
    function toggleChildren(d) {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else if (d._children) {
        d.children = d._children;
        d._children = null;
      }

      return d;
    };

    function showChart(d) {
      var loadChart = window.LoadChart;

      $('.load-graph').empty().append('<svg></svg>');

      if (storageShown && storageLoads) {
        new loadChart([
          { values: d.load,    name: d.name + ' (with storage)', color: '#95BB95', area: true },
          { values: d.altLoad, name: d.name, area: true, color: '#1F77B4' }
        ], d.capacity).render('.load-graph svg')
      } else {
        new loadChart([
          { values: d.load, name: d.name, area: true, color: '#1F77B4' }
        ], d.capacity).render('.load-graph svg')
      }
    }

    // Toggle children on click.
    function click(d) {
      if (d3.event && d3.event.defaultPrevented) return; // click suppressed

      toggleSelectedNode.call(this);
      lastClicked = d;

      // Load chart.
      var values = d.load;

      showChart(d);

      $('#technologies .row-fluid').hide();
      $('#technologies .row-fluid[data-node="' + d.name + '"]').show();

      $('.node-info .download-curve').show().unbind('click')
      .click(function(event) {
        var file = document.createElement('a');

        file.setAttribute('download', d.name + ' Curve.csv');
        file.setAttribute('href', 'data:text/csv;charset=utf-8,' +
         encodeURIComponent(d.load.join("\n")));
        file.click();

        event.preventDefault();
      });
    };

    function toggleSelectedNode(){
      d3.selectAll("text").style("font-weight", "normal");
      d3.selectAll("text").style("text-decoration", "none");
      d3.select(this).select("text").style("font-weight", "bold");
      d3.select(this).select("text").style("text-decoration", "underline");
    };

    function dblClick(d) {
      if (d3.event.defaultPrevented) return; // click suppressed

      if (d._children || d.children) {
        toggleChildren(d);
        update(d);
        centerNode(d);
      }
    };

    function toggleStorage() {
      if (storageShown) {
        if (storageLoads === true) {
          // Already loading...
          return false;
        } else if (storageLoads) {
          // immediately toggle storage on
          swapLoads(root);
        } else {
          storageLoads = true;

          d3.json(url + '?storage=1', function(error, offData) {
            // fetch then toggle on
            storageLoads = {};

            eachNode([offData.graph], function(node) {
              storageLoads[node.name] = node.load;
            });

            svgGroup.selectAll('g.node').data().forEach(function(datum) {
              datum.altLoad = storageLoads[datum.name];
            });

            $('#enable-storage')
              .text('Storage Enabled')
              .addClass('btn-success')
              .removeClass('disabled');

            swapLoads(root);
          });
        }
      } else {
        swapLoads(root);
      }
    }

    function swapLoads(root) {
      eachNode([root], function(node) {
        var otherLoad = node.altLoad;

        node.altLoad = node.load;
        node.load    = otherLoad;
      });

      if (lastClicked) {
        click(lastClicked);
      }

      update(root);
    }

    function update(source) {
      // Compute the new height, function counts total children of root node
      // and sets tree height accordingly. This prevents the layout looking
      // squashed when new nodes are made visible or looking sparse when nodes
      // are removed. This makes the layout more consistent.
      var levelWidth = [1],

      childCount = function(level, n) {
        if (n.children && n.children.length > 0) {
          if (levelWidth.length <= level + 1) levelWidth.push(0);

          levelWidth[level + 1] += n.children.length;
          n.children.forEach(function(d) {
            childCount(level + 1, d);
          });
        }
      },

      newHeight, nodes, links, nodeEnter, nodeUpdate, nodeExit, link;

      childCount(0, root);
      newHeight = d3.max(levelWidth) * 35; // 25 pixels per line

      // Update the SVG height to fit the contents.
      var svgDivHeight = Math.min(newHeight, maxViewerHeight);
      var baseSvgSelector = $(baseSvg[0][0]);
      baseSvgSelector.height(newHeight + 50);
      baseSvgSelector.parent().height(svgDivHeight + 50);

      tree = tree.size([newHeight, viewerWidth]);

      viewerHeight = newHeight + 50;

      // Compute the new tree layout.
      nodes = tree.nodes(root).reverse();
      links = tree.links(nodes);

      // Set widths between levels based on maxLabelLength.
      nodes.forEach(function(d) {
        d.y = (d.depth * (maxLabelLength * 10));
          // alternatively to keep a fixed scale one can set a fixed depth per
          // level. Normalize for fixed-depth by commenting out below line>
          // d.y = (d.depth * 175); //175px per level.
        });

      // Update the nodes…
      node = svgGroup.selectAll('g.node')
      .data(nodes, function(d) { return d.id || (d.id = ++nodeIds); });

      node.classed('collapsed', function(d) { return d._children; });

      // Enter any new nodes at the parent's previous position.
      nodeEnter = node.enter().append('g')
        .call(dragListener)
        .attr('class', 'node')
        .classed('collapsed', function(d) {
                // We have to run the "collapsed" function again (it is already
                // defined above), as the above version does not run the first
                // time the update() function is called.
                return d._children;
              })
        .attr('transform', function(d) {
          return 'translate(' + source.y0 + ',' + source.x0 + ')';
        })
        .on('dblclick', dblClick)
        .on('click', click);

      node.classed('exceedance', function(d) {
        return d.capacity && d3.max(d.load) > d.capacity
      });

      nodeEnter.append('circle')
      .attr('class', 'nodeCircle')
      .attr('r', 0)

      nodeEnter.append('text')
      .attr('x', function(d) { return d === root ? -10 : 10; })
      .attr('dy', '.38em')
      .attr('class', 'nodeText')
      .text(function(d) { return d.name; })
      .style('fill-opacity', 0)
      .attr('text-anchor', function(d) {
        return d === root ? 'end' : 'start';
      });

      // Update the text to reflect whether node has children or not.
      node.select('text')
      .attr('x', function(d) { return d === root ? -10 : 10; })
      .text(function(d) { return d.name; })
      .attr('text-anchor', function(d) {
        return d === root ? 'end' : 'start';
      });

      // Change the circle fill depending on whether it has children and is collapsed
      node.select('circle.nodeCircle').attr('r', 7.5);

      // Transition nodes to their new position.
      nodeUpdate = node.transition()
      .duration(duration).ease(ease)
      .attr('transform', function(d) {
        return 'translate(' + d.y + ',' + d.x + ')';
      });

      // Fade the text in
      nodeUpdate.select('text').style('fill-opacity', 1);

      // Transition exiting nodes to the parent's new position.
      nodeExit = node.exit().transition()
      .duration(duration).ease(ease)
      .attr('transform', function(d) {
        return 'translate(' + source.y + ',' + source.x + ')';
      })
      .remove();

      nodeExit.select('circle').attr('r', 0);
      nodeExit.select('text').style('fill-opacity', 0);

      // Update the links…
      link = svgGroup.selectAll('path.link')
      .data(links, function(d) {
        return d.target.id;
      });

      // Enter any new links at the parent's previous position.
      link.enter().insert('path', 'g')
      .attr('class', 'link')
      .attr('d', function(d) {
        var o = {
          x: source.x0,
          y: source.y0
        };
        return diagonal({
          source: o,
          target: o
        });
      });

      // Transition links to their new position.
      link.transition()
      .duration(duration).ease(ease)
      .attr('d', diagonal);

      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
      .duration(duration).ease(ease)
      .attr('d', function(d) {
        var o = { x: source.x, y: source.y };
        return diagonal({ source: o, target: o });
      })
      .remove();

      // Stash the old positions for transition.
      nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    };

    $('.loading').remove();

    $('#enable-storage').click(function(event) {
      event.preventDefault();

      var element = $(this);

      if (element.hasClass('disabled')) {
        return true;
      }

      if (storageShown) {
        storageShown = false;
        element.text('Enable Storage').removeClass('btn-success');
      } else {
        storageShown = true;

        if (!storageLoads || storageLoads === true) {
          element.text('Loading...').addClass('disabled');
        } else {
          element.text('Storage Enabled').addClass('btn-success');
        }
      }

      toggleStorage();
    });

    // Show nodes from the top-most two levels of the tree; nodes beneath will
    // be hidden until the user chooses to view them.
    toggleChildren(treeData);
    treeData.children.forEach(toggleChildren);

    // Append a group which holds all nodes and upon which the zoom Listener
    // can act.
    var svgGroup = baseSvg.append('g');

    // Define the root
    root = treeData;
    root.x0 = viewerHeight / 2;
    root.y0 = 0;

    eachNode([root], function(node) {
      node.loads = {};
      node.loads[false] = node.load;
    });

    // Layout the tree initially and center on the root node.
    update(root);

    // Center the diagram with an offset such that *children* of the root will
    // appear to be in the center.
    centerNode(root, maxLabelLength * 10);

    duration = 250;
  });
}
<commit_msg>Set storage loads on hidden nodes<commit_after>function showTree(url, container) {
  'use strict';

  function eachNode(nodes, iterator) {
    var length = nodes.length, i;

    for (i = 0; i < length; i++) {
      iterator.call(this, nodes[i]);

      if (nodes[i]['children'] && nodes[i]['children'].length) {
        eachNode(nodes[i]['children'], iterator);
      }

      if (nodes[i]['_children'] && nodes[i]['_children'].length) {
        eachNode(nodes[i]['_children'], iterator);
      }
    }
  }

  d3.json(url, function(error, treeData) {
    if (error) {
      var errorData = JSON.parse(error.response),
      errorEl   = $('

'),
      messageEl = $('

');

      $('.loading').remove();

      $(container).append(
        errorEl.append(messageEl.text(errorData.error))
      );

      if (errorData.message) {
        errorEl.append(
          $('

').text(errorData.message)
        );
      }

      if (errorData.backtrace) {
        errorEl.append(
          $('

')
          .html(errorData.backtrace.join('
'))
        );
      }

      return false;
    }

    // Calculate total nodes, max label length
    var maxLabelLength = 0,
        maxViewerHeight = 500,
        // Misc. variables
        nodeIds = 0,
        duration = 0,
        ease = 'cubic-out',
        root,

        // D3 components.
        dragListener, zoomListener, node, baseSvg,

        // Size of the diagram. Height is just a placeholder since we'll change it
        // depending on the contents.
        viewerWidth = $(container).width(),
        viewerHeight = 500,

        // The diagram.
        tree = d3.layout.tree().size([viewerHeight, viewerWidth]),

        // Define a d3 diagonal projection for use by the node paths later on.
        diagonal = d3.svg.diagonal()
          .projection(function(d) { return [d.y, d.x]; }),

        // Keep track of "storage on" data.
        storageShown = false,
        storageLoads = false,

        // Most recently clicked node, so that we can update the load chart if
        // the user opts to show storage.
        lastClicked;

        treeData = treeData.graph;

    // A recursive helper function for performing some setup by walking through
    // all nodes.
    function visit(parent, visitFn, childrenFn) {
      if (!parent) return;

      var children, count, i;

      visitFn(parent);
      children = childrenFn(parent);

      if (children) {
        count = children.length;

        for (i = 0; i < count; i++) {
          visit(children[i], visitFn, childrenFn);
        }
      }
    };

    // Call visit function to establish maxLabelLength
    visit(treeData, function(d) {
      maxLabelLength = Math.max(d.name.length, maxLabelLength);

      if (d.children && d.children.length === 0) {
        d.children = null;
      }
    }, function(d) {
      return d.children && d.children.length > 0 ? d.children : null;
    });

    visit(treeData, toggleChildren, function(n) { return n._children });

    // Sort the tree according to the node names
    tree.sort(function(a, b) {
      return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
    });

    // Define the zoomListener which calls the zoom function on the "zoom"
    // event constrained within the scaleExtents
    zoomListener = d3.behavior.zoom()
    .scaleExtent([0.1, 3]).on('zoom', function () {
      svgGroup.attr('transform',
        'translate(' + d3.event.translate + ')' +
        'scale(' + d3.event.scale + ')');
    });

    // Define the baseSvg, attaching a class for styling and the zoomListener
    baseSvg = d3.select(container).append('svg')
      .attr('width', viewerWidth)
      .attr('height', viewerHeight)
      .attr('class', 'overlay')
      .call(zoomListener)
          // Disable double-click causing a zoom-in.
          .on('wheel.zoom', null)
          .on('dblclick.zoom', null);

    // Listener which prevents the drag movement of the diagram when clicking on
    // a node.
    dragListener = d3.behavior.drag()
      .on('dragstart', function(d) {
        d3.event.sourceEvent.stopPropagation();
      });

    // Function to center node when clicked/dropped so node doesn't get lost
    // when collapsing/moving with large amount of children.
    function centerNode(source, xOffset) {
      xOffset = xOffset || 0;

      var height = Math.min(maxViewerHeight, viewerHeight);
      var scale = zoomListener.scale(),
      x = -source.y0 - xOffset,
      y = -source.x0;

      x = x * scale + viewerWidth  / 2;
      y = y * scale + height / 2;

      d3.select('g').transition()
      .duration(duration).ease(ease)
      .attr('transform', 'translate(' + x + ',' + y + ')' +
       'scale(' + scale + ')');

      zoomListener.scale(scale);
      zoomListener.translate([x, y]);
    };

    // Shows or hides children depending on the current state.
    function toggleChildren(d) {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else if (d._children) {
        d.children = d._children;
        d._children = null;
      }

      return d;
    };

    function showChart(d) {
      var loadChart = window.LoadChart;

      $('.load-graph').empty().append('<svg></svg>');

      if (storageShown && storageLoads) {
        new loadChart([
          { values: d.load,    name: d.name + ' (with storage)', color: '#95BB95', area: true },
          { values: d.altLoad, name: d.name, area: true, color: '#1F77B4' }
        ], d.capacity).render('.load-graph svg')
      } else {
        new loadChart([
          { values: d.load, name: d.name, area: true, color: '#1F77B4' }
        ], d.capacity).render('.load-graph svg')
      }
    }

    // Toggle children on click.
    function click(d) {
      if (d3.event && d3.event.defaultPrevented) return; // click suppressed

      toggleSelectedNode.call(this);
      lastClicked = d;

      // Load chart.
      var values = d.load;

      showChart(d);

      $('#technologies .row-fluid').hide();
      $('#technologies .row-fluid[data-node="' + d.name + '"]').show();

      $('.node-info .download-curve').show().unbind('click')
      .click(function(event) {
        var file = document.createElement('a');

        file.setAttribute('download', d.name + ' Curve.csv');
        file.setAttribute('href', 'data:text/csv;charset=utf-8,' +
         encodeURIComponent(d.load.join("\n")));
        file.click();

        event.preventDefault();
      });
    };

    function toggleSelectedNode(){
      d3.selectAll("text").style("font-weight", "normal");
      d3.selectAll("text").style("text-decoration", "none");
      d3.select(this).select("text").style("font-weight", "bold");
      d3.select(this).select("text").style("text-decoration", "underline");
    };

    function dblClick(d) {
      if (d3.event.defaultPrevented) return; // click suppressed

      if (d._children || d.children) {
        toggleChildren(d);
        update(d);
        centerNode(d);
      }
    };

    function toggleStorage() {
      if (storageShown) {
        if (storageLoads === true) {
          // Already loading...
          return false;
        } else if (storageLoads) {
          // immediately toggle storage on
          swapLoads(root);
        } else {
          storageLoads = true;

          d3.json(url + '?storage=1', function(error, offData) {
            // fetch then toggle on
            storageLoads = {};

            eachNode([offData.graph], function(node) {
              storageLoads[node.name] = node.load;
            });

            eachNode([treeData], function(node) {
              node.altLoad = storageLoads[node.name];
            });

            $('#enable-storage')
              .text('Storage Enabled')
              .addClass('btn-success')
              .removeClass('disabled');

            swapLoads(root);
          });
        }
      } else {
        swapLoads(root);
      }
    }

    function swapLoads(root) {
      eachNode([root], function(node) {
        var otherLoad = node.altLoad;

        node.altLoad = node.load;
        node.load    = otherLoad;
      });

      if (lastClicked) {
        click(lastClicked);
      }

      update(root);
    }

    function update(source) {
      // Compute the new height, function counts total children of root node
      // and sets tree height accordingly. This prevents the layout looking
      // squashed when new nodes are made visible or looking sparse when nodes
      // are removed. This makes the layout more consistent.
      var levelWidth = [1],

      childCount = function(level, n) {
        if (n.children && n.children.length > 0) {
          if (levelWidth.length <= level + 1) levelWidth.push(0);

          levelWidth[level + 1] += n.children.length;
          n.children.forEach(function(d) {
            childCount(level + 1, d);
          });
        }
      },

      newHeight, nodes, links, nodeEnter, nodeUpdate, nodeExit, link;

      childCount(0, root);
      newHeight = d3.max(levelWidth) * 35; // 25 pixels per line

      // Update the SVG height to fit the contents.
      var svgDivHeight = Math.min(newHeight, maxViewerHeight);
      var baseSvgSelector = $(baseSvg[0][0]);
      baseSvgSelector.height(newHeight + 50);
      baseSvgSelector.parent().height(svgDivHeight + 50);

      tree = tree.size([newHeight, viewerWidth]);

      viewerHeight = newHeight + 50;

      // Compute the new tree layout.
      nodes = tree.nodes(root).reverse();
      links = tree.links(nodes);

      // Set widths between levels based on maxLabelLength.
      nodes.forEach(function(d) {
        d.y = (d.depth * (maxLabelLength * 10));
          // alternatively to keep a fixed scale one can set a fixed depth per
          // level. Normalize for fixed-depth by commenting out below line>
          // d.y = (d.depth * 175); //175px per level.
        });

      // Update the nodes…
      node = svgGroup.selectAll('g.node')
      .data(nodes, function(d) { return d.id || (d.id = ++nodeIds); });

      node.classed('collapsed', function(d) { return d._children; });

      // Enter any new nodes at the parent's previous position.
      nodeEnter = node.enter().append('g')
        .call(dragListener)
        .attr('class', 'node')
        .classed('collapsed', function(d) {
                // We have to run the "collapsed" function again (it is already
                // defined above), as the above version does not run the first
                // time the update() function is called.
                return d._children;
              })
        .attr('transform', function(d) {
          return 'translate(' + source.y0 + ',' + source.x0 + ')';
        })
        .on('dblclick', dblClick)
        .on('click', click);

      node.classed('exceedance', function(d) {
        return d.capacity && d3.max(d.load) > d.capacity
      });

      nodeEnter.append('circle')
      .attr('class', 'nodeCircle')
      .attr('r', 0)

      nodeEnter.append('text')
      .attr('x', function(d) { return d === root ? -10 : 10; })
      .attr('dy', '.38em')
      .attr('class', 'nodeText')
      .text(function(d) { return d.name; })
      .style('fill-opacity', 0)
      .attr('text-anchor', function(d) {
        return d === root ? 'end' : 'start';
      });

      // Update the text to reflect whether node has children or not.
      node.select('text')
      .attr('x', function(d) { return d === root ? -10 : 10; })
      .text(function(d) { return d.name; })
      .attr('text-anchor', function(d) {
        return d === root ? 'end' : 'start';
      });

      // Change the circle fill depending on whether it has children and is collapsed
      node.select('circle.nodeCircle').attr('r', 7.5);

      // Transition nodes to their new position.
      nodeUpdate = node.transition()
      .duration(duration).ease(ease)
      .attr('transform', function(d) {
        return 'translate(' + d.y + ',' + d.x + ')';
      });

      // Fade the text in
      nodeUpdate.select('text').style('fill-opacity', 1);

      // Transition exiting nodes to the parent's new position.
      nodeExit = node.exit().transition()
      .duration(duration).ease(ease)
      .attr('transform', function(d) {
        return 'translate(' + source.y + ',' + source.x + ')';
      })
      .remove();

      nodeExit.select('circle').attr('r', 0);
      nodeExit.select('text').style('fill-opacity', 0);

      // Update the links…
      link = svgGroup.selectAll('path.link')
      .data(links, function(d) {
        return d.target.id;
      });

      // Enter any new links at the parent's previous position.
      link.enter().insert('path', 'g')
      .attr('class', 'link')
      .attr('d', function(d) {
        var o = {
          x: source.x0,
          y: source.y0
        };
        return diagonal({
          source: o,
          target: o
        });
      });

      // Transition links to their new position.
      link.transition()
      .duration(duration).ease(ease)
      .attr('d', diagonal);

      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
      .duration(duration).ease(ease)
      .attr('d', function(d) {
        var o = { x: source.x, y: source.y };
        return diagonal({ source: o, target: o });
      })
      .remove();

      // Stash the old positions for transition.
      nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    };

    $('.loading').remove();

    $('#enable-storage').click(function(event) {
      event.preventDefault();

      var element = $(this);

      if (element.hasClass('disabled')) {
        return true;
      }

      if (storageShown) {
        storageShown = false;
        element.text('Enable Storage').removeClass('btn-success');
      } else {
        storageShown = true;

        if (!storageLoads || storageLoads === true) {
          element.text('Loading...').addClass('disabled');
        } else {
          element.text('Storage Enabled').addClass('btn-success');
        }
      }

      toggleStorage();
    });

    // Show nodes from the top-most two levels of the tree; nodes beneath will
    // be hidden until the user chooses to view them.
    toggleChildren(treeData);
    treeData.children.forEach(toggleChildren);

    // Append a group which holds all nodes and upon which the zoom Listener
    // can act.
    var svgGroup = baseSvg.append('g');

    // Define the root
    root = treeData;
    root.x0 = viewerHeight / 2;
    root.y0 = 0;

    eachNode([root], function(node) {
      node.loads = {};
      node.loads[false] = node.load;
    });

    // Layout the tree initially and center on the root node.
    update(root);

    // Center the diagram with an offset such that *children* of the root will
    // appear to be in the center.
    centerNode(root, maxLabelLength * 10);

    duration = 250;
  });
}
<|endoftext|>


<commit_before>const sinon = require('sinon')

module.exports = class NSearch {
  constructor() {
    this.Type = {
      CUSTOMER:"customer",
      ITEM:"item",
      LOCATION:"location"
    }
    this.Sort = {
      ASC:"asc",
      DESC:"desc"
    }
    this.Operator = {
      AFTER:"AFTER",
      ALLOF:"ALLOF",
      ANY:"ANY",
      ANYOF:"ANYOF",
      BEFORE:"BEFORE",
      BETWEEN:"BETWEEN",
      CONTAINS:"CONTAINS",
      DOESNOTCONTAIN:"DOESNOTCONTAIN",
      DOESNOTSTARTWITH:"DOESNOTSTARTWITH",
      EQUALTO:"EQUALTO",
      GREATERTHAN:"GREATERTHAN",
      GREATERTHANOREQUALTO:"GREATERTHANOREQUALTO",
      HASKEYWORDS:"HASKEYWORDS",
      IS:"IS",
      ISEMPTY:"ISEMPTY",
      ISNOT:"ISNOT",
      ISNOTEMPTY:"ISNOTEMPTY",
      LESSTHAN:"LESSTHAN",
      LESSTHANOREQUALTO:"LESSTHANOREQUALTO",
      NONEOF:"NONEOF",
      NOTAFTER:"NOTAFTER",
      NOTALLOF:"NOTALLOF",
      NOTBEFORE:"NOTBEFORE",
      NOTBETWEEN:"NOTBETWEEN",
      NOTEQUALTO:"NOTEQUALTO",
      NOTGREATERTHAN:"NOTGREATERTHAN",
      NOTGREATERTHANOREQUALTO:"NOTGREATERTHANOREQUALTO",
      NOTLESSTHAN:"NOTLESSTHAN",
      NOTLESSTHANOREQUALT:"ONOTLESSTHANOREQUALTO",
      NOTON:"NOTON",
      NOTONORAFTER:"NOTONORAFTER",
      NOTONORBEFORE:"NOTONORBEFORE",
      NOTWITHIN:"NOTWITHIN",
      ON:"ON",
      ONORAFTER:"ONORAFTER",
      ONORBEFORE:"ONORBEFORE",
      STARTSWITH:"STARTSWITH",
      WITHIN:"WITHIN"
    }
    this.Summary = {
      GROUP:"GROUP",
      COUNT:"COUNT",
      SUM:"SUM",
      AVG:"AVG",
      MIN:"MIN",
      MAX:"MAX"
    }
    this.records = [];
  }

  create(options) {

  }

  searchShouldYield(objectToReturn) {
    var run = sinon.stub();
    var each = sinon.stub();
    each.yields(objectToReturn)

    run.returns({
      each:each
    })

    this.create = sinon.stub();
    this.create.returns({
      run:run
    })
  }

  searchShouldYieldMultipleResults(results) {

    const run = function() {
      return {
        results: results,
        each: function(callback) {
          this.results.forEach(result => {
            callback(result);
          });
        }
      }
    }

    this.create = sinon.stub();
    this.create.returns({
      run:run
    })
  }

  searchShouldYieldNoResults() {
    var run = sinon.stub();
    var each = sinon.stub();

    run.returns({
      each:each
    })

    this.create = sinon.stub();
    this.create.returns({
      run:run
    })
  }

  lookupFields(data) {
    var records = this.records.filter(record => (record.id == data.id && record.type == data.type));
    var response = {};
    if(records && records.length > 0) {
      var record = records[0];
      for(let column of data.columns) {
          response[column] = record.getValue(column)
      }
    }

    return response;
  }

  addRecord(record) {
    this.records.push(record)
  }
}
<commit_msg>Fixing issue with lookupfields when using a Type of ITEM<commit_after>const sinon = require('sinon')
const NRecord = require("./NRecord")

module.exports = class NSearch {
  constructor() {
    this.Type = {
      CUSTOMER:"customer",
      ITEM:"item",
      LOCATION:"location"
    }
    this.Sort = {
      ASC:"asc",
      DESC:"desc"
    }
    this.Operator = {
      AFTER:"AFTER",
      ALLOF:"ALLOF",
      ANY:"ANY",
      ANYOF:"ANYOF",
      BEFORE:"BEFORE",
      BETWEEN:"BETWEEN",
      CONTAINS:"CONTAINS",
      DOESNOTCONTAIN:"DOESNOTCONTAIN",
      DOESNOTSTARTWITH:"DOESNOTSTARTWITH",
      EQUALTO:"EQUALTO",
      GREATERTHAN:"GREATERTHAN",
      GREATERTHANOREQUALTO:"GREATERTHANOREQUALTO",
      HASKEYWORDS:"HASKEYWORDS",
      IS:"IS",
      ISEMPTY:"ISEMPTY",
      ISNOT:"ISNOT",
      ISNOTEMPTY:"ISNOTEMPTY",
      LESSTHAN:"LESSTHAN",
      LESSTHANOREQUALTO:"LESSTHANOREQUALTO",
      NONEOF:"NONEOF",
      NOTAFTER:"NOTAFTER",
      NOTALLOF:"NOTALLOF",
      NOTBEFORE:"NOTBEFORE",
      NOTBETWEEN:"NOTBETWEEN",
      NOTEQUALTO:"NOTEQUALTO",
      NOTGREATERTHAN:"NOTGREATERTHAN",
      NOTGREATERTHANOREQUALTO:"NOTGREATERTHANOREQUALTO",
      NOTLESSTHAN:"NOTLESSTHAN",
      NOTLESSTHANOREQUALT:"ONOTLESSTHANOREQUALTO",
      NOTON:"NOTON",
      NOTONORAFTER:"NOTONORAFTER",
      NOTONORBEFORE:"NOTONORBEFORE",
      NOTWITHIN:"NOTWITHIN",
      ON:"ON",
      ONORAFTER:"ONORAFTER",
      ONORBEFORE:"ONORBEFORE",
      STARTSWITH:"STARTSWITH",
      WITHIN:"WITHIN"
    }
    this.Summary = {
      GROUP:"GROUP",
      COUNT:"COUNT",
      SUM:"SUM",
      AVG:"AVG",
      MIN:"MIN",
      MAX:"MAX"
    }
    this.records = [];
  }

  create(options) {

  }

  searchShouldYield(objectToReturn) {
    var run = sinon.stub();
    var each = sinon.stub();
    each.yields(objectToReturn)

    run.returns({
      each:each
    })

    this.create = sinon.stub();
    this.create.returns({
      run:run
    })
  }

  searchShouldYieldMultipleResults(results) {

    const run = function() {
      return {
        results: results,
        each: function(callback) {
          this.results.forEach(result => {
            callback(result);
          });
        }
      }
    }

    this.create = sinon.stub();
    this.create.returns({
      run:run
    })
  }

  searchShouldYieldNoResults() {
    var run = sinon.stub();
    var each = sinon.stub();

    run.returns({
      each:each
    })

    this.create = sinon.stub();
    this.create.returns({
      run:run
    })
  }

  lookupFields(data) {

    var records = [];
    if(data.type === this.Type.ITEM) {
      const nRecord = new NRecord();
      records = this.records.filter(record => (
        record.id == data.id &&
        (
          record.type == nRecord.Type.INVENTORY_ITEM ||
          record.type == nRecord.Type.ASSEMBLY_ITEM ||
          record.type == nRecord.Type.DESCRIPTION_ITEM ||
          record.type == nRecord.Type.DISCOUNT_ITEM ||
          record.type == nRecord.Type.GIFT_CERTIFICATE_ITEM ||
          record.type == nRecord.Type.ITEM_GROUP ||
          record.type == nRecord.Type.KIT_ITEM ||
          record.type == nRecord.Type.MARKUP_ITEM ||
          record.type == nRecord.Type.NON_INVENTORY_ITEM ||
          record.type == nRecord.Type.OTHER_CHARGE_ITEM ||
          record.type == nRecord.Type.PAYMENT_ITEM ||
          record.type == nRecord.Type.SERVICE_ITEM ||
          record.type == nRecord.Type.SUBTOTAL_ITEM
        )
      ));
    } else {
      records = this.records.filter(record => (record.id == data.id && record.type == data.type));
    }

    var response = {};
    if(records && records.length > 0) {
      var record = records[0];
      for(let column of data.columns) {
          response[column] = record.getValue(column)
      }
    }

    return response;
  }

  addRecord(record) {
    this.records.push(record)
  }
}
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var yeoman = require('yeoman-generator');
var fs = require('fs');
module.exports = Generator;

function Generator(args, options) {
  yeoman.generators.Base.apply(this, arguments);

  if (typeof args[0] === 'string') {
    this.component = args[0];
  }
}

util.inherits(Generator, yeoman.generators.NamedBase);

Generator.prototype.askFor = function askFor() {
  if (this.component) {
    return;
  }

  var cb = this.async();

  this.prompt({
    name: 'component',
    message: 'component?'
  }, function (props) {
    this.component = props.component;

    cb();
  }.bind(this));
};

Generator.prototype.createFiles = function createFiles() {

  //Work out what the app is bassed on the current folder.
  //Not ideal but until we have a .admo properties file this is good enough
  var folders = fs.readdirSync('app');
  var app = null;
  folders.forEach(function(f){
    if (fs.lstatSync('app/'+f).isDirectory()){
      app = f;
    }
  });
  //TODO: fix app pathing.
  this.mkdir('app/'+app+'/components/'+this.component);
  this.write('app/'+app+'/components/'+this.component+'/'+this.component+'.js', '//Fill this with your own file');
  this.write('app/'+app+'/components/'+this.component+'/'+this.component+'.scss', '//Fill this with your own file');
};
<commit_msg>Using package.json to get the apps name<commit_after>'use strict';
var util = require('util');
var yeoman = require('yeoman-generator');
var fs = require('fs');
module.exports = Generator;

function Generator(args, options) {
  yeoman.generators.Base.apply(this, arguments);

  if (typeof args[0] === 'string') {
    this.component = args[0];
  }
}

util.inherits(Generator, yeoman.generators.NamedBase);

Generator.prototype.askFor = function askFor() {
  if (this.component) {
    return;
  }

  var cb = this.async();

  this.prompt({
    name: 'component',
    message: 'component?'
  }, function (props) {
    this.component = props.component;

    cb();
  }.bind(this));
};

Generator.prototype.createFiles = function createFiles() {
  var packageJson = fs.readFileSync( "package.json" );
  var app = JSON.parse(packageJson).name;
  this.mkdir('app/'+app+'/components/'+this.component);
  this.write('app/'+app+'/components/'+this.component+'/'+this.component+'.js', '//Fill this with your own file');
  this.write('app/'+app+'/components/'+this.component+'/'+this.component+'.scss', '//Fill this with your own file');
};
<|endoftext|>


<commit_before>var Parser = (function(Node, TextNode, Fragment, Component) {

	var interp_START = '~',
		interp_CLOSE = ']',

		// ~
		interp_code_START = 126,
		// [
		interp_code_OPEN = 91,
		// ]
		interp_code_CLOSE = 93,

		_serialize;

	function ensureTemplateFunction(template) {
		var index = -1;

/*
		 * - single char indexOf is much faster then '~[' search
		 * - function is divided in 2 parts: interpolation start lookup/ interpolation parse
		 * for better performance
		 */
		while ((index = template.indexOf(interp_START, index)) !== -1) {
			if (template.charCodeAt(index + 1) === interp_code_OPEN) {
				break;
			}
			index++;
		}

		if (index === -1) {
			return template;
		}

		var array = [],
			lastIndex = 0,
			i = 0,
			end;

		while (true) {
			end = template.indexOf(interp_CLOSE, index + 2);
			if (end === -1) {
				break;
			}

			array[i++] = lastIndex === index ? '' : template.substring(lastIndex, index);
			array[i++] = template.substring(index + 2, end);

			lastIndex = index = end + 1;

			while ((index = template.indexOf(interp_START, index)) !== -1) {
				if (template.charCodeAt(index + 1) === interp_code_OPEN) {
					break;
				}
				index++;
			}

			if (index === -1) {
				break;
			}

		}

		if (lastIndex < template.length) {
			array[i] = template.substring(lastIndex);
		}

		template = null;
		return function(type, model, cntx, element, controller, name) {
			if (type == null) {
				// http://jsperf.com/arguments-length-vs-null-check
				// this should be used to stringify parsed MaskDOM
				var string = '';
				for (var i = 0, x, length = array.length; i < length; i++) {
					x = array[i];
					if (i % 2 === 1) {
						string += '~[' + x + ']';
					} else {
						string += x;
					}
				}
				return string;
			}

			return util_interpolate(array, type, model, cntx, element, controller, name);
		};

	}

	function _throw(template, index, state, token) {
		var parsing = {
				2: 'tag',
				3: 'tag',
				5: 'attribute key',
				6: 'attribute value',
				8: 'literal'
			}[state],

			lines = template.substring(0, index).split('\n'),
			line = lines.length,
			row = lines[line - 1].length,

			message = ['Mask - Unexpected:', token, 'at(', line, ':', row, ') [ in', parsing, ']'];

		console.error(message.join(' '), {
			stopped: template.substring(index),
			template: template
		});
	}

	return {

		/** @out : nodes */
		parse: function(template) {

			//_serialize = T.serialize;

			var current = new Fragment(),
				fragment = current,
				state = 2,
				last = 3,
				index = 0,
				length = template.length,
				classNames, token, key, value, next, c, start;

			var go_tag = 2,
				state_tag = 3,
				state_attr = 5,
				go_attrVal = 6,
				go_attrHeadVal = 7,
				state_literal = 8,
				go_up = 9;

			outer: while (true) {

				if (index < length && (c = template.charCodeAt(index)) < 33) {
					index++;
					continue;
				}

				// inline comments
				if (c === 47 && template.charCodeAt(index + 1) === 47) {
					// /
					index++;
					while (c !== 10 && c !== 13 && index < length) {
						// goto newline
						c = template.charCodeAt(++index);
					}
					continue;
				}

				if (last === state_attr) {
					if (classNames != null) {
						current.attr['class'] = ensureTemplateFunction(classNames);
						classNames = null;
					}
					if (key != null) {
						current.attr[key] = key;
						key = null;
						token = null;
					}
				}

				if (token != null) {

					if (state === state_attr) {

						if (key == null) {
							key = token;
						} else {
							value = token;
						}

						if (key != null && value != null) {
							if (key !== 'class') {
								current.attr[key] = value;
							} else {
								classNames = classNames == null ? value : classNames + ' ' + value;
							}

							key = null;
							value = null;
						}

					} else if (last === state_tag) {

						next = CustomTags[token] != null ? new Component(token, current, CustomTags[token]) : new Node(token, current);

						if (current.nodes == null) {
							current.nodes = [next];
						} else {
							current.nodes.push(next);
						}

						current = next;

						state = state_attr;

					} else if (last === state_literal) {

						next = new TextNode(token, current);

						if (current.nodes == null) {
							current.nodes = [next];
						} else {
							current.nodes.push(next);
						}

						if (current.__single === true) {
							do {
								current = current.parent;
							} while (current != null && current.__single != null);
						}
						state = go_tag;

					}

					token = null;
				}

				if (index >= length) {
					if (state === state_attr) {
						if (classNames != null) {
							current.attr['class'] = ensureTemplateFunction(classNames);
						}
						if (key != null) {
							current.attr[key] = key;
						}
					}

					break;
				}

				if (state === go_up) {
					current = current.parent;
					while (current != null && current.__single != null) {
						current = current.parent;
					}
					state = go_tag;
				}

				// IF statements should be faster then switch due to strict comparison

				if (c === 123) {
					// {

					last = state;
					state = go_tag;
					index++;

					continue;
				}

				if (c === 62) {
					// >
					last = state;
					state = go_tag;
					index++;
					current.__single = true;
					continue;
				}

				if (c === 59) {
					// ;

					// skip ; , when node is not a single tag (else goto 125)
					if (current.nodes != null) {
						index++;
						continue;
					}
				}

				if (c === 59 || c === 125) {
					// ;}

					index++;
					last = state;
					state = go_up;
					continue;
				}

				if (c === 39 || c === 34) {
					// '"
					// Literal - could be as textnode or attribute value
					if (state === go_attrVal) {
						state = state_attr;
					} else {
						last = state = state_literal;
					}

					index++;

					var isEscaped = false,
						nindex, _char = c === 39 ? "'" : '"';

					start = index;

					while ((nindex = template.indexOf(_char, index)) > -1) {
						index = nindex;
						if (template.charCodeAt(nindex - 1) !== 92 /*'\\'*/ ) {
							break;
						}
						isEscaped = true;
						index++;
					}

					token = template.substring(start, index);
					if (isEscaped === true) {
						token = token.replace(regexpEscapedChar[_char], _char);
					}

					token = ensureTemplateFunction(token);

					index++;
					continue;
				}

				if (state === go_tag) {
					last = state_tag;
					state = state_tag;

					if (c === 46 /* . */ || c === 35 /* # */ ) {
						token = 'div';
						continue;
					}
				}

				if (state === state_attr) {
					if (c === 46) {
						// .
						index++;
						key = 'class';
						state = go_attrHeadVal;
					} else if (c === 35) {
						// #
						index++;
						key = 'id';
						state = go_attrHeadVal;
					} else if (c === 61) {
						// =;
						index++;
						state = go_attrVal;
						continue;
					} else {

						if (key != null) {
							token = key;
							continue;
						}
					}
				}

				if (state === go_attrVal || state === go_attrHeadVal) {
					last = state;
					state = state_attr;
				}

				/* TOKEN */

				var isInterpolated = null;

				start = index;
				while (index < length) {

					c = template.charCodeAt(index);

					if (c === interp_code_START && template.charCodeAt(index + 1) === interp_code_OPEN) {
						isInterpolated = true;
						++index;
						do {
							// goto end of template declaration
							c = template.charCodeAt(++index);
						}
						while (c !== interp_code_CLOSE && index < length);
					}

					// if DEBUG
					if (c === 0x0027 || c === 0x0022 || c === 0x002F || c === 0x003C || c === 0x002C) {
						// '"/<,
						_throw(template, index, state, String.fromCharCode(c));
						break;
					}
					// endif

					if (last !== go_attrVal && (c === 46 || c === 35 || c === 61)) {
						// .#=
						break;
					}

					if (c === 62 || c === 123 || c < 33 || c === 59) {
						// >{ ;
						break;
					}

					index++;
				}

				token = template.substring(start, index);

				// if DEBUG
				if (!token) {
					_throw(template, index, state, '*EMPTY*');
					break;
				}
				if (isInterpolated === true && state === state_tag){
					_throw(template, index, state, 'Tag Names cannt be interpolated (in dev)');
					break;
				}
				// endif

				if (isInterpolated === true && (state === state_attr && key === 'class') === false) {
					token = ensureTemplateFunction(token);
				}

			}

			////if (isNaN(c)) {
			////	_throw(template, index, state, 'Parse IndexOverflow');
			////
			////}

			// if DEBUG
			if (current.parent != null && current.parent !== fragment && current.parent.__single !== true && current.nodes != null) {
				console.warn('Mask - ', current.parent.tagName, JSON.stringify(current.parent.attr), 'was not proper closed.');
			}
			// endif

			return fragment.nodes.length === 1 ? fragment.nodes[0] : fragment;
		},
		cleanObject: function(obj) {
			if (obj instanceof Array) {
				for (var i = 0; i < obj.length; i++) {
					this.cleanObject(obj[i]);
				}
				return obj;
			}
			delete obj.parent;
			delete obj.__single;

			if (obj.nodes != null) {
				this.cleanObject(obj.nodes);
			}

			return obj;
		},
		setInterpolationQuotes: function(start, end) {
			if (!start || start.length !== 2) {
				console.error('Interpolation Start must contain 2 Characters');
				return;
			}
			if (!end || end.length !== 1) {
				console.error('Interpolation End must be of 1 Character');
				return;
			}

			interp_code_START = start.charCodeAt(0);
			interp_code_OPEN = start.charCodeAt(1);
			interp_code_CLOSE = end.charCodeAt(0);
			interp_CLOSE = end;
			interp_START = start.charAt(0);
		}
	};
}(Node, TextNode, Fragment, Component));
<commit_msg>[fix] '=' in quoteless attributes<commit_after>var Parser = (function(Node, TextNode, Fragment, Component) {

	var interp_START = '~',
		interp_CLOSE = ']',

		// ~
		interp_code_START = 126,
		// [
		interp_code_OPEN = 91,
		// ]
		interp_code_CLOSE = 93,

		_serialize;

	function ensureTemplateFunction(template) {
		var index = -1;

/*
		 * - single char indexOf is much faster then '~[' search
		 * - function is divided in 2 parts: interpolation start lookup/ interpolation parse
		 * for better performance
		 */
		while ((index = template.indexOf(interp_START, index)) !== -1) {
			if (template.charCodeAt(index + 1) === interp_code_OPEN) {
				break;
			}
			index++;
		}

		if (index === -1) {
			return template;
		}

		var array = [],
			lastIndex = 0,
			i = 0,
			end;

		while (true) {
			end = template.indexOf(interp_CLOSE, index + 2);
			if (end === -1) {
				break;
			}

			array[i++] = lastIndex === index ? '' : template.substring(lastIndex, index);
			array[i++] = template.substring(index + 2, end);

			lastIndex = index = end + 1;

			while ((index = template.indexOf(interp_START, index)) !== -1) {
				if (template.charCodeAt(index + 1) === interp_code_OPEN) {
					break;
				}
				index++;
			}

			if (index === -1) {
				break;
			}

		}

		if (lastIndex < template.length) {
			array[i] = template.substring(lastIndex);
		}

		template = null;
		return function(type, model, cntx, element, controller, name) {
			if (type == null) {
				// http://jsperf.com/arguments-length-vs-null-check
				// this should be used to stringify parsed MaskDOM
				var string = '';
				for (var i = 0, x, length = array.length; i < length; i++) {
					x = array[i];
					if (i % 2 === 1) {
						string += '~[' + x + ']';
					} else {
						string += x;
					}
				}
				return string;
			}

			return util_interpolate(array, type, model, cntx, element, controller, name);
		};

	}

	function _throw(template, index, state, token) {
		var parsing = {
				2: 'tag',
				3: 'tag',
				5: 'attribute key',
				6: 'attribute value',
				8: 'literal'
			}[state],

			lines = template.substring(0, index).split('\n'),
			line = lines.length,
			row = lines[line - 1].length,

			message = ['Mask - Unexpected:', token, 'at(', line, ':', row, ') [ in', parsing, ']'];

		console.error(message.join(' '), {
			stopped: template.substring(index),
			template: template
		});
	}

	return {

		/** @out : nodes */
		parse: function(template) {

			//_serialize = T.serialize;

			var current = new Fragment(),
				fragment = current,
				state = 2,
				last = 3,
				index = 0,
				length = template.length,
				classNames, token, key, value, next, c, start;

			var go_tag = 2,
				state_tag = 3,
				state_attr = 5,
				go_attrVal = 6,
				go_attrHeadVal = 7,
				state_literal = 8,
				go_up = 9;

			outer: while (true) {

				if (index < length && (c = template.charCodeAt(index)) < 33) {
					index++;
					continue;
				}

				// inline comments
				if (c === 47 && template.charCodeAt(index + 1) === 47) {
					// /
					index++;
					while (c !== 10 && c !== 13 && index < length) {
						// goto newline
						c = template.charCodeAt(++index);
					}
					continue;
				}

				if (last === state_attr) {
					if (classNames != null) {
						current.attr['class'] = ensureTemplateFunction(classNames);
						classNames = null;
					}
					if (key != null) {
						current.attr[key] = key;
						key = null;
						token = null;
					}
				}

				if (token != null) {

					if (state === state_attr) {

						if (key == null) {
							key = token;
						} else {
							value = token;
						}

						if (key != null && value != null) {
							if (key !== 'class') {
								current.attr[key] = value;
							} else {
								classNames = classNames == null ? value : classNames + ' ' + value;
							}

							key = null;
							value = null;
						}

					} else if (last === state_tag) {

						next = CustomTags[token] != null ? new Component(token, current, CustomTags[token]) : new Node(token, current);

						if (current.nodes == null) {
							current.nodes = [next];
						} else {
							current.nodes.push(next);
						}

						current = next;

						state = state_attr;

					} else if (last === state_literal) {

						next = new TextNode(token, current);

						if (current.nodes == null) {
							current.nodes = [next];
						} else {
							current.nodes.push(next);
						}

						if (current.__single === true) {
							do {
								current = current.parent;
							} while (current != null && current.__single != null);
						}
						state = go_tag;

					}

					token = null;
				}

				if (index >= length) {
					if (state === state_attr) {
						if (classNames != null) {
							current.attr['class'] = ensureTemplateFunction(classNames);
						}
						if (key != null) {
							current.attr[key] = key;
						}
					}

					break;
				}

				if (state === go_up) {
					current = current.parent;
					while (current != null && current.__single != null) {
						current = current.parent;
					}
					state = go_tag;
				}

				// IF statements should be faster then switch due to strict comparison

				if (c === 123) {
					// {

					last = state;
					state = go_tag;
					index++;

					continue;
				}

				if (c === 62) {
					// >
					last = state;
					state = go_tag;
					index++;
					current.__single = true;
					continue;
				}

				if (c === 59) {
					// ;

					// skip ; , when node is not a single tag (else goto 125)
					if (current.nodes != null) {
						index++;
						continue;
					}
				}

				if (c === 59 || c === 125) {
					// ;}

					index++;
					last = state;
					state = go_up;
					continue;
				}

				if (c === 39 || c === 34) {
					// '"
					// Literal - could be as textnode or attribute value
					if (state === go_attrVal) {
						state = state_attr;
					} else {
						last = state = state_literal;
					}

					index++;

					var isEscaped = false,
						nindex, _char = c === 39 ? "'" : '"';

					start = index;

					while ((nindex = template.indexOf(_char, index)) > -1) {
						index = nindex;
						if (template.charCodeAt(nindex - 1) !== 92 /*'\\'*/ ) {
							break;
						}
						isEscaped = true;
						index++;
					}

					token = template.substring(start, index);
					if (isEscaped === true) {
						token = token.replace(regexpEscapedChar[_char], _char);
					}

					token = ensureTemplateFunction(token);

					index++;
					continue;
				}

				if (state === go_tag) {
					last = state_tag;
					state = state_tag;

					if (c === 46 /* . */ || c === 35 /* # */ ) {
						token = 'div';
						continue;
					}
				}

				if (state === state_attr) {
					if (c === 46) {
						// .
						index++;
						key = 'class';
						state = go_attrHeadVal;
					} else if (c === 35) {
						// #
						index++;
						key = 'id';
						state = go_attrHeadVal;
					} else if (c === 61) {
						// =;
						index++;
						state = go_attrVal;
						continue;
					} else {

						if (key != null) {
							token = key;
							continue;
						}
					}
				}

				if (state === go_attrVal || state === go_attrHeadVal) {
					last = state;
					state = state_attr;
				}

				/* TOKEN */

				var isInterpolated = null;

				start = index;
				while (index < length) {

					c = template.charCodeAt(index);

					if (c === interp_code_START && template.charCodeAt(index + 1) === interp_code_OPEN) {
						isInterpolated = true;
						++index;
						do {
							// goto end of template declaration
							c = template.charCodeAt(++index);
						}
						while (c !== interp_code_CLOSE && index < length);
					}

					// if DEBUG
					if (c === 0x0027 || c === 0x0022 || c === 0x002F || c === 0x003C || c === 0x002C) {
						// '"/<,
						_throw(template, index, state, String.fromCharCode(c));
						break;
					}
					// endif

					if (last !== go_attrVal && (c === 46 || c === 35)) {
						// .#
						// break on .# only if parsing attribute head values
						break;
					}

					if (c===61 || c === 62 || c === 123 || c < 33 || c === 59) {
						// =>{ ;
						break;
					}

					index++;
				}

				token = template.substring(start, index);

				// if DEBUG
				if (!token) {
					_throw(template, index, state, '*EMPTY*');
					break;
				}
				if (isInterpolated === true && state === state_tag){
					_throw(template, index, state, 'Tag Names cannt be interpolated (in dev)');
					break;
				}
				// endif

				if (isInterpolated === true && (state === state_attr && key === 'class') === false) {
					token = ensureTemplateFunction(token);
				}

			}

			////if (isNaN(c)) {
			////	_throw(template, index, state, 'Parse IndexOverflow');
			////
			////}

			// if DEBUG
			if (current.parent != null && current.parent !== fragment && current.parent.__single !== true && current.nodes != null) {
				console.warn('Mask - ', current.parent.tagName, JSON.stringify(current.parent.attr), 'was not proper closed.');
			}
			// endif

			return fragment.nodes.length === 1 ? fragment.nodes[0] : fragment;
		},
		cleanObject: function(obj) {
			if (obj instanceof Array) {
				for (var i = 0; i < obj.length; i++) {
					this.cleanObject(obj[i]);
				}
				return obj;
			}
			delete obj.parent;
			delete obj.__single;

			if (obj.nodes != null) {
				this.cleanObject(obj.nodes);
			}

			return obj;
		},
		setInterpolationQuotes: function(start, end) {
			if (!start || start.length !== 2) {
				console.error('Interpolation Start must contain 2 Characters');
				return;
			}
			if (!end || end.length !== 1) {
				console.error('Interpolation End must be of 1 Character');
				return;
			}

			interp_code_START = start.charCodeAt(0);
			interp_code_OPEN = start.charCodeAt(1);
			interp_code_CLOSE = end.charCodeAt(0);
			interp_CLOSE = end;
			interp_START = start.charAt(0);
		}
	};
}(Node, TextNode, Fragment, Component));
<|endoftext|>


<commit_before>const express = require('express');
const bodyParser = require('body-parser');
const bot = require('./bot');
const functions = require('./functions');
let app = express();

app.use(bodyParser.json())

app.get('/push/user/:userId/updated', function(req, res) {
	console.log(req.params.userId);
   //functions.refreshUser(bot.users.get(req.params.userId));
 res.send('updated');
});

var listener = app.listen( 8080, function() {
    console.log('API now running on port: ' + listener.address().port);
});

module.exports = app;
<commit_msg>trying a fix<commit_after>const express = require('express');
const bodyParser = require('body-parser');
const bot = require('./bot');
const functions = require('./functions');
let app = express();

app.use(bodyParser.json())

app.get('/push/user/:userId/updated', function(req, res) {
	console.log(req.params.userId);
   //functions.refreshUser(bot.users.get(req.params.userId));
 res.send('updated');
 res.end();
});

var listener = app.listen( 8080, function() {
    console.log('API now running on port: ' + listener.address().port);
});

module.exports = app;
<|endoftext|>