<commit_before>var MVVMScene = (function (iterateEntries) {
    "use strict";

    function MVVMScene(model, view, viewModel) {
        this.services = model;

        this.stage = model.stage;
        this.sceneStorage = model.sceneStorage;
        this.buttons = model.buttons;
        this.messages = model.messages;
        this.timer = model.timer;
        this.device = model.device;
        this.loop = model.loop;
        this.events = model.events;
        this.tap = model.tap;
        this.sounds = model.sounds;

        this.view = view;
        this.viewModel = viewModel;

        this.dict = {};
    }

    MVVMScene.prototype.show = function (next) {
        var drawables = [];
        var buttons = [];
        var taps = [];

        var sceneRect;

        function xFn(x) {
            return Width.get(sceneRect.width, x);
        }

        function yFn(y) {
            return Height.get(sceneRect.height, y);
        }

        iterateEntries(this.view, function (layer, layerKey) {
            if (layerKey == 'screen') {
                sceneRect = layer;
                return;
            }

            var zIndex = layerKey[layerKey.length - 1];
            layer.forEach(function (elem) {
                var drawable;
                if (elem.type == 'image') {
                    var imgName = elem.filename.substring(0, elem.filename.lastIndexOf('.'));
                    drawable = this.stage.drawFresh(xFn(elem.x), yFn(elem.y), imgName, zIndex, undefined, elem.alpha,
                        elem.rotation, elem.scale);
                    drawables.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }

                } else if (elem.type == 'text') {
                    drawable = this.stage.drawText(xFn(elem.x), yFn(elem.y), elem.msg, yFn(elem.size), elem.font,
                        elem.color, zIndex, undefined, elem.rotation, elem.alpha, undefined, undefined, elem.scale);
                    drawables.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }

                } else if (elem.type == 'rectangle') {
                    drawable = this.stage.drawRectangle(xFn(elem.x), yFn(elem.y), xFn(elem.width), yFn(elem.height),
                        elem.color, elem.filled, undefined, zIndex, elem.alpha, elem.rotation, elem.scale, undefined);
                    drawables.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }

                } else if (elem.type == 'button') {
                    drawable = this.buttons.createPrimaryButton(xFn(elem.text.x), yFn(elem.text.y), elem.text.msg,
                        undefined, zIndex, undefined, xFn(elem.background.width), yFn(elem.background.height));
                    // todo: rethink this shit with buttons

                    buttons.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }
                }

            }, this);
        }, this);
    };

    return MVVMScene;
})(iterateEntries);<commit_msg>positioning with anchors and relative position to its size #core (cherry picked from commit c234709)<commit_after>var MVVMScene = (function (iterateEntries, Width, Height) {
    "use strict";

    function MVVMScene(model, view, viewModel) {
        this.services = model;

        this.stage = model.stage;
        this.sceneStorage = model.sceneStorage;
        this.buttons = model.buttons;
        this.messages = model.messages;
        this.timer = model.timer;
        this.device = model.device;
        this.loop = model.loop;
        this.events = model.events;
        this.tap = model.tap;
        this.sounds = model.sounds;

        this.view = view;
        this.viewModel = viewModel;

        this.dict = {};
    }

    MVVMScene.prototype.show = function (next) {
        var drawables = [];
        var buttons = [];
        var taps = [];

        var sceneRect;

        function xFn(x) {
            return Width.get(sceneRect.width, x);
        }

        function yFn(y) {
            return Height.get(sceneRect.height, y);
        }

        iterateEntries(this.view, function (layer, layerKey) {
            if (layerKey == 'screen') {
                sceneRect = layer;
                return;
            }

            var zIndex = layerKey[layerKey.length - 1];
            layer.forEach(function (elem) {

                var x = xFn(elem.x);
                var y = yFn(elem.y);

                if (elem.tags && elem.tags.some(function (tag) {
                        return tag.position == 'relativeToSize';
                    }) && elem.tags.some(function (tag) {
                        return tag.anchor == 'widthHalf';
                    })) {
                    // very specific use case

                    x = function (width, height) {
                        return Math.floor(width / 2 +
                            ((elem.x - sceneRect.width / 2) / elem.height) * yFn(elem.height)(height));
                    };

                }

                var drawable;
                if (elem.type == 'image') {
                    var imgName = elem.filename.substring(0, elem.filename.lastIndexOf('.'));
                    drawable = this.stage.drawFresh(x, y, imgName, zIndex, undefined, elem.alpha, elem.rotation,
                        elem.scale);
                    drawables.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }

                } else if (elem.type == 'text') {
                    drawable = this.stage.drawText(x, y, elem.msg, yFn(elem.size), elem.font, elem.color, zIndex,
                        undefined, elem.rotation, elem.alpha, undefined, undefined, elem.scale);
                    drawables.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }

                } else if (elem.type == 'rectangle') {
                    drawable = this.stage.drawRectangle(x, y, xFn(elem.width), yFn(elem.height), elem.color,
                        elem.filled, undefined, zIndex, elem.alpha, elem.rotation, elem.scale, undefined);
                    drawables.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }

                } else if (elem.type == 'button') {
                    x = xFn(elem.text.x);
                    y = yFn(elem.text.y);

                    drawable = this.buttons.createPrimaryButton(x, y, elem.text.msg, undefined, zIndex, undefined,
                        xFn(elem.background.width), yFn(elem.background.height));
                    // todo: rethink this shit with buttons

                    buttons.push(drawable);
                    if (elem.viewId) {
                        this.dict[elem.viewId] = drawable;
                    }
                }

            }, this);
        }, this);
    };

    return MVVMScene;
})(iterateEntries, Width, Height);<|endoftext|>


<commit_before>var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('../../../../../../../../javascripts/cartodb3/data/config-model');
var AnalysisDefinitionNodeSourceModel = require('../../../../../../../../javascripts/cartodb3/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('../../../../../../../../javascripts/cartodb3/data/analysis-definition-node-model');
var LayerDefinitionModel = require('../../../../../../../../javascripts/cartodb3/data/layer-definition-model');
var AreaOfInfluenceFormModel = require('../../../../../../../../javascripts/cartodb3/editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-form-model');

describe('editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-form-model', function () {
  beforeEach(function () {
    var configModel = new ConfigModel({base_url: '/u/pepe'});
    this.analysisDefinitionNodesCollection = new Backbone.Collection();

    this.a0 = new AnalysisDefinitionNodeSourceModel({
      id: 'a0',
      type: 'buffer',
      query: 'SELECT * FROM a_table'
    }, {
      configModel: configModel,
      collection: this.analysisDefinitionNodesCollection
    });
    spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);

    this.a1 = new AnalysisDefinitionNodeModel({
      id: 'a1',
      type: 'buffer',
      radius: undefined,
      source: 'a0'
    }, {
      configModel: configModel,
      collection: this.analysisDefinitionNodesCollection
    });

    this.layerDefinitionModel = new LayerDefinitionModel(null, {
      configModel: configModel
    });
    spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
      switch (id) {
        case 'a0': return this.a0;
        case 'a1': return this.a1;
        default: return null;
      }
    }.bind(this));
    this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
      layerDefinitionModel: this.layerDefinitionModel,
      analysisSourceOptionsModel: {},
      parse: true
    });
  });

  it('should add analysis schema', function () {
    expect(this.model.schema).toBeDefined();
  });

  it('should have attributes set with defaults', function () {
    expect(this.model.attributes).toEqual({
      id: 'a1',
      source: 'a0',
      type: 'buffer',
      distance: 'meters',
      radius: 100
    });
  });

  it('should not have disabled types', function () {
    var typeOptions = this.model.schema.type.options.map(function (d) {
      return _.pick(d, 'val', 'disabled');
    });
    expect(typeOptions).toEqual([
      {val: 'buffer', disabled: false},
      {val: 'trade-area', disabled: false}
    ]);
    expect(this.a0.isValidAsInputForType).toHaveBeenCalledWith('trade-area');
  });

  describe('when model is created with custom distance and radius', function () {
    beforeEach(function () {
      this.a1.set({
        radius: 2000,
        distance: 'kilometers'
      });

      this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
        layerDefinitionModel: this.layerDefinitionModel,
        analysisSourceOptionsModel: {},
        parse: true
      });
    });

    it('should convert the radius to the distance scale', function () {
      expect(this.model.get('radius')).toEqual(2);
      expect(this.model.get('distance')).toEqual('kilometers');
    });
  });

  describe('when source is not valid for other type', function () {
    beforeEach(function () {
      this.a0.isValidAsInputForType.and.returnValue(false);
      this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
        layerDefinitionModel: this.layerDefinitionModel,
        analysisSourceOptionsModel: {},
        parse: true
      });
    });

    it('should disable type since source is not valid input for the other type', function () {
      var typeOptions = this.model.schema.type.options.map(function (d) {
        return _.pick(d, 'val', 'disabled');
      });
      expect(typeOptions).toEqual([
        {val: 'buffer', disabled: false},
        {val: 'trade-area', disabled: true}
      ]);
      expect(this.a0.isValidAsInputForType).toHaveBeenCalledWith('trade-area');
    });
  });

  describe('when type is changed', function () {
    describe('for a new model', function () {
      beforeEach(function () {
        this.prevSchema = this.model.schema;
        this.model.set('type', 'trade-area');
      });

      it('should update the schema', function () {
        expect(this.model.schema).not.toBe(this.prevSchema);
      });

      it('should replace the attributes but maintain persisted state', function () {
        expect(this.model.attributes).toEqual({
          id: 'a1',
          source: 'a0',
          type: 'trade-area',
          kind: 'walk',
          isolines: 1,
          time: 100,
          dissolved: false
        });
      });
    });

    describe('for an persisted model', function () {
      beforeEach(function () {
        this.prevSchema = this.model.schema;
        this.model.set('persisted', true);
        this.model.set('type', 'trade-area');
      });

      it('should update the schema', function () {
        expect(this.model.schema).not.toBe(this.prevSchema);
      });

      it('should replace the attributes but maintain persisted state', function () {
        expect(this.model.attributes).toEqual({
          id: 'a1',
          source: 'a0',
          type: 'trade-area',
          kind: 'walk',
          isolines: 1,
          time: 100,
          dissolved: false,
          persisted: true
        });
      });
    });
  });

  describe('.getTemplate', function () {
    it('should return the template', function () {
      expect(this.model.getTemplate()).toEqual(jasmine.any(Function));
    });
  });

  describe('.getTemplateData', function () {
    it('should return the default data for given type', function () {
      var bufferTemplateData = this.model.getTemplateData();
      expect(bufferTemplateData).toEqual({
        parametersDataFields: jasmine.any(String)
      });

      this.model.set('type', 'trade-area');

      expect(this.model.getTemplateData()).toEqual({
        parametersDataFields: jasmine.any(String)
      });
      expect(this.model.getTemplateData().parametersDataFields).not.toEqual(bufferTemplateData.parametersDataFields);
    });
  });

  describe('.save', function () {
    it('should replace the attributes on the given node model but maintain values not visible/editable in the form', function () {
      this.model.save();
      expect(this.a1.attributes).toEqual({
        id: 'a1',
        type: 'buffer',
        source: 'a0',
        radius: 100,
        distance: 'meters'
      });
    });

    it('should convert string values to integers before setting the attrs on model', function () {
      this.model.set({
        type: 'trade-area',
        dissolved: 'true',
        isolines: '3',
        time: '60'
      });
      this.model.save();
      expect(this.a1.attributes).toEqual({
        dissolved: true,
        isolines: 3,
        time: 60,
        type: 'trade-area',
        id: 'a1',
        source: 'a0',
        kind: 'walk'
      });
    });

    it('should convert radius to meters according to the selected distance', function () {
      this.model.set({
        distance: 'kilometers',
        radius: 3
      });

      this.model.save();
      expect(this.a1.get('radius')).toEqual(3000);
      expect(this.a1.get('distance')).toEqual('kilometers');
    });
  });
});
<commit_msg>More spec fixes<commit_after>var _ = require('underscore');
var Backbone = require('backbone');
var ConfigModel = require('../../../../../../../../javascripts/cartodb3/data/config-model');
var AnalysisDefinitionNodeSourceModel = require('../../../../../../../../javascripts/cartodb3/data/analysis-definition-node-source-model');
var AnalysisDefinitionNodeModel = require('../../../../../../../../javascripts/cartodb3/data/analysis-definition-node-model');
var LayerDefinitionModel = require('../../../../../../../../javascripts/cartodb3/data/layer-definition-model');
var AreaOfInfluenceFormModel = require('../../../../../../../../javascripts/cartodb3/editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-form-model');

describe('editor/layers/layer-content-views/analyses/analysis-form-models/area-of-influence-form-model', function () {
  beforeEach(function () {
    var configModel = new ConfigModel({base_url: '/u/pepe'});
    this.analysisDefinitionNodesCollection = new Backbone.Collection();

    this.a0 = new AnalysisDefinitionNodeSourceModel({
      id: 'a0',
      type: 'buffer',
      query: 'SELECT * FROM a_table'
    }, {
      configModel: configModel,
      collection: this.analysisDefinitionNodesCollection
    });
    spyOn(this.a0, 'isValidAsInputForType').and.returnValue(true);

    this.a1 = new AnalysisDefinitionNodeModel({
      id: 'a1',
      type: 'buffer',
      radius: undefined,
      source: 'a0'
    }, {
      configModel: configModel,
      collection: this.analysisDefinitionNodesCollection
    });

    this.layerDefinitionModel = new LayerDefinitionModel(null, {
      configModel: configModel
    });
    spyOn(this.layerDefinitionModel, 'findAnalysisDefinitionNodeModel').and.callFake(function (id) {
      switch (id) {
        case 'a0': return this.a0;
        case 'a1': return this.a1;
        default: return null;
      }
    }.bind(this));
    this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
      configModel: {},
      layerDefinitionModel: this.layerDefinitionModel,
      analysisSourceOptionsModel: {},
      parse: true
    });
  });

  it('should add analysis schema', function () {
    expect(this.model.schema).toBeDefined();
  });

  it('should have attributes set with defaults', function () {
    expect(this.model.attributes).toEqual({
      id: 'a1',
      source: 'a0',
      type: 'buffer',
      distance: 'meters',
      radius: 100
    });
  });

  it('should not have disabled types', function () {
    var typeOptions = this.model.schema.type.options.map(function (d) {
      return _.pick(d, 'val', 'disabled');
    });
    expect(typeOptions).toEqual([
      {val: 'buffer', disabled: false},
      {val: 'trade-area', disabled: false}
    ]);
    expect(this.a0.isValidAsInputForType).toHaveBeenCalledWith('trade-area');
  });

  describe('when model is created with custom distance and radius', function () {
    beforeEach(function () {
      this.a1.set({
        radius: 2000,
        distance: 'kilometers'
      });

      this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
        configModel: {},
        layerDefinitionModel: this.layerDefinitionModel,
        analysisSourceOptionsModel: {},
        parse: true
      });
    });

    it('should convert the radius to the distance scale', function () {
      expect(this.model.get('radius')).toEqual(2);
      expect(this.model.get('distance')).toEqual('kilometers');
    });
  });

  describe('when source is not valid for other type', function () {
    beforeEach(function () {
      this.a0.isValidAsInputForType.and.returnValue(false);
      this.model = new AreaOfInfluenceFormModel(this.a1.attributes, {
        configModel: {},
        layerDefinitionModel: this.layerDefinitionModel,
        analysisSourceOptionsModel: {},
        parse: true
      });
    });

    it('should disable type since source is not valid input for the other type', function () {
      var typeOptions = this.model.schema.type.options.map(function (d) {
        return _.pick(d, 'val', 'disabled');
      });
      expect(typeOptions).toEqual([
        {val: 'buffer', disabled: false},
        {val: 'trade-area', disabled: true}
      ]);
      expect(this.a0.isValidAsInputForType).toHaveBeenCalledWith('trade-area');
    });
  });

  describe('when type is changed', function () {
    describe('for a new model', function () {
      beforeEach(function () {
        this.prevSchema = this.model.schema;
        this.model.set('type', 'trade-area');
      });

      it('should update the schema', function () {
        expect(this.model.schema).not.toBe(this.prevSchema);
      });

      it('should replace the attributes but maintain persisted state', function () {
        expect(this.model.attributes).toEqual({
          id: 'a1',
          source: 'a0',
          type: 'trade-area',
          kind: 'walk',
          isolines: 1,
          time: 100,
          dissolved: false
        });
      });
    });

    describe('for an persisted model', function () {
      beforeEach(function () {
        this.prevSchema = this.model.schema;
        this.model.set('persisted', true);
        this.model.set('type', 'trade-area');
      });

      it('should update the schema', function () {
        expect(this.model.schema).not.toBe(this.prevSchema);
      });

      it('should replace the attributes but maintain persisted state', function () {
        expect(this.model.attributes).toEqual({
          id: 'a1',
          source: 'a0',
          type: 'trade-area',
          kind: 'walk',
          isolines: 1,
          time: 100,
          dissolved: false,
          persisted: true
        });
      });
    });
  });

  describe('.getTemplate', function () {
    it('should return the template', function () {
      expect(this.model.getTemplate()).toEqual(jasmine.any(Function));
    });
  });

  describe('.getTemplateData', function () {
    it('should return the default data for given type', function () {
      var bufferTemplateData = this.model.getTemplateData();
      expect(bufferTemplateData).toEqual({
        parametersDataFields: jasmine.any(String)
      });

      this.model.set('type', 'trade-area');

      expect(this.model.getTemplateData()).toEqual({
        parametersDataFields: jasmine.any(String)
      });
      expect(this.model.getTemplateData().parametersDataFields).not.toEqual(bufferTemplateData.parametersDataFields);
    });
  });

  describe('.save', function () {
    it('should replace the attributes on the given node model but maintain values not visible/editable in the form', function () {
      this.model.save();
      expect(this.a1.attributes).toEqual({
        id: 'a1',
        type: 'buffer',
        source: 'a0',
        radius: 100,
        distance: 'meters'
      });
    });

    it('should convert string values to integers before setting the attrs on model', function () {
      this.model.set({
        type: 'trade-area',
        dissolved: 'true',
        isolines: '3',
        time: '60'
      });
      this.model.save();
      expect(this.a1.attributes).toEqual({
        dissolved: true,
        isolines: 3,
        time: 60,
        type: 'trade-area',
        id: 'a1',
        source: 'a0',
        kind: 'walk'
      });
    });

    it('should convert radius to meters according to the selected distance', function () {
      this.model.set({
        distance: 'kilometers',
        radius: 3
      });

      this.model.save();
      expect(this.a1.get('radius')).toEqual(3000);
      expect(this.a1.get('distance')).toEqual('kilometers');
    });
  });
});
<|endoftext|>


<commit_before>var Email = function() {

}
module.exports = new Email();

// From node-validator (https://github.com/chriso/node-validator) until I can find something better
Email.prototype._regex = /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/;

<commit_msg>license and attribution for email regex<commit_after>/*
* Copyright (c) 2012 Walmart. All rights reserved. Copyrights licensed under the New BSD License.
* See LICENSE file included with this code project for license terms.
*
* Email regular expression adapted from node-validator, Copyright (c) 2010-2012 Chris O'Hara <cohara87@gmail.com>
* node-validator is released under the MIT License and is available at https://github.com/chriso/node-validator
*/
var Email = function() {

}
module.exports = new Email();

Email.prototype._regex = /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/;

<|endoftext|>


<commit_before>/*
 SelectFilter2 - Turns a multiple-select box into a filter interface.

 Different than SelectFilter because this is coupled to the admin framework.

 Requires core.js and OrderedSelectBox.js
 */

function findForm(node) {
    // returns the node of the form containing the given node
    if (node.tagName.toLowerCase()  != 'form') {
        return findForm(node.parentNode);
    }
    return node;
}

var OrderedSelectFilter = {
    init: function(field_id, field_name, is_stacked) {
        if (field_id.match(/__prefix__/)){
            // Don't intialize on empty forms.
            return;
        }

        var from_box = document.getElementById(field_id);

        if(!from_box) {
          return;
        }

        from_box.id += '_from'; // change its ID
        from_box.className = 'filtered';

        var ps = from_box.parentNode.getElementsByTagName('p');
        for (var i=0; i<ps.length; i++) {
            if (ps[i].className.indexOf("info") != -1) {
                // Remove
, because it just gets in the way.
                from_box.parentNode.removeChild(ps[i]);
            } else if (ps[i].className.indexOf("help") != -1) {
                // Move help text up to the top so it isn't below the select
                // boxes or wrapped off on the side to the right of the add
                // button:
                from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild);
            }
        }

        //
 or

        var selector_div = quickElement('div', from_box.parentNode);
        selector_div.className = is_stacked ? 'selector stacked' : 'selector';

        //

        var selector_available = quickElement('div', selector_div, '');
        selector_available.className = 'selector-available';
        var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name]));
        quickElement(
                'span', title_available, '',
                'class', 'help help-tooltip help-icon',
                'title', interpolate(
                    gettext(
                        'This is the list of available %s. You may choose some by ' +
                        'selecting them in the box below and then clicking the ' +
                        '"Choose" arrow between the two boxes.'
                    ),
                    [field_name]
                )
            );

        var filter_p = quickElement('p', selector_available, '');
        filter_p.className = 'selector-filter';

        var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + '_input');

        quickElement(
                'span', search_filter_label, '',
                'class', 'help-tooltip search-label-icon',
                'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name])
            );

        filter_p.appendChild(document.createTextNode(' '));

        var filter_input = quickElement('input', filter_p, '', 'type', 'text');
        filter_input.id = field_id + '_input';
        selector_available.appendChild(from_box);
        var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', gettext('Choose all'), 'href', '#', 'id', field_id + '_add_all_link');
        choose_all.className = 'selector-chooseall';

        //

        var selector_chooser = quickElement('ul', selector_div);
        selector_chooser.className = 'selector-chooser';
        var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', '#', 'id', field_id + '_add_link');
        add_link.className = 'selector-add';
        var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', '#', 'id', field_id + '_remove_link');
        remove_link.className = 'selector-remove';

        //

        var selector_chosen = quickElement('div', selector_div, '');
        selector_chosen.className = 'selector-chosen';
        var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
        quickElement(
                'span', title_chosen, '',
                'class', 'help help-tooltip help-icon',
                'title', interpolate(
                    gettext(
                        'This is the list of chosen %s. You may remove some by ' +
                        'selecting them in the box below and then clicking the ' +
                        '"Remove" arrow between the two boxes.'
                    ),
                    [field_name]
                )
            );

        var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
        to_box.className = 'filtered';
        var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', gettext('Clear all'), 'href', '#', 'id', field_id + '_remove_all_link');
        clear_all.className = 'selector-clearall';

        //

        var selector_orderer = quickElement('ul', selector_div, '');
        selector_orderer.className = 'selector-chooser';
        var up_link = quickElement('a', quickElement('li', selector_orderer, ''), gettext('Up'), 'href', 'javascript: (function(){ OrderedSelectBox.orderUp("' + field_id + '_to");})()');
        up_link.className = 'selector-up';
        var down_link = quickElement('a', quickElement('li', selector_orderer, ''), gettext('Down'), 'href', 'javascript: (function(){ OrderedSelectBox.orderDown("' + field_id + '_to");})()');
        down_link.className = 'selector-down';

        from_box.setAttribute('name', from_box.getAttribute('name') + '_old');

        // Set up the JavaScript event handlers for the select box filter interface
        var move_selection = function(e, elem, move_func, from, to) {
            if (elem.className.indexOf('active') !== -1) {
                move_func(from, to);
                OrderedSelectFilter.refresh_icons(field_id);
            }
            e.preventDefault();
        };
        choose_all.addEventListener('click', function(e) {
                move_selection(e, this, OrderedSelectBox.move_all, field_id + '_from', field_id + '_to');
            });
        add_link.addEventListener('click', function(e) {
            move_selection(e, this, OrderedSelectBox.move, field_id + '_from', field_id + '_to');
        });
        remove_link.addEventListener('click', function(e) {
            move_selection(e, this, OrderedSelectBox.move, field_id + '_to', field_id + '_from');
        });
        clear_all.addEventListener('click', function(e) {
            move_selection(e, this, OrderedSelectBox.move_all, field_id + '_to', field_id + '_from');
        });
        filter_input.addEventListener('keypress', function(e) {
            OrderedSelectFilter.filter_key_press(e, field_id);
        });
        filter_input.addEventListener('keyup', function(e) {
            OrderedSelectFilter.filter_key_up(e, field_id);
        });
        filter_input.addEventListener('keydown', function(e) {
            OrderedSelectFilter.filter_key_down(e, field_id);
        });
        selector_div.addEventListener('change', function(e) {
            if (e.target.tagName === 'SELECT') {
                OrderedSelectFilter.refresh_icons(field_id);
            }
        });
        selector_div.addEventListener('dblclick', function(e) {
            if (e.target.tagName === 'OPTION') {
                if (e.target.closest('select').id === field_id + '_to') {
                    OrderedSelectBox.move(field_id + '_to', field_id + '_from');
                } else {
                    OrderedSelectBox.move(field_id + '_from', field_id + '_to');
                }
                OrderedSelectFilter.refresh_icons(field_id);
            }
        });
        findForm(from_box).addEventListener('submit', function() {
            OrderedSelectBox.select_all(field_id + '_to');
        });
        OrderedSelectBox.init(field_id + '_from');
        OrderedSelectBox.init(field_id + '_to');
        // Move selected from_box options to to_box
        OrderedSelectBox.move(field_id + '_from', field_id + '_to');

        if (!is_stacked) {
            // In horizontal mode, give the same height to the two boxes.
            var j_from_box = $(from_box);
            var j_to_box = $(to_box);
            var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); };
            if (j_from_box.outerHeight() > 0) {
                resize_filters(); // This fieldset is already open. Resize now.
            } else {
                // This fieldset is probably collapsed. Wait for its 'show' event.
                j_to_box.closest('fieldset').one('show.fieldset', resize_filters);
            }
        }
        // Initial icon refresh
        OrderedSelectFilter.refresh_icons(field_id);
    },
    refresh_icons: function(field_id) {
        var from = $('#' + field_id + '_from');
        var to = $('#' + field_id + '_to');
        var is_from_selected  = from.find('option:selected').length > 0;
        var is_to_selected    =   to.find('option:selected').length > 0;
        var is_from_non_empty = from.find('option').length > 0;
        var is_to_non_empty   =   to.find('option').length > 0;
        // Active if at least one item is selected
        $('#' + field_id + '_add_link').toggleClass('active', is_from_selected);
        $('#' + field_id + '_remove_link').toggleClass('active', is_to_selected);
        // Active if the corresponding box isn't empty
        $('#' + field_id + '_add_all_link').toggleClass('active', is_from_non_empty);
        $('#' + field_id + '_remove_all_link').toggleClass('active', is_to_non_empty);
    },
    filter_key_up: function(event, field_id) {
        from = document.getElementById(field_id + '_from');
        // don't submit form if user pressed Enter
        if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
            from.selectedIndex = 0;
            OrderedSelectBox.move(field_id + '_from', field_id + '_to');
            from.selectedIndex = 0;
            return false;
        }
        var temp = from.selectedIndex;
        OrderedSelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
        from.selectedIndex = temp;
        return true;
    },
    filter_key_down: function(event, field_id) {
        from = document.getElementById(field_id + '_from');
        // right arrow -- move across
        if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
            var old_index = from.selectedIndex;
            OrderedSelectBox.move(field_id + '_from', field_id + '_to');
            from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
            return false;
        }
        // down arrow -- wrap around
        if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
            from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
        }
        // up arrow -- wrap around
        if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
            from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
        }
        return true;
    }
};
<commit_msg>Use consistent var names<commit_after>/*
 SelectFilter2 - Turns a multiple-select box into a filter interface.

 Different than SelectFilter because this is coupled to the admin framework.

 Requires core.js and OrderedSelectBox.js
 */

function findForm(node) {
    // returns the node of the form containing the given node
    if (node.tagName.toLowerCase()  != 'form') {
        return findForm(node.parentNode);
    }
    return node;
}

var OrderedSelectFilter = {
    init: function(field_id, field_name, is_stacked) {
        if (field_id.match(/__prefix__/)){
            // Don't intialize on empty forms.
            return;
        }

        var from_box = document.getElementById(field_id);

        if(!from_box) {
          return;
        }

        from_box.id += '_from'; // change its ID
        from_box.className = 'filtered';

        var ps = from_box.parentNode.getElementsByTagName('p');
        for (var i=0; i<ps.length; i++) {
            if (ps[i].className.indexOf("info") != -1) {
                // Remove
, because it just gets in the way.
                from_box.parentNode.removeChild(ps[i]);
            } else if (ps[i].className.indexOf("help") != -1) {
                // Move help text up to the top so it isn't below the select
                // boxes or wrapped off on the side to the right of the add
                // button:
                from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild);
            }
        }

        //
 or

        var selector_div = quickElement('div', from_box.parentNode);
        selector_div.className = is_stacked ? 'selector stacked' : 'selector';

        //

        var selector_available = quickElement('div', selector_div, '');
        selector_available.className = 'selector-available';
        var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name]));
        quickElement(
                'span', title_available, '',
                'class', 'help help-tooltip help-icon',
                'title', interpolate(
                    gettext(
                        'This is the list of available %s. You may choose some by ' +
                        'selecting them in the box below and then clicking the ' +
                        '"Choose" arrow between the two boxes.'
                    ),
                    [field_name]
                )
            );

        var filter_p = quickElement('p', selector_available, '');
        filter_p.className = 'selector-filter';

        var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + '_input');

        quickElement(
                'span', search_filter_label, '',
                'class', 'help-tooltip search-label-icon',
                'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name])
            );

        filter_p.appendChild(document.createTextNode(' '));

        var filter_input = quickElement('input', filter_p, '', 'type', 'text');
        filter_input.id = field_id + '_input';
        selector_available.appendChild(from_box);
        var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', gettext('Choose all'), 'href', '#', 'id', field_id + '_add_all_link');
        choose_all.className = 'selector-chooseall';

        //

        var selector_chooser = quickElement('ul', selector_div);
        selector_chooser.className = 'selector-chooser';
        var add_link = quickElement('a', quickElement('li', selector_chooser), gettext('Choose'), 'title', gettext('Choose'), 'href', '#', 'id', field_id + '_add_link');
        add_link.className = 'selector-add';
        var remove_link = quickElement('a', quickElement('li', selector_chooser), gettext('Remove'), 'title', gettext('Remove'), 'href', '#', 'id', field_id + '_remove_link');
        remove_link.className = 'selector-remove';

        //

        var selector_chosen = quickElement('div', selector_div, '');
        selector_chosen.className = 'selector-chosen';
        var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
        quickElement(
                'span', title_chosen, '',
                'class', 'help help-tooltip help-icon',
                'title', interpolate(
                    gettext(
                        'This is the list of chosen %s. You may remove some by ' +
                        'selecting them in the box below and then clicking the ' +
                        '"Remove" arrow between the two boxes.'
                    ),
                    [field_name]
                )
            );

        var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
        to_box.className = 'filtered';
        var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', gettext('Clear all'), 'href', '#', 'id', field_id + '_remove_all_link');
        clear_all.className = 'selector-clearall';

        //

        var selector_chooser = quickElement('ul', selector_div);
        selector_chooser.className = 'selector-chooser';
        var up_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Up'), 'href', 'javascript: (function(){ OrderedSelectBox.orderUp("' + field_id + '_to");})()');
        up_link.className = 'selector-up';
        var down_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Down'), 'href', 'javascript: (function(){ OrderedSelectBox.orderDown("' + field_id + '_to");})()');
        down_link.className = 'selector-down';

        from_box.setAttribute('name', from_box.getAttribute('name') + '_old');

        // Set up the JavaScript event handlers for the select box filter interface
        var move_selection = function(e, elem, move_func, from, to) {
            if (elem.className.indexOf('active') !== -1) {
                move_func(from, to);
                OrderedSelectFilter.refresh_icons(field_id);
            }
            e.preventDefault();
        };
        choose_all.addEventListener('click', function(e) {
                move_selection(e, this, OrderedSelectBox.move_all, field_id + '_from', field_id + '_to');
            });
        add_link.addEventListener('click', function(e) {
            move_selection(e, this, OrderedSelectBox.move, field_id + '_from', field_id + '_to');
        });
        remove_link.addEventListener('click', function(e) {
            move_selection(e, this, OrderedSelectBox.move, field_id + '_to', field_id + '_from');
        });
        clear_all.addEventListener('click', function(e) {
            move_selection(e, this, OrderedSelectBox.move_all, field_id + '_to', field_id + '_from');
        });
        filter_input.addEventListener('keypress', function(e) {
            OrderedSelectFilter.filter_key_press(e, field_id);
        });
        filter_input.addEventListener('keyup', function(e) {
            OrderedSelectFilter.filter_key_up(e, field_id);
        });
        filter_input.addEventListener('keydown', function(e) {
            OrderedSelectFilter.filter_key_down(e, field_id);
        });
        selector_div.addEventListener('change', function(e) {
            if (e.target.tagName === 'SELECT') {
                OrderedSelectFilter.refresh_icons(field_id);
            }
        });
        selector_div.addEventListener('dblclick', function(e) {
            if (e.target.tagName === 'OPTION') {
                if (e.target.closest('select').id === field_id + '_to') {
                    OrderedSelectBox.move(field_id + '_to', field_id + '_from');
                } else {
                    OrderedSelectBox.move(field_id + '_from', field_id + '_to');
                }
                OrderedSelectFilter.refresh_icons(field_id);
            }
        });
        findForm(from_box).addEventListener('submit', function() {
            OrderedSelectBox.select_all(field_id + '_to');
        });
        OrderedSelectBox.init(field_id + '_from');
        OrderedSelectBox.init(field_id + '_to');
        // Move selected from_box options to to_box
        OrderedSelectBox.move(field_id + '_from', field_id + '_to');

        if (!is_stacked) {
            // In horizontal mode, give the same height to the two boxes.
            var j_from_box = $(from_box);
            var j_to_box = $(to_box);
            var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); };
            if (j_from_box.outerHeight() > 0) {
                resize_filters(); // This fieldset is already open. Resize now.
            } else {
                // This fieldset is probably collapsed. Wait for its 'show' event.
                j_to_box.closest('fieldset').one('show.fieldset', resize_filters);
            }
        }
        // Initial icon refresh
        OrderedSelectFilter.refresh_icons(field_id);
    },
    refresh_icons: function(field_id) {
        var from = $('#' + field_id + '_from');
        var to = $('#' + field_id + '_to');
        var is_from_selected  = from.find('option:selected').length > 0;
        var is_to_selected    =   to.find('option:selected').length > 0;
        var is_from_non_empty = from.find('option').length > 0;
        var is_to_non_empty   =   to.find('option').length > 0;
        // Active if at least one item is selected
        $('#' + field_id + '_add_link').toggleClass('active', is_from_selected);
        $('#' + field_id + '_remove_link').toggleClass('active', is_to_selected);
        // Active if the corresponding box isn't empty
        $('#' + field_id + '_add_all_link').toggleClass('active', is_from_non_empty);
        $('#' + field_id + '_remove_all_link').toggleClass('active', is_to_non_empty);
    },
    filter_key_up: function(event, field_id) {
        from = document.getElementById(field_id + '_from');
        // don't submit form if user pressed Enter
        if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
            from.selectedIndex = 0;
            OrderedSelectBox.move(field_id + '_from', field_id + '_to');
            from.selectedIndex = 0;
            return false;
        }
        var temp = from.selectedIndex;
        OrderedSelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
        from.selectedIndex = temp;
        return true;
    },
    filter_key_down: function(event, field_id) {
        from = document.getElementById(field_id + '_from');
        // right arrow -- move across
        if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
            var old_index = from.selectedIndex;
            OrderedSelectBox.move(field_id + '_from', field_id + '_to');
            from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
            return false;
        }
        // down arrow -- wrap around
        if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
            from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
        }
        // up arrow -- wrap around
        if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
            from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
        }
        return true;
    }
};
<|endoftext|>


<commit_before>// Copyright 2017 Telefónica Digital España S.L.
//
// This file is part of URBO PGSQL connector.
//
// URBO PGSQL connector is free software: you can redistribute it and/or
// modify it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// URBO PGSQL connector is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
// General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with URBO PGSQL connector. If not, see http://www.gnu.org/licenses/.
//
// For those usages not covered by this license please contact with
// iot_support at tid dot es

var config = require('../config.js');
var logParams = config.getLogOpt();
var log = require('log4js').getLogger(logParams.output);
var request = require('request');
var _ = require('underscore');

module.exports.getPostgresType = function(type){
  if (type === 'coords')
    return 'geometry(Point,4326)';
  else if (type === 'string' || type === 'stringOrList')
    return 'text';
  else if (type === 'boolean')
    return 'boolean';
  else if (type === 'integer')
    return 'integer';
  else if (type === 'float' || type === 'percent' || type === 'outcome')
    return 'double precision';
  else if (type === 'ISO8601' || type === 'timestamp')
    return 'timestamp without time zone';
  else if (type.startsWith('list'))
    return this.getPostgresListType(type) + ' ARRAY';
  else if (type === 'json')
    return 'JSONB';
  else if (type.startsWith('geojson'))
    return 'geometry(' + this.getPostgresGeoJSONType(type) + ', 4326)';
}

module.exports.getPostgresListType = function(type) {
  if (type === 'list' || type === 'list-string')
    return 'text';
  else if (type === 'list-numeric')
    return 'numeric';
};

module.exports.getPostgresGeoJSONType = function(type) {
  if (type === 'geojson-point')
    return 'Point';
  else if (type === 'geojson-line')
    return 'LineString';
  else if (type === 'geojson-polygon')
    return 'Polygon';
  else if (type === 'geojson-multipoint')
    return 'MultiPoint';
  else if (type === 'geojson-multiline')
    return 'MultiLineString';
  else if (type === 'geojson-multipolygon')
    return 'MultiPolygon';
  else if (type === 'geojson')
    return 'Geometry';
};

module.exports.getValueForType = function(value, type, outcome){
  if (typeof value === 'string' && value.toLowerCase() === 'null') {
    return null;
  }

  if (type === 'coords') {
    var s = value;
    if(typeof value === 'object' && Array.isArray(value)){
      s = s.join(',');
    }
    s = s.split(',');
    return 'ST_SetSRID(ST_MakePoint(' + s[1].trim() + ',' + s[0].trim() + '), 4326)';

  } else if (type.startsWith('geojson')) {
    if (typeof value !== 'object') {
      value = JSON.parse(value);
    }

    if (!value.hasOwnProperty('type') ||
        !value.hasOwnProperty('coordinates') ||
        value.coordinates.constructor !== Array) {
      var errorMsg = type + ' isn\'t a valid GeoJSON';
      log.error(errorMsg);
      throw Error(errorMsg);
    }

    var postgresGeoJSONType = this.getPostgresGeoJSONType(type);
    if (type !== 'geojson' && value.type !== postgresGeoJSONType) {
      var errorMsg = type + ' isn\'t a valid ' + postgresGeoJSONType + ' GeoJSON';
      log.error(errorMsg);
      throw Error(errorMsg);
    }

    return 'ST_SetSRID(ST_GeomFromGeoJSON(\'' + JSON.stringify(value) + '\'), 4326)';

  } else if (type.startsWith('list')) {
    if (!Array.isArray(value)) {
      value = [];
    }

    var sep = '';
    var cast = '';  // Casting is necesary for empty arrays

    if (type === 'list' || type === 'list-string') {
      var sep = value.length ? '\'' : '';
      var cast = 'text';

    } else if (type === 'list-numeric') {
      var cast = 'numeric';
    }

    return 'ARRAY[' + sep + value.join(sep + ', ' + sep) + sep + ']::' + cast + '[]';

  } else if (type === 'ISO8601' || type === 'timestamp') {
    if (!value || value === '' || new Date(value) == 'Invalid Date'){
      return null;

    } else {
      return value;
    }

  } else if (type === 'boolean' || type === 'integer' || type === 'float' || type === 'string') {
    return value;

  } else  if (type === 'stringOrList') {
    if(typeof value === 'string') {
      return value;

    } else {
      if (!Array.isArray(value)) {
        var errorMsg =  type + ' isn\'t a valid Array';
        log.error(errorMsg);
        throw Error(errorMsg);
      }
      return value[0];
    }

  } else if (type === 'json') {
    return JSON.stringify(value);

  } else if (type === 'percent') {
    return value * 100;

  } else if (type === 'outcome') {

    if(outcome && outcome.factor && outcome.operation) {
      var operation = outcome.operation;
      if (operation === 'SUM') {
        return value + outcome.factor;

      } else if(operation === 'PROD') {
        return value * outcome.factor;

      } else if(operation === 'MIN') {
        return value - outcome.factor;

      } else if(operation === 'DIV') {
        return value / outcome.factor;
      }
    }
    return value;

  } else {
    log.error('Unknown type: ' + type);
    throw Error('Unknown type: ' + type);
  }
};

module.exports.isTypeQuoted = function(type){
  if (type === 'coords' || type.startsWith('geojson') || type.startsWith('list') || type === 'boolean' || type === 'integer' || type === 'float' || type === 'percent' || type === 'outcome') {
    return false;

  } else if (type === 'string' || type === 'ISO8601' || type === 'timestamp' || type === 'json' || type === 'stringOrList' ) {
    return true;

  } else {
    log.error('Unknown type: ' + type);
    throw Error('Unknown type: ' + type);
  }
};

module.exports.wrapStrings = function(value,wrap) {
  if (wrap.length == 1)
    return [wrap[0],value,wrap[0]].join('')
  else if (wrap.length == 2)
    return [wrap[0],value,wrap[1]].join('')
  else{
    log.error('Wrap length must be 1 or 2');
    throw Error('Wrap length must be 1 or 2');
  }
}

module.exports.parseLatLon = function(subattr) {
  if (_.find(subattr,{namedb:'lat',type:'coords'}) && _.find(subattr,{namedb:'lon',type:'coords'}) &&
      (!_.find(subattr,{name:'position',type:'coords'}) || !_.find(subattr,{namedb:'position',type:'coords'}))){
        subattr.push({name:'position',type:'coords',cartodb:true});
        subattr = _.without(subattr,_.find(subattr,{namedb:'lat'}),_.find(subattr,{namedb:'lon'}));
  }
  return subattr;
}

module.exports.retryRequest = function(options, retries, cb, lastParams) {
  if (retries > 0) {
    request(options, function(error, response, body) {
      if (error) {
        log.info(`Request failed, retrying ${retries - 1} more times: ` + options);
        this.retryRequest(options, retries - 1, cb, {
          error: error,
          response: response,
          body: body
        });

      } else {
        cb(error, response, body);
      }
    }.bind(this));

  } else {
    cb(lastParams.error, lastParams.response, lastParams.body);
  }
};

module.exports.fixContextResponse = function(contextResponse) {
  var currentKeys = [];
  var newAttributes = [];
  for (var attribute of contextResponse.contextElement.attributes) {
    if (!currentKeys.includes(attribute.name)) {
      currentKeys.push((attribute.name);
      newAttributes.push(attribute);
    }
  }

  contextResponse.contextElement.attributes = newAttributes;
  return contextResponse;
};

module.exports.storeData = function(subscription, contextResponses) {
  var processingConfig = config.getData().processing;
  var options = {
    'url': processingConfig.url,
    'method': 'POST',
    'headers': {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    'json': {
      'type': undefined,
      'data': {
        'title': undefined,
        'contextResponses': undefined,
        'subscription': subscription
      },
      'options' : {
        'attempts': processingConfig.jobAttempts,
        'priority': processingConfig.priority
      }
    }
  };

  contextResponses.forEach(function(contextResponse) {

    var psqlOptions = JSON.parse(JSON.stringify(options));  // Cheap deep clone
    psqlOptions.json.type = processingConfig.psqlJob;
    psqlOptions.json.data.title = `${subscription.schemaname} ${subscription.id} to PSQL'`;
    psqlOptions.json.data.contextResponses = [contextResponse];
    this.retryRequest(psqlOptions, processingConfig.requestAttempts, function(error, response, body) {
      if (error) {
        log.error('Error inserting at PGSQL');
        log.error(error);
      }
    });

    var cdbActiveFields = config.cdbActiveFields(subscription);
    var cdbActive = config.getData().cartodb.active;
    if (cdbActive && cdbActiveFields) {

      var cartoOptions = JSON.parse(JSON.stringify(options));  // Cheap deep clone
      cartoOptions.json.type = processingConfig.cartoJob;
      cartoOptions.json.data.title = `${subscription.schemaname} ${subscription.id} to CARTO'`;
      cartoOptions.json.data.contextResponses = [contextResponse];
      cartoOptions.json.data.cartoUser = config.getData().cartodb.user;
      this.retryRequest(cartoOptions, processingConfig.requestAttempts, function(error, response, body) {
        if (error) {
          log.error('Error inserting at CARTO');
          log.error(error);
        }
      });

    }
  }.bind(this));
};

module.exports.toArrayOfNumbers = function(coordinates) {
  var result = [];
  for (var coordinate of coordinates) {
    if (Array.isArray(coordinate)) {
      result.push(this.toArrayOfNumbers(coordinate));
    } else {
      result.push(Number(coordinate));
    }
  }

  return result;
};

module.exports.getNotificationValueForType = function(value, type, outcome) {
  if (type === 'coords') {

    var s = value;
    if(typeof value === 'object' && Array.isArray(value)){
      s = s.join(',');
    }
    value = s.split(',');
    return {
        type: 'Point',
        coordinates: [
            Number(value[1]),
            Number(value[0])
        ]
    };

  } else if (type === 'json' || type.startsWith('geojson')) {
    if (value.constructor !== {}.constructor) {
      return JSON.parse(value);
    }

    if (type.startsWith('geojson')) {
      value.coordinates = this.toArrayOfNumbers(value.coordinates);
    }

    return value;

  } else if (type.startsWith('list')) {
    if (!Array.isArray(value)) {
      if (value.startsWith('[') && value.endsWith(']')) {
        value = value.slice(1, -1);
      }

      value = value.split(',');

      if (type === 'list-numeric') {
        value = value.map(Number);
      }
    }

    return value;

  } else if (type === 'integer') {
    return parseInt(value);

  } else if (type === 'float') {
    return parseFloat(value);

  } else if (type === 'percent') {
    return  Number(value) * 100;

  } else if (type === 'outcome') {
    if (outcome && outcome.factor && outcome.operation) {
      value = Number(value);
      if (outcome.operation === 'SUM') {
        return value + outcome.factor;

      } else if (outcome.operation === 'PROD') {
        return value * outcome.factor;

      } else if (outcome.operation === 'MIN') {
        return value - outcome.factor;

      } else if (outcome.operation === 'DIV') {
        return value / outcome.factor;
      }
    }
    return value;

  } else {  // As it comes: strings, timestamps, ...
    return value;  // As it comes
  }
};
<commit_msg>HOTFIX: Trying to fix Orion bug when multiple dates are sent, 2nd part<commit_after>// Copyright 2017 Telefónica Digital España S.L.
//
// This file is part of URBO PGSQL connector.
//
// URBO PGSQL connector is free software: you can redistribute it and/or
// modify it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// URBO PGSQL connector is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
// General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with URBO PGSQL connector. If not, see http://www.gnu.org/licenses/.
//
// For those usages not covered by this license please contact with
// iot_support at tid dot es

var config = require('../config.js');
var logParams = config.getLogOpt();
var log = require('log4js').getLogger(logParams.output);
var request = require('request');
var _ = require('underscore');

module.exports.getPostgresType = function(type){
  if (type === 'coords')
    return 'geometry(Point,4326)';
  else if (type === 'string' || type === 'stringOrList')
    return 'text';
  else if (type === 'boolean')
    return 'boolean';
  else if (type === 'integer')
    return 'integer';
  else if (type === 'float' || type === 'percent' || type === 'outcome')
    return 'double precision';
  else if (type === 'ISO8601' || type === 'timestamp')
    return 'timestamp without time zone';
  else if (type.startsWith('list'))
    return this.getPostgresListType(type) + ' ARRAY';
  else if (type === 'json')
    return 'JSONB';
  else if (type.startsWith('geojson'))
    return 'geometry(' + this.getPostgresGeoJSONType(type) + ', 4326)';
}

module.exports.getPostgresListType = function(type) {
  if (type === 'list' || type === 'list-string')
    return 'text';
  else if (type === 'list-numeric')
    return 'numeric';
};

module.exports.getPostgresGeoJSONType = function(type) {
  if (type === 'geojson-point')
    return 'Point';
  else if (type === 'geojson-line')
    return 'LineString';
  else if (type === 'geojson-polygon')
    return 'Polygon';
  else if (type === 'geojson-multipoint')
    return 'MultiPoint';
  else if (type === 'geojson-multiline')
    return 'MultiLineString';
  else if (type === 'geojson-multipolygon')
    return 'MultiPolygon';
  else if (type === 'geojson')
    return 'Geometry';
};

module.exports.getValueForType = function(value, type, outcome){
  if (typeof value === 'string' && value.toLowerCase() === 'null') {
    return null;
  }

  if (type === 'coords') {
    var s = value;
    if(typeof value === 'object' && Array.isArray(value)){
      s = s.join(',');
    }
    s = s.split(',');
    return 'ST_SetSRID(ST_MakePoint(' + s[1].trim() + ',' + s[0].trim() + '), 4326)';

  } else if (type.startsWith('geojson')) {
    if (typeof value !== 'object') {
      value = JSON.parse(value);
    }

    if (!value.hasOwnProperty('type') ||
        !value.hasOwnProperty('coordinates') ||
        value.coordinates.constructor !== Array) {
      var errorMsg = type + ' isn\'t a valid GeoJSON';
      log.error(errorMsg);
      throw Error(errorMsg);
    }

    var postgresGeoJSONType = this.getPostgresGeoJSONType(type);
    if (type !== 'geojson' && value.type !== postgresGeoJSONType) {
      var errorMsg = type + ' isn\'t a valid ' + postgresGeoJSONType + ' GeoJSON';
      log.error(errorMsg);
      throw Error(errorMsg);
    }

    return 'ST_SetSRID(ST_GeomFromGeoJSON(\'' + JSON.stringify(value) + '\'), 4326)';

  } else if (type.startsWith('list')) {
    if (!Array.isArray(value)) {
      value = [];
    }

    var sep = '';
    var cast = '';  // Casting is necesary for empty arrays

    if (type === 'list' || type === 'list-string') {
      var sep = value.length ? '\'' : '';
      var cast = 'text';

    } else if (type === 'list-numeric') {
      var cast = 'numeric';
    }

    return 'ARRAY[' + sep + value.join(sep + ', ' + sep) + sep + ']::' + cast + '[]';

  } else if (type === 'ISO8601' || type === 'timestamp') {
    if (!value || value === '' || new Date(value) == 'Invalid Date'){
      return null;

    } else {
      return value;
    }

  } else if (type === 'boolean' || type === 'integer' || type === 'float' || type === 'string') {
    return value;

  } else  if (type === 'stringOrList') {
    if(typeof value === 'string') {
      return value;

    } else {
      if (!Array.isArray(value)) {
        var errorMsg =  type + ' isn\'t a valid Array';
        log.error(errorMsg);
        throw Error(errorMsg);
      }
      return value[0];
    }

  } else if (type === 'json') {
    return JSON.stringify(value);

  } else if (type === 'percent') {
    return value * 100;

  } else if (type === 'outcome') {

    if(outcome && outcome.factor && outcome.operation) {
      var operation = outcome.operation;
      if (operation === 'SUM') {
        return value + outcome.factor;

      } else if(operation === 'PROD') {
        return value * outcome.factor;

      } else if(operation === 'MIN') {
        return value - outcome.factor;

      } else if(operation === 'DIV') {
        return value / outcome.factor;
      }
    }
    return value;

  } else {
    log.error('Unknown type: ' + type);
    throw Error('Unknown type: ' + type);
  }
};

module.exports.isTypeQuoted = function(type){
  if (type === 'coords' || type.startsWith('geojson') || type.startsWith('list') || type === 'boolean' || type === 'integer' || type === 'float' || type === 'percent' || type === 'outcome') {
    return false;

  } else if (type === 'string' || type === 'ISO8601' || type === 'timestamp' || type === 'json' || type === 'stringOrList' ) {
    return true;

  } else {
    log.error('Unknown type: ' + type);
    throw Error('Unknown type: ' + type);
  }
};

module.exports.wrapStrings = function(value,wrap) {
  if (wrap.length == 1)
    return [wrap[0],value,wrap[0]].join('')
  else if (wrap.length == 2)
    return [wrap[0],value,wrap[1]].join('')
  else{
    log.error('Wrap length must be 1 or 2');
    throw Error('Wrap length must be 1 or 2');
  }
}

module.exports.parseLatLon = function(subattr) {
  if (_.find(subattr,{namedb:'lat',type:'coords'}) && _.find(subattr,{namedb:'lon',type:'coords'}) &&
      (!_.find(subattr,{name:'position',type:'coords'}) || !_.find(subattr,{namedb:'position',type:'coords'}))){
        subattr.push({name:'position',type:'coords',cartodb:true});
        subattr = _.without(subattr,_.find(subattr,{namedb:'lat'}),_.find(subattr,{namedb:'lon'}));
  }
  return subattr;
}

module.exports.retryRequest = function(options, retries, cb, lastParams) {
  if (retries > 0) {
    request(options, function(error, response, body) {
      if (error) {
        log.info(`Request failed, retrying ${retries - 1} more times: ` + options);
        this.retryRequest(options, retries - 1, cb, {
          error: error,
          response: response,
          body: body
        });

      } else {
        cb(error, response, body);
      }
    }.bind(this));

  } else {
    cb(lastParams.error, lastParams.response, lastParams.body);
  }
};

module.exports.fixContextResponse = function(contextResponse) {
  var currentKeys = [];
  var newAttributes = [];
  for (var attribute of contextResponse.contextElement.attributes) {
    if (!currentKeys.includes(attribute.name)) {
      currentKeys.push(attribute.name);
      newAttributes.push(attribute);
    }
  }

  contextResponse.contextElement.attributes = newAttributes;
  return contextResponse;
};

module.exports.storeData = function(subscription, contextResponses) {
  var processingConfig = config.getData().processing;
  var options = {
    'url': processingConfig.url,
    'method': 'POST',
    'headers': {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    'json': {
      'type': undefined,
      'data': {
        'title': undefined,
        'contextResponses': undefined,
        'subscription': subscription
      },
      'options' : {
        'attempts': processingConfig.jobAttempts,
        'priority': processingConfig.priority
      }
    }
  };

  contextResponses.forEach(function(contextResponse) {

    var psqlOptions = JSON.parse(JSON.stringify(options));  // Cheap deep clone
    psqlOptions.json.type = processingConfig.psqlJob;
    psqlOptions.json.data.title = `${subscription.schemaname} ${subscription.id} to PSQL'`;
    psqlOptions.json.data.contextResponses = [contextResponse];
    this.retryRequest(psqlOptions, processingConfig.requestAttempts, function(error, response, body) {
      if (error) {
        log.error('Error inserting at PGSQL');
        log.error(error);
      }
    });

    var cdbActiveFields = config.cdbActiveFields(subscription);
    var cdbActive = config.getData().cartodb.active;
    if (cdbActive && cdbActiveFields) {

      var cartoOptions = JSON.parse(JSON.stringify(options));  // Cheap deep clone
      cartoOptions.json.type = processingConfig.cartoJob;
      cartoOptions.json.data.title = `${subscription.schemaname} ${subscription.id} to CARTO'`;
      cartoOptions.json.data.contextResponses = [contextResponse];
      cartoOptions.json.data.cartoUser = config.getData().cartodb.user;
      this.retryRequest(cartoOptions, processingConfig.requestAttempts, function(error, response, body) {
        if (error) {
          log.error('Error inserting at CARTO');
          log.error(error);
        }
      });

    }
  }.bind(this));
};

module.exports.toArrayOfNumbers = function(coordinates) {
  var result = [];
  for (var coordinate of coordinates) {
    if (Array.isArray(coordinate)) {
      result.push(this.toArrayOfNumbers(coordinate));
    } else {
      result.push(Number(coordinate));
    }
  }

  return result;
};

module.exports.getNotificationValueForType = function(value, type, outcome) {
  if (type === 'coords') {

    var s = value;
    if(typeof value === 'object' && Array.isArray(value)){
      s = s.join(',');
    }
    value = s.split(',');
    return {
        type: 'Point',
        coordinates: [
            Number(value[1]),
            Number(value[0])
        ]
    };

  } else if (type === 'json' || type.startsWith('geojson')) {
    if (value.constructor !== {}.constructor) {
      return JSON.parse(value);
    }

    if (type.startsWith('geojson')) {
      value.coordinates = this.toArrayOfNumbers(value.coordinates);
    }

    return value;

  } else if (type.startsWith('list')) {
    if (!Array.isArray(value)) {
      if (value.startsWith('[') && value.endsWith(']')) {
        value = value.slice(1, -1);
      }

      value = value.split(',');

      if (type === 'list-numeric') {
        value = value.map(Number);
      }
    }

    return value;

  } else if (type === 'integer') {
    return parseInt(value);

  } else if (type === 'float') {
    return parseFloat(value);

  } else if (type === 'percent') {
    return  Number(value) * 100;

  } else if (type === 'outcome') {
    if (outcome && outcome.factor && outcome.operation) {
      value = Number(value);
      if (outcome.operation === 'SUM') {
        return value + outcome.factor;

      } else if (outcome.operation === 'PROD') {
        return value * outcome.factor;

      } else if (outcome.operation === 'MIN') {
        return value - outcome.factor;

      } else if (outcome.operation === 'DIV') {
        return value / outcome.factor;
      }
    }
    return value;

  } else {  // As it comes: strings, timestamps, ...
    return value;  // As it comes
  }
};
<|endoftext|>


<commit_before>const cli = require('commander')
const moment = require('moment')
const chalk = require('chalk')
const apiCall = require('./apiCall')

const periods = []

cli
  .option('-f, --from <date>', 'select from <date>.', validateDate)
  .option('-t, --to <date>', 'select to <date>.', validateDate)
  .parse(process.argv)

function printReports () {
  definePeriods()
  periods.forEach(period => {
    apiCall('completed', '', {
      start_date: period[0].format('YYYY-MM-DDTHH:MM:ssZ'),
      end_date: period[1].format('YYYY-MM-DDTHH:MM:ssZ')
    })
    .then(res => {
      let report = new Report(res.body, period)
      report.create()
      report.print()
    })
  })
}

function definePeriods () {
  // Add one hour to the default end time to take inconsistencies into account.
  const defaultEndTime = moment.utc().add(1, 'h')
  // Make default periods if the user didn't input any.
  if (!cli.args.length && !cli.to && !cli.from) {
    let month = formatMonth((moment().month() + 1).toString())
    periods.push([moment.utc('2014-01-01'), defaultEndTime])
    periods.push([moment.utc(month), defaultEndTime])
  }
  // Make options periods.
  if (cli.from || cli.to) {
    let start = cli.from ? cli.from : moment.utc('2014-01-01')
    let end = cli.to ? cli.to : defaultEndTime
    periods.push([start, end])
  }
  // Regex expressions to match user input.
  // Matches numbers from 1-12.
  const matchMonth = /^[1-9]|1[012]$/
  // Matches the format YYYY-MM.
  const matchYearMonth = /^(19|20)\d\d[- /.](0[1-9]|1[012])$/

  cli.args.forEach(arg => {
    // Testing what arguments we got from the user.
    if (matchYearMonth.test(arg)) {
      let start = moment(arg).utc()
      let end = moment(arg).utc().add(1, 'M')
      periods.push([start, end])
    } else if (matchMonth.test(arg)) {
      let year = moment().year()
      const month = moment().month() + 1
      if (arg > month) {
        year -= 1
      }
      const yearAndMonth = arg < 9 ? `${year}-0${arg}` : `${year}-${arg}`
      let start = moment(yearAndMonth).utc()
      let end = moment(yearAndMonth).utc().add(1, 'M')
      periods.push([start, end])
    }  else if (arg === 'today') {
      let start = moment.utc().startOf('day')
      let end = moment.utc().endOf('day')
      periods.push([start, end])
    } else if (arg === 'yesterday') {
      let start = moment.utc().subtract(1, 'd').startOf('day')
      let end = moment.utc().subtract(1, 'd').endOf('day')
      periods.push([start, end])
    }
  })
}

function formatMonth (month) {
  return month.length === 1 ? `${moment().year()}-0${month}`
                            : `${moment().year()}-${month}`
}

function validateDate (date) {
  date = moment.utc(date)
  if (!date.isValid()) {
    throw new Error(`Invalid date: ${date}`)
  }
  return date
}

class Report {
  constructor (reviews, period) {
    this.period = period
    this.reviews = reviews
    this.projects = {}
    this.totalEarned = 0
    this.totalHours = 0
  }

  create () {
    this.reviews.forEach(review => {
      this.countReview(review)
    })
  }

  countReview (review) {
    let id = review.project_id
    let price = parseInt(review.price)
    let assigned_at = moment.utc(review.assigned_at)
    let completed_at = moment.utc(review.completed_at)
    let hours = completed_at.diff(assigned_at, 'hours', true)

    // If the report does not yet contain an entry for the project type, create
    // the entry and try counting the review again.
    if (!this.projects.hasOwnProperty(id)) {
      this.projects[id] = {
        name: review.project.name,
        id: id,
        passed: 0,
        failed: 0,
        ungradeable: 0,
        earned: 0,
        hours: 0
      }
      this.countReview(review)
    } else {
      this.projects[id][review.result] += 1
      this.projects[id].earned += price
      this.projects[id].hours += hours
      this.totalEarned += price
      this.totalHours += hours
    }
  }

  print () {
    let startDate = moment(this.period[0]).format('YYYY-MM-DD')
    let endDate = moment(this.period[1]).format('YYYY-MM-DD')

    console.log('========================================')
    console.log(chalk.blue(`Earnings Report for ${startDate} to ${endDate}:`))

    for (let p in this.projects) {
      let {name, id, ungradeable, passed, failed, earned, hours} =
          this.projects[p]
      console.log(`
      ${chalk.white(`Project: ${name} (${id}):`)}
          ${chalk.white(`Total reviewed: ${passed + failed}`)}
          ${chalk.white(`Ungradeable: ${ungradeable}`)}
          ${chalk.white(`Earned: ${earned}`)}
          ${chalk.white(`Working Hours: ${hours.toFixed(2)}`)}
          ${chalk.white(`Earned per Hour: ${(earned/hours).toFixed(2)}`)}
          `)
    }
    console.log(chalk.bgBlack.white(`Total Earned: ${this.totalEarned}`))
    console.log(chalk.bgBlack.white(`Total Working Hours: ` +
                `${this.totalHours.toFixed(2)}`))
    console.log(chalk.bgBlack.white(`Total Earned per Hour: ` +
                `${(this.totalEarned/this.totalHours).toFixed(2)}`))
    console.log('========================================')
  }
}

printReports()
<commit_msg>Removes "Earned per working hours"<commit_after>const cli = require('commander')
const moment = require('moment')
const chalk = require('chalk')
const apiCall = require('./apiCall')

const periods = []

cli
  .option('-f, --from <date>', 'select from <date>.', validateDate)
  .option('-t, --to <date>', 'select to <date>.', validateDate)
  .parse(process.argv)

function printReports () {
  definePeriods()
  periods.forEach(period => {
    apiCall('completed', '', {
      start_date: period[0].format('YYYY-MM-DDTHH:MM:ssZ'),
      end_date: period[1].format('YYYY-MM-DDTHH:MM:ssZ')
    })
    .then(res => {
      let report = new Report(res.body, period)
      report.create()
      report.print()
    })
  })
}

function definePeriods () {
  // Add one hour to the default end time to take inconsistencies into account.
  const defaultEndTime = moment.utc().add(1, 'h')
  // Make default periods if the user didn't input any.
  if (!cli.args.length && !cli.to && !cli.from) {
    let month = formatMonth((moment().month() + 1).toString())
    periods.push([moment.utc('2014-01-01'), defaultEndTime])
    periods.push([moment.utc(month), defaultEndTime])
  }
  // Make options periods.
  if (cli.from || cli.to) {
    let start = cli.from ? cli.from : moment.utc('2014-01-01')
    let end = cli.to ? cli.to : defaultEndTime
    periods.push([start, end])
  }
  // Regex expressions to match user input.
  // Matches numbers from 1-12.
  const matchMonth = /^[1-9]|1[012]$/
  // Matches the format YYYY-MM.
  const matchYearMonth = /^(19|20)\d\d[- /.](0[1-9]|1[012])$/

  cli.args.forEach(arg => {
    // Testing what arguments we got from the user.
    if (matchYearMonth.test(arg)) {
      let start = moment(arg).utc()
      let end = moment(arg).utc().add(1, 'M')
      periods.push([start, end])
    } else if (matchMonth.test(arg)) {
      let year = moment().year()
      const month = moment().month() + 1
      if (arg > month) {
        year -= 1
      }
      const yearAndMonth = arg < 9 ? `${year}-0${arg}` : `${year}-${arg}`
      let start = moment(yearAndMonth).utc()
      let end = moment(yearAndMonth).utc().add(1, 'M')
      periods.push([start, end])
    }  else if (arg === 'today') {
      let start = moment.utc().startOf('day')
      let end = moment.utc().endOf('day')
      periods.push([start, end])
    } else if (arg === 'yesterday') {
      let start = moment.utc().subtract(1, 'd').startOf('day')
      let end = moment.utc().subtract(1, 'd').endOf('day')
      periods.push([start, end])
    }
  })
}

function formatMonth (month) {
  return month.length === 1 ? `${moment().year()}-0${month}`
                            : `${moment().year()}-${month}`
}

function validateDate (date) {
  date = moment.utc(date)
  if (!date.isValid()) {
    throw new Error(`Invalid date: ${date}`)
  }
  return date
}

class Report {
  constructor (reviews, period) {
    this.period = period
    this.reviews = reviews
    this.projects = {}
    this.totalEarned = 0
    this.totalHours = 0
  }

  create () {
    this.reviews.forEach(review => {
      this.countReview(review)
    })
  }

  countReview (review) {
    let id = review.project_id
    let price = parseInt(review.price)
    let assigned_at = moment.utc(review.assigned_at)
    let completed_at = moment.utc(review.completed_at)
    let hours = completed_at.diff(assigned_at, 'hours', true)

    // If the report does not yet contain an entry for the project type, create
    // the entry and try counting the review again.
    if (!this.projects.hasOwnProperty(id)) {
      this.projects[id] = {
        name: review.project.name,
        id: id,
        passed: 0,
        failed: 0,
        ungradeable: 0,
        earned: 0,
        hours: 0
      }
      this.countReview(review)
    } else {
      this.projects[id][review.result] += 1
      this.projects[id].earned += price
      this.projects[id].hours += hours
      this.totalEarned += price
      this.totalHours += hours
    }
  }

  print () {
    let startDate = moment(this.period[0]).format('YYYY-MM-DD')
    let endDate = moment(this.period[1]).format('YYYY-MM-DD')

    console.log('========================================')
    console.log(chalk.blue(`Earnings Report for ${startDate} to ${endDate}:`))

    for (let p in this.projects) {
      let {name, id, ungradeable, passed, failed, earned, hours} =
          this.projects[p]
      console.log(`
      ${chalk.white(`Project: ${name} (${id}):`)}
          ${chalk.white(`Total reviewed: ${passed + failed}`)}
          ${chalk.white(`Ungradeable: ${ungradeable}`)}
          ${chalk.white(`Earned: ${earned}`)}
          ${chalk.white(`Working Hours: ${hours.toFixed(2)}`)}
          `)
    }
    console.log(chalk.bgBlack.white(`Total Earned: ${this.totalEarned}`))
    console.log(chalk.bgBlack.white(`Total Working Hours: ` +
                `${this.totalHours.toFixed(2)}`))
    console.log('========================================')
  }
}

printReports()
<|endoftext|>


<commit_before>'use strict';

var winston = require('winston'),
    logger = winston.loggers.get('webapi');

// other routes
var User = require('./User'),
    Orgs = require('./Orgs'),
    Room = require('./Room'),
    Channel = require('./Channel'),
    Public = require ('./Public');

var routes = function (app, storage, settings) {
    logger.info('register routes');

    // register additional routes
    User(app, storage, settings);
    Orgs(app, storage, settings);
    Room(app, storage, settings);
    Channel(app, storage, settings);
    Public(app, storage, settings);

};

// Expose routes
module.exports = routes;<commit_msg>chanfix upercase filename in dependency<commit_after>'use strict';

var winston = require('winston'),
    logger = winston.loggers.get('webapi');

// other routes
var User = require('./User'),
    Orgs = require('./Orgs'),
    Room = require('./Room'),
    Channel = require('./Channel'),
    Public = require ('./public');

var routes = function (app, storage, settings) {
    logger.info('register routes');

    // register additional routes
    User(app, storage, settings);
    Orgs(app, storage, settings);
    Room(app, storage, settings);
    Channel(app, storage, settings);
    Public(app, storage, settings);

};

// Expose routes
module.exports = routes;<|endoftext|>