<commit_before>goog.provide('olcs.AbstractSynchronizer');

goog.require('goog.object');

goog.require('ol.Observable');
goog.require('ol.layer.Group');

/**
 * @param {!ol.Map} map
 * @param {!Cesium.Scene} scene
 * @constructor
 * @template T
 * @api
 */
olcs.AbstractSynchronizer = function(map, scene) {
  /**
   * @type {!ol.Map}
   * @protected
   */
  this.map = map;

  /**
   * @type {ol.View}
   * @protected
   */
  this.view = map.getView();

  /**
   * @type {!Cesium.Scene}
   * @protected
   */
  this.scene = scene;

  /**
   * @type {ol.Collection.<ol.layer.Base>}
   * @protected
   */
  this.olLayers = map.getLayerGroup().getLayers();

  /**
   * @type {ol.layer.Group}
   */
  this.mapLayerGroup = map.getLayerGroup();

  /**
   * Map of ol3 layer ids (from goog.getUid) to the Cesium ImageryLayers.
   * null value means, that we are unable to create equivalent layer.
   * @type {Object.<number, ?T>}
   * @protected
   */
  this.layerMap = {};

  /**
   * Map of listen keys for ol3 layer layers ids (from goog.getUid).
   * @type {!Object.<number, !goog.events.Key>}
   * @private
   */
  this.olLayerListenKeys_ = {};

  /**
   * Map of listen keys for ol3 layer groups ids (from goog.getUid).
   * @type {!Object.<number, !Array.<goog.events.Key>>}
   * @private
   */
  this.olGroupListenKeys_ = {};
};

/**
 * Destroy all and perform complete synchronization of the layers.
 * @api
 */
olcs.AbstractSynchronizer.prototype.synchronize = function() {
  this.destroyAll();
  this.addLayers_(this.mapLayerGroup);
};

/**
 * Populate the foundLayers and foundGroups using a breadth-first algorithm.
 * A map of layer uid to z-index is also populated.
 * @param {ol.layer.Base} layer
 * @param {Array.<ol.layer.Layer>} foundLayers Found leaves.
 * @param {Array.<ol.layer.Group>} foundGroups Found nodes.
 * @param {Object.<number, number>=} opt_zIndices Map of layer uid to z-index.
 * @protected
 */
olcs.AbstractSynchronizer.flattenLayers =
    function(layer, foundLayers, foundGroups, opt_zIndices) {
  if (layer instanceof ol.layer.Group) {
    foundGroups.push(layer);
    var sublayers = layer.getLayers();
    if (goog.isDef(sublayers)) {
      sublayers.forEach(function(el) {
        olcs.AbstractSynchronizer.flattenLayers(el, foundLayers, foundGroups,
            opt_zIndices);
      });
    }
  } else {
    foundLayers.push(layer);
    if (opt_zIndices) {
      opt_zIndices[goog.getUid(layer)] = layer.getZIndex();
    }
  }
};

/**
 * Order counterparts using the same algorithm as the Openlayers renderer:
 * z-index then original sequence order.
 * @protected
 */
olcs.AbstractSynchronizer.prototype.orderLayers = function() {
  // Ordering logics is handled in subclasses.
};

/**
 * Add a layer hierarchy.
 * @param {ol.layer.Base} root
 * @private
 */
olcs.AbstractSynchronizer.prototype.addLayers_ = function(root) {
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers(root, layers, groups);

  layers.forEach(function(olLayer) {
    if (goog.isNull(olLayer)) {
      return;
    }
    var olLayerId = goog.getUid(olLayer);

    var cesiumObject = this.layerMap[olLayerId];
    goog.asserts.assert(!goog.isDef(cesiumObject));

    // no mapping -> create new layer and set up synchronization
    cesiumObject = this.createSingleCounterpart(olLayer);

    // add Cesium layers
    if (!goog.isNull(cesiumObject)) {
      cesiumObject.zIndex = olLayer.getZIndex();
      this.addCesiumObject(cesiumObject);
      this.layerMap[olLayerId] = cesiumObject;
      this.olLayerListenKeys_[olLayerId] = olLayer.on('change:zIndex',
          this.orderLayers, this);
    }
  }, this);

  groups.forEach(function(el) {
    this.listenForGroupChanges_(el);
  }, this);

  this.orderLayers();
};

/**
 * Remove and destroy a single layer.
 * @param {ol.layer.Layer} layer
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeAndDestroySingleLayer_ =
    function(layer) {
  var uid = goog.getUid(layer);
  var counterpart = this.layerMap[uid];
  if (!!counterpart) {
    this.removeSingleCesiumObject(counterpart, false);
    this.destroyCesiumObject(counterpart);
    ol.Observable.unByKey(this.olLayerListenKeys_[uid]);
    delete this.olLayerListenKeys_[uid];
  }
  delete this.layerMap[uid];
};

/**
 * Unlisten a single layer group.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.unlistenSingleGroup_ =
    function(group) {
  if (group === this.mapLayerGroup) {
    return;
  }
  var uid = goog.getUid(group);
  var keys = this.olGroupListenKeys_[uid];
  keys.forEach(function(key) {
    ol.Observable.unByKey(key);
  });
  delete this.olGroupListenKeys_[uid];
};

/**
 * Remove layer hierarchy.
 * @param {ol.layer.Base} root
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeLayer_ = function(root) {
  if (!root) {
    return;
  }
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers(root, layers, groups);

  layers.forEach(function(el) {
    this.removeAndDestroySingleLayer_(el);
  }, this);

  groups.forEach(function(el) {
    this.unlistenSingleGroup_(el);
  }, this);
};

/**
 * Register listeners for single layer group change.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.listenForGroupChanges_ = function(group) {
  var uuid = goog.getUid(group);

  goog.asserts.assert(!goog.isDef(this.olGroupListenKeys_[uuid]));

  var listenKeyArray = [];
  this.olGroupListenKeys_[uuid] = listenKeyArray;

  // only the keys that need to be relistened when collection changes
  var contentKeys = [];
  var listenAddRemove = (function() {
    var collection = group.getLayers();
    if (goog.isDef(collection)) {
      contentKeys = [
        collection.on('add', function(event) {
          this.addLayers_(event.element);
        }, this),
        collection.on('remove', function(event) {
          this.removeLayer_(event.element);
        }, this)
      ];
      listenKeyArray.push.apply(listenKeyArray, contentKeys);
    }
  }).bind(this);

  listenAddRemove();

  listenKeyArray.push(group.on('change:layers', function(e) {
    contentKeys.forEach(function(el) {
      var i = listenKeyArray.indexOf(el);
      if (i >= 0) {
        listenKeyArray.splice(i, 1);
      }
      ol.Observable.unByKey(el);
    });
    listenAddRemove();
  }));
};

/**
 * Destroys all the created Cesium objects.
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyAll = function() {
  this.removeAllCesiumObjects(true); // destroy
  goog.object.forEach(this.olGroupListenKeys, function(keys) {
    keys.forEach(ol.Observable.unByKey);
  });
  goog.object.forEach(this.olLayerListenKeys, ol.Observable.unByKey);
  this.olGroupListenKeys = {};
  this.olLayerListenKeys = {};
  this.layerMap = {};
};

/**
 * Adds a single Cesium object to the collection.
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.addCesiumObject = goog.abstractMethod;

/**
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyCesiumObject = goog.abstractMethod;

/**
 * Remove single Cesium object from the collection.
 * @param {!T} object
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeSingleCesiumObject =
    goog.abstractMethod;

/**
 * Remove all Cesium objects from the collection.
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeAllCesiumObjects =
    goog.abstractMethod;

/**
 * @param {!ol.layer.Layer} olLayer
 * @return {T}
 * @protected
 */
olcs.AbstractSynchronizer.prototype.createSingleCounterpart =
    goog.abstractMethod;
<commit_msg>Cleanup<commit_after>goog.provide('olcs.AbstractSynchronizer');

goog.require('goog.object');

goog.require('ol.Observable');
goog.require('ol.layer.Group');

/**
 * @param {!ol.Map} map
 * @param {!Cesium.Scene} scene
 * @constructor
 * @template T
 * @api
 */
olcs.AbstractSynchronizer = function(map, scene) {
  /**
   * @type {!ol.Map}
   * @protected
   */
  this.map = map;

  /**
   * @type {ol.View}
   * @protected
   */
  this.view = map.getView();

  /**
   * @type {!Cesium.Scene}
   * @protected
   */
  this.scene = scene;

  /**
   * @type {ol.Collection.<ol.layer.Base>}
   * @protected
   */
  this.olLayers = map.getLayerGroup().getLayers();

  /**
   * @type {ol.layer.Group}
   */
  this.mapLayerGroup = map.getLayerGroup();

  /**
   * Map of ol3 layer ids (from goog.getUid) to the Cesium ImageryLayers.
   * null value means, that we are unable to create equivalent layer.
   * @type {Object.<number, ?T>}
   * @protected
   */
  this.layerMap = {};

  /**
   * Map of listen keys for ol3 layer layers ids (from goog.getUid).
   * @type {!Object.<number, !goog.events.Key>}
   * @private
   */
  this.olLayerListenKeys_ = {};

  /**
   * Map of listen keys for ol3 layer groups ids (from goog.getUid).
   * @type {!Object.<number, !Array.<goog.events.Key>>}
   * @private
   */
  this.olGroupListenKeys_ = {};
};

/**
 * Destroy all and perform complete synchronization of the layers.
 * @api
 */
olcs.AbstractSynchronizer.prototype.synchronize = function() {
  this.destroyAll();
  this.addLayers_(this.mapLayerGroup);
};

/**
 * Populate the foundLayers and foundGroups using a breadth-first algorithm.
 * A map of layer uid to z-index is also populated.
 * @param {ol.layer.Base} layer
 * @param {Array.<ol.layer.Layer>} foundLayers Found leaves.
 * @param {Array.<ol.layer.Group>} foundGroups Found nodes.
 * @param {Object.<number, number>=} opt_zIndices Map of layer uid to z-index.
 * @protected
 */
olcs.AbstractSynchronizer.flattenLayers =
    function(layer, foundLayers, foundGroups, opt_zIndices) {
  if (layer instanceof ol.layer.Group) {
    foundGroups.push(layer);
    var sublayers = layer.getLayers();
    if (goog.isDef(sublayers)) {
      sublayers.forEach(function(el) {
        olcs.AbstractSynchronizer.flattenLayers(el, foundLayers, foundGroups,
            opt_zIndices);
      });
    }
  } else {
    foundLayers.push(layer);
    if (opt_zIndices) {
      opt_zIndices[goog.getUid(layer)] = layer.getZIndex();
    }
  }
};

/**
 * Order counterparts using the same algorithm as the Openlayers renderer:
 * z-index then original sequence order.
 * @protected
 */
olcs.AbstractSynchronizer.prototype.orderLayers = function() {
  // Ordering logics is handled in subclasses.
};

/**
 * Add a layer hierarchy.
 * @param {ol.layer.Base} root
 * @private
 */
olcs.AbstractSynchronizer.prototype.addLayers_ = function(root) {
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers(root, layers, groups);

  layers.forEach(function(olLayer) {
    if (goog.isNull(olLayer)) {
      return;
    }
    var olLayerId = goog.getUid(olLayer);

    // create new layer and set up synchronization
    goog.asserts.assert(!goog.isDef(this.layerMap[olLayerId]));
    var cesiumObject = this.createSingleCounterpart(olLayer);

    // add Cesium layers
    if (!goog.isNull(cesiumObject)) {
      cesiumObject.zIndex = olLayer.getZIndex();
      this.addCesiumObject(cesiumObject);
      this.layerMap[olLayerId] = cesiumObject;
      this.olLayerListenKeys_[olLayerId] = olLayer.on('change:zIndex',
          this.orderLayers, this);
    }
  }, this);

  groups.forEach(function(el) {
    this.listenForGroupChanges_(el);
  }, this);

  this.orderLayers();
};

/**
 * Remove and destroy a single layer.
 * @param {ol.layer.Layer} layer
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeAndDestroySingleLayer_ =
    function(layer) {
  var uid = goog.getUid(layer);
  var counterpart = this.layerMap[uid];
  if (!!counterpart) {
    this.removeSingleCesiumObject(counterpart, false);
    this.destroyCesiumObject(counterpart);
    ol.Observable.unByKey(this.olLayerListenKeys_[uid]);
    delete this.olLayerListenKeys_[uid];
  }
  delete this.layerMap[uid];
};

/**
 * Unlisten a single layer group.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.unlistenSingleGroup_ =
    function(group) {
  if (group === this.mapLayerGroup) {
    return;
  }
  var uid = goog.getUid(group);
  var keys = this.olGroupListenKeys_[uid];
  keys.forEach(function(key) {
    ol.Observable.unByKey(key);
  });
  delete this.olGroupListenKeys_[uid];
};

/**
 * Remove layer hierarchy.
 * @param {ol.layer.Base} root
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeLayer_ = function(root) {
  if (!root) {
    return;
  }
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers(root, layers, groups);

  layers.forEach(function(el) {
    this.removeAndDestroySingleLayer_(el);
  }, this);

  groups.forEach(function(el) {
    this.unlistenSingleGroup_(el);
  }, this);
};

/**
 * Register listeners for single layer group change.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.listenForGroupChanges_ = function(group) {
  var uuid = goog.getUid(group);

  goog.asserts.assert(!goog.isDef(this.olGroupListenKeys_[uuid]));

  var listenKeyArray = [];
  this.olGroupListenKeys_[uuid] = listenKeyArray;

  // only the keys that need to be relistened when collection changes
  var contentKeys = [];
  var listenAddRemove = (function() {
    var collection = group.getLayers();
    if (goog.isDef(collection)) {
      contentKeys = [
        collection.on('add', function(event) {
          this.addLayers_(event.element);
        }, this),
        collection.on('remove', function(event) {
          this.removeLayer_(event.element);
        }, this)
      ];
      listenKeyArray.push.apply(listenKeyArray, contentKeys);
    }
  }).bind(this);

  listenAddRemove();

  listenKeyArray.push(group.on('change:layers', function(e) {
    contentKeys.forEach(function(el) {
      var i = listenKeyArray.indexOf(el);
      if (i >= 0) {
        listenKeyArray.splice(i, 1);
      }
      ol.Observable.unByKey(el);
    });
    listenAddRemove();
  }));
};

/**
 * Destroys all the created Cesium objects.
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyAll = function() {
  this.removeAllCesiumObjects(true); // destroy
  goog.object.forEach(this.olGroupListenKeys, function(keys) {
    keys.forEach(ol.Observable.unByKey);
  });
  goog.object.forEach(this.olLayerListenKeys, ol.Observable.unByKey);
  this.olGroupListenKeys = {};
  this.olLayerListenKeys = {};
  this.layerMap = {};
};

/**
 * Adds a single Cesium object to the collection.
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.addCesiumObject = goog.abstractMethod;

/**
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyCesiumObject = goog.abstractMethod;

/**
 * Remove single Cesium object from the collection.
 * @param {!T} object
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeSingleCesiumObject =
    goog.abstractMethod;

/**
 * Remove all Cesium objects from the collection.
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeAllCesiumObjects =
    goog.abstractMethod;

/**
 * @param {!ol.layer.Layer} olLayer
 * @return {T}
 * @protected
 */
olcs.AbstractSynchronizer.prototype.createSingleCounterpart =
    goog.abstractMethod;
<|endoftext|>


<commit_before>'use strict'

// var request = require('request');
var Q = require('q');
var companySearch = require('../services/company-api-gh');
var postSearch = require('../services/post-api-gh');
var utilities = require('../brain/utilities');

function processRequest(genericRequest) {
  return Q.when(queryCompany(genericRequest))
    .then((queryResult) => {
      return structureGenericResponse(queryResult)
    });
}

/**
 * Processes generic request into Company object, with attached data
 *
 * @param genericRequest
 * @return {queryResult}
 */
function queryCompany(genericRequest) {
  var queryResult = {
    userInput: genericRequest.userInput,
    // Company object, with Posts and Other Companies attached
    data: {},
    // types: 'standard', 'no-input', 'not-found'
    type: '',
    context: genericRequest.context
  };
  var userInput = genericRequest.userInput;
  var company = {};

  // todo: do pre-response here (is this where it belongs?)
  utilities.preResponse(genericRequest.context);

  if (!userInput) {
    queryResult.type = 'no-input';
    return Q.when(queryResult);
  }

  return Q.when(companySearch.findByText(userInput))
  .then(function(companySearchResults) {
    // ----------------
    // Pick out Company of interest from search results
    // ( can we separate out this as function?)
    var exactMatch = companySearchResults.filter(function(eachCompany) {
      return eachCompany.name.toLowerCase() === userInput.toLowerCase();
    });

    if (!companySearchResults.length) {
      console.log('Nothing found in initial Company search');
      queryResult.type = 'nothing-found';
      // returning an empty object as Posts for the next step in chain
      return Q.when({});
    }
    else if (exactMatch && exactMatch.length) {
      company = exactMatch[0];
      console.log('Found an exact match from Companies search');
    }
    else {
      company = companySearchResults[0];
      console.log("Going with first result from Companies search");
    };
    // ----------------

    console.log('About to attach other companies list');
    company = attachOtherCompanies(company, companySearchResults, userInput);

    console.log('Other companies attached, about to query Posts of Company');
    return postSearch.findByCompany(company)
  })
  .then(function(posts) {
    console.log('Posts of Company returned in action handler');
    queryResult.type = 'standard';
    company.posts = posts;
    queryResult.data = company;
    // console.log('Result of API queries: ' + JSON.stringify(queryResult));
    return queryResult;
  });
}

function structureGenericResponse(queryResult) {
  var genericResponse = {
    userInput: queryResult.userInput,
    data: {
      name: queryResult.data.name || '' ,
      contactMethods: {
        phone: '',
        email: '',
        twitter: '',
        web: '',
        chat: '',
        facebook: ''
      },
      posts: [],
      otherCompanies: queryResult.data.otherCompanies
    },
    type: queryResult.type || '',
    context: queryResult.context || ''
  }
// Extract contact methods:
  genericResponse.data.contactMethods = extractContactMethods(queryResult.data);
// Extract Posts info
  var posts = queryResult.data.posts;
  if (posts && posts.length) {
    genericResponse.data.posts = posts.map((post) => {
      return {
        title: post.title || '',
        urlId: post.urlId || '',
      };
    })
  };
  return genericResponse;
}

// Should this exist in another module?
function attachOtherCompanies(company, companySearchResults, userInput) {
    var companyNames = companySearchResults.map((eachCompany) => {
      return eachCompany.name;
    });
    console.log("List of company names: " + JSON.stringify(companyNames));
    company.otherCompanies = companyNames.filter((name) => {
      return name.toLowerCase() !== userInput.toLowerCase();
    });
    return company;
};

// takes Company object, puts associated info into an result object
// should this exist in another module?
function extractContactMethods(queryResultData) {
  var contactInfo = {
    phone: '',
    email: '',
    twitter: '',
    web: '',
    chat: '',
    facebook: ''
  };
  var company = queryResultData;
  contactInfo.phone = company.callback.phone || '';

  let emailContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "email";
    });
  contactInfo.email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';

  let twitterContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "twitter";
    });
  contactInfo.twitter = (twitterContactMethods && twitterContactMethods.length) ? twitterContactMethods[0].target : '';

  let webContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "web";
    });
  contactInfo.web = (webContactMethods && webContactMethods.length) ? webContactMethods[0].target : '';

  let chatContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "chat";
    });
  contactInfo.chat = (chatContactMethods && chatContactMethods.length) ? chatContactMethods[0].target : '';

  let facebookContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "facebook";
    });
  contactInfo.facebook = (facebookContactMethods && facebookContactMethods.length) ? facebookContactMethods[0].target : '';

  console.log("Extracted contact info from company: " + JSON.stringify(contactInfo));
  return contactInfo;
}

module.exports = {
  processRequest: processRequest
};
<commit_msg>debug<commit_after>'use strict'

// var request = require('request');
var Q = require('q');
var companySearch = require('../services/company-api-gh');
var postSearch = require('../services/post-api-gh');
var utilities = require('../brain/utilities');

function processRequest(genericRequest) {
  return Q.when(queryCompany(genericRequest))
    .then((queryResult) => {
      return structureGenericResponse(queryResult)
    });
}

/**
 * Processes generic request into Company object, with attached data
 *
 * @param genericRequest
 * @return {queryResult}
 */
function queryCompany(genericRequest) {
  var queryResult = {
    userInput: genericRequest.userInput,
    // Company object, with Posts and Other Companies attached
    data: {},
    // types: 'standard', 'no-input', 'not-found'
    type: '',
    context: genericRequest.context
  };
  var userInput = genericRequest.userInput;
  var company = {};

  // todo: do pre-response here (is this where it belongs?)
  utilities.preResponse(genericRequest.context);

  if (!userInput) {
    queryResult.type = 'no-input';
    return Q.when(queryResult);
  }

  return Q.when(companySearch.findByText(userInput))
  .then(function(companySearchResults) {
    // ----------------
    // Pick out Company of interest from search results
    // ( can we separate out this as function?)
    var exactMatch = companySearchResults.filter(function(eachCompany) {
      return eachCompany.name.toLowerCase() === userInput.toLowerCase();
    });

    if (!companySearchResults.length) {
      console.log('Nothing found in initial Company search');
      queryResult.type = 'nothing-found';
      // returning an empty object as Posts for the next step in chain
      return Q.when({});
    }
    else if (exactMatch && exactMatch.length) {
      company = exactMatch[0];
      console.log('Found an exact match from Companies search');
    }
    else {
      company = companySearchResults[0];
      console.log("Going with first result from Companies search");
    };
    // ----------------

    console.log('About to attach other companies list');
    company = attachOtherCompanies(company, companySearchResults, userInput);

    console.log('Other companies attached, about to query Posts of Company');
    return postSearch.findByCompany(company)
  })
  .then(function(posts) {
    console.log('Posts of Company returned in action handler');
    queryResult.type = 'standard';
    company.posts = posts;
    queryResult.data = company;
    // console.log('Result of API queries: ' + JSON.stringify(queryResult));
    return queryResult;
  });
}

function structureGenericResponse(queryResult) {
  var genericResponse = {
    userInput: queryResult.userInput,
    data: {
      name: queryResult.data.name || '' ,
      contactMethods: {
        phone: '',
        email: '',
        twitter: '',
        web: '',
        chat: '',
        facebook: ''
      },
      posts: [],
      otherCompanies: queryResult.data.otherCompanies
    },
    type: queryResult.type || '',
    context: queryResult.context || ''
  }
  // return early if no data or no input
  if ((queryResult.type === 'no-input') || (queryResult.type === 'nothing-found')) {
    return genericResponse;
  }
// Extract contact methods:
  genericResponse.data.contactMethods = extractContactMethods(queryResult.data);
// Extract Posts info
  var posts = queryResult.data.posts;
  if (posts && posts.length) {
    genericResponse.data.posts = posts.map((post) => {
      return {
        title: post.title || '',
        urlId: post.urlId || '',
      };
    })
  };
  return genericResponse;
}

// Should this exist in another module?
function attachOtherCompanies(company, companySearchResults, userInput) {
    var companyNames = companySearchResults.map((eachCompany) => {
      return eachCompany.name;
    });
    console.log("List of company names: " + JSON.stringify(companyNames));
    company.otherCompanies = companyNames.filter((name) => {
      return name.toLowerCase() !== userInput.toLowerCase();
    });
    return company;
};

// takes Company object, puts associated info into an result object
// should this exist in another module?
function extractContactMethods(queryResultData) {
  var contactInfo = {
    phone: '',
    email: '',
    twitter: '',
    web: '',
    chat: '',
    facebook: ''
  };
  var company = queryResultData;
  contactInfo.phone = company.callback.phone || '';

  let emailContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "email";
    });
  contactInfo.email = (emailContactMethods && emailContactMethods.length) ? emailContactMethods[0].target : '';

  let twitterContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "twitter";
    });
  contactInfo.twitter = (twitterContactMethods && twitterContactMethods.length) ? twitterContactMethods[0].target : '';

  let webContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "web";
    });
  contactInfo.web = (webContactMethods && webContactMethods.length) ? webContactMethods[0].target : '';

  let chatContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "chat";
    });
  contactInfo.chat = (chatContactMethods && chatContactMethods.length) ? chatContactMethods[0].target : '';

  let facebookContactMethods = company.contactMethods.filter(function (method) {
        return method.type === "facebook";
    });
  contactInfo.facebook = (facebookContactMethods && facebookContactMethods.length) ? facebookContactMethods[0].target : '';

  console.log("Extracted contact info from company: " + JSON.stringify(contactInfo));
  return contactInfo;
}

module.exports = {
  processRequest: processRequest
};
<|endoftext|>


<commit_before>const magicForm = (function(){
  const $formInput    = $("input");
  const $inputLabel   = $(".input-label");
  const $textArea     = $("textarea");

  const $selectYears  = $("select.year");
  const $selectMonths = $("select.month");
  const $selectDays   = $("select.day");

  const dateMap = {
    1  : 31,
    2  : 28,
    3  : 31,
    4  : 30,
    5  : 31,
    6  : 30,
    7  : 31,
    8  : 31,
    9  : 30,
    10 : 31,
    11 : 30,
    12 : 31
  };

  /*
   * initializes magicForm.
   * - autoresize textareas
   * - customizable actions for input focusing and blurring
   */
  const init = function main() {

    /*
     * ajusts height of textarea automatically
     */
    const h = function adjustHeight(e) {
      $(e).css({'height':'auto','overflow-y':'hidden'}).height(e.scrollHeight);
    }

    $textArea.each( function() {
      h(this);
    }).on('input',  function() {
      h(this);
    }).on('focus blur', ( e ) => {
      const $input = $( e.target );
      const $label = $input.prev(".input-label");

      if( $input.val() != "" )
        return;

      $label.toggleClass("active");
    });

    $formInput.on("focus blur", ( e ) => {
      const $input = $( e.target );
      const $label = $input.prev(".input-label");

      if( $input.val() != "" )
        return;

      $label.toggleClass("active");
    })

    $inputLabel.on("click", ( e ) => {
      const $label = $( e.target );
      const $inputSibling = $label.next("input");

      if( !$inputSibling.length ) $label.next("textarea").focus();

      $inputSibling.focus();
      $label.addClass("active");
    })
  }

  const dateDropDowns = function initializeDateDropDowns() {
    const yearRange = $selectYears[0].dataset.yearRange.split('-');
    let start = yearRange[0];
    let end   = yearRange[1];
    console.log("If there is more than one select.years in this document, you may experience unexpected behavior.")

    $selectYears.append("<option value='default'>Year</option>");
    for( let i = end; i >= start; i-- ) {
      $selectYears.append(`<option value=${ i }>${ i }</option>`);
    }

    $selectMonths.append("<option value='default'>Month</option>");
    for( let i = 1; i < 13; i++ ) {
      $selectMonths.append(`<option value='${ i }'>${ i }</option>`);
    }

    /*
     * $element - jQuery element - select to be updated
     * noOfDays - number - number of days in said month
     *                   - calculated from dateMap
     */
    const updateDays = function changeDaySelectValues( $element, noOfDays ) {
      $element.html("");
      $element.append("<option value='default'>Day</option>")
      for( let i = 1; i < noOfDays + 1; i++ ) {
        $element.append(`<option value='${ i }'>${ i }</option>`);
      }
    }

    // initialize all select.days to have 31 days until specified otherwise.
    updateDays( $selectDays, 31 );

    $selectMonths.on("change", ( e ) => {
      const $changed = $( e.target );

      const month    = $changed.val();
      const noOfDays = dateMap[ month ];

      const $select   = $( "#" + $changed.attr("for") );

      updateDays( $select, noOfDays );
    })
  }

  /*
   * validates form input.
   * $input - jQuery element - input to be validated
   * type   - string - _email, phone number, password_ - type of input
   * fail   - function - action to take on fail
   */
  const validate = function validateFormInput( $input, type, fail = ( $failedInput, warning ) => {

      const $containerDiv = $failedInput.parent();
      $containerDiv.children(".warning").remove();

      $containerDiv.append(`<p class='warning'>${ warning }</p>`);
      return false;
    }, success = ( $failedInput ) => {
      const $warning = $failedInput.parent(".input").children(".warning");

      $warning.remove();
      return true;
    }) {
    const value = $input.val();

    if ( value == undefined )
      console.error("Failed to find value for element:", $failedInput );

    switch( type ) {
      case "email" :
        const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

        if( value == "" ){
          const warning = "This is a required field.";
          return fail( $input, warning );
        }
        else if ( !re.test( value ) ) {
          const warning = "Email is not valid!";
          return fail( $input, warning );
        }

        return success( $input );
        break;

      case "password" :
        const length = value.length;

        if( length < 6 ) {
          fail( $input, "Password must be at least 6 characters" );
          return;
        }

        if( $input[0].dataset.confirmPassword != undefined ) {
          const $password = $("#" + $input[0].dataset.confirmPassword );

          if( $password.val() != $input.val() ) {
            fail( $input, "Passwords do not match!" );
            return;
          }
        }
        success( $input );
        break;

      case 'text':

        const textRegEx = /^[0-9a-zA-Z.,! ]+$/;

          if( value.match( textRegEx )){
            return success( $input );
          }
          else if( value === "" ){
            return fail( $input, "This is a required field.");
          }
          else {
            return fail( $input, "invalid characters used, please try again" );
          }
        break;

      case "phone number" :
          let number = parseInt(value, 10)

          let allNumbers = true
          for( i in number ) {
            if ( typeof(number[i]) != number) {
              console.log( number[i] + " " + "is" + " " + "Not a Number")
              allNumbers = false;
            }
          }
          if ( value.length != 10 ) {
            return false;
          }
          else if( !allNumbers ) {
            return false;
          }
          else {
            return true;
          }
          break;

      default :
        return "unable to determine type";
        break;
    }
  }

  /*
   * Create a custom submit for $form.
   */
  const ajaxSubmit = function submitFormWithAjax( $form ) {
    $form.submit(( e ) => {
      e.preventDefault();

      const $thisForm = $( e.target );
      const data = $thisForm.serializeArray();
      const url  = $thisForm.attr("action");

      console.log( data );
      $.ajax({
        type: "POST",
        url : url,
        data: data,
      });
    })
  }

  return {
    init: init,
    dateDropDowns: dateDropDowns,
    validate: validate,
    ajaxSubmit: ajaxSubmit,
  }
}());
<commit_msg>Fixed double init() bug<commit_after>const magicForm = (function(){
  const $formInput    = $("input");
  const $inputLabel   = $(".input-label");
  const $textArea     = $("textarea");

  const $selectYears  = $("select.year");
  const $selectMonths = $("select.month");
  const $selectDays   = $("select.day");

  const dateMap = {
    1  : 31,
    2  : 28,
    3  : 31,
    4  : 30,
    5  : 31,
    6  : 30,
    7  : 31,
    8  : 31,
    9  : 30,
    10 : 31,
    11 : 30,
    12 : 31
  };

  /*
   * initializes magicForm.
   * - autoresize textareas
   * - customizable actions for input focusing and blurring
   */
  const init = function main() {

    /*
     * ajusts height of textarea automatically
     */
    const h = function adjustHeight(e) {
      $(e).css({'height':'auto','overflow-y':'hidden'}).height(e.scrollHeight);
    }

    $textArea.each( function() {
      h(this);
    }).on('input',  function() {
      h(this);
    }).on('focus blur', ( e ) => {
      const $input = $( e.target );
      const $label = $input.prev(".input-label");

      if( $input.val() != "" )
        return;

      $label.toggleClass("active");
    });

    $formInput.off('focus blur'); // if a user calls magicFrom.init() twice, this will fire two times, opening and minimizing the input on focus

    $formInput.on("focus blur", ( e ) => {
      const $input = $( e.target );
      const $label = $input.prev(".input-label");

      if( $input.val() != "" )
        return;

      $label.toggleClass("active");
    })

    $inputLabel.on("click", ( e ) => {
      const $label = $( e.target );
      const $inputSibling = $label.next("input");

      if( !$inputSibling.length ) $label.next("textarea").focus();

      $inputSibling.focus();
      $label.addClass("active");
    })
  }

  const dateDropDowns = function initializeDateDropDowns() {
    const yearRange = $selectYears[0].dataset.yearRange.split('-');
    let start = yearRange[0];
    let end   = yearRange[1];
    console.log("If there is more than one select.years in this document, you may experience unexpected behavior.")

    $selectYears.append("<option value='default'>Year</option>");
    for( let i = end; i >= start; i-- ) {
      $selectYears.append(`<option value=${ i }>${ i }</option>`);
    }

    $selectMonths.append("<option value='default'>Month</option>");
    for( let i = 1; i < 13; i++ ) {
      $selectMonths.append(`<option value='${ i }'>${ i }</option>`);
    }

    /*
     * $element - jQuery element - select to be updated
     * noOfDays - number - number of days in said month
     *                   - calculated from dateMap
     */
    const updateDays = function changeDaySelectValues( $element, noOfDays ) {
      $element.html("");
      $element.append("<option value='default'>Day</option>")
      for( let i = 1; i < noOfDays + 1; i++ ) {
        $element.append(`<option value='${ i }'>${ i }</option>`);
      }
    }

    // initialize all select.days to have 31 days until specified otherwise.
    updateDays( $selectDays, 31 );

    $selectMonths.on("change", ( e ) => {
      const $changed = $( e.target );

      const month    = $changed.val();
      const noOfDays = dateMap[ month ];

      const $select   = $( "#" + $changed.attr("for") );

      updateDays( $select, noOfDays );
    })
  }

  /*
   * validates form input.
   * $input - jQuery element - input to be validated
   * type   - string - _email, phone number, password_ - type of input
   * fail   - function - action to take on fail
   */
  const validate = function validateFormInput( $input, type, fail = ( $failedInput, warning ) => {

      const $containerDiv = $failedInput.parent();
      $containerDiv.children(".warning").remove();

      $containerDiv.append(`<p class='warning'>${ warning }</p>`);
      return false;
    }, success = ( $failedInput ) => {
      const $warning = $failedInput.parent(".input").children(".warning");

      $warning.remove();
      return true;
    }) {
    const value = $input.val();

    if ( value == undefined )
      console.error("Failed to find value for element:", $failedInput );

    switch( type ) {
      case "email" :
        const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

        if( value == "" ){
          const warning = "This is a required field.";
          return fail( $input, warning );
        }
        else if ( !re.test( value ) ) {
          const warning = "Email is not valid!";
          return fail( $input, warning );
        }

        return success( $input );
        break;

      case "password" :
        const length = value.length;

        if( length < 6 ) {
          fail( $input, "Password must be at least 6 characters" );
          return;
        }

        if( $input[0].dataset.confirmPassword != undefined ) {
          const $password = $("#" + $input[0].dataset.confirmPassword );

          if( $password.val() != $input.val() ) {
            fail( $input, "Passwords do not match!" );
            return;
          }
        }
        success( $input );
        break;

      case 'text':

        const textRegEx = /^[0-9a-zA-Z.,! ]+$/;

          if( value.match( textRegEx )){
            return success( $input );
          }
          else if( value === "" ){
            return fail( $input, "This is a required field.");
          }
          else {
            return fail( $input, "invalid characters used, please try again" );
          }
        break;

      case "phone number" :
          let number = parseInt(value, 10)

          let allNumbers = true
          for( i in number ) {
            if ( typeof(number[i]) != number) {
              console.log( number[i] + " " + "is" + " " + "Not a Number")
              allNumbers = false;
            }
          }
          if ( value.length != 10 ) {
            return false;
          }
          else if( !allNumbers ) {
            return false;
          }
          else {
            return true;
          }
          break;

      default :
        return "unable to determine type";
        break;
    }
  }

  /*
   * Create a custom submit for $form.
   */
  const ajaxSubmit = function submitFormWithAjax( $form ) {
    $form.submit(( e ) => {
      e.preventDefault();

      const $thisForm = $( e.target );
      const data = $thisForm.serializeArray();
      const url  = $thisForm.attr("action");

      console.log( data );
      $.ajax({
        type: "POST",
        url : url,
        data: data,
      });
    })
  }

  return {
    init: init,
    dateDropDowns: dateDropDowns,
    validate: validate,
    ajaxSubmit: ajaxSubmit,
  }
}());
<|endoftext|>


<commit_before>define(
[
  "dojo/_base/lang",
  "dojo/_base/json",
  "dojo/_base/url",
  "dojo/cookie",
  "dojo/Deferred",
  "dojo/io-query",
  "esri/IdentityManager"
],
function(lang, dojoJson, Url, cookie, Deferred, ioquery, idManager) {
    var OAuthHelper = {
        portal: "http://www.arcgis.com",
        init: function(parameters) {
            /**
             * parameters = {
             *   appId:       "<String>",
             *   portal:      "<String>", // deafult is "http://www.arcgis.com"
             *   expiration:   <Number> // in minutes
             * }
             */
            lang.mixin(this, parameters);
            this.portalUrl = this.portal + "/sharing/rest";
            // Read OAuth response from the page url fragment if available,
            // and register with identity manager
            this.checkOAuthResponse(window.location.href, true);
            // Read token from cookie if available, and register
            // with identity manager
            this.checkCookie();
            // You don't need this if you require your users to sign-in
            // before using the app. This override helps trigger OAuth
            // flow instead of the legacy generateToken flow.
            this.overrideIdentityManager();
        },
        isSignedIn: function() {
            return !!idManager.findCredential(this.portalUrl);
        },
        signIn: function() {
            var deferred = (this.deferred = new Deferred());
            var authParameters = {
                client_id: this.appId,
                response_type: "token",
                expiration: this.expiration // in minutes. Default is 30.
            };
            //if there are url params append the auth parameters with an &
            var redirect_uri, l = window.location.href;
            if (l.indexOf("?") > 0) {
                redirect_uri = window.location.href.replace(/#.*$/, "") + "&";
            } else {
                redirect_uri = window.location.href.replace(/#.*$/, "");
            }
            authParameters.redirect_uri = redirect_uri;
            var authUrl = this.portal.replace(/^http:/i, "https:") + "/sharing/oauth2/authorize?" + ioquery.objectToQuery(authParameters);
            window.location = authUrl;
            return deferred;
        },
        signOut: function() {
            // Delete the cookie
            cookie("arcgis_auth", null, {
                expires: -1,
                path: "/",
                domain: document.domain
            });
            window.location.reload();
        },
        checkOAuthResponse: function(url, clearHash) {
            // This method will be called from popup callback page as well
            var oauthResponse = this.parseFragment(url);
            if (oauthResponse) {
                if (clearHash) { // redirection flow
                    // Remove OAuth bits from the URL fragment
                    window.location.hash = "";
                }
                if (oauthResponse.error) {
                    var error = new Error(oauthResponse.error);
                    error.details = [oauthResponse.error_description];
                    if (this.deferred) {
                        this.deferred.reject(error);
                    }
                } else {
                    var credential = this.registerToken(oauthResponse);
                    // User checked "Keep me signed in" option
                    if (oauthResponse.persist) {
                        cookie("arcgis_auth", dojoJson.toJson(oauthResponse), {
                            expires: new Date(oauthResponse.expires_at),
                            path: "/",
                            domain: document.domain
                        });
                        console.log("[Cookie] Write: ", cookie("arcgis_auth"));
                    }
                    if (this.deferred) {
                        this.deferred.resolve(credential);
                    }
                }
            }
        },
        checkCookie: function() {
            var ckie = cookie("arcgis_auth");
            if (ckie) {
                console.log("[Cookie] Read: ", ckie);
                var oauthResponse = dojoJson.fromJson(ckie);
                this.registerToken(oauthResponse);
            }
        },
        registerToken: function(oauthResponse) {
            // Register the access token with Identity Manager, so that
            // it can be added to all ArcGIS Online REST API requests
            idManager.registerToken({
                server: this.portalUrl,
                userId: oauthResponse.username,
                token: oauthResponse.access_token,
                expires: oauthResponse.expires_at,
                ssl: oauthResponse.ssl
            });
            var credential = idManager.findCredential(this.portalUrl, oauthResponse.username);
            console.log("Token registered with Identity Manager: ", credential);
            return credential;
        },
        parseFragment: function(url) {
            var urlObj = new Url(url),
                fragment = urlObj.fragment ? ioquery.queryToObject(urlObj.fragment) : null;
            if (fragment) {
                if (fragment.access_token) {
                    console.log("[OAuth Response]: ", fragment);
                    // Convert from String to Number
                    fragment.expires_in = Number(fragment.expires_in);
                    // Calculate universal time
                    fragment.expires_at = (new Date()).getTime() + (fragment.expires_in * 1000);
                    fragment.ssl = (fragment.ssl === "true");
                } else if (fragment.error) {
                    console.log("[OAuth Error]: ", fragment.error, " - ", fragment.error_description);
                }
                return fragment;
            }
        },
        overrideIdentityManager: function() {
            var signInMethod = idManager.signIn,
                helper = this;
            idManager.signIn = function(resUrl, serverInfo, options) {
                return (serverInfo.server.indexOf(".arcgis.com") !== -1) ?
                // OAuth flow
                helper.signIn() :
                // generateToken flow
                signInMethod.apply(this, arguments);
            };
        }
    };
    window.OAuthHelper = OAuthHelper;
    return OAuthHelper;
});
<commit_msg>Special handling of 'localhost' for the auth cookie.<commit_after>define(
[
  "dojo/_base/lang",
  "dojo/_base/json",
  "dojo/_base/url",
  "dojo/cookie",
  "dojo/Deferred",
  "dojo/io-query",
  "esri/IdentityManager"
],
function(lang, dojoJson, Url, cookie, Deferred, ioquery, idManager) {
    var OAuthHelper = {
        portal: "http://www.arcgis.com",
        init: function(parameters) {
            /**
             * parameters = {
             *   appId:       "<String>",
             *   portal:      "<String>", // deafult is "http://www.arcgis.com"
             *   expiration:   <Number> // in minutes
             * }
             */
            lang.mixin(this, parameters);
            this.portalUrl = this.portal + "/sharing/rest";
            // Read OAuth response from the page url fragment if available,
            // and register with identity manager
            this.checkOAuthResponse(window.location.href, true);
            // Read token from cookie if available, and register
            // with identity manager
            this.checkCookie();
            // You don't need this if you require your users to sign-in
            // before using the app. This override helps trigger OAuth
            // flow instead of the legacy generateToken flow.
            this.overrideIdentityManager();
        },
        isSignedIn: function() {
            return !!idManager.findCredential(this.portalUrl);
        },
        signIn: function() {
            var deferred = (this.deferred = new Deferred());
            var authParameters = {
                client_id: this.appId,
                response_type: "token",
                expiration: this.expiration // in minutes. Default is 30.
            };
            //if there are url params append the auth parameters with an &
            var redirect_uri, l = window.location.href;
            if (l.indexOf("?") > 0) {
                redirect_uri = window.location.href.replace(/#.*$/, "") + "&";
            } else {
                redirect_uri = window.location.href.replace(/#.*$/, "");
            }
            authParameters.redirect_uri = redirect_uri;
            var authUrl = this.portal.replace(/^http:/i, "https:") + "/sharing/oauth2/authorize?" + ioquery.objectToQuery(authParameters);
            window.location = authUrl;
            return deferred;
        },
        signOut: function() {
            // Delete the cookie
            cookie("arcgis_auth", null, {
                expires: -1,
                path: "/",
                domain: document.domain
            });
            window.location.reload();
        },
        checkOAuthResponse: function(url, clearHash) {
            // This method will be called from popup callback page as well
            var oauthResponse = this.parseFragment(url);
            if (oauthResponse) {
                if (clearHash) { // redirection flow
                    // Remove OAuth bits from the URL fragment
                    window.location.hash = "";
                }
                if (oauthResponse.error) {
                    var error = new Error(oauthResponse.error);
                    error.details = [oauthResponse.error_description];
                    if (this.deferred) {
                        this.deferred.reject(error);
                    }
                } else {
                    var credential = this.registerToken(oauthResponse);
                    // User checked "Keep me signed in" option
                    if (oauthResponse.persist) {
                        if (document.domain === "localhost") {
                            // Do not include the domain because "localhost" won't work. See http://stackoverflow.com/a/489465
                            cookie("arcgis_auth", dojoJson.toJson(oauthResponse), {
                                expires: new Date(oauthResponse.expires_at),
                                path: "/"
                            });
                        }
                        else {
                            // Include the domain
                            cookie("arcgis_auth", dojoJson.toJson(oauthResponse), {
                                expires: new Date(oauthResponse.expires_at),
                                path: "/",
                                domain: document.domain
                            });
                        }
                        console.log("[Cookie] Write: ", cookie("arcgis_auth"));
                    }
                    if (this.deferred) {
                        this.deferred.resolve(credential);
                    }
                }
            }
        },
        checkCookie: function() {
            var ckie = cookie("arcgis_auth");
            if (ckie) {
                console.log("[Cookie] Read: ", ckie);
                var oauthResponse = dojoJson.fromJson(ckie);
                this.registerToken(oauthResponse);
            }
        },
        registerToken: function(oauthResponse) {
            // Register the access token with Identity Manager, so that
            // it can be added to all ArcGIS Online REST API requests
            idManager.registerToken({
                server: this.portalUrl,
                userId: oauthResponse.username,
                token: oauthResponse.access_token,
                expires: oauthResponse.expires_at,
                ssl: oauthResponse.ssl
            });
            var credential = idManager.findCredential(this.portalUrl, oauthResponse.username);
            console.log("Token registered with Identity Manager: ", credential);
            return credential;
        },
        parseFragment: function(url) {
            var urlObj = new Url(url),
                fragment = urlObj.fragment ? ioquery.queryToObject(urlObj.fragment) : null;
            if (fragment) {
                if (fragment.access_token) {
                    console.log("[OAuth Response]: ", fragment);
                    // Convert from String to Number
                    fragment.expires_in = Number(fragment.expires_in);
                    // Calculate universal time
                    fragment.expires_at = (new Date()).getTime() + (fragment.expires_in * 1000);
                    fragment.ssl = (fragment.ssl === "true");
                } else if (fragment.error) {
                    console.log("[OAuth Error]: ", fragment.error, " - ", fragment.error_description);
                }
                return fragment;
            }
        },
        overrideIdentityManager: function() {
            var signInMethod = idManager.signIn,
                helper = this;
            idManager.signIn = function(resUrl, serverInfo, options) {
                return (serverInfo.server.indexOf(".arcgis.com") !== -1) ?
                // OAuth flow
                helper.signIn() :
                // generateToken flow
                signInMethod.apply(this, arguments);
            };
        }
    };
    window.OAuthHelper = OAuthHelper;
    return OAuthHelper;
});
<|endoftext|>


<commit_before>'use strict';

angular.module('openspecimen', [
  'ngMessages',
  'ngSanitize',
  'ui.router',
  'ui.bootstrap',
  'ui.mask',
  'ui.select',
  'mgcrea.ngStrap.popover',
  'angular-loading-bar'])

  .config(function($stateProvider, $urlRouterProvider, $httpProvider, uiSelectConfig, ApiUrlsProvider) {
    $stateProvider
      .state('login', {
        url: '/',
        templateUrl: 'modules/user/signin.html',
        controller: 'LoginCtrl'
      })
      .state('signed-in', {
        abstract: true,
        templateUrl: 'modules/common/appmenu.html'
      })
      .state('cps', {
        url: '/cps',
        abstract: true,
        template: '

',
        parent: 'signed-in'
      })
      .state('cp-list', {
        url: '',
        templateUrl: 'modules/biospecimen/cp/list.html',
        controller: 'CpListCtrl',
        parent: 'cps'
      })
      .state('cp-detail', {
        url: '/:cpId',
        templateUrl: 'modules/biospecimen/cp/detail.html',
        parent: 'cps',
        resolve: {
          cp: function($stateParams, CollectionProtocolService) {
            return CollectionProtocolService.getCp($stateParams.cpId).then(
              function(result) {
                return result.data;
              }
            );
          }
        },
        controller: function($scope, $state, cp) {
          $scope.cp = cp;
        },
        breadcrumb: {
          title: '{{cp.title}}',
          state: 'cp-detail.overview'
        }
      })
      .state('cp-detail.overview', {
        url: '/overview',
        templateUrl: 'modules/biospecimen/cp/overview.html',
        parent: 'cp-detail'
      })
      .state('cp-detail.consents', {
        url: '/consents',
        templateUrl: 'modules/biospecimen/cp/consents.html',
        parent: 'cp-detail'
      })
      .state('cp-detail.events', {
        url: '/events',
        templateUrl: 'modules/biospecimen/cp/events.html',
        parent: 'cp-detail'
      })
      .state('participant-list', {
        url: '/participants',
        templateUrl: 'modules/biospecimen/participant/list.html',
        controller: 'ParticipantListCtrl',
        parent: 'cp-detail'
      })
      .state('participant-new', {
        url: '/new-participant',
        templateProvider: function($stateParams, CpConfigSvc) {
          var tmpl = CpConfigSvc.getRegParticipantTmpl($stateParams.cpId);
          return '

';
        },
        controllerProvider: function($stateParams, CpConfigSvc) {
          return CpConfigSvc.getRegParticipantCtrl($stateParams.cpId);
        },
        parent: 'cp-detail'
      })
      .state('participant-detail', {
        url: '/participants/:cprId',
        templateUrl: 'modules/biospecimen/participant/detail.html',
        resolve: {
          cpr: function($stateParams, CprService) {
            return CprService.getRegistration($stateParams.cprId).then(
              function(result) {
                return result.data;
              }
            );
          },

          visits: function($stateParams, CprService) {
            return CprService.getVisits($stateParams.cprId, true).then(
              function(result) {
                return result.data;
              }
            );
          }
        },
        controller: 'ParticipantDetailCtrl',
        parent: 'signed-in'
      })
      .state('participant-detail.overview', {
        url: '/overview',
        templateUrl: 'modules/biospecimen/participant/overview.html',
        controller: function() {
        },
        parent: 'participant-detail'
      })
      .state('participant-detail.consents', {
        url: '/consents',
        templateUrl: 'modules/biospecimen/participant/consents.html',
        controller: function() {
        },
        parent: 'participant-detail'
      })
      .state('participant-detail.visits', {
        url: '/visits',
        templateUrl: 'modules/biospecimen/participant/visits.html',
        controller: function($scope, cpr, visits) {
          $scope.cpr = cpr;
          $scope.visits = visits;
        },
        parent: 'participant-detail'
      })
      .state('participant-detail.specimens', {
        url: '/?eventId&visitId',
        templateUrl: 'modules/biospecimen/participant/specimens.html',
        resolve: {
          specimens: function($stateParams, CprService) {
            var visitDetail = {visitId: $stateParams.visitId, eventId: $stateParams.eventId};
            return CprService.getVisitSpecimensTree($stateParams.cprId, visitDetail).then(
              function(result) {
                return result.data;
              }
            );
          }
        },
        controller: 'ParticipantSpecimensTreeCtrl',
        parent: 'participant-detail.visits'
      })
      .state('participant-detail.annotations', {
        url: '/annotations',
        templateUrl: 'modules/biospecimen/participant/annotations.html',
        controller: function() {
        },
        parent: 'participant-detail'
      });

    $urlRouterProvider.otherwise('/');

    //$httpProvider.interceptors.push('httpRespInterceptor');

    ApiUrlsProvider.hostname = "localhost"; // used for testing purpose
    ApiUrlsProvider.port = 9090;
    ApiUrlsProvider.secure = false;
    ApiUrlsProvider.app = "/openspecimen";
    ApiUrlsProvider.urls = {
      'sessions': '/rest/ng/sessions',
      'collection-protocols': '/rest/ng/collection-protocols',
      'cprs': '/rest/ng/collection-protocol-registrations',
      'participants': '/rest/ng/participants',
      'sites': '/rest/ng/sites',
      'form-files': '/rest/ng/form-files'
    };

    uiSelectConfig.theme = 'bootstrap';
  })
  .factory('httpRespInterceptor', function($q, $injector, $window) {
    return {
      request: function(result) {
        return result;
      },

      requestError: function(result) {
        $q.reject(result);
      },

      response: function(result) {
        return result;
      },

      responseError: function(result) {
        if (result.status == 401) {
          $window.localStorage['osAuthToken'] = '';
          $injector.get('$state').go('login'); // using injector to get rid of circular dependencies
        }

        $q.reject(result);
      }
    };
  })
  .factory('ApiUtil', function($window, $http) {
    return {
      processResp: function(result) {
        var response = {};
        if (result.status / 100 == 2) {
          response.status = "ok";
        } else if (result.status / 100 == 4) {
          response.status = "user_error";
        } else if (result.status / 100 == 5) {
          response.status = "server_error";
        }

        response.data = result.data;
        return response;
      },

      initialize: function(token) {
        if (!token) {
          token = $window.localStorage['osAuthToken'];
          if (!token) {
            return;
          }

          //token = JSON.parse(token);
        }

        $http.defaults.headers.common['X-OS-API-TOKEN'] = token;
        $http.defaults.withCredentials = true;
        //$cookieStore.put('JESSIONID', token);
        //$http.defaults.headers.common['Cookie'] = 'JSESSIONID=' + token;
      }
    };
  })
  .provider('ApiUrls', function() {
    var that = this;

    this.hostname = "";
    this.port = "";
    this.secure = false;
    this.app = "";
    this.urls = {};

    this.$get = function() {
      return {
        hostname: that.hostname,
        port    : that.port,
        secure  : that.secure,
        app     : that.app,
        urls    : that.urls,

        getUrl  : function(key) {
          var url = '';
          if (key) {
            url = this.urls[key];
          }

          var prefix = "";
          if (this.hostname) {
            var protocol = this.secure ? 'https://' : 'http://';
            prefix = protocol + this.hostname + ":" + this.port;
          }

          return prefix + this.app + url;
        }
      };
    }
  })
  .run(function($rootScope, $window, ApiUtil) {
    if ($window.localStorage['osAuthToken']) {
      $rootScope.loggedIn = true;
    }

    ApiUtil.initialize();

    $rootScope.$on('$stateChangeSuccess',
      function(event, toState, toParams, fromState, fromParams) {
        $rootScope.state = toState;
      });
  });
<commit_msg>Show specimen tree of first visit by default<commit_after>'use strict';

angular.module('openspecimen', [
  'ngMessages',
  'ngSanitize',
  'ui.router',
  'ui.bootstrap',
  'ui.mask',
  'ui.select',
  'mgcrea.ngStrap.popover',
  'angular-loading-bar'])

  .config(function($stateProvider, $urlRouterProvider, $httpProvider, uiSelectConfig, ApiUrlsProvider) {
    $stateProvider
      .state('login', {
        url: '/',
        templateUrl: 'modules/user/signin.html',
        controller: 'LoginCtrl'
      })
      .state('signed-in', {
        abstract: true,
        templateUrl: 'modules/common/appmenu.html'
      })
      .state('cps', {
        url: '/cps',
        abstract: true,
        template: '

',
        parent: 'signed-in'
      })
      .state('cp-list', {
        url: '',
        templateUrl: 'modules/biospecimen/cp/list.html',
        controller: 'CpListCtrl',
        parent: 'cps'
      })
      .state('cp-detail', {
        url: '/:cpId',
        templateUrl: 'modules/biospecimen/cp/detail.html',
        parent: 'cps',
        resolve: {
          cp: function($stateParams, CollectionProtocolService) {
            return CollectionProtocolService.getCp($stateParams.cpId).then(
              function(result) {
                return result.data;
              }
            );
          }
        },
        controller: function($scope, $state, cp) {
          $scope.cp = cp;
        },
        breadcrumb: {
          title: '{{cp.title}}',
          state: 'cp-detail.overview'
        }
      })
      .state('cp-detail.overview', {
        url: '/overview',
        templateUrl: 'modules/biospecimen/cp/overview.html',
        parent: 'cp-detail'
      })
      .state('cp-detail.consents', {
        url: '/consents',
        templateUrl: 'modules/biospecimen/cp/consents.html',
        parent: 'cp-detail'
      })
      .state('cp-detail.events', {
        url: '/events',
        templateUrl: 'modules/biospecimen/cp/events.html',
        parent: 'cp-detail'
      })
      .state('participant-list', {
        url: '/participants',
        templateUrl: 'modules/biospecimen/participant/list.html',
        controller: 'ParticipantListCtrl',
        parent: 'cp-detail'
      })
      .state('participant-new', {
        url: '/new-participant',
        templateProvider: function($stateParams, CpConfigSvc) {
          var tmpl = CpConfigSvc.getRegParticipantTmpl($stateParams.cpId);
          return '

';
        },
        controllerProvider: function($stateParams, CpConfigSvc) {
          return CpConfigSvc.getRegParticipantCtrl($stateParams.cpId);
        },
        parent: 'cp-detail'
      })
      .state('participant-detail', {
        url: '/participants/:cprId',
        templateUrl: 'modules/biospecimen/participant/detail.html',
        resolve: {
          cpr: function($stateParams, CprService) {
            return CprService.getRegistration($stateParams.cprId).then(
              function(result) {
                return result.data;
              }
            );
          },

          visits: function($stateParams, CprService) {
            return CprService.getVisits($stateParams.cprId, true).then(
              function(result) {
                return result.data;
              }
            );
          }
        },
        controller: 'ParticipantDetailCtrl',
        parent: 'signed-in'
      })
      .state('participant-detail.overview', {
        url: '/overview',
        templateUrl: 'modules/biospecimen/participant/overview.html',
        controller: function() {
        },
        parent: 'participant-detail'
      })
      .state('participant-detail.consents', {
        url: '/consents',
        templateUrl: 'modules/biospecimen/participant/consents.html',
        controller: function() {
        },
        parent: 'participant-detail'
      })
      .state('participant-detail.visits', {
        url: '/visits',
        templateUrl: 'modules/biospecimen/participant/visits.html',
        controller: function($scope, $state, cpr, visits) {
          $scope.cpr = cpr;
          $scope.visits = visits;

          if (visits && visits.length > 0) {
            $state.go(
              'participant-detail.specimens',
              {visitId: visits[0].id, eventId: visits[0].eventId}
            );
          }
        },
        parent: 'participant-detail'
      })
      .state('participant-detail.specimens', {
        url: '/?eventId&visitId',
        templateUrl: 'modules/biospecimen/participant/specimens.html',
        resolve: {
          specimens: function($stateParams, CprService) {
            var visitDetail = {visitId: $stateParams.visitId, eventId: $stateParams.eventId};
            return CprService.getVisitSpecimensTree($stateParams.cprId, visitDetail).then(
              function(result) {
                return result.data;
              }
            );
          }
        },
        controller: 'ParticipantSpecimensTreeCtrl',
        parent: 'participant-detail.visits'
      })
      .state('participant-detail.annotations', {
        url: '/annotations',
        templateUrl: 'modules/biospecimen/participant/annotations.html',
        controller: function() {
        },
        parent: 'participant-detail'
      });

    $urlRouterProvider.otherwise('/');

    //$httpProvider.interceptors.push('httpRespInterceptor');

    ApiUrlsProvider.hostname = "localhost"; // used for testing purpose
    ApiUrlsProvider.port = 9090;
    ApiUrlsProvider.secure = false;
    ApiUrlsProvider.app = "/openspecimen";
    ApiUrlsProvider.urls = {
      'sessions': '/rest/ng/sessions',
      'collection-protocols': '/rest/ng/collection-protocols',
      'cprs': '/rest/ng/collection-protocol-registrations',
      'participants': '/rest/ng/participants',
      'sites': '/rest/ng/sites',
      'form-files': '/rest/ng/form-files'
    };

    uiSelectConfig.theme = 'bootstrap';
  })
  .factory('httpRespInterceptor', function($q, $injector, $window) {
    return {
      request: function(result) {
        return result;
      },

      requestError: function(result) {
        $q.reject(result);
      },

      response: function(result) {
        return result;
      },

      responseError: function(result) {
        if (result.status == 401) {
          $window.localStorage['osAuthToken'] = '';
          $injector.get('$state').go('login'); // using injector to get rid of circular dependencies
        }

        $q.reject(result);
      }
    };
  })
  .factory('ApiUtil', function($window, $http) {
    return {
      processResp: function(result) {
        var response = {};
        if (result.status / 100 == 2) {
          response.status = "ok";
        } else if (result.status / 100 == 4) {
          response.status = "user_error";
        } else if (result.status / 100 == 5) {
          response.status = "server_error";
        }

        response.data = result.data;
        return response;
      },

      initialize: function(token) {
        if (!token) {
          token = $window.localStorage['osAuthToken'];
          if (!token) {
            return;
          }

          //token = JSON.parse(token);
        }

        $http.defaults.headers.common['X-OS-API-TOKEN'] = token;
        $http.defaults.withCredentials = true;
        //$cookieStore.put('JESSIONID', token);
        //$http.defaults.headers.common['Cookie'] = 'JSESSIONID=' + token;
      }
    };
  })
  .provider('ApiUrls', function() {
    var that = this;

    this.hostname = "";
    this.port = "";
    this.secure = false;
    this.app = "";
    this.urls = {};

    this.$get = function() {
      return {
        hostname: that.hostname,
        port    : that.port,
        secure  : that.secure,
        app     : that.app,
        urls    : that.urls,

        getUrl  : function(key) {
          var url = '';
          if (key) {
            url = this.urls[key];
          }

          var prefix = "";
          if (this.hostname) {
            var protocol = this.secure ? 'https://' : 'http://';
            prefix = protocol + this.hostname + ":" + this.port;
          }

          return prefix + this.app + url;
        }
      };
    }
  })
  .run(function($rootScope, $window, ApiUtil) {
    if ($window.localStorage['osAuthToken']) {
      $rootScope.loggedIn = true;
    }

    ApiUtil.initialize();

    $rootScope.$on('$stateChangeSuccess',
      function(event, toState, toParams, fromState, fromParams) {
        $rootScope.state = toState;
      });
  });
<|endoftext|>


<commit_before>function runTest()
{
    FBTest.sysout("issue4322.START");
    FBTest.openNewTab(basePath + "console/4322/issue4322.html", function(win)
    {
        FBTest.openFirebug();
        FBTest.enableConsolePanel(function(win)
        {
            FBTest.openFirebug();
            FBTest.selectPanel("console");

            var config = {tagName: "div", classes: "logRow logRow-errorMessage"};
            FBTest.waitForDisplayedElement("console", config, function(row)
            {
                var title = row.querySelector(".errorTitle");
                FBTest.compare(/getSession is not defined/, title.textContent,
                    "The error title must match.");

                var link = row.querySelector(".objectLink.objectLink-sourceLink");
                FBTest.compare(FW.FBL.$STRF("Line", ["issue4322.html", 10]), link.textContent,
                    "The source link must match.");

                FBTest.testDone("issue4322.DONE");
            });

            FBTest.clearConsole();
            FBTest.click(win.document.getElementById("testButton"));
        });
    });
}
<commit_msg>Fixed FBTest console/4322/issue4322.js<commit_after>function runTest()
{
    FBTest.sysout("issue4322.START");
    FBTest.openNewTab(basePath + "console/4322/issue4322.html", function(win)
    {
        FBTest.openFirebug();
        FBTest.enableConsolePanel(function(win)
        {
            var config = {tagName: "div", classes: "logRow logRow-errorMessage"};
            FBTest.waitForDisplayedElement("console", config, function(row)
            {
                var title = row.querySelector(".errorTitle");
                FBTest.compare(/getSession is not defined/, title.textContent,
                    "The error title must match.");

                var link = row.querySelector(".objectLink.objectLink-sourceLink");
                FBTest.compare(FW.FBL.$STRF("Line", ["issue4322.html", 10]), link.textContent,
                    "The source link must match.");

                FBTest.testDone("issue4322.DONE");
            });

            FBTest.clearConsole();
            FBTest.click(win.document.getElementById("testButton"));
        });
    });
}
<|endoftext|>


<commit_before>angular.module('simpleWizard', []).directive('wizard', function() {

  return {

    restrict: 'E',

    scope: {
      template: '=',
      model: '=',
      step: '=?',
    },

    template: '<ng-include src="getViewUrl()"/>',

    controller: function($scope) {

      // Save step and model in original state so we can
      // reset the entire wizard
      var _model = angular.copy($scope.model);
      var _step = angular.copy($scope.step);

      $scope.getViewUrl = function() {
        // Use first element in template if step not specified
        // or doesn't exist
        if (!$scope.step || !$scope.template[$scope.step]) {
          $scope.step = Object.keys($scope.template)[0];
        }
        return $scope.template[$scope.step].view;
      };

      $scope.wizardBack = function(toDelete) {
        toDelete = toDelete ||  [];
        toDelete.forEach(function(e) {
          delete $scope.model[e];
        });
        var step = $scope.template[$scope.step];
        if (typeof step.prev === 'function') {
          $scope.step = step.prev($scope.model);
        }
      };

      $scope.wizardNext = function() {
        var step = $scope.template[$scope.step];
        if (typeof step.next === 'function') {
          $scope.step = step.next($scope.model);
        }
      };

      $scope.wizardSubmit = function(data) {
        var step = $scope.template[$scope.step];
        if (typeof step.validate === 'function') {
          if (!step.validate($scope.model)) {
            return;
          }
        }
        $scope.wizardNext();
      };

      $scope.wizardReset = function() {
        $scope.model = _model;
        $scope.step = _step || Object.keys($scope.template)[0];
      };

      $scope.wizardGoTo = function(step) {
        if ($scope.template[step]) {
          $scope.step = step;
        }
      };

    },

  };

});
<commit_msg>Allow next/prev to be strings<commit_after>angular.module('simpleWizard', []).directive('wizard', function() {

  return {

    restrict: 'E',

    scope: {
      template: '=',
      model: '=',
      step: '=?',
    },

    template: '<ng-include src="getViewUrl()"/>',

    controller: function($scope) {

      // Save step and model in original state so we can
      // reset the entire wizard
      var _model = angular.copy($scope.model);
      var _step = angular.copy($scope.step);

      $scope.getViewUrl = function() {
        // Use first element in template if step not specified
        // or doesn't exist
        if (!$scope.step || !$scope.template[$scope.step]) {
          $scope.step = Object.keys($scope.template)[0];
        }
        return $scope.template[$scope.step].view;
      };

      $scope.wizardBack = function(toDelete) {
        toDelete = toDelete ||  [];
        toDelete.forEach(function(e) {
          delete $scope.model[e];
        });
        var step = $scope.template[$scope.step];
        $scope.step = (typeof step.prev === 'function')
          ? step.prev($scope.model)
          : step.prev;
      };

      $scope.wizardNext = function() {
        var step = $scope.template[$scope.step];
        $scope.step = (typeof step.next === 'function')
          ? step.next($scope.model)
          : step.next;
      };

      $scope.wizardSubmit = function(data) {
        var step = $scope.template[$scope.step];
        if (typeof step.validate === 'function') {
          if (!step.validate($scope.model)) {
            return;
          }
        }
        $scope.wizardNext();
      };

      $scope.wizardReset = function() {
        $scope.model = _model;
        $scope.step = _step || Object.keys($scope.template)[0];
      };

      $scope.wizardGoTo = function(step) {
        if ($scope.template[step]) {
          $scope.step = step;
        }
      };

    },

  };

});
<|endoftext|>


<commit_before>#!/usr/bin/env node

const fs = require('fs');
const moment = require('moment');
const argv = require('yargs')
    .options({
        office: {
            type: 'string',
            describe: 'include only offices that match string',
            default: '',
            requiredArg: true,
        },
        pretty: {
            type: 'boolean',
            describe: 'pretty-print the JSON',
        },
        threshold: {
            type: 'number',
            describe: 'include only committees receiving at least threshold',
            default: 0,
            requiredArg: true,
        },
    })
    .strict(true)
    .argv;
const db = require('./lib/db');
const outputDir = __dirname + '/src';

main()
    .catch(console.trace)
    .finally(() => db.close());

async function main() {
    let offices;
    if (argv.office) {
        offices = [await db.getMatchingOffice(argv.office)];
    }
    else {
        offices = await db.getOfficesForReport(argv.threshold);
    }
    for (const office of offices) {
        await processOffice(office);
    }
    const outputFile = outputDir + '/available.json';
    const availableContests = await db.getAvailableContests(argv.threshold);
    console.warn(`Writing ${outputFile}`);
    fs.writeFileSync(outputFile, JSON.stringify(availableContests, null, argv.pretty ? 2 : 0));
}

async function processOffice(office) {
    const codeToHead = {
        candidate_short_name: 'Candidate',
        contributions: 'Contributions',
        contributors: 'All contributors',
        dc_contributors: 'DC contributors',
        ward_contributors: 'Ward contributors',
        // dc_ind_contributors: 'DC ind. contributors',
        // ward_ind_contributors: 'Ward ind. contributors',
        amount: 'All $',
        dc_amount: 'DC $',
        ward_amount: 'Ward $',
        candidate_amount: 'Candidate/Family $',
        fair_elections_total: 'Projected $ w/ Fair Elections',
        // ind_amount: 'Individual $',
        // dc_ind_amount: 'DC individual $',
        // ward_ind_amount: 'Ward individual $',
        dc_percent: 'DC % of $',
        ward_percent: 'Ward % of $',
        candidate_percent: 'Candidate/Family % of $',
        mean: 'Mean $',
        median: 'Median $',
    };
    const m = office.match(/Ward (\d)/);
    const ward = m ? +m[1] : null;
    if (!ward) {
        for (const key of Object.keys(codeToHead)) {
            if (/^ward_/.test(key)) {
                delete codeToHead[key];
            }
        }
    }
    const columnHeads = Object.values(codeToHead);
    const columnCodes = Object.keys(codeToHead);
    const filters = {office};
    const stats = await db.getContributionStats({filters, ward});
    const minMax = {};
    for (const code of columnCodes) {
        if (code !== 'candidate_short_name') {
            const values = stats.map(row => row[code]);
            minMax[code] = {
                min: Math.min(...values),
                max: Math.max(...values),
            };
        }
    }
    const tableData = stats.map(formatRow);
    const officeCode = hyphenize(office);
    const data = {
        updated: new Date().toLocaleDateString('en-US', {year: 'numeric', day: 'numeric', month: 'long'}),
        office,
        ward,
        extras: getExtras(officeCode),
        points: await db.getDcContributionsWithPositions(filters),
        stats: {columnHeads, tableData},
        dateData: await getDateData(filters, ward),
        placeData: await getPlaceData(argv.office, ward),
    };
    const outputFile = `${outputDir}/ocf-2020-${officeCode}.json`;
    console.warn(`Writing ${outputFile}`);
    fs.writeFileSync(outputFile, JSON.stringify(data, null, argv.pretty ? 2 : 0));

    function formatRow(row) {
        return columnCodes.map(function (code) {
            const value = row[code];
            if (typeof value !== 'number') {
                return value;
            }
            let className = 'text-right';
            for (const extreme of ['min', 'max']) {
                if (value === minMax[code][extreme]) {
                    className += ' ' + extreme;
                }
            }
            className = className.replace(' min max', ''); // don't use class if all values are the same
            return {
                value: Math.round(value).toLocaleString(),
                class: className,
            };
        });
    }
}

async function getDateData(baseFilters, ward) {
    const lastDeadlines = await db.getLastDeadlines(baseFilters.office);
    const sets = {
        all: {},
        dc: {state: 'DC'},
    };
    if (ward) {
        sets.ward = {ward};
    }
    const contributors = {};
    let start;
    let end;
    for (const [key, addedFilters] of Object.entries(sets)) {
        contributors[key] = [];
        const filters = {...baseFilters, ...addedFilters};
        const rows = await db.getContributionsByDate(filters);
        const data = {};
        for (const row of rows) {
            if (!data[row.receipt_date]) {
                data[row.receipt_date] = {};
            }
            data[row.receipt_date][row.candidate_short_name] = row.contributors;
        }
        const runningTotals = {};
        let i = 0;
        for (const candidate of Object.keys(lastDeadlines)) {
            contributors[key][i] = [candidate];
            runningTotals[candidate] = 0;
            i++;
        }
        if (key === 'all') {
            start = rows[0].receipt_date;
            end = rows[rows.length - 1].receipt_date;
        }
        const cursorDate = moment(start);
        const endDate = moment(end);
        while (cursorDate <= endDate) {
            const isoDate = cursorDate.format('YYYY-MM-DD');
            let i = 0;
            for (const [candidate, lastDeadline] of Object.entries(lastDeadlines)) {
                if (isoDate <= lastDeadline) {
                    runningTotals[candidate] += +(data[isoDate] && data[isoDate][candidate]) || 0;
                    contributors[key][i].push(runningTotals[candidate]);
                    i++;
                }
            }
            cursorDate.add(1, 'day');
        }
    }
    return {start, end, contributors};
}

async function getPlaceData(office, ward = null) {
    const candidates = await db.getCandidatesForOffice(office);
    const allStates = await db.getContributorPlaces(office);
    const allWards = await db.getContributorPlaces(office, true);
    const placeData = [];
    for (const candidate of candidates) {
        const contributorsByState = await db.getContributorsByPlace(candidate);
        const stateColumns = makePlaceContributorColumns(allStates, contributorsByState);
        const contributorStates = stateColumns.map(item => item[0]);
        const stateColors = makeColors(allStates, contributorStates, 'DC');
        const contributorsByWard = await db.getContributorsByPlace(candidate, true);
        const wardColumns = makePlaceContributorColumns(allWards, contributorsByWard);
        const contributorWards = wardColumns.map(item => item[0]);
        const wardColors = makeColors(allWards, contributorWards, ward);
        placeData.push({
            candidate,
            code: hyphenize(candidate),
            state: {
                columns: stateColumns,
                colors: stateColors,
            },
            ward: {
                columns: wardColumns,
                colors: wardColors,
            },
        });
    }
    return placeData;
}

function makePlaceContributorColumns(places, contributorsByPlace) {
    const total = Object.values(contributorsByPlace)
        .reduce((a, b) => a + b, 0);
    const threshold = total * 0.02;
    const columns = [];
    let otherContributors = 0;
    const otherPlaces = [];
    for (const place of places) {
        const contributors = contributorsByPlace[place];
        if (!contributors) {
            continue;
        }
        if (contributors >= threshold) {
            columns.push([place, contributors]);
        }
        else {
            otherContributors += contributors;
            otherPlaces.push(place);
            columns.push([place, 0]);
        }
    }
    if (otherPlaces.length === 1) {
        // If there's only 1 place counted as 'Other', count it as itself
        const place = otherPlaces[0];
        const index = places.indexOf(place);
        columns[index] = [place, otherContributors];
        otherContributors = 0;
    }
    columns.push(['Other', otherContributors]);
    return columns.filter(item => item[1] > 0);
}

function makeColors(allPlaces, contributorPlaces, primaryPlace) {
    const primaryColor = '#ff0000';
    const colorSeries = [ // taken from d3.schemeCategory10 and tableau10, with red one moved to 10th position
        '#1f77b4',
        '#ff7f0e',
        '#2ca02c',
        '#9467bd',
        '#8c564b',
        '#e377c2',
        '#7f7f7f',
        '#bcbd22',
        '#17becf',
        '#d62728',
        '#4e79a7',
        // '#f28e2c', too similar to #ff7f0e
        '#e15759',
        '#76b7b2',
        '#59a14f',
        '#edc949',
        '#af7aa1',
        '#ff9da7',
        '#9c755f',
        '#bab0ab',
    ];
    const colors = {};
    let i = 0;
    for (const place of allPlaces) {
        if (place === primaryPlace) {
            colors[place] = primaryColor;
        }
        else {
            colors[place] = colorSeries[i];
            i++;
            if (i >= colorSeries.length) {
                i = 0;
            }
        }
        if (!contributorPlaces.includes(place)) {
            delete colors[place];
        }
    }
    if (contributorPlaces.includes('Other')) {
        colors['Other'] = '#dddddd';
    }
    return colors;
}

function getExtras(officeCode) {
    const inputFile = `${outputDir}/ocf-2020-${officeCode}.txt`;
    let text;
    try {
        text = fs.readFileSync(inputFile, 'utf8').trim();
    }
    catch (err) {
        text = '';
    }
    const extras = {};
    if (text) {
        let key;
        for (const line of text.split('\n')) {
            if (/^=(\w+)\s*$/.test(line)) {
                key = line.substr(1).trim();
                extras[key] = '';
            }
            else if (key) {
                extras[key] += '\n' + line;
            }
            else {
                throw new Error(`Missing key in ${inputFile}`);
            }
        }
        for (const key of Object.keys(extras)) {
            extras[key] = '
' +
                extras[key].trim().replace(/\s*\n\s*\n\s*/g, '
\n
') +
                '
';
        }
    }
    return extras;
}

function hyphenize(s) {
    return s.replace(/([a-z])(?=[A-Z])/g, '$1-')
        .toLowerCase()
        .replace(/[^a-z\d]+/g, '-')
        .replace(/^-|-$/g, '');
}
<commit_msg>fix JSON for pie charts<commit_after>#!/usr/bin/env node

const fs = require('fs');
const moment = require('moment');
const argv = require('yargs')
    .options({
        office: {
            type: 'string',
            describe: 'include only offices that match string',
            default: '',
            requiredArg: true,
        },
        pretty: {
            type: 'boolean',
            describe: 'pretty-print the JSON',
        },
        threshold: {
            type: 'number',
            describe: 'include only committees receiving at least threshold',
            default: 0,
            requiredArg: true,
        },
    })
    .strict(true)
    .argv;
const db = require('./lib/db');
const outputDir = __dirname + '/src';

main()
    .catch(console.trace)
    .finally(() => db.close());

async function main() {
    let offices;
    if (argv.office) {
        offices = [await db.getMatchingOffice(argv.office)];
    }
    else {
        offices = await db.getOfficesForReport(argv.threshold);
    }
    for (const office of offices) {
        await processOffice(office);
    }
    const outputFile = outputDir + '/available.json';
    const availableContests = await db.getAvailableContests(argv.threshold);
    console.warn(`Writing ${outputFile}`);
    fs.writeFileSync(outputFile, JSON.stringify(availableContests, null, argv.pretty ? 2 : 0));
}

async function processOffice(office) {
    const codeToHead = {
        candidate_short_name: 'Candidate',
        contributions: 'Contributions',
        contributors: 'All contributors',
        dc_contributors: 'DC contributors',
        ward_contributors: 'Ward contributors',
        // dc_ind_contributors: 'DC ind. contributors',
        // ward_ind_contributors: 'Ward ind. contributors',
        amount: 'All $',
        dc_amount: 'DC $',
        ward_amount: 'Ward $',
        candidate_amount: 'Candidate/Family $',
        fair_elections_total: 'Projected $ w/ Fair Elections',
        // ind_amount: 'Individual $',
        // dc_ind_amount: 'DC individual $',
        // ward_ind_amount: 'Ward individual $',
        dc_percent: 'DC % of $',
        ward_percent: 'Ward % of $',
        candidate_percent: 'Candidate/Family % of $',
        mean: 'Mean $',
        median: 'Median $',
    };
    const m = office.match(/Ward (\d)/);
    const ward = m ? +m[1] : null;
    if (!ward) {
        for (const key of Object.keys(codeToHead)) {
            if (/^ward_/.test(key)) {
                delete codeToHead[key];
            }
        }
    }
    const columnHeads = Object.values(codeToHead);
    const columnCodes = Object.keys(codeToHead);
    const filters = {office};
    const stats = await db.getContributionStats({filters, ward});
    const minMax = {};
    for (const code of columnCodes) {
        if (code !== 'candidate_short_name') {
            const values = stats.map(row => row[code]);
            minMax[code] = {
                min: Math.min(...values),
                max: Math.max(...values),
            };
        }
    }
    const tableData = stats.map(formatRow);
    const officeCode = hyphenize(office);
    const data = {
        updated: new Date().toLocaleDateString('en-US', {year: 'numeric', day: 'numeric', month: 'long'}),
        office,
        ward,
        extras: getExtras(officeCode),
        points: await db.getDcContributionsWithPositions(filters),
        stats: {columnHeads, tableData},
        dateData: await getDateData(filters, ward),
        placeData: await getPlaceData(office, ward),
    };
    const outputFile = `${outputDir}/ocf-2020-${officeCode}.json`;
    console.warn(`Writing ${outputFile}`);
    fs.writeFileSync(outputFile, JSON.stringify(data, null, argv.pretty ? 2 : 0));

    function formatRow(row) {
        return columnCodes.map(function (code) {
            const value = row[code];
            if (typeof value !== 'number') {
                return value;
            }
            let className = 'text-right';
            for (const extreme of ['min', 'max']) {
                if (value === minMax[code][extreme]) {
                    className += ' ' + extreme;
                }
            }
            className = className.replace(' min max', ''); // don't use class if all values are the same
            return {
                value: Math.round(value).toLocaleString(),
                class: className,
            };
        });
    }
}

async function getDateData(baseFilters, ward) {
    const lastDeadlines = await db.getLastDeadlines(baseFilters.office);
    const sets = {
        all: {},
        dc: {state: 'DC'},
    };
    if (ward) {
        sets.ward = {ward};
    }
    const contributors = {};
    let start;
    let end;
    for (const [key, addedFilters] of Object.entries(sets)) {
        contributors[key] = [];
        const filters = {...baseFilters, ...addedFilters};
        const rows = await db.getContributionsByDate(filters);
        const data = {};
        for (const row of rows) {
            if (!data[row.receipt_date]) {
                data[row.receipt_date] = {};
            }
            data[row.receipt_date][row.candidate_short_name] = row.contributors;
        }
        const runningTotals = {};
        let i = 0;
        for (const candidate of Object.keys(lastDeadlines)) {
            contributors[key][i] = [candidate];
            runningTotals[candidate] = 0;
            i++;
        }
        if (key === 'all') {
            start = rows[0].receipt_date;
            end = rows[rows.length - 1].receipt_date;
        }
        const cursorDate = moment(start);
        const endDate = moment(end);
        while (cursorDate <= endDate) {
            const isoDate = cursorDate.format('YYYY-MM-DD');
            let i = 0;
            for (const [candidate, lastDeadline] of Object.entries(lastDeadlines)) {
                if (isoDate <= lastDeadline) {
                    runningTotals[candidate] += +(data[isoDate] && data[isoDate][candidate]) || 0;
                    contributors[key][i].push(runningTotals[candidate]);
                    i++;
                }
            }
            cursorDate.add(1, 'day');
        }
    }
    return {start, end, contributors};
}

async function getPlaceData(office, ward = null) {
    const candidates = await db.getCandidatesForOffice(office);
    const allStates = await db.getContributorPlaces(office);
    const allWards = await db.getContributorPlaces(office, true);
    const placeData = [];
    for (const candidate of candidates) {
        const contributorsByState = await db.getContributorsByPlace(candidate);
        const stateColumns = makePlaceContributorColumns(allStates, contributorsByState);
        const contributorStates = stateColumns.map(item => item[0]);
        const stateColors = makeColors(allStates, contributorStates, 'DC');
        const contributorsByWard = await db.getContributorsByPlace(candidate, true);
        const wardColumns = makePlaceContributorColumns(allWards, contributorsByWard);
        const contributorWards = wardColumns.map(item => item[0]);
        const wardColors = makeColors(allWards, contributorWards, ward);
        placeData.push({
            candidate,
            code: hyphenize(candidate),
            state: {
                columns: stateColumns,
                colors: stateColors,
            },
            ward: {
                columns: wardColumns,
                colors: wardColors,
            },
        });
    }
    return placeData;
}

function makePlaceContributorColumns(places, contributorsByPlace) {
    const total = Object.values(contributorsByPlace)
        .reduce((a, b) => a + b, 0);
    const threshold = total * 0.02;
    const columns = [];
    let otherContributors = 0;
    const otherPlaces = [];
    for (const place of places) {
        const contributors = contributorsByPlace[place];
        if (!contributors) {
            continue;
        }
        if (contributors >= threshold) {
            columns.push([place, contributors]);
        }
        else {
            otherContributors += contributors;
            otherPlaces.push(place);
            columns.push([place, 0]);
        }
    }
    if (otherPlaces.length === 1) {
        // If there's only 1 place counted as 'Other', count it as itself
        const place = otherPlaces[0];
        const index = places.indexOf(place);
        columns[index] = [place, otherContributors];
        otherContributors = 0;
    }
    columns.push(['Other', otherContributors]);
    return columns.filter(item => item[1] > 0);
}

function makeColors(allPlaces, contributorPlaces, primaryPlace) {
    const primaryColor = '#ff0000';
    const colorSeries = [ // taken from d3.schemeCategory10 and tableau10, with red one moved to 10th position
        '#1f77b4',
        '#ff7f0e',
        '#2ca02c',
        '#9467bd',
        '#8c564b',
        '#e377c2',
        '#7f7f7f',
        '#bcbd22',
        '#17becf',
        '#d62728',
        '#4e79a7',
        // '#f28e2c', too similar to #ff7f0e
        '#e15759',
        '#76b7b2',
        '#59a14f',
        '#edc949',
        '#af7aa1',
        '#ff9da7',
        '#9c755f',
        '#bab0ab',
    ];
    const colors = {};
    let i = 0;
    for (const place of allPlaces) {
        if (place === primaryPlace) {
            colors[place] = primaryColor;
        }
        else {
            colors[place] = colorSeries[i];
            i++;
            if (i >= colorSeries.length) {
                i = 0;
            }
        }
        if (!contributorPlaces.includes(place)) {
            delete colors[place];
        }
    }
    if (contributorPlaces.includes('Other')) {
        colors['Other'] = '#dddddd';
    }
    return colors;
}

function getExtras(officeCode) {
    const inputFile = `${outputDir}/ocf-2020-${officeCode}.txt`;
    let text;
    try {
        text = fs.readFileSync(inputFile, 'utf8').trim();
    }
    catch (err) {
        text = '';
    }
    const extras = {};
    if (text) {
        let key;
        for (const line of text.split('\n')) {
            if (/^=(\w+)\s*$/.test(line)) {
                key = line.substr(1).trim();
                extras[key] = '';
            }
            else if (key) {
                extras[key] += '\n' + line;
            }
            else {
                throw new Error(`Missing key in ${inputFile}`);
            }
        }
        for (const key of Object.keys(extras)) {
            extras[key] = '
' +
                extras[key].trim().replace(/\s*\n\s*\n\s*/g, '
\n
') +
                '
';
        }
    }
    return extras;
}

function hyphenize(s) {
    return s.replace(/([a-z])(?=[A-Z])/g, '$1-')
        .toLowerCase()
        .replace(/[^a-z\d]+/g, '-')
        .replace(/^-|-$/g, '');
}
<|endoftext|>