<commit_before>import co from 'co';
import { extend, merge } from 'extend-merge';
import { Model, Collector } from 'chaos-orm';

/**
 * The Query wrapper.
 */
class Query {

  /**
   * Gets/sets classes dependencies.
   *
   * @param  Object classes The classes dependencies to set or none to get it.
   * @return mixed          The classes dependencies.
   */
  static classes(classes) {
    if (arguments.length) {
      this._classes = extend({}, this._classes, classes);
    }
    return this._classes;
  }

  /**
   * Creates a new record object with default values.
   *
   * @param array config Possible options are:
   *                      - `'model'`  _Function_ : A model class.
   *                      - `'schema'` _Object_   : Alternatively a schema instance can be provided instead of the model.
   *                      - `'query'`  _Object_   : The query.
   */
  constructor(config) {
    var defaults = {
      model: undefined,
      schema: undefined,
      query: {}
    };
    config = extend({}, defaults, config);

    if (config.model) {
      this._model = config.model;
      this._schema = this._model.definition();
    } else {
      this._schema = config.schema;
    }

    /**
     * Count the number of identical aliases in a query for building unique aliases.
     *
     * @var Object
     */
    this._aliasCounter = {};

    /**
     * Map beetween relation pathsand corresponding aliases.
     *
     * @var Object
     */
    this._aliases = {};

    /**
     * Map beetween generated aliases and corresponding schema.
     *
     * @var Object
     */
    this._schemas = {};

    /**
     * The relations to include.
     *
     * @var Array
     */
    this._embed = [];

    /**
     * Some conditions over some relations.
     *
     * @var Array
     */
    this._has = [];

    /**
     * Pagination.
     *
     * @var Array
     */
    this._page = [];

    var schema = this.schema();

    /**
     * The select statement instance.
     *
     * @var Function
     */
    this._statement = schema.connection().dialect().statement('select');

    var source = schema.source();
    var from = {};
    from[source] = this.alias('', schema);
    this.statement().from(from);

    for (var key in config.query) {
      this[key](config.query[key]);
    }
  }

  /**
   * Gets the schema.
   *
   * @return Function Returns the schema.
   */
  schema() {
    if (!this._schema) {
      throw new Error("Error, missing schema for this query.");
    }
    return this._schema;
  }

  /**
   * Gets the model.
   *
   * @return Function Returns the model.
   */
  model() {
    return this._model;
  }

  /**
   * Gets the query statement.
   *
   * @return Function Returns a connection instance.
   */
  statement() {
    return this._statement;
  }

  /**
   * Executes the query and returns the result.
   *
   * @param  Object  Options The fetching options.
   * @return Promise         A Promise.
   */
  get(options) {
    return co(function*(){
      var defaults = {
        collector: undefined,
        return: 'entity'
      };
      options = extend({}, defaults, options);

      var classname = this.constructor.classes().collector;
      var collector = options.collector = options.collector ? options.collector : new classname();

      this._applyHas();
      this._applyLimit();

      var noFields = !this.statement().data('fields').length;
      if (noFields) {
        var star = {};
        star[this.alias()] = ['*'];
        this.statement().fields([star]);
      }

      var collection = [];
      var ret = options['return'];

      var schema = this.schema();
      var cursor = yield schema.connection().query(this.statement().toString());

      switch (ret) {
        case 'entity':
          var source = schema.source();
          var key = schema.key();

          var model = this.model();
          if (!model) {
            throw new Error("Missing model for this query, set `'return'` to `'object'` to get row data.");
          }

          collection = model.create([], {
            collector: collector,
            type: 'set',
            exists: true
          });

          if (this.statement().data('limit')) {
            var count = this.count();
            collection.meta({ count: yield count });
          }

          for (var record of cursor) {
            var uuid = source + ':' + record[key];
            if (record[key] && collector.has(uuid)) {
              collection.push(collector.get(uuid));
            } else {
              collection.push(model.create(record, {
                collector: collector,
                exists: noFields ? true : null
              }));
            }
          }
          break;
        case 'array':
        case 'object':
          for (var record of cursor) {
            collection.push(extend({}, record));
          }
          break;
        default:
          throw new Error("Invalid `'" + options['return'] + "'` mode as `'return'` value");
          break;
      }
      return schema.embed(collection, this._embed, { fetchOptions: options });
    }.bind(this));
  }

  /**
   * Alias for `get()`
   *
   * @return object An iterator instance.
   */
  all(options) {
    return this.get(options);
  }

  /**
   * Executes the query and returns the first result only.
   *
   * @return object An entity instance.
   */
  first(options) {
    return co(function*() {
      var result = yield this.get(options);
      return Array.isArray(result) ? result[0] : result.get(0);
    }.bind(this));
  }

  /**
   * Executes the query and returns the count number.
   *
   * @return integer The number of rows in result.
   */
  count() {
    return co(function*() {
      var statement = this.statement();
      var counter = this.schema().connection().dialect().statement('select');
      counter.fields({':plain': 'COUNT(*)'});
      counter.data('from', statement.data('from'));
      counter.data('joins', statement.data('joins'));
      counter.data('where', statement.data('where'));
      counter.data('group', statement.data('group'));
      counter.data('having', statement.data('having'));
      var cursor = yield this.schema().connection().query(counter.toString());
      var result = cursor.next();
      var key = Object.keys(result)[0]
      return Number.parseInt(result[key]);
    }.bind(this));
  }

  /**
   * Adds some fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  fields(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);

    var schema = this.schema();

    for (var value of fields) {
      if (typeof value === 'string' && schema.has(value)) {
        var aliased = {};
        aliased[this.alias()] = [value]
        this.statement().fields(aliased);
      } else {
        this.statement().fields(value);
      }
    }
    return this;
  }

  /**
   * Adds some where conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  where(conditions, alias) {
    var conditions = this.statement().dialect().prefix(conditions, alias ? alias : this.alias(), false);
    this.statement().where(conditions);
    return this;
  }

  /**
   * Alias for `where()`.
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  conditions(conditions) {
    return this.where(conditions);
  }

  /**
   * Adds some group by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  group(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().group(fields);
    return this;
  }

  /**
   * Adds some having conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  having(conditions) {
    conditions = this.statement().dialect().prefix(conditions, this.alias());
    this.statement().having(conditions);
    return this;
  }

  /**
   * Adds some order by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  order(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().order(fields);
    return this;
  }

  /**
   * Sets page number.
   *
   * @param  integer page The page number
   * @return self
   */
  page(page)
  {
    this._page.page = page;
    return this;
  }

  /**
   * Sets offset value.
   *
   * @param  integer offset The offset value.
   * @return self
   */
  offset(offset)
  {
    this._page.offset = offset;
    return this;
  }

  /**
   * Sets limit value.
   *
   * @param  integer limit The number of results to limit or `0` for limit at all.
   * @return self
   */
  limit(limit)
  {
    this._page.limit = Number.parseInt(limit);
    return this;
  }

  /**
   * Applies a query handler
   *
   * @param  Closure  closure A closure.
   * @return Function         Returns `this`.
   */
  handler(closure) {
    if (typeof closure === 'function') {
      closure(this);
    }
    return this;
  }

  /**
   * Sets the relations to retrieve.
   *
   * @param  array  embed The relations to load with the query.
   * @return object        Returns `this`.
   */
  embed(embed, conditions) {
    if (!arguments.length) {
      return this._embed;
    }
    if (typeof embed === "string" && arguments.length === 2) {
      var mix = {};
      mix[embed] = conditions || [];
      embed = [mix];
    } else {
      embed = Array.isArray(embed) ? embed : [embed];
    }
    this._embed = this._embed.concat(embed);
    return this;
  }

  /**
   * Sets the conditionnal dependency over some relations.
   *
   * @param array The conditionnal dependency.
   */
  has(has, conditions) {
    if (!arguments.length) {
      return this._has;
    }
    if (typeof has === "string" && arguments.length === 2) {
      var mix = {};
      mix[has] = conditions || [];
      has = [mix];
    }
    this._has = this._has.concat(has)
    return this;
  }

  /**
   * Gets a unique alias for the query or a query's relation if `relpath` is set.
   *
   * @param  string path   A dotted relation name or for identifying the query's relation.
   * @param  object schema The corresponding schema to alias.
   * @return string        A string alias.
   */
  alias(path, schema) {
    if (arguments.length < 2) {
      path = path || '';
      if (this._aliases[path] !== undefined) {
        return this._aliases[path];
      } else {
        throw new Error("No alias has been defined for `'" + path + "'`.");
      }
    }

    var alias = schema.source();
    if (this._aliasCounter[alias] === undefined) {
      this._aliasCounter[alias] = 0;
      this._aliases[path] = alias;
    } else {
      alias = this._aliases[path] = alias + '__' + this._aliasCounter[alias]++;
    }
    this._schemas[alias] = schema;
    return alias;
  }

  /**
   * Applies the has conditions.
   */
  _applyHas() {
    var schema = this.schema();
    var tree = schema.treeify(this.has());
    this._applyJoins(this.schema(), tree, '', this.alias());
    var has = this.has();
    for (var value of has) {
      var key = Object.keys(value)[0];
      var conditions = value[key];
      this.where(conditions, this.alias(key));
    }
  }

  /**
   * Applies the limit range when applicable.
   */
  _applyLimit() {
    if (!this._page.limit) {
        return;
    }
    var offset;
    if (this._page.offset) {
      offset = this._page.offset;
    } else {
      var page = this._page.page ? this._page.page : 1;
      offset = (page - 1) * this._page.limit;
    }
    this.statement().limit(this._page.limit, offset);
  }

  /**
   * Applies joins.
   *
   * @param Object schema    The schema to perform joins on.
   * @param Array  tree      The tree of relations to join.
   * @param Array  basePath  The base relation path.
   * @param String aliasFrom The alias name of the from model.
   */
  _applyJoins(schema, tree, basePath, aliasFrom) {
    for (var key in tree) {
      var childs = tree[key];
      var rel = schema.relation(key);
      var path = basePath ? basePath + '.' + key : key;
      var to;

      if (rel.type() !== 'hasManyThrough') {
        to = this._join(path, rel, aliasFrom);
      } else {
        var name = rel.using();
        var nameThrough = rel.through();
        var pathThrough = path ? path + '.' + nameThrough : nameThrough;
        var from = rel.from();

        var relThrough = from.definition().relation(nameThrough);
        var aliasThrough = this._join(pathThrough, relThrough, aliasFrom);

        var modelThrough = relThrough.to();
        var relTo = modelThrough.definition().relation(name);
        to = this._join(path, relTo, aliasThrough);
      }

      if (childs && Object.keys(childs).length) {
        this._applyJoins(rel.to().definition(), childs, path, to);
      }
    }
  }

  /**
   * Set a query's join according a Relationship.
   *
   * @param  string path      The relation path.
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @return string           The "to" model alias.
   */
  _join(path, rel, fromAlias) {
    if (this._aliases[path] !== undefined) {
      return this._aliases[path];
    }

    var schema = rel.to().definition();
    var source = schema.source();
    var toAlias = this.alias(path, schema);

    var table = {};
    table[source] = toAlias;

    this.statement().join(
      table,
      this._on(rel, fromAlias, toAlias),
      'LEFT'
    );
    return toAlias;
  }

  /**
   * Build the `ON` constraints from a `Relationship` instance.
   *
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @param  string toAlias   The "to" model alias.
   * @return array             A constraints array.
   */
  _on(rel, fromAlias, toAlias) {
    if (rel.type() === 'hasManyThrough') {
      return [];
    }
    var keys = rel.keys();
    var fromField = Object.keys(keys)[0];
    var toField = keys[fromField];
    return { '=': [{ ':name': fromAlias + '.' + fromField },  { ':name': toAlias + '.' + toField }] };
  }
}

/**
 * Class dependencies.
 *
 * @var array
 */
Query._classes = {
  collector: Collector
};

export default Query;
<commit_msg>Minor fix.<commit_after>import co from 'co';
import { extend, merge } from 'extend-merge';
import { Model, Collector } from 'chaos-orm';

/**
 * The Query wrapper.
 */
class Query {

  /**
   * Gets/sets classes dependencies.
   *
   * @param  Object classes The classes dependencies to set or none to get it.
   * @return mixed          The classes dependencies.
   */
  static classes(classes) {
    if (arguments.length) {
      this._classes = extend({}, this._classes, classes);
    }
    return this._classes;
  }

  /**
   * Creates a new record object with default values.
   *
   * @param array config Possible options are:
   *                      - `'model'`  _Function_ : A model class.
   *                      - `'schema'` _Object_   : Alternatively a schema instance can be provided instead of the model.
   *                      - `'query'`  _Object_   : The query.
   */
  constructor(config) {
    var defaults = {
      model: undefined,
      schema: undefined,
      query: {}
    };
    config = extend({}, defaults, config);

    if (config.model) {
      this._model = config.model;
      this._schema = this._model.definition();
    } else {
      this._schema = config.schema;
    }

    /**
     * Count the number of identical aliases in a query for building unique aliases.
     *
     * @var Object
     */
    this._aliasCounter = {};

    /**
     * Map beetween relation pathsand corresponding aliases.
     *
     * @var Object
     */
    this._aliases = {};

    /**
     * Map beetween generated aliases and corresponding schema.
     *
     * @var Object
     */
    this._schemas = {};

    /**
     * The relations to include.
     *
     * @var Array
     */
    this._embed = [];

    /**
     * Some conditions over some relations.
     *
     * @var Array
     */
    this._has = [];

    /**
     * Pagination.
     *
     * @var Array
     */
    this._page = [];

    var schema = this.schema();

    /**
     * The select statement instance.
     *
     * @var Function
     */
    this._statement = schema.connection().dialect().statement('select');

    var source = schema.source();
    var from = {};
    from[source] = this.alias('', schema);
    this.statement().from(from);

    for (var key in config.query) {
      this[key](config.query[key]);
    }
  }

  /**
   * Gets the schema.
   *
   * @return Function Returns the schema.
   */
  schema() {
    if (!this._schema) {
      throw new Error("Error, missing schema for this query.");
    }
    return this._schema;
  }

  /**
   * Gets the model.
   *
   * @return Function Returns the model.
   */
  model() {
    return this._model;
  }

  /**
   * Gets the query statement.
   *
   * @return Function Returns a connection instance.
   */
  statement() {
    return this._statement;
  }

  /**
   * Executes the query and returns the result.
   *
   * @param  Object  Options The fetching options.
   * @return Promise         A Promise.
   */
  get(options) {
    return co(function*(){
      var defaults = {
        collector: undefined,
        return: 'entity'
      };
      options = extend({}, defaults, options);

      var classname = this.constructor.classes().collector;
      var collector = options.collector = options.collector ? options.collector : new classname();

      this._applyHas();
      this._applyLimit();

      var noFields = !this.statement().data('fields').length;
      if (noFields) {
        var star = {};
        star[this.alias()] = ['*'];
        this.statement().fields([star]);
      }

      var collection;
      var ret = options['return'];

      var schema = this.schema();
      var cursor = yield schema.connection().query(this.statement().toString());

      switch (ret) {
        case 'entity':
          var source = schema.source();
          var key = schema.key();

          var model = this.model();
          if (!model) {
            throw new Error("Missing model for this query, set `'return'` to `'object'` to get row data.");
          }

          collection = model.create([], {
            collector: collector,
            type: 'set',
            exists: true
          });

          if (this.statement().data('limit')) {
            var count = this.count();
            collection.meta({ count: yield count });
          }

          for (var record of cursor) {
            var uuid = source + ':' + record[key];
            if (record[key] && collector.has(uuid)) {
              collection.push(collector.get(uuid));
            } else {
              collection.push(model.create(record, {
                collector: collector,
                exists: noFields ? true : null
              }));
            }
          }
          break;
        case 'array':
        case 'object':
          collection = [];
          for (var record of cursor) {
            collection.push(extend({}, record));
          }
          break;
        default:
          throw new Error("Invalid `'" + options['return'] + "'` mode as `'return'` value");
          break;
      }
      return schema.embed(collection, this._embed, { fetchOptions: options });
    }.bind(this));
  }

  /**
   * Alias for `get()`
   *
   * @return object An iterator instance.
   */
  all(options) {
    return this.get(options);
  }

  /**
   * Executes the query and returns the first result only.
   *
   * @return object An entity instance.
   */
  first(options) {
    return co(function*() {
      var result = yield this.get(options);
      return Array.isArray(result) ? result[0] : result.get(0);
    }.bind(this));
  }

  /**
   * Executes the query and returns the count number.
   *
   * @return integer The number of rows in result.
   */
  count() {
    return co(function*() {
      var statement = this.statement();
      var counter = this.schema().connection().dialect().statement('select');
      counter.fields({':plain': 'COUNT(*)'});
      counter.data('from', statement.data('from'));
      counter.data('joins', statement.data('joins'));
      counter.data('where', statement.data('where'));
      counter.data('group', statement.data('group'));
      counter.data('having', statement.data('having'));
      var cursor = yield this.schema().connection().query(counter.toString());
      var result = cursor.next();
      var key = Object.keys(result)[0]
      return Number.parseInt(result[key]);
    }.bind(this));
  }

  /**
   * Adds some fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  fields(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);

    var schema = this.schema();

    for (var value of fields) {
      if (typeof value === 'string' && schema.has(value)) {
        var aliased = {};
        aliased[this.alias()] = [value]
        this.statement().fields(aliased);
      } else {
        this.statement().fields(value);
      }
    }
    return this;
  }

  /**
   * Adds some where conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  where(conditions, alias) {
    var conditions = this.statement().dialect().prefix(conditions, alias ? alias : this.alias(), false);
    this.statement().where(conditions);
    return this;
  }

  /**
   * Alias for `where()`.
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  conditions(conditions) {
    return this.where(conditions);
  }

  /**
   * Adds some group by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  group(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().group(fields);
    return this;
  }

  /**
   * Adds some having conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  having(conditions) {
    conditions = this.statement().dialect().prefix(conditions, this.alias());
    this.statement().having(conditions);
    return this;
  }

  /**
   * Adds some order by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  order(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().order(fields);
    return this;
  }

  /**
   * Sets page number.
   *
   * @param  integer page The page number
   * @return self
   */
  page(page)
  {
    this._page.page = page;
    return this;
  }

  /**
   * Sets offset value.
   *
   * @param  integer offset The offset value.
   * @return self
   */
  offset(offset)
  {
    this._page.offset = offset;
    return this;
  }

  /**
   * Sets limit value.
   *
   * @param  integer limit The number of results to limit or `0` for limit at all.
   * @return self
   */
  limit(limit)
  {
    this._page.limit = Number.parseInt(limit);
    return this;
  }

  /**
   * Applies a query handler
   *
   * @param  Closure  closure A closure.
   * @return Function         Returns `this`.
   */
  handler(closure) {
    if (typeof closure === 'function') {
      closure(this);
    }
    return this;
  }

  /**
   * Sets the relations to retrieve.
   *
   * @param  array  embed The relations to load with the query.
   * @return object        Returns `this`.
   */
  embed(embed, conditions) {
    if (!arguments.length) {
      return this._embed;
    }
    if (typeof embed === "string" && arguments.length === 2) {
      var mix = {};
      mix[embed] = conditions || [];
      embed = [mix];
    } else {
      embed = Array.isArray(embed) ? embed : [embed];
    }
    this._embed = this._embed.concat(embed);
    return this;
  }

  /**
   * Sets the conditionnal dependency over some relations.
   *
   * @param array The conditionnal dependency.
   */
  has(has, conditions) {
    if (!arguments.length) {
      return this._has;
    }
    if (typeof has === "string" && arguments.length === 2) {
      var mix = {};
      mix[has] = conditions || [];
      has = [mix];
    }
    this._has = this._has.concat(has)
    return this;
  }

  /**
   * Gets a unique alias for the query or a query's relation if `relpath` is set.
   *
   * @param  string path   A dotted relation name or for identifying the query's relation.
   * @param  object schema The corresponding schema to alias.
   * @return string        A string alias.
   */
  alias(path, schema) {
    if (arguments.length < 2) {
      path = path || '';
      if (this._aliases[path] !== undefined) {
        return this._aliases[path];
      } else {
        throw new Error("No alias has been defined for `'" + path + "'`.");
      }
    }

    var alias = schema.source();
    if (this._aliasCounter[alias] === undefined) {
      this._aliasCounter[alias] = 0;
      this._aliases[path] = alias;
    } else {
      alias = this._aliases[path] = alias + '__' + this._aliasCounter[alias]++;
    }
    this._schemas[alias] = schema;
    return alias;
  }

  /**
   * Applies the has conditions.
   */
  _applyHas() {
    var schema = this.schema();
    var tree = schema.treeify(this.has());
    this._applyJoins(this.schema(), tree, '', this.alias());
    var has = this.has();
    for (var value of has) {
      var key = Object.keys(value)[0];
      var conditions = value[key];
      this.where(conditions, this.alias(key));
    }
  }

  /**
   * Applies the limit range when applicable.
   */
  _applyLimit() {
    if (!this._page.limit) {
        return;
    }
    var offset;
    if (this._page.offset) {
      offset = this._page.offset;
    } else {
      var page = this._page.page ? this._page.page : 1;
      offset = (page - 1) * this._page.limit;
    }
    this.statement().limit(this._page.limit, offset);
  }

  /**
   * Applies joins.
   *
   * @param Object schema    The schema to perform joins on.
   * @param Array  tree      The tree of relations to join.
   * @param Array  basePath  The base relation path.
   * @param String aliasFrom The alias name of the from model.
   */
  _applyJoins(schema, tree, basePath, aliasFrom) {
    for (var key in tree) {
      var childs = tree[key];
      var rel = schema.relation(key);
      var path = basePath ? basePath + '.' + key : key;
      var to;

      if (rel.type() !== 'hasManyThrough') {
        to = this._join(path, rel, aliasFrom);
      } else {
        var name = rel.using();
        var nameThrough = rel.through();
        var pathThrough = path ? path + '.' + nameThrough : nameThrough;
        var from = rel.from();

        var relThrough = from.definition().relation(nameThrough);
        var aliasThrough = this._join(pathThrough, relThrough, aliasFrom);

        var modelThrough = relThrough.to();
        var relTo = modelThrough.definition().relation(name);
        to = this._join(path, relTo, aliasThrough);
      }

      if (childs && Object.keys(childs).length) {
        this._applyJoins(rel.to().definition(), childs, path, to);
      }
    }
  }

  /**
   * Set a query's join according a Relationship.
   *
   * @param  string path      The relation path.
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @return string           The "to" model alias.
   */
  _join(path, rel, fromAlias) {
    if (this._aliases[path] !== undefined) {
      return this._aliases[path];
    }

    var schema = rel.to().definition();
    var source = schema.source();
    var toAlias = this.alias(path, schema);

    var table = {};
    table[source] = toAlias;

    this.statement().join(
      table,
      this._on(rel, fromAlias, toAlias),
      'LEFT'
    );
    return toAlias;
  }

  /**
   * Build the `ON` constraints from a `Relationship` instance.
   *
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @param  string toAlias   The "to" model alias.
   * @return array             A constraints array.
   */
  _on(rel, fromAlias, toAlias) {
    if (rel.type() === 'hasManyThrough') {
      return [];
    }
    var keys = rel.keys();
    var fromField = Object.keys(keys)[0];
    var toField = keys[fromField];
    return { '=': [{ ':name': fromAlias + '.' + fromField },  { ':name': toAlias + '.' + toField }] };
  }
}

/**
 * Class dependencies.
 *
 * @var array
 */
Query._classes = {
  collector: Collector
};

export default Query;
<|endoftext|>


<commit_before>'use strict';

var request = require('request');
var Q = require('q');
var moment = require('moment');

require('./db');
var Post = require('mongoose').model('Post');

var reddit = {
    url: 'http://www.reddit.com',
    limit: 100,
    uaString: 'thedigest/0.1 by connyay'
};
var echojs = {
    url: 'http://www.echojs.com',
    limit: 32
};
var hackernews = {
    url: 'http://hnify.herokuapp.com',
    limit: 90
};

var posts = [];

function fetchRedditFeed(subreddit) {
    var deferred = Q.defer();
    var opts = {
        headers: {
            'User-Agent': reddit.uaString
        }
    };
    request(reddit.url + '/r/' + subreddit + '/hot.json?limit=' + reddit.limit, opts, function(error, response, body) {
        if (!error && response.statusCode === 200) {
            var data = JSON.parse(body);
            data.data.children.forEach(function(post) {
                if (post.data.stickied) {
                    return;
                }
                var data = post.data;
                posts.push({
                    'title': data.title,
                    'source': '/r/' + subreddit,
                    'time': moment(data.created_utc * 1000).fromNow(),
                    'link': data.url,
                    'comment_count': data.num_comments,
                    'comment_link': reddit.url + data.permalink
                });
            });
            deferred.resolve();
        }
    });
    return deferred.promise;
}

function fetchEchoJsFeed(page) {
    var deferred = Q.defer();
    var start = (page === 1) ? 1 : 33;
    request(echojs.url + '/api/getnews/top/' + start + '/' + echojs.limit, function(error, response, body) {
        if (!error && response.statusCode === 200) {
            var data = JSON.parse(body);
            data.news.forEach(function(post) {
                posts.push({
                    'title': post.title,
                    'source': 'Echo JS',
                    'time': moment(post.ctime * 1000).fromNow(),
                    'link': post.url,
                    'comment_count': post.comments,
                    'comment_link': echojs.url + '/news/' + post.id
                });
            });
            deferred.resolve();
        }
    });
    return deferred.promise;
}

function fetchHackerNewsFeed() {
    var deferred = Q.defer();
    request(hackernews.url + '/get/top?limit=' + hackernews.limit, function(error, response, body) {
        if (!error && response.statusCode === 200) {
            var data = JSON.parse(body);
            data.stories.forEach(function(post) {
                posts.push({
                    'title': post.title,
                    'source': 'HackerNews',
                    'time': post.published_time,
                    'link': post.link,
                    'comment_count': post.num_comments,
                    'comment_link': post.comments_link
                });
            });
            deferred.resolve();
        }
    });
    return deferred.promise;
}

function sortAndStorePosts() {
    var sortedPosts = [],
        category,
        separatedPosts = {
            secondsAgo: [],
            minutesAgo: [],
            hoursAgo: [],
            daysAgo: [],
            weeksAgo: [],
            monthsAgo: [],
            yearsAgo: []
        };

    var categories = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
    var length = posts.length;
    for (var i = 0; i < length; i++) {
        var post = posts[i];
        var time = post.time;

        for (var ci = 0; ci < categories.length; ci++) {
            category = categories[ci];
            if (time.indexOf(category) !== -1) {
                separatedPosts[category + 'sAgo'].push(post);
            }
        }
    }
    var sort = function(a, b) {
        var aTime = a.time.match(/\d+/);
        var bTime = b.time.match(/\d+/);
        return parseInt(aTime === null ? 0 : aTime[0]) - parseInt(bTime === null ? 0 : bTime[0]);
    };
    for (category in separatedPosts) {
        separatedPosts[category].sort(sort);
    }

    posts = sortedPosts.concat(separatedPosts.secondsAgo, separatedPosts.minutesAgo, separatedPosts.hoursAgo,
        separatedPosts.daysAgo, separatedPosts.weeksAgo, separatedPosts.monthsAgo,
        separatedPosts.yearsAgo);
}

var deferredList = [];
// Grab Reddit Feeds
['programming', 'technology', 'science', 'webdev', 'blackhat'].forEach(function(subreddit) {
    deferredList.push(fetchRedditFeed(subreddit));
});

// Grab EchoJS feed
// Page 1
deferredList.push(fetchEchoJsFeed(1));
// Page 2
deferredList.push(fetchEchoJsFeed(2));

// Grab HackerNews feed
deferredList.push(fetchHackerNewsFeed());

Q.all(deferredList).then(function() {
    sortAndStorePosts();
    // Wipe out DB
    Post.collection.remove({}, function(err) {
        if (!err) {
            console.log('Collection dropped.');
        } else {
            console.log('Error dropping collection?');
        }
    });
    Post.collection.insert(posts, function(err, docs) {
        if (!err) {
            console.log('Collection saved! Stored ' + docs.length + ' posts in the DB.');
        } else {
            console.log('Error saving docs?');
        }
    });
});
<commit_msg>add /r/web_design<commit_after>'use strict';

var request = require('request');
var Q = require('q');
var moment = require('moment');

require('./db');
var Post = require('mongoose').model('Post');

var reddit = {
    url: 'http://www.reddit.com',
    limit: 100,
    uaString: 'thedigest/0.1 by connyay'
};
var echojs = {
    url: 'http://www.echojs.com',
    limit: 32
};
var hackernews = {
    url: 'http://hnify.herokuapp.com',
    limit: 90
};

var posts = [];

function fetchRedditFeed(subreddit) {
    var deferred = Q.defer();
    var opts = {
        headers: {
            'User-Agent': reddit.uaString
        }
    };
    request(reddit.url + '/r/' + subreddit + '/hot.json?limit=' + reddit.limit, opts, function(error, response, body) {
        if (!error && response.statusCode === 200) {
            var data = JSON.parse(body);
            data.data.children.forEach(function(post) {
                if (post.data.stickied) {
                    return;
                }
                var data = post.data;
                posts.push({
                    'title': data.title,
                    'source': '/r/' + subreddit,
                    'time': moment(data.created_utc * 1000).fromNow(),
                    'link': data.url,
                    'comment_count': data.num_comments,
                    'comment_link': reddit.url + data.permalink
                });
            });
            deferred.resolve();
        }
    });
    return deferred.promise;
}

function fetchEchoJsFeed(page) {
    var deferred = Q.defer();
    var start = (page === 1) ? 1 : 33;
    request(echojs.url + '/api/getnews/top/' + start + '/' + echojs.limit, function(error, response, body) {
        if (!error && response.statusCode === 200) {
            var data = JSON.parse(body);
            data.news.forEach(function(post) {
                posts.push({
                    'title': post.title,
                    'source': 'Echo JS',
                    'time': moment(post.ctime * 1000).fromNow(),
                    'link': post.url,
                    'comment_count': post.comments,
                    'comment_link': echojs.url + '/news/' + post.id
                });
            });
            deferred.resolve();
        }
    });
    return deferred.promise;
}

function fetchHackerNewsFeed() {
    var deferred = Q.defer();
    request(hackernews.url + '/get/top?limit=' + hackernews.limit, function(error, response, body) {
        if (!error && response.statusCode === 200) {
            var data = JSON.parse(body);
            data.stories.forEach(function(post) {
                posts.push({
                    'title': post.title,
                    'source': 'HackerNews',
                    'time': post.published_time,
                    'link': post.link,
                    'comment_count': post.num_comments,
                    'comment_link': post.comments_link
                });
            });
            deferred.resolve();
        }
    });
    return deferred.promise;
}

function sortAndStorePosts() {
    var sortedPosts = [],
        category,
        separatedPosts = {
            secondsAgo: [],
            minutesAgo: [],
            hoursAgo: [],
            daysAgo: [],
            weeksAgo: [],
            monthsAgo: [],
            yearsAgo: []
        };

    var categories = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
    var length = posts.length;
    for (var i = 0; i < length; i++) {
        var post = posts[i];
        var time = post.time;

        for (var ci = 0; ci < categories.length; ci++) {
            category = categories[ci];
            if (time.indexOf(category) !== -1) {
                separatedPosts[category + 'sAgo'].push(post);
            }
        }
    }
    var sort = function(a, b) {
        var aTime = a.time.match(/\d+/);
        var bTime = b.time.match(/\d+/);
        return parseInt(aTime === null ? 0 : aTime[0]) - parseInt(bTime === null ? 0 : bTime[0]);
    };
    for (category in separatedPosts) {
        separatedPosts[category].sort(sort);
    }

    posts = sortedPosts.concat(separatedPosts.secondsAgo, separatedPosts.minutesAgo, separatedPosts.hoursAgo,
        separatedPosts.daysAgo, separatedPosts.weeksAgo, separatedPosts.monthsAgo,
        separatedPosts.yearsAgo);
}

var deferredList = [];
// Grab Reddit Feeds
['programming', 'technology', 'science', 'webdev', 'web_design', 'blackhat'].forEach(function(subreddit) {
    deferredList.push(fetchRedditFeed(subreddit));
});

// Grab EchoJS feed
// Page 1
deferredList.push(fetchEchoJsFeed(1));
// Page 2
deferredList.push(fetchEchoJsFeed(2));

// Grab HackerNews feed
deferredList.push(fetchHackerNewsFeed());

Q.all(deferredList).then(function() {
    sortAndStorePosts();
    // Wipe out DB
    Post.collection.remove({}, function(err) {
        if (!err) {
            console.log('Collection dropped.');
        } else {
            console.log('Error dropping collection?');
        }
    });
    Post.collection.insert(posts, function(err, docs) {
        if (!err) {
            console.log('Collection saved! Stored ' + docs.length + ' posts in the DB.');
        } else {
            console.log('Error saving docs?');
        }
    });
});
<|endoftext|>


<commit_before>// Settings = new Meteor.Collection('settings');

Settings = new Meteor.Collection("settings", {
    schema: new SimpleSchema({
        title: {
            type: String,
            label: "Title",
        },
        tagline: {
            type: String,
            label: "Tagline"
        },
        copies: {
            type: Number,
            label: "Number of copies",
        },
        requireViewInvite: {
            type: Boolean,
            label: "Require invite to view",
        },
        requirePostInvite: {
            type: Boolean,
            label: "Require invite to post",
        },
        requirePostsApproval: {
            type: Boolean,
            label: "Posts must be approved by admin",
        },
        emailNotifications: {
            type: Boolean,
            label: "Enable email notifications",
        },
        nestedComments: {
            type: Boolean,
            label: "Enable nested comments",
        },
        redistributeKarma: {
            type: Boolean,
            label: "Enable redistributed karma",
        },
        defaultEmail: {
            type: String,
        },
        scoreUpdateInterval: {
            type: Number,
        },
        postInterval: {
            type: Number,
        },
        commentInterval: {
            type: Number,
        },
        maxPostsPerDay: {
            type: Number,
        },
        startInvitesCount: {
            type: Number,
            defaultValue: 3
        },
        postsPerPage: {
            type: Number,
            defaultValue: 10
        },
        logoUrl: {
            type: String
        },
        logoHeight: {
            type: Number
        },
        logoWidth: {
            type: Number
        },
        language: {
            type: String,
            defaultValue: 'en'
        },
        backgroundColor: {
            type: String
        },
        secondaryColor: {
            type: String
        },
        buttonColor: {
            type: String
        },
        headerColor: {
            type: String
        },
        googleAnalyticsId: {
            type: String
        },
        mixpanelId: {
            type: String
        },
        clickyId: {
            type: String
        },
        embedlyId: {
            type: String
        },
        mailChimpAPIKey: {
            type: String
        },
        mailChimpListId: {
            type: String
        },
        footerCode: {
            type: String
        },
        extraCode: {
            type: String
        },
        notes: {
            type: String
        },
    })
});

Settings.allow({
  insert: isAdminById
, update: isAdminById
, remove: isAdminById
});

<commit_msg>Made settings fields optional.<commit_after>// Settings = new Meteor.Collection('settings');

Settings = new Meteor.Collection("settings", {
    schema: new SimpleSchema({
        title: {
            type: String,
            label: "Title",
            optional: true
        },
        tagline: {
            type: String,
            label: "Tagline",
            optional: true
        },
        copies: {
            type: Number,
            label: "Number of copies",
            optional: true
        },
        requireViewInvite: {
            type: Boolean,
            label: "Require invite to view",
            optional: true
        },
        requirePostInvite: {
            type: Boolean,
            label: "Require invite to post",
            optional: true
        },
        requirePostsApproval: {
            type: Boolean,
            label: "Posts must be approved by admin",
            optional: true
        },
        emailNotifications: {
            type: Boolean,
            label: "Enable email notifications",
            optional: true
        },
        nestedComments: {
            type: Boolean,
            label: "Enable nested comments",
            optional: true
        },
        redistributeKarma: {
            type: Boolean,
            label: "Enable redistributed karma",
            optional: true
        },
        defaultEmail: {
            type: String,
            optional: true
        },
        scoreUpdateInterval: {
            type: Number,
            optional: true
        },
        postInterval: {
            type: Number,
            optional: true
        },
        commentInterval: {
            type: Number,
            optional: true
        },
        maxPostsPerDay: {
            type: Number,
            optional: true
        },
        startInvitesCount: {
            type: Number,
            defaultValue: 3,
            optional: true
        },
        postsPerPage: {
            type: Number,
            defaultValue: 10,
            optional: true
        },
        logoUrl: {
            type: String,
            optional: true
        },
        logoHeight: {
            type: Number,
            optional: true
        },
        logoWidth: {
            type: Number,
            optional: true
        },
        language: {
            type: String,
            defaultValue: 'en',
            optional: true
        },
        backgroundColor: {
            type: String,
            optional: true
        },
        secondaryColor: {
            type: String,
            optional: true
        },
        buttonColor: {
            type: String,
            optional: true
        },
        headerColor: {
            type: String,
            optional: true
        },
        googleAnalyticsId: {
            type: String,
            optional: true
        },
        mixpanelId: {
            type: String,
            optional: true
        },
        clickyId: {
            type: String,
            optional: true
        },
        embedlyId: {
            type: String,
            optional: true
        },
        mailChimpAPIKey: {
            type: String,
            optional: true
        },
        mailChimpListId: {
            type: String,
            optional: true
        },
        footerCode: {
            type: String,
            optional: true
        },
        extraCode: {
            type: String,
            optional: true
        },
        notes: {
            type: String,
            optional: true
        },
    })
});

Settings.allow({
  insert: isAdminById
, update: isAdminById
, remove: isAdminById
});

<|endoftext|>


<commit_before>// Widgets.Temperature
// -------------------

Widgets.Temperature = Widgets.Device.extend({

    defaults: {
        kind: 'temperature',
        buffer: {
            pairing: true,
            shadowing: true
        }
    },

    onInitUI: function () {
        Widgets.Device.prototype.onInitUI.apply(this, arguments);

        this._$aside.css({
            'line-height': parseInt(this.computed('svg.height')) + "px"
        });
    },

    onBeforeInitD3: function () {
        if (_.isFunction(Widgets.Device.prototype.onBeforeInitD3)) {
            Widgets.Device.prototype.onBeforeInitD3.apply(this, arguments);
        }

        // Setup d3 functions
        this.valueFn = function (d) {
            try {
                if (d.timestamp) {
                    return parseInt(d.data.event.state.value);
                } else {
                    return parseInt(d.event.state.value);
                }
            } catch (e) {
                return 0;
            }
        };
    },

    onInitD3: function () {
        Widgets.Device.prototype.onInitD3.apply(this, arguments);

        this.y = d3.scale.linear()
            .range([0, this.computed('svg.height') - this.options.theme.device.state.border.width]);

        this.initD3Chart();
    },

    onDestroyD3: function() {
        Widgets.Device.prototype.onDestroyD3.apply(this, arguments);

        delete this.y;
        this.destroyD3Chart();
    },

    onRender: function () {
        Widgets.Device.prototype.onRender.apply(this, arguments);

        var self = this;

        this.y.domain(d3.extent(
            self.buffer.select(function (d) {
                return ensure(d.data, 'event') && d.data.event.type == 'update'}
            ), self.valueFn)
        );

        this.renderD3Chart();
    },

    onRulerFocusUpdate: function (coordinate, direction, timestamp, focusedFrame, lastFocusedFrame) {
        Widgets.Device.prototype.onRulerFocusUpdate.apply(this, arguments);

        if (ensure(focusedFrame, 'data.event.type', 'update') && ensure(focusedFrame, 'data.event.picto')) {
            this._$picto.attr({class: 'picto'}).text(this.valueFn(focusedFrame.data)+'°');
        } else {
            // fallback
            this._$picto.attr({class: 'picto picto-temperature_type'}).text('');
        }

        this.updateD3ChartFocus(focusedFrame, lastFocusedFrame);
    }
});

_.extend(Widgets.Temperature.prototype, Widgets.Mixins.Chart, Widgets.Mixins.Focus);
<commit_msg>remove check for parent's prototype definition<commit_after>// Widgets.Temperature
// -------------------

Widgets.Temperature = Widgets.Device.extend({

    defaults: {
        kind: 'temperature',
        buffer: {
            pairing: true,
            shadowing: true
        }
    },

    onInitUI: function () {
        Widgets.Device.prototype.onInitUI.apply(this, arguments);

        this._$aside.css({
            'line-height': parseInt(this.computed('svg.height')) + "px"
        });
    },

    onBeforeInitD3: function () {
        Widgets.Device.prototype.onBeforeInitD3.apply(this, arguments);

        // Setup d3 functions
        this.valueFn = function (d) {
            try {
                if (d.timestamp) {
                    return parseInt(d.data.event.state.value);
                } else {
                    return parseInt(d.event.state.value);
                }
            } catch (e) {
                return 0;
            }
        };
    },

    onInitD3: function () {
        Widgets.Device.prototype.onInitD3.apply(this, arguments);

        this.y = d3.scale.linear()
            .range([0, this.computed('svg.height') - this.options.theme.device.state.border.width]);

        this.initD3Chart();
    },

    onDestroyD3: function() {
        Widgets.Device.prototype.onDestroyD3.apply(this, arguments);

        delete this.y;
        this.destroyD3Chart();
    },

    onRender: function () {
        Widgets.Device.prototype.onRender.apply(this, arguments);

        var self = this;

        this.y.domain(d3.extent(
            self.buffer.select(function (d) {
                return ensure(d.data, 'event') && d.data.event.type == 'update'}
            ), self.valueFn)
        );

        this.renderD3Chart();
    },

    onRulerFocusUpdate: function (coordinate, direction, timestamp, focusedFrame, lastFocusedFrame) {
        Widgets.Device.prototype.onRulerFocusUpdate.apply(this, arguments);

        if (ensure(focusedFrame, 'data.event.type', 'update') && ensure(focusedFrame, 'data.event.picto')) {
            this._$picto.attr({class: 'picto'}).text(this.valueFn(focusedFrame.data)+'°');
        } else {
            // fallback
            this._$picto.attr({class: 'picto picto-temperature_type'}).text('');
        }

        this.updateD3ChartFocus(focusedFrame, lastFocusedFrame);
    }
});

_.extend(Widgets.Temperature.prototype, Widgets.Mixins.Chart, Widgets.Mixins.Focus);
<|endoftext|>


<commit_before>const Base64 = require('js-base64').Base64;
const d3Interpolate = require('d3-interpolate');
const SpotUtils = require('../utils/SpotUtils');

function getRadious(area) {
    return Math.sqrt(area/Math.PI);
}

const PolloNetworkViewMixin = {
    buildChart() {
        const svgSel = d3.select(this.svg);

        // Enable zoom behaviour
        this.zoom = d3.behavior.zoom().on("zoom", this.onZoom);
        this.canvas = svgSel.append('g');
        svgSel.call(this.zoom);

        // Node related scales
        const hitsDomain = [1, this.state.data.maxNodes];
        const ipDomain = [false, true];
        this.nodeSizeScale = d3.scale.linear().domain(hitsDomain);
        this.chargeScale = d3.scale.linear().domain(hitsDomain);
        this.typeScale = d3.scale.ordinal().domain(ipDomain).range(["circle", "diamond"]);
        this.colorScale = d3.scale.ordinal().domain(ipDomain).range(["#fdb813", "#0071C5"]);

        // Link related scales
        this.opacityScale = d3.scale.threshold().domain([13]).range([0.1, 1]);
        this.linkColorScale = d3.scale.linear()
                                      .domain([16, 13, 12, 2])
                                      .range([d3.hsl(214, 0.04, 0.34), d3.hsl(216, 0.02, 0.59), d3.hsl(216, 0.69, 0.84), d3.hsl(201, 0.1, 0.72)])
                                      .interpolate(d3Interpolate.interpolateCubehelix);

        // Create a new force layout
        this.force = d3.layout.force();

        // Add and update nodes and links on every tick event from the force layout
        this.force.on('tick', () => {
            this.drawLinks();
            this.drawNodes();
        });

        // Create a tooltip
        this.tooltip = d3.tip()
            .attr('class', 'd3-tip')
            .html(n => `${n.label}<br/> <small class="text-muted">Right click to apply this node as filter</small>`);

        this.canvas.call(this.tooltip);
    },
    onZoom: function () {
        const translate = d3.event.translate;
        const scale = d3.event.scale;

        this.canvas.attr('transform', `translate(${translate})scale(${scale})`);
    },
    draw() {
        const svgElement = $(this.svg);

        svgElement.width('100%').height('100%');

        const size = [svgElement.width(), svgElement.height()];
        /*
         * Calculate the max area a node can have.
         *
         * 1. Get the smaller value betwenn width and height of the canvas.
         * 2. Calculate the area of imaginary square whose edge length equals the previous value.
         * 3. Set maxNodeArea to the 40% of the available area of our imaginary square
         */
        const maxNodeArea = Math.pow(Math.min(size[0], size[1]), 2) * 0.1;

        // The biggest node we can have goes up to the 50% of maxNodeArea
        this.nodeSizeScale.range([maxNodeArea*0.01, maxNodeArea*0.5]);
        const sizeDomain = this.nodeSizeScale.domain();
        this.chargeScale.range([-this.nodeSizeScale(sizeDomain[0]), -this.nodeSizeScale(sizeDomain[1])])

        this.force
            .stop()
            .size(size)
            .charge(-1000)
            .linkDistance(l => {
                return [l.source, l.target].reduce((length, n) => {
                    return length + getRadious(this.nodeSizeScale(n.hits));
                }, 50);
            })
            .chargeDistance(getRadious(this.nodeSizeScale(this.nodeSizeScale.domain()[1])) * 4)
            .nodes(this.state.data.nodes)
            .links(this.state.data.links);

        this.force.start();

        setTimeout(() => {
            this.force.stop()
                .charge(n => {
                    return this.chargeScale(n.weight);
                })
                .start();
        }, 1000);

        // Tooltip margins
        this.tooltip.box = [size[1]*.4, size[0]*.8, size[1]*.8, size[0]*.4];
    },
    drawNodes(nodes) {
        this.nodesSel = {
            update: this.canvas.selectAll('.node').data(this.state.data.nodes, n => SpotUtils.encodeId(n.id))
        };
        this.nodesSel.enter = this.nodesSel.update.enter();

        // Add new nodes
        this.nodesSel.enter
            .append('path', '.edge')
            .classed('node', true)
            .attr("id", n => SpotUtils.encodeId(n.id))
            .attr("d", d3.svg.symbol()
               .size(n => this.nodeSizeScale(n.hits))
               .type(n => this.typeScale(n.internalIp))
            )
            .attr('fill', n => this.colorScale(n.internalIp))
            .call(this.force.drag)
            .on('mouseover', (n) => {
                var direction = '';

                // Decide where should the tooltip be displayed?

                // Vertically
                if (d3.event.layerY<this.tooltip.box[0]) {
                    direction = 's';
                }
                else if (d3.event.layerY>this.tooltip.box[2]) {
                    direction = 'n';
                }

                // Horizontally
                if (d3.event.layerX>this.tooltip.box[1]) {
                    direction += 'w';
                }
                else if (d3.event.layerX<this.tooltip.box[3]) {
                    direction += 'e'
                }

                direction = direction || 'n';

                this.tooltip.direction(direction);
                this.tooltip.show.call(this, n);
            })
            .on("mousedown", n => {
                d3.event.stopPropagation();
            })
            .on("click", n => {
                this._onClick(n.id);
            })
            .on("contextmenu", n => {
                this.tooltip.hide();

                this._onContextualClick(n.id);
            })
            .on('mouseout', () => {
                this.tooltip.hide();
            });

        this.nodesSel.update
            .attr('transform', n => `translate(${n.x},${n.y})`)
            .transition()
                .attr("d", d3.svg.symbol()
                   .size(n => this.nodeSizeScale(n.hits))
                   .type(n => this.typeScale(n.internalIp))
                );
    },
    drawLinks(links) {
        this.linksSel = {
            update: this.canvas.selectAll('.edge').data(this.state.data.links, l => SpotUtils.encodeId(l.id))
        };
        this.linksSel.enter = this.linksSel.update.enter();

        // Add new links
        this.linksSel.enter
             .append('line')
             .classed('edge', true)
             .attr("id", l => SpotUtils.encodeId(l.id))
             .style('stroke', l => this.linkColorScale(l.score))
             .style('stroke-opacity', l => this.opacityScale(l.score));

        // Update links
        this.linksSel.update
            .attr("x1", l => l.source.x)
            .attr("y1", l => l.source.y)
            .attr("x2", l => l.target.x)
            .attr("y2", l => l.target.y);
    },
    highlightNodes(ids) {
        ids = ids instanceof Array ? ids : [ids];

        this.canvas.selectAll('.node').classed('node-faded', true);

        ids.forEach(id => {
            id = SpotUtils.encodeId(id);
            this.canvas.select(`#${id}.node`).classed("node-faded", false);
        })
    },
    highlightEdge(id) {
        id = SpotUtils.encodeId(id);
        this.canvas.selectAll('.edge').classed('edge-faded', true);

        this.canvas.select(`#${id}.edge`)
            .classed('edge-faded', false)
            .classed('highlight', true);
    },
    unhighlight() {
        this.canvas.selectAll('.node').classed('node-faded', false);
        this.canvas.selectAll('.edge')
            .classed('edge-faded', false)
            .classed('highlight', false);
    },
    selectNodes(ids) {
        ids = ids instanceof Array ? ids : [ids];

        this.canvas.selectAll('.node').classed('blink_me', false);

        ids.forEach((id) => {
            id = SpotUtils.encodeId(id);
            this.canvas.select(`#${id}.node`).classed('blink_me', true);
        });
    },
    selectEdge(id) {
        id = SpotUtils.encodeId(id);
        this.canvas.selectAll('.edge')
            .classed('blink_me', false)
            .filter(`#${id}`)
                .classed('active', true)
                .classed('blink_me', true);
    }
};

module.exports = PolloNetworkViewMixin;
<commit_msg>Fixed: Unselect previos link on network view (#152)<commit_after>const Base64 = require('js-base64').Base64;
const d3Interpolate = require('d3-interpolate');
const SpotUtils = require('../utils/SpotUtils');

function getRadious(area) {
    return Math.sqrt(area/Math.PI);
}

const PolloNetworkViewMixin = {
    buildChart() {
        const svgSel = d3.select(this.svg);

        // Enable zoom behaviour
        this.zoom = d3.behavior.zoom().on("zoom", this.onZoom);
        this.canvas = svgSel.append('g');
        svgSel.call(this.zoom);

        // Node related scales
        const hitsDomain = [1, this.state.data.maxNodes];
        const ipDomain = [false, true];
        this.nodeSizeScale = d3.scale.linear().domain(hitsDomain);
        this.chargeScale = d3.scale.linear().domain(hitsDomain);
        this.typeScale = d3.scale.ordinal().domain(ipDomain).range(["circle", "diamond"]);
        this.colorScale = d3.scale.ordinal().domain(ipDomain).range(["#fdb813", "#0071C5"]);

        // Link related scales
        this.opacityScale = d3.scale.threshold().domain([13]).range([0.1, 1]);
        this.linkColorScale = d3.scale.linear()
                                      .domain([16, 13, 12, 2])
                                      .range([d3.hsl(214, 0.04, 0.34), d3.hsl(216, 0.02, 0.59), d3.hsl(216, 0.69, 0.84), d3.hsl(201, 0.1, 0.72)])
                                      .interpolate(d3Interpolate.interpolateCubehelix);

        // Create a new force layout
        this.force = d3.layout.force();

        // Add and update nodes and links on every tick event from the force layout
        this.force.on('tick', () => {
            this.drawLinks();
            this.drawNodes();
        });

        // Create a tooltip
        this.tooltip = d3.tip()
            .attr('class', 'd3-tip')
            .html(n => `${n.label}<br/> <small class="text-muted">Right click to apply this node as filter</small>`);

        this.canvas.call(this.tooltip);
    },
    onZoom: function () {
        const translate = d3.event.translate;
        const scale = d3.event.scale;

        this.canvas.attr('transform', `translate(${translate})scale(${scale})`);
    },
    draw() {
        const svgElement = $(this.svg);

        svgElement.width('100%').height('100%');

        const size = [svgElement.width(), svgElement.height()];
        /*
         * Calculate the max area a node can have.
         *
         * 1. Get the smaller value betwenn width and height of the canvas.
         * 2. Calculate the area of imaginary square whose edge length equals the previous value.
         * 3. Set maxNodeArea to the 40% of the available area of our imaginary square
         */
        const maxNodeArea = Math.pow(Math.min(size[0], size[1]), 2) * 0.1;

        // The biggest node we can have goes up to the 50% of maxNodeArea
        this.nodeSizeScale.range([maxNodeArea*0.01, maxNodeArea*0.5]);
        const sizeDomain = this.nodeSizeScale.domain();
        this.chargeScale.range([-this.nodeSizeScale(sizeDomain[0]), -this.nodeSizeScale(sizeDomain[1])])

        this.force
            .stop()
            .size(size)
            .charge(-1000)
            .linkDistance(l => {
                return [l.source, l.target].reduce((length, n) => {
                    return length + getRadious(this.nodeSizeScale(n.hits));
                }, 50);
            })
            .chargeDistance(getRadious(this.nodeSizeScale(this.nodeSizeScale.domain()[1])) * 4)
            .nodes(this.state.data.nodes)
            .links(this.state.data.links);

        this.force.start();

        setTimeout(() => {
            this.force.stop()
                .charge(n => {
                    return this.chargeScale(n.weight);
                })
                .start();
        }, 1000);

        // Tooltip margins
        this.tooltip.box = [size[1]*.4, size[0]*.8, size[1]*.8, size[0]*.4];
    },
    drawNodes(nodes) {
        this.nodesSel = {
            update: this.canvas.selectAll('.node').data(this.state.data.nodes, n => SpotUtils.encodeId(n.id))
        };
        this.nodesSel.enter = this.nodesSel.update.enter();

        // Add new nodes
        this.nodesSel.enter
            .append('path', '.edge')
            .classed('node', true)
            .attr("id", n => SpotUtils.encodeId(n.id))
            .attr("d", d3.svg.symbol()
               .size(n => this.nodeSizeScale(n.hits))
               .type(n => this.typeScale(n.internalIp))
            )
            .attr('fill', n => this.colorScale(n.internalIp))
            .call(this.force.drag)
            .on('mouseover', (n) => {
                var direction = '';

                // Decide where should the tooltip be displayed?

                // Vertically
                if (d3.event.layerY<this.tooltip.box[0]) {
                    direction = 's';
                }
                else if (d3.event.layerY>this.tooltip.box[2]) {
                    direction = 'n';
                }

                // Horizontally
                if (d3.event.layerX>this.tooltip.box[1]) {
                    direction += 'w';
                }
                else if (d3.event.layerX<this.tooltip.box[3]) {
                    direction += 'e'
                }

                direction = direction || 'n';

                this.tooltip.direction(direction);
                this.tooltip.show.call(this, n);
            })
            .on("mousedown", n => {
                d3.event.stopPropagation();
            })
            .on("click", n => {
                this._onClick(n.id);
            })
            .on("contextmenu", n => {
                this.tooltip.hide();

                this._onContextualClick(n.id);
            })
            .on('mouseout', () => {
                this.tooltip.hide();
            });

        this.nodesSel.update
            .attr('transform', n => `translate(${n.x},${n.y})`)
            .transition()
                .attr("d", d3.svg.symbol()
                   .size(n => this.nodeSizeScale(n.hits))
                   .type(n => this.typeScale(n.internalIp))
                );
    },
    drawLinks(links) {
        this.linksSel = {
            update: this.canvas.selectAll('.edge').data(this.state.data.links, l => SpotUtils.encodeId(l.id))
        };
        this.linksSel.enter = this.linksSel.update.enter();

        // Add new links
        this.linksSel.enter
             .append('line')
             .classed('edge', true)
             .attr("id", l => SpotUtils.encodeId(l.id))
             .style('stroke', l => this.linkColorScale(l.score))
             .style('stroke-opacity', l => this.opacityScale(l.score));

        // Update links
        this.linksSel.update
            .attr("x1", l => l.source.x)
            .attr("y1", l => l.source.y)
            .attr("x2", l => l.target.x)
            .attr("y2", l => l.target.y);
    },
    highlightNodes(ids) {
        ids = ids instanceof Array ? ids : [ids];

        this.canvas.selectAll('.node').classed('node-faded', true);

        ids.forEach(id => {
            id = SpotUtils.encodeId(id);
            this.canvas.select(`#${id}.node`).classed("node-faded", false);
        })
    },
    highlightEdge(id) {
        id = SpotUtils.encodeId(id);
        this.canvas.selectAll('.edge').classed('edge-faded', true);

        this.canvas.select(`#${id}.edge`)
            .classed('edge-faded', false)
            .classed('highlight', true);
    },
    unhighlight() {
        this.canvas.selectAll('.node').classed('node-faded', false);
        this.canvas.selectAll('.edge')
            .classed('edge-faded', false)
            .classed('highlight', false);
    },
    selectNodes(ids) {
        ids = ids instanceof Array ? ids : [ids];

        this.canvas.selectAll('.node.blink_me').classed('blink_me', false);

        ids.forEach((id) => {
            id = SpotUtils.encodeId(id);
            this.canvas.select(`#${id}.node`).classed('blink_me', true);
        });
    },
    selectEdge(id) {
        id = SpotUtils.encodeId(id);

        this.canvas.selectAll('.edge.blink_me,.edge.active').classed('blink_me', false).classed('active', false);

        this.canvas.selectAll('.edge')
            .classed('blink_me', false)
            .filter(`#${id}`)
                .classed('active', true)
                .classed('blink_me', true);
    }
};

module.exports = PolloNetworkViewMixin;
<|endoftext|>


<commit_before>sap.ui.define([
	"sap/ui/Device"
], function(Device) {

	"use strict";
	return {
		name: "QUnit TestSuite for sap.m",
		defaults: {
			bootCore: true,
			ui5: {
				libs: "sap.m",
				theme: "sap_belize",
				noConflict: true,
				preload: "auto",
				"xx-waitForTheme": "init"
			},
			qunit: {
				version: 1,
				reorder: false
			},
			sinon: {
				version: 1,
				qunitBridge: true,
				useFakeTimers: false
			},
			module: "./{name}.qunit"
		},
		tests: {
			ComboBox: {
				title: "Test Page for sap.m.ComboBox",
				_alternativeTitle: "QUnit tests: sap.m.ComboBox",
				ui5: {
					libs: "sap.m, sap.ui.layout",
					language: "en"
				},
				sinon: {
					useFakeTimers: true
				}
			},
			DatePicker: {
				title: "DatePicker - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DatePicker",
				qunit: {
					// one test checks a module for not being loaded, another checks it for being loaded
					// -> order of tests is significant!
					reorder: false
				},
				ui5: {
					language: "en-US"
				}
			},
			DateRangeSelection: {
				title: "DateRangeSelection - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DateRangeSelection",
				ui5: {
					language: "en-US"
				}
			},
			DateTimeField: {
				title: "DateTimeField - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DatePicker",
				ui5: {
					language: "en-US"
				},
				qunit: {
					version: 2
				}
			},
			DateTimeInput: {
				title: "Test Page for sap.m.DateTimeInput",
				_alternativeTitle: "QUnit page for sap.m.DateTimeInput",
				ui5: {
					language: "en-US"
				},
				sinon: {
					useFakeTimers: true
				}
			},
			DateTimePicker: {
				title: "DatePicker - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DateTimePicker",
				ui5: {
					language: "en-US"
				}
			},
			FeedInput: {
				title: "Test Page for sap.m.FeedInput",
				qunit: {
					version: 2
				},
				sinon: {
					version: 4
				},
				coverage: {
					only: "//sap\/m\/FeedInput.*/"
				}
			},
			Input: {
				title: "QUnit page for sap.m.Input",
				sinon: {
					useFakeTimers: true
				}
			},
			InputBase: {
				title: "QUnit tests: sap.m.InputBase",
				sinon: {
					useFakeTimers: true
				}
			},
			MaskInput: {
				title: "Test Page for sap.m.MaskInput",
				_alternativeTitle: "QUnit page for sap.m.MaskInput",
				ui5: {
					language: "en-US",
					bindingSyntax: "simple"
				},
				sinon: {
					useFakeTimers: true
				}
			},
			MultiComboBox: {
				title: "QUnit tests: sap.m.MultiComboBox",
				sinon: {
					useFakeTimers: true
				}
			},
			MultiInput: {
				title: "QUnit page for sap.m.MultiInput",
				sinon: {
					useFakeTimers: true
				}
			},
			StepInput: {
				title: "QUnit Page for sap.m.StepInput",
				sinon: {
					useFakeTimers: true
				}
			},
			SuggestionsPopover: {
				title: "QUnit Page for sap.m.SuggestionsPopover",
				ui5: {
					compatVersion: "1.65"
				},
				qunit: {
					version: 2
				},
				coverage: {
					only: [
						"sap/m/SuggestionsPopover"
					]
				}
			},
			"inputUtils/highlightDOMElements": {
				title: "QUnit Page for sap.m.inputs.highlightDOMElements",
				ui5: {
					compatVersion: "1.81"
				},
				qunit: {
					version: 2
				}
			},
			"inputUtils/wordStartsWithValue": {
				title: "QUnit Page for sap.m.inputs.wordStartsWithValue",
				ui5: {
					compatVersion: "1.81"
				},
				qunit: {
					version: 2
				}
			},
			"inputUtils/completeTextSelected": {
				title: "QUnit Page for sap.m.inputs.completeTextSelected",
				ui5: {
					compatVersion: "1.83"
				},
				qunit: {
					version: 2
				}
			},
			TextArea: {
				title: "Test Page for sap.m.TextArea",
				_alternativeTitle: "QUnit page for sap.m.TextArea",
				sinon: {
					useFakeTimers: true
				}
			},
			TimePicker: {
				title: "Test Page for sap.m.TimePicker",
				_alternativeTitle: "QUnit page for sap.m.TimePicker",
				ui5: {
					language: "en-US"
				}
			},
			Token: {
				title: "Test Page for sap.m.Token",
				_alternativeTitle: "QUnit page for sap.m.Token"
			},
			Tokenizer: {
				title: "Test Page for sap.m.Tokenizer",
				_alternativeTitle: "QUnit page for sap.m.Tokenizer"
			},
			ValueStateMessage: {
				title: "Test page for sap.m.delegate.ValueStateMessage",
				_alternativeTitle: "QUnit tests for sap.m.delegate.ValueStateMessage",
				sinon: {
					useFakeTimers: true
				}
			}
		}
	};
});
<commit_msg>[INTERNAL] sap.m.TimePicker: testsuite.inputs.qunit config update<commit_after>sap.ui.define([
	"sap/ui/Device"
], function(Device) {

	"use strict";
	return {
		name: "QUnit TestSuite for sap.m",
		defaults: {
			bootCore: true,
			ui5: {
				libs: "sap.m",
				theme: "sap_belize",
				noConflict: true,
				preload: "auto",
				"xx-waitForTheme": "init"
			},
			qunit: {
				version: 1,
				reorder: false
			},
			sinon: {
				version: 1,
				qunitBridge: true,
				useFakeTimers: false
			},
			module: "./{name}.qunit"
		},
		tests: {
			ComboBox: {
				title: "Test Page for sap.m.ComboBox",
				_alternativeTitle: "QUnit tests: sap.m.ComboBox",
				ui5: {
					libs: "sap.m, sap.ui.layout",
					language: "en"
				},
				sinon: {
					useFakeTimers: true
				}
			},
			DatePicker: {
				title: "DatePicker - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DatePicker",
				qunit: {
					// one test checks a module for not being loaded, another checks it for being loaded
					// -> order of tests is significant!
					reorder: false
				},
				ui5: {
					language: "en-US"
				}
			},
			DateRangeSelection: {
				title: "DateRangeSelection - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DateRangeSelection",
				ui5: {
					language: "en-US"
				}
			},
			DateTimeField: {
				title: "DateTimeField - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DatePicker",
				ui5: {
					language: "en-US"
				},
				qunit: {
					version: 2
				}
			},
			DateTimeInput: {
				title: "Test Page for sap.m.DateTimeInput",
				_alternativeTitle: "QUnit page for sap.m.DateTimeInput",
				ui5: {
					language: "en-US"
				},
				sinon: {
					useFakeTimers: true
				}
			},
			DateTimePicker: {
				title: "DatePicker - sap.m",
				_alternativeTitle: "QUnit tests: sap.m.DateTimePicker",
				ui5: {
					language: "en-US"
				}
			},
			FeedInput: {
				title: "Test Page for sap.m.FeedInput",
				qunit: {
					version: 2
				},
				sinon: {
					version: 4
				},
				coverage: {
					only: "//sap\/m\/FeedInput.*/"
				}
			},
			Input: {
				title: "QUnit page for sap.m.Input",
				sinon: {
					useFakeTimers: true
				}
			},
			InputBase: {
				title: "QUnit tests: sap.m.InputBase",
				sinon: {
					useFakeTimers: true
				}
			},
			MaskInput: {
				title: "Test Page for sap.m.MaskInput",
				_alternativeTitle: "QUnit page for sap.m.MaskInput",
				ui5: {
					language: "en-US",
					bindingSyntax: "simple"
				},
				sinon: {
					useFakeTimers: true
				}
			},
			MultiComboBox: {
				title: "QUnit tests: sap.m.MultiComboBox",
				sinon: {
					useFakeTimers: true
				}
			},
			MultiInput: {
				title: "QUnit page for sap.m.MultiInput",
				sinon: {
					useFakeTimers: true
				}
			},
			StepInput: {
				title: "QUnit Page for sap.m.StepInput",
				sinon: {
					useFakeTimers: true
				}
			},
			SuggestionsPopover: {
				title: "QUnit Page for sap.m.SuggestionsPopover",
				ui5: {
					compatVersion: "1.65"
				},
				qunit: {
					version: 2
				},
				coverage: {
					only: [
						"sap/m/SuggestionsPopover"
					]
				}
			},
			"inputUtils/highlightDOMElements": {
				title: "QUnit Page for sap.m.inputs.highlightDOMElements",
				ui5: {
					compatVersion: "1.81"
				},
				qunit: {
					version: 2
				}
			},
			"inputUtils/wordStartsWithValue": {
				title: "QUnit Page for sap.m.inputs.wordStartsWithValue",
				ui5: {
					compatVersion: "1.81"
				},
				qunit: {
					version: 2
				}
			},
			"inputUtils/completeTextSelected": {
				title: "QUnit Page for sap.m.inputs.completeTextSelected",
				ui5: {
					compatVersion: "1.83"
				},
				qunit: {
					version: 2
				}
			},
			TextArea: {
				title: "Test Page for sap.m.TextArea",
				_alternativeTitle: "QUnit page for sap.m.TextArea",
				sinon: {
					useFakeTimers: true
				}
			},
			TimePicker: {
				title: "Test Page for sap.m.TimePicker",
				_alternativeTitle: "QUnit page for sap.m.TimePicker",
				ui5: {
					language: "en-US"
				},
				sinon: {
					version: 4
				}
			},
			Token: {
				title: "Test Page for sap.m.Token",
				_alternativeTitle: "QUnit page for sap.m.Token"
			},
			Tokenizer: {
				title: "Test Page for sap.m.Tokenizer",
				_alternativeTitle: "QUnit page for sap.m.Tokenizer"
			},
			ValueStateMessage: {
				title: "Test page for sap.m.delegate.ValueStateMessage",
				_alternativeTitle: "QUnit tests for sap.m.delegate.ValueStateMessage",
				sinon: {
					useFakeTimers: true
				}
			}
		}
	};
});
<|endoftext|>


<commit_before>//More functions, related to comprehensions and iterables
var exports=null;//IGNORE
var $finished=null;//IGNORE
var $true=true;//IGNORE
function Integer(x){}//IGNORE
function String$(a,b){}//IGNORE
function StringBuilder(){}//IGNORE

function byIncreasing/*<Element,Value>*/(/*Callable<Value?,Element>*/comp) {
    return function(x, y) {
        var a = comp(x);
        if (a !== null) {
            var b = comp(y);
            if (b !== null) {
                return a.compare(b);
            }
        }
        return null;
    };
}

function byDecreasing/*<Element,Value>*/(/*Callable<Value?,Element>*/comp) {
    return function(x, y) {
        var a = comp(x);
        if (a !== null) {
            var b = comp(y);
            if (b !== null) {
                return b.compare(a);
            }
        }
        return null;
    };
}

function count(/*Iterable<Boolean>*/truths) {
    if (truths === undefined) return Integer(0);
    var c=0;
    var iter = truths.getIterator();
    var i; while ((i = iter.next()) !== $finished) {
        if (i === $true) c++;
    }
    return Integer(c);
}

function string(/*Iterable<Character>*/chars) {
    if (chars === undefined) return String$('',0);
    var s = StringBuilder();
    var iter = chars.getIterator();
    var c; while ((c = iter.next()) !== $finished) {
        s.appendCharacter(c);
    }
    return s.getString();
}

exports.byIncreasing=byIncreasing;
exports.byDecreasing=byDecreasing;
exports.count=count;
exports.string=string;
<commit_msg>Implement equalTo, greaterThan, lessThan to close #91<commit_after>//More functions, related to comprehensions and iterables
var exports=null;//IGNORE
var $finished=null;//IGNORE
var $true=true;//IGNORE
function Integer(x){}//IGNORE
function String$(a,b){}//IGNORE
function StringBuilder(){}//IGNORE

function byIncreasing/*<Element,Value>*/(/*Callable<Value?,Element>*/comp) {
    return function(x, y) {
        var a = comp(x);
        if (a !== null) {
            var b = comp(y);
            if (b !== null) {
                return a.compare(b);
            }
        }
        return null;
    };
}

function byDecreasing/*<Element,Value>*/(/*Callable<Value?,Element>*/comp) {
    return function(x, y) {
        var a = comp(x);
        if (a !== null) {
            var b = comp(y);
            if (b !== null) {
                return b.compare(a);
            }
        }
        return null;
    };
}

function count(/*Iterable<Boolean>*/truths) {
    if (truths === undefined) return Integer(0);
    var c=0;
    var iter = truths.getIterator();
    var i; while ((i = iter.next()) !== $finished) {
        if (i === $true) c++;
    }
    return Integer(c);
}

function string(/*Iterable<Character>*/chars) {
    if (chars === undefined) return String$('',0);
    var s = StringBuilder();
    var iter = chars.getIterator();
    var c; while ((c = iter.next()) !== $finished) {
        s.appendCharacter(c);
    }
    return s.getString();
}

function equalTo(v) {
    return function(e) {
        return v.equals(e);
    };
}
function greaterThan(v) {
    return function(e) {
        return e.compare(v) === larger;
    };
}
function lessThan(v) {
    return function(e) {
        return e.compare(v) === smaller;
    };
}

exports.equalTo=equalTo;
exports.greaterThan=greaterThan;
exports.lessThan=lessThan;
exports.byIncreasing=byIncreasing;
exports.byDecreasing=byDecreasing;
exports.count=count;
exports.string=string;
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides an abstract property binding.
sap.ui.define(['jquery.sap.global', './PropertyBinding', './SimpleType'],
	function(jQuery, PropertyBinding, SimpleType) {
	"use strict";

	/**
	 * Constructor for CompositeBinding
	 *
	 * @class
	 * The CompositeBinding is used to bundle multiple property bindings which are be used to provide a single binding against
	 * these property bindings. Note: Only One Way binding is supported. So setValue and setExternalValue throw exceptions.
	 *
	 * @public
	 * @alias sap.ui.model.CompositeBinding
	 */

	var CompositeBinding = PropertyBinding.extend("sap.ui.model.CompositeBinding", /** @lends sap.ui.model.CompositeBinding.prototype */ {

		constructor : function (aBindings, bRawValues) {
			PropertyBinding.apply(this, [null,""]);
			this.aBindings = aBindings;
			this.bRawValues = bRawValues;
		},
		metadata : {

		  publicMethods : [
				  "getBindings", "attachChange", "detachChange"
		  ]
		}

	});

	CompositeBinding.prototype.getPath = function() {
		jQuery.sap.assert(null, "Composite Binding has no path!");
		return null;
	};

	CompositeBinding.prototype.getModel = function() {
		jQuery.sap.assert(null, "Composite Binding has no model!");
		return null;
	};

	CompositeBinding.prototype.getContext = function() {
		jQuery.sap.assert(null, "Composite Binding has no context!");
		return null;
	};

	CompositeBinding.prototype.getType = function() {
		jQuery.sap.assert(null, "Composite Binding type is not supported!");
		return null;
	};

	/**
	 * sets the context for each property binding in this composite binding
	 * @param {object} oContext the new context for the bindings
	 */
	CompositeBinding.prototype.setContext = function(oContext) {
		jQuery.each(this.aBindings, function(i, oBinding){
			// null context could also be set
			if (!oContext || oBinding.updateRequired(oContext.getModel())) {
				oBinding.setContext(oContext);
			}
		});
	};

	/**
	 * Not supported for CompositeBinding as a composite binding contains an array of property bindings.
	 * An exception will be thrown.
	 *
	 * @param {object} oValue the value to set for this binding
	 *
	 * @throws sap.ui.base.Exception
	 *
	 * @public
	 */
	CompositeBinding.prototype.setValue = function(oValue) {
		throw new sap.ui.base.Exception("Composite Binding does not support setValue because it contains multiple property bindings!");
	};

	/**
	 * Returns the raw values of the property bindings in an array.
	 *
	 * @return {object} the values of the internal property bindings in an array
	 *
	 * @public
	 */
	CompositeBinding.prototype.getValue = function() {
		var aValues = [],
		oValue;

		jQuery.each(this.aBindings, function(i, oBinding) {
			oValue = oBinding.getValue();
			aValues.push(oValue);
		});

		return aValues;
	};

	/**
	 * Returns the current external value of the bound target which is formatted via a type or formatter function.
	 *
	 * @return {object} the current value of the bound target
	 *
	 *@throws sap.ui.model.FormatException
	 *
	 * @public
	 */
	CompositeBinding.prototype.getExternalValue = function() {
		var aValues = [],
			oValue;
		if (this.bRawValues) {
			// type of property bindings is ignored here because we call getValue().
			aValues = this.getValue();
		} else {
			// composite type is ignored here and the property binding types are used in getExternalValue()
			jQuery.each(this.aBindings, function(i, oBinding) {
				oValue = oBinding.getExternalValue();
				aValues.push(oValue);
			});
		}

		if (this.fnFormatter) {
			oValue = this.fnFormatter.apply(this, aValues);
		} else {
			if ( aValues.length > 1) {
				// default: multiple values are joined together if no formatter specified
				oValue = aValues.join(" ");
			} else {
				oValue = aValues[0];
			}
		}

		return oValue;
	};

	/**
	 * Not supported for CompositeBinding as a composite binding contains an array of property bindings.
	 * An exception will be thrown.
	 *
	 * @param {object} oValue the value to set for this binding
	 *
	 * @throws sap.ui.base.Exception
	 *
	 * @public
	 */
	CompositeBinding.prototype.setExternalValue = function(oValue) {
		throw new sap.ui.base.Exception("Composite Binding does not support setExternalValue because it contains multiple property bindings!");
	};

	/**
	 * Returns the property bindings contained in this composite binding.
	 *
	 * @return {array} the property bindings in this composite binding
	 *
	 * @public
	 */
	CompositeBinding.prototype.getBindings = function() {
		return this.aBindings;
	};

	//Eventing and related
	/**
	* Attach event-handler fnFunction to the '_change' event of this sap.ui.model.Model.

	* @param {function} fnFunction The function to call, when the event occurs.
	* @param {object} [oListener] object on which to call the given function.
	* @protected
	*/
	CompositeBinding.prototype.attachChange = function(fnFunction, oListener) {
		this.attachEvent("change", fnFunction, oListener);
		if (this.aBindings) {
			var that = this;
			jQuery.each(this.aBindings, function(i,oBinding) {
				oBinding.attachChange(that.checkUpdate, that);
			});
		}
	};

	/**
	* Detach event-handler fnFunction from the '_change' event of this sap.ui.model.Model.

	* @param {function} fnFunction The function to call, when the event occurs.
	* @param {object} [oListener] object on which to call the given function.
	* @protected
	*/
	CompositeBinding.prototype.detachChange = function(fnFunction, oListener) {
		this.detachEvent("change", fnFunction, oListener);
		if (this.aBindings) {
			var that = this;
			jQuery.each(this.aBindings, function(i,oBinding) {
				oBinding.detachChange(that.checkUpdate, that);
			});
		}
	};

	/**
	 * Determines if the property bindings in the composite binding should be updated by calling updateRequired on all property bindings with the specified model.
	 * @param {object} oModel The model instance to compare against
	 * @returns {boolean} true if this binding should be updated
	 * @protected
	 */
	CompositeBinding.prototype.updateRequired = function(oModel) {
		var bUpdateRequired = false;
		jQuery.each(this.aBindings, function(i, oBinding){
			bUpdateRequired = bUpdateRequired || oBinding.updateRequired(oModel);
		});
		return bUpdateRequired;
	};
	/**
	 * Check whether this Binding would provide new values and in case it changed,
	 * inform interested parties about this.
	 *
	 * @param {boolean} bForceupdate
	 *
	 */
	CompositeBinding.prototype.checkUpdate = function(bForceupdate){
		var oValue = this.getExternalValue();
		if (!jQuery.sap.equal(oValue, this.oValue) || bForceupdate) {// optimize for not firing the events when unneeded
			this.oValue = oValue;
			this._fireChange({reason: sap.ui.model.ChangeReason.Change});
		}
	};

	return CompositeBinding;

}, /* bExport= */ true);
<commit_msg>[FIX] CompositeBinding: Don't call formatter function twice<commit_after>/*!
 * ${copyright}
 */

// Provides an abstract property binding.
sap.ui.define(['jquery.sap.global', './PropertyBinding', './SimpleType'],
	function(jQuery, PropertyBinding, SimpleType) {
	"use strict";

	/**
	 * Constructor for CompositeBinding
	 *
	 * @class
	 * The CompositeBinding is used to bundle multiple property bindings which are be used to provide a single binding against
	 * these property bindings. Note: Only One Way binding is supported. So setValue and setExternalValue throw exceptions.
	 *
	 * @public
	 * @alias sap.ui.model.CompositeBinding
	 */

	var CompositeBinding = PropertyBinding.extend("sap.ui.model.CompositeBinding", /** @lends sap.ui.model.CompositeBinding.prototype */ {

		constructor : function (aBindings, bRawValues) {
			PropertyBinding.apply(this, [null,""]);
			this.aBindings = aBindings;
			this.aValues = null;
			this.bRawValues = bRawValues;
		},
		metadata : {

		  publicMethods : [
				  "getBindings", "attachChange", "detachChange"
		  ]
		}

	});

	CompositeBinding.prototype.getPath = function() {
		jQuery.sap.assert(null, "Composite Binding has no path!");
		return null;
	};

	CompositeBinding.prototype.getModel = function() {
		jQuery.sap.assert(null, "Composite Binding has no model!");
		return null;
	};

	CompositeBinding.prototype.getContext = function() {
		jQuery.sap.assert(null, "Composite Binding has no context!");
		return null;
	};

	CompositeBinding.prototype.getType = function() {
		jQuery.sap.assert(null, "Composite Binding type is not supported!");
		return null;
	};

	/**
	 * sets the context for each property binding in this composite binding
	 * @param {object} oContext the new context for the bindings
	 */
	CompositeBinding.prototype.setContext = function(oContext) {
		jQuery.each(this.aBindings, function(i, oBinding){
			// null context could also be set
			if (!oContext || oBinding.updateRequired(oContext.getModel())) {
				oBinding.setContext(oContext);
			}
		});
	};

	/**
	 * Not supported for CompositeBinding as a composite binding contains an array of property bindings.
	 * An exception will be thrown.
	 *
	 * @param {object} oValue the value to set for this binding
	 *
	 * @throws sap.ui.base.Exception
	 *
	 * @public
	 */
	CompositeBinding.prototype.setValue = function(oValue) {
		throw new sap.ui.base.Exception("Composite Binding does not support setValue because it contains multiple property bindings!");
	};

	/**
	 * Returns the raw values of the property bindings in an array.
	 *
	 * @return {object} the values of the internal property bindings in an array
	 *
	 * @public
	 */
	CompositeBinding.prototype.getValue = function() {
		var aValues = [],
		oValue;

		jQuery.each(this.aBindings, function(i, oBinding) {
			oValue = oBinding.getValue();
			aValues.push(oValue);
		});

		return aValues;
	};

	/**
	 * Returns the current external value of the bound target which is formatted via a type or formatter function.
	 *
	 * @return {object} the current value of the bound target
	 *
	 *@throws sap.ui.model.FormatException
	 *
	 * @public
	 */
	CompositeBinding.prototype.getExternalValue = function() {
		var aValues = [],
			oValue;
		if (this.bRawValues) {
			// type of property bindings is ignored here because we call getValue().
			aValues = this.getValue();
		} else {
			// composite type is ignored here and the property binding types are used in getExternalValue()
			jQuery.each(this.aBindings, function(i, oBinding) {
				oValue = oBinding.getExternalValue();
				aValues.push(oValue);
			});
		}

		if (this.fnFormatter) {
			oValue = this.fnFormatter.apply(this, aValues);
		} else {
			if ( aValues.length > 1) {
				// default: multiple values are joined together if no formatter specified
				oValue = aValues.join(" ");
			} else {
				oValue = aValues[0];
			}
		}

		return oValue;
	};

	/**
	 * Not supported for CompositeBinding as a composite binding contains an array of property bindings.
	 * An exception will be thrown.
	 *
	 * @param {object} oValue the value to set for this binding
	 *
	 * @throws sap.ui.base.Exception
	 *
	 * @public
	 */
	CompositeBinding.prototype.setExternalValue = function(oValue) {
		throw new sap.ui.base.Exception("Composite Binding does not support setExternalValue because it contains multiple property bindings!");
	};

	/**
	 * Returns the property bindings contained in this composite binding.
	 *
	 * @return {array} the property bindings in this composite binding
	 *
	 * @public
	 */
	CompositeBinding.prototype.getBindings = function() {
		return this.aBindings;
	};

	//Eventing and related
	/**
	* Attach event-handler fnFunction to the '_change' event of this sap.ui.model.Model.

	* @param {function} fnFunction The function to call, when the event occurs.
	* @param {object} [oListener] object on which to call the given function.
	* @protected
	*/
	CompositeBinding.prototype.attachChange = function(fnFunction, oListener) {
		this.attachEvent("change", fnFunction, oListener);
		if (this.aBindings) {
			var that = this;
			jQuery.each(this.aBindings, function(i,oBinding) {
				oBinding.attachChange(that.checkUpdate, that);
			});
		}
	};

	/**
	* Detach event-handler fnFunction from the '_change' event of this sap.ui.model.Model.

	* @param {function} fnFunction The function to call, when the event occurs.
	* @param {object} [oListener] object on which to call the given function.
	* @protected
	*/
	CompositeBinding.prototype.detachChange = function(fnFunction, oListener) {
		this.detachEvent("change", fnFunction, oListener);
		if (this.aBindings) {
			var that = this;
			jQuery.each(this.aBindings, function(i,oBinding) {
				oBinding.detachChange(that.checkUpdate, that);
			});
		}
	};

	/**
	 * Determines if the property bindings in the composite binding should be updated by calling updateRequired on all property bindings with the specified model.
	 * @param {object} oModel The model instance to compare against
	 * @returns {boolean} true if this binding should be updated
	 * @protected
	 */
	CompositeBinding.prototype.updateRequired = function(oModel) {
		var bUpdateRequired = false;
		jQuery.each(this.aBindings, function(i, oBinding){
			bUpdateRequired = bUpdateRequired || oBinding.updateRequired(oModel);
		});
		return bUpdateRequired;
	};
	/**
	 * Check whether this Binding would provide new values and in case it changed,
	 * inform interested parties about this.
	 *
	 * @param {boolean} bForceupdate
	 *
	 */
	CompositeBinding.prototype.checkUpdate = function(bForceupdate){
		var aValues = this.getValue();
		if (!jQuery.sap.equal(aValues, this.aValues) || bForceupdate) {// optimize for not firing the events when unneeded
			this.aValues = aValues;
			this._fireChange({reason: sap.ui.model.ChangeReason.Change});
		}
	};

	return CompositeBinding;

}, /* bExport= */ true);
<|endoftext|>


<commit_before>// tests based on:
// https://dev.to/piczmar_0/serverless-authorizers---custom-rest-authorizer-16

import { resolve } from 'path'
import fetch from 'node-fetch'
import { joinUrl, setup, teardown } from '../_testHelpers/index.js'

jest.setTimeout(30000)

describe('HttpApi Cors Tests', () => {
  // init
  beforeAll(() =>
    setup({
      servicePath: resolve(__dirname),
    }),
  )

  // cleanup
  afterAll(() => teardown())

  test('Fetch OPTIONS with valid origin', async () => {
    const url = joinUrl(TEST_BASE_URL, '/dev/user')
    const options = {
      method: 'OPTIONS',
      headers: {
        origin: 'http://www.mytestapp.com',
        'access-control-request-headers': 'authorization,content-type',
        'access-control-request-method': 'GET',
      },
    }

    const response = await fetch(url, options)
    expect(response.status).toEqual(204)

    expect(response.headers.get('access-control-allow-origin')).toEqual(
      'http://www.mytestapp.com',
    )
    expect(response.headers.get('access-control-allow-credentials')).toEqual(
      'true',
    )
    expect(response.headers.get('access-control-max-age')).toEqual('60')
    expect(response.headers.get('access-control-expose-headers')).toEqual(
      'status,origin',
    )
    expect(response.headers.get('access-control-allow-methods')).toEqual(
      'GET,POST',
    )
    expect(response.headers.get('access-control-allow-headers')).toEqual(
      'authorization,content-type',
    )
  })

  test('Fetch OPTIONS with invalid origin', async () => {
    const url = joinUrl(TEST_BASE_URL, '/dev/user')
    const options = {
      method: 'OPTIONS',
      headers: {
        origin: 'http://www.wrongapp.com',
        'access-control-request-headers': 'authorization,content-type',
        'access-control-request-method': 'GET',
      },
    }

    const response = await fetch(url, options)
    expect(response.status).toEqual(204)
    expect(response.headers.get('access-control-allow-origin')).toEqual(null)
  })
})
<commit_msg>test: Fix http cors integration test URL paths<commit_after>// tests based on:
// https://dev.to/piczmar_0/serverless-authorizers---custom-rest-authorizer-16

import { resolve } from 'path'
import fetch from 'node-fetch'
import { joinUrl, setup, teardown } from '../_testHelpers/index.js'

jest.setTimeout(30000)

describe('HttpApi Cors Tests', () => {
  // init
  beforeAll(() =>
    setup({
      servicePath: resolve(__dirname),
    }),
  )

  // cleanup
  afterAll(() => teardown())

  test('Fetch OPTIONS with valid origin', async () => {
    const url = joinUrl(TEST_BASE_URL, '/user')
    const options = {
      method: 'OPTIONS',
      headers: {
        origin: 'http://www.mytestapp.com',
        'access-control-request-headers': 'authorization,content-type',
        'access-control-request-method': 'GET',
      },
    }

    const response = await fetch(url, options)
    expect(response.status).toEqual(204)

    expect(response.headers.get('access-control-allow-origin')).toEqual(
      'http://www.mytestapp.com',
    )
    expect(response.headers.get('access-control-allow-credentials')).toEqual(
      'true',
    )
    expect(response.headers.get('access-control-max-age')).toEqual('60')
    expect(response.headers.get('access-control-expose-headers')).toEqual(
      'status,origin',
    )
    expect(response.headers.get('access-control-allow-methods')).toEqual(
      'GET,POST',
    )
    expect(response.headers.get('access-control-allow-headers')).toEqual(
      'authorization,content-type',
    )
  })

  test('Fetch OPTIONS with invalid origin', async () => {
    const url = joinUrl(TEST_BASE_URL, '/user')
    const options = {
      method: 'OPTIONS',
      headers: {
        origin: 'http://www.wrongapp.com',
        'access-control-request-headers': 'authorization,content-type',
        'access-control-request-method': 'GET',
      },
    }

    const response = await fetch(url, options)
    expect(response.status).toEqual(204)
    expect(response.headers.get('access-control-allow-origin')).toEqual(null)
  })
})
<|endoftext|>