<commit_before>'use strict';
var Q = require('q');
var errors = require('./errors');
var debug = require('debug')('dynamoose:query');

function Query (Model, query, options) {
  this.Model = Model;
  this.options = options || {'all': {'delay': 0, 'max': 1}};

  // {
  //   hashKey: {
  //     name: 'name',
  //     value: 'value'
  //   },
  //   rangeKey: {
  //     name: 'name',
  //     value: 'value',
  //     comparison: 'string'
  //   }
  // }
  this.query = {hashKey: {}};

  this.filters = {};
  this.buildState = false;
  this.validationError = null;

  var hashKeyName, hashKeyVal;
  if(typeof query === 'string') {
    this.buildState = 'hashKey';
    this.query.hashKey.name = query;
  } else if (query.hash) {
    hashKeyName = Object.keys(query.hash)[0];
    hashKeyVal = query.hash[hashKeyName];
    if(hashKeyVal.eq !== null && hashKeyVal.eq !== undefined) {
      hashKeyVal = hashKeyVal.eq;
    }
    this.query.hashKey.name = hashKeyName;
    this.query.hashKey.value = hashKeyVal;

    if(query.range) {
      var rangeKeyName = Object.keys(query.range)[0];
      var rangeKeyVal = query.range[rangeKeyName];
      var rangeKeyComp = Object.keys(rangeKeyVal)[0];
      rangeKeyVal = rangeKeyVal[rangeKeyComp];
      this.query.rangeKey = {
        name: rangeKeyName,
        values: [rangeKeyVal],
        comparison: rangeKeyComp
      };
    }
  } else {
    hashKeyName = Object.keys(query)[0];
    hashKeyVal = query[hashKeyName];
    if(hashKeyVal.eq !== null && hashKeyVal.eq !== undefined) {
      hashKeyVal = hashKeyVal.eq;
    }
    this.query.hashKey.name = hashKeyName;
    this.query.hashKey.value = hashKeyVal;  }
}

Query.prototype.exec = function (next) {
  debug('exec query for ', this.query);
  if (this.validationError) {
    if (next) {
      next(this.validationError);
    }
    return Q.reject(this.validationError);
  }

  var Model = this.Model;
  var Model$ = Model.$__;
  var schema = Model$.schema;
  var options = this.options;

  debug('Query with schema', schema);

  var queryReq = {
    TableName: Model.$__.name,
    KeyConditions: {}
  };

  var indexName, index;
  // Check both hash key and range key in the query to see if they do not match
  // the hash and range key on the primary table.  If they don't match then we
  // can look for a secondary index to query.
  if(schema.hashKey.name !== this.query.hashKey.name ||
    (this.query.rangeKey && schema.rangeKey && schema.rangeKey.name !== this.query.rangeKey.name)) {
    debug('query is on global secondary index');
    for(indexName in schema.indexes.global) {
      index = schema.indexes.global[indexName];
      if(index.name === this.query.hashKey.name) {
        debug('using index', indexName);
        queryReq.IndexName = indexName;
        break;
      }
    }
  }

  var hashAttr = schema.attributes[this.query.hashKey.name];

  queryReq.KeyConditions[this.query.hashKey.name] = {
    AttributeValueList: [hashAttr.toDynamo(this.query.hashKey.value)],
    ComparisonOperator: 'EQ'
  };

  var i, val;

  if(this.query.rangeKey) {
    var rangeKey = this.query.rangeKey;
    var rangeAttr = schema.attributes[rangeKey.name];

    if(!queryReq.IndexName && schema.rangeKey.name !== rangeKey.name) {
      debug('query is on local secondary index');
      for(indexName in schema.indexes.local) {
        index = schema.indexes.local[indexName];
        if(index.name === rangeKey.name) {
          debug('using local index', indexName);
          queryReq.IndexName = indexName;
          break;
        }
      }
    }

    if(!rangeKey || rangeKey.values === undefined) {
      debug('No range key value (i.e. get all)');
    } else {
      debug('Range key: %s', rangeKey.name);
      var keyConditions = queryReq.KeyConditions[rangeKey.name] = {
        AttributeValueList: [],
        ComparisonOperator: rangeKey.comparison.toUpperCase()
      };
      for (i = 0; i < rangeKey.values.length; i++) {
        val = rangeKey.values [i];
        keyConditions.AttributeValueList.push(
          rangeAttr.toDynamo(val, true)
        );
      }
    }
  }

  // if the index name has been explicitly set via the api then let that override
  // anything that has been previously derived
  if(this.options.indexName){
    debug('forcing index: %s', this.options.indexName);
    queryReq.IndexName = this.options.indexName;
  }

  if(this.filters && Object.keys(this.filters).length > 0) {
    queryReq.QueryFilter = {};
    for(var name in this.filters) {
      debug('Filter on: %s', name);
      var filter = this.filters[name];
      var filterAttr = schema.attributes[name];
      queryReq.QueryFilter[name] = {
        AttributeValueList: [],
        ComparisonOperator: filter.comparison.toUpperCase()
      };

      var isContains = filter.comparison === 'CONTAINS' || filter.comparison === 'NOT_CONTAINS';
      var isListContains = isContains && filterAttr.type.name === 'list';

      if(filter.values) {
        for (i = 0; i < filter.values.length; i++) {
          val = filter.values[i];
          queryReq.QueryFilter[name].AttributeValueList.push(
            isListContains ? filterAttr.attributes[0].toDynamo(val, true) : filterAttr.toDynamo(val, true)
          );
        }
      }
    }
  }

  if(options.or) {
    queryReq.ConditionalOperator = 'OR'; // defualts to AND
  }

  if(options.attributes) {
    queryReq.AttributesToGet = options.attributes;
  }

  if(options.count) {
    queryReq.Select = 'COUNT';
  }

  if(options.counts) {
    queryReq.Select = 'COUNT';
  }

  if(options.consistent) {
    queryReq.ConsistentRead = true;
  }

  if(options.limit) {
    queryReq.Limit = options.limit;
  }

  if(options.one) {
    queryReq.Limit = 1;
  }

  if(options.descending) {
    queryReq.ScanIndexForward = false;
  }

  if(options.ExclusiveStartKey) {
    queryReq.ExclusiveStartKey = options.ExclusiveStartKey;
  }

  function query () {
    var deferred = Q.defer();

    if (!options.all) {
      options.all = {'delay': 0, 'max': 1};
    }

    var models = {}, totalCount = 0, scannedCount = 0, timesQueried = 0, lastKey;
    queryOne();

    function queryOne() {
      debug('DynamoDB Query: %j', queryReq);
      Model$.base.ddb().query(queryReq, function(err, data) {
        if(err) {
          debug('Error returned by query', err);
          return deferred.reject(err);
        }
        debug('DynamoDB Query Response: %j', data);

        if(!Object.keys(data).length) {
          return deferred.resolve();
        }

        function toModel (item) {
          var model = new Model();
          model.$__.isNew = false;
          schema.parseDynamo(model, item);

          debug('query parsed model', model);

          return model;
        }

        try {
          if (options.count) {
            return deferred.resolve(data.Count);
          }
          if (options.counts) {
            var counts = { count: data.Count, scannedCount: data.ScannedCount };
            return deferred.resolve(counts);
          }
          if (data.Items !== undefined) {
            if (!models.length) {
              models = data.Items.map(toModel);
            } else {
              models = models.concat(data.Items.map(toModel));
            }

            if(options.one) {
              if (!models || models.length === 0) {
                return deferred.resolve();
              }
              return deferred.resolve(models[0]);
            }
            lastKey = data.LastEvaluatedKey;
          }
          totalCount += data.Count;
          scannedCount += data.ScannedCount;
          timesQueried++;

          if ((options.all.max === 0 || timesQueried < options.all.max) && lastKey) {
            // query.all need to query again
            queryReq.ExclusiveStartKey = lastKey;
            setTimeout(queryOne, options.all.delay * 1000);
          } else {
            models.lastKey = lastKey;
            models.count = totalCount;
            models.scannedCount = scannedCount;
            models.timesQueried = timesQueried;
            deferred.resolve(models);
          }
        } catch (err) {
          deferred.reject(err);
        }
      });
    }

    return deferred.promise.nodeify(next);
  }

  if(Model$.options.waitForActive) {
    return Model$.table.waitForActive().then(query).catch(query);
  }

  return query();
};

Query.prototype.where = function (rangeKey) {
  if (this.validationError) {
    return this;
  }
  if(this.buildState) {
    this.validationError = new errors.QueryError('Invalid query state; where() must follow eq()');
    return this;
  }
  if(typeof rangeKey === 'string') {
    this.buildState = 'rangeKey';
    this.query.rangeKey = {name: rangeKey};
  } else {
    var rangeKeyName = Object.keys(rangeKey)[0];
    var rangeKeyVal = rangeKey[rangeKeyName];
    var rangeKeyComp = Object.keys(rangeKeyVal)[0];
    rangeKeyVal = rangeKeyVal[rangeKeyComp];
    this.query.rangeKey = {
      name: rangeKeyName,
      values: [rangeKeyVal],
      comparison: rangeKeyComp
    };
  }

  return this;
};

Query.prototype.filter = function (filter) {
  if (this.validationError) {
    return this;
  }
  if(this.buildState) {
    this.validationError = new errors.QueryError('Invalid query state; filter() must follow comparison');
    return this;
  }
  if(typeof filter === 'string') {
    this.buildState = 'filter';
    this.currentFilter = filter;
    if(this.filters[filter]) {
      this.validationError =  new errors.QueryError('Invalid query state; %s filter can only be used once', filter);
      return this;
    }
    this.filters[filter] = {name: filter};
  }

  return this;
};

var VALID_RANGE_KEYS = ['EQ', 'LE', 'LT', 'GE', 'GT', 'BEGINS_WITH', 'BETWEEN'];
Query.prototype.compVal = function (vals, comp) {
  if (this.validationError) {
    return this;
  }
  if(this.buildState === 'hashKey') {
    if(comp !== 'EQ') {
      this.validationError = new errors.QueryError('Invalid query state; eq must follow query()');
      return this;
    }
    this.query.hashKey.value = vals[0];
  } else if (this.buildState === 'rangeKey'){
    if(VALID_RANGE_KEYS.indexOf(comp) < 0) {
      this.validationError =  new errors.QueryError('Invalid query state; %s must follow filter()', comp);
      return this;
    }
    this.query.rangeKey.values = vals;
    this.query.rangeKey.comparison = comp;
  } else if (this.buildState === 'filter') {
    this.filters[this.currentFilter].values = vals;
    this.filters[this.currentFilter].comparison = comp;
  } else {
    this.validationError =  new errors.QueryError('Invalid query state; %s must follow query(), where() or filter()', comp);
    return this;
  }

  this.buildState = false;
  this.notState = false;

  return this;
};

Query.prototype.and = function() {
  this.options.or = false;

  return this;
};

Query.prototype.or = function() {
  this.options.or = true;

  return this;
};

Query.prototype.not = function() {
  this.notState = true;
  return this;
};

Query.prototype.null = function() {
  if(this.notState) {
    return this.compVal(null, 'NOT_NULL');
  } else {
    return this.compVal(null, 'NULL');
  }
};

Query.prototype.eq = function (val) {
  if(this.notState) {
    return this.compVal([val], 'NE');
  } else {
    return this.compVal([val], 'EQ');
  }
};

Query.prototype.lt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GE');
  } else {
    return this.compVal([val], 'LT');
  }
};

Query.prototype.le = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GT');
  } else {
    return this.compVal([val], 'LE');
  }
};

Query.prototype.ge = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LT');
  } else {
    return this.compVal([val], 'GE');
  }
};

Query.prototype.gt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LE');
  } else {
    return this.compVal([val], 'GT');
  }
};

Query.prototype.contains = function (val) {
  if(this.notState) {
    return this.compVal([val], 'NOT_CONTAINS');
  } else {
    return this.compVal([val], 'CONTAINS');
  }
};

Query.prototype.beginsWith = function (val) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError =  new errors.QueryError('Invalid Query state: beginsWith() cannot follow not()');
    return this;
  }
  return this.compVal([val], 'BEGINS_WITH');
};

Query.prototype.in = function (vals) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.QueryError('Invalid Query state: in() cannot follow not()');
    return this;
  }

  return this.compVal(vals, 'IN');
};

Query.prototype.between = function (a, b) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.QueryError('Invalid Query state: between() cannot follow not()');
    return this;
  }
  return this.compVal([a, b], 'BETWEEN');
};

Query.prototype.limit = function (limit) {
  this.options.limit = limit;
  return this;
};

Query.prototype.one = function () {
  this.options.one = true;
  return this;
};

Query.prototype.consistent = function () {
  this.options.consistent = true;
  return this;
};

Query.prototype.descending = function () {
  this.options.descending = true;
  return this;
};

Query.prototype.ascending = function () {
  this.options.descending = false;
  return this;
};

Query.prototype.startAt = function (key) {
  this.options.ExclusiveStartKey = key;
  return this;
};

Query.prototype.attributes = function (attributes) {
  this.options.attributes = attributes;
  return this;
};

Query.prototype.count = function () {
  this.options.count = true;
  this.options.select = 'COUNT';
  return this;
};

Query.prototype.counts = function () {
  this.options.counts = true;
  this.options.select = 'COUNT';
  return this;
};

Query.prototype.using = function (indexName) {
  this.options.indexName = indexName;
  return this;
};

Query.prototype.all = function (delay, max) {
  delay = delay || 1;
  max = max || 0;
  this.options.all = {'delay': delay, 'max': max};
  return this;
};

module.exports = Query;
<commit_msg>Making query errors more consistent<commit_after>'use strict';
var Q = require('q');
var errors = require('./errors');
var debug = require('debug')('dynamoose:query');

function Query (Model, query, options) {
  this.Model = Model;
  this.options = options || {'all': {'delay': 0, 'max': 1}};

  // {
  //   hashKey: {
  //     name: 'name',
  //     value: 'value'
  //   },
  //   rangeKey: {
  //     name: 'name',
  //     value: 'value',
  //     comparison: 'string'
  //   }
  // }
  this.query = {hashKey: {}};

  this.filters = {};
  this.buildState = false;
  this.validationError = null;

  var hashKeyName, hashKeyVal;
  if(typeof query === 'string') {
    this.buildState = 'hashKey';
    this.query.hashKey.name = query;
  } else if (query.hash) {
    hashKeyName = Object.keys(query.hash)[0];
    hashKeyVal = query.hash[hashKeyName];
    if(hashKeyVal.eq !== null && hashKeyVal.eq !== undefined) {
      hashKeyVal = hashKeyVal.eq;
    }
    this.query.hashKey.name = hashKeyName;
    this.query.hashKey.value = hashKeyVal;

    if(query.range) {
      var rangeKeyName = Object.keys(query.range)[0];
      var rangeKeyVal = query.range[rangeKeyName];
      var rangeKeyComp = Object.keys(rangeKeyVal)[0];
      rangeKeyVal = rangeKeyVal[rangeKeyComp];
      this.query.rangeKey = {
        name: rangeKeyName,
        values: [rangeKeyVal],
        comparison: rangeKeyComp
      };
    }
  } else {
    hashKeyName = Object.keys(query)[0];
    hashKeyVal = query[hashKeyName];
    if(hashKeyVal.eq !== null && hashKeyVal.eq !== undefined) {
      hashKeyVal = hashKeyVal.eq;
    }
    this.query.hashKey.name = hashKeyName;
    this.query.hashKey.value = hashKeyVal;  }
}

Query.prototype.exec = function (next) {
  debug('exec query for ', this.query);
  if (this.validationError) {
    if (next) {
      next(this.validationError);
    }
    return Q.reject(this.validationError);
  }

  var Model = this.Model;
  var Model$ = Model.$__;
  var schema = Model$.schema;
  var options = this.options;

  debug('Query with schema', schema);

  var queryReq = {
    TableName: Model.$__.name,
    KeyConditions: {}
  };

  var indexName, index;
  // Check both hash key and range key in the query to see if they do not match
  // the hash and range key on the primary table.  If they don't match then we
  // can look for a secondary index to query.
  if(schema.hashKey.name !== this.query.hashKey.name ||
    (this.query.rangeKey && schema.rangeKey && schema.rangeKey.name !== this.query.rangeKey.name)) {
    debug('query is on global secondary index');
    for(indexName in schema.indexes.global) {
      index = schema.indexes.global[indexName];
      if(index.name === this.query.hashKey.name) {
        debug('using index', indexName);
        queryReq.IndexName = indexName;
        break;
      }
    }
  }

  var hashAttr = schema.attributes[this.query.hashKey.name];

  queryReq.KeyConditions[this.query.hashKey.name] = {
    AttributeValueList: [hashAttr.toDynamo(this.query.hashKey.value)],
    ComparisonOperator: 'EQ'
  };

  var i, val;

  if(this.query.rangeKey) {
    var rangeKey = this.query.rangeKey;
    var rangeAttr = schema.attributes[rangeKey.name];

    if(!queryReq.IndexName && schema.rangeKey.name !== rangeKey.name) {
      debug('query is on local secondary index');
      for(indexName in schema.indexes.local) {
        index = schema.indexes.local[indexName];
        if(index.name === rangeKey.name) {
          debug('using local index', indexName);
          queryReq.IndexName = indexName;
          break;
        }
      }
    }

    if(!rangeKey || rangeKey.values === undefined) {
      debug('No range key value (i.e. get all)');
    } else {
      debug('Range key: %s', rangeKey.name);
      var keyConditions = queryReq.KeyConditions[rangeKey.name] = {
        AttributeValueList: [],
        ComparisonOperator: rangeKey.comparison.toUpperCase()
      };
      for (i = 0; i < rangeKey.values.length; i++) {
        val = rangeKey.values [i];
        keyConditions.AttributeValueList.push(
          rangeAttr.toDynamo(val, true)
        );
      }
    }
  }

  // if the index name has been explicitly set via the api then let that override
  // anything that has been previously derived
  if(this.options.indexName){
    debug('forcing index: %s', this.options.indexName);
    queryReq.IndexName = this.options.indexName;
  }

  if(this.filters && Object.keys(this.filters).length > 0) {
    queryReq.QueryFilter = {};
    for(var name in this.filters) {
      debug('Filter on: %s', name);
      var filter = this.filters[name];
      var filterAttr = schema.attributes[name];
      queryReq.QueryFilter[name] = {
        AttributeValueList: [],
        ComparisonOperator: filter.comparison.toUpperCase()
      };

      var isContains = filter.comparison === 'CONTAINS' || filter.comparison === 'NOT_CONTAINS';
      var isListContains = isContains && filterAttr.type.name === 'list';

      if(filter.values) {
        for (i = 0; i < filter.values.length; i++) {
          val = filter.values[i];
          queryReq.QueryFilter[name].AttributeValueList.push(
            isListContains ? filterAttr.attributes[0].toDynamo(val, true) : filterAttr.toDynamo(val, true)
          );
        }
      }
    }
  }

  if(options.or) {
    queryReq.ConditionalOperator = 'OR'; // defualts to AND
  }

  if(options.attributes) {
    queryReq.AttributesToGet = options.attributes;
  }

  if(options.count) {
    queryReq.Select = 'COUNT';
  }

  if(options.counts) {
    queryReq.Select = 'COUNT';
  }

  if(options.consistent) {
    queryReq.ConsistentRead = true;
  }

  if(options.limit) {
    queryReq.Limit = options.limit;
  }

  if(options.one) {
    queryReq.Limit = 1;
  }

  if(options.descending) {
    queryReq.ScanIndexForward = false;
  }

  if(options.ExclusiveStartKey) {
    queryReq.ExclusiveStartKey = options.ExclusiveStartKey;
  }

  function query () {
    var deferred = Q.defer();

    if (!options.all) {
      options.all = {'delay': 0, 'max': 1};
    }

    var models = {}, totalCount = 0, scannedCount = 0, timesQueried = 0, lastKey;
    queryOne();

    function queryOne() {
      debug('DynamoDB Query: %j', queryReq);
      Model$.base.ddb().query(queryReq, function(err, data) {
        if(err) {
          debug('Error returned by query', err);
          return deferred.reject(err);
        }
        debug('DynamoDB Query Response: %j', data);

        if(!Object.keys(data).length) {
          return deferred.resolve();
        }

        function toModel (item) {
          var model = new Model();
          model.$__.isNew = false;
          schema.parseDynamo(model, item);

          debug('query parsed model', model);

          return model;
        }

        try {
          if (options.count) {
            return deferred.resolve(data.Count);
          }
          if (options.counts) {
            var counts = { count: data.Count, scannedCount: data.ScannedCount };
            return deferred.resolve(counts);
          }
          if (data.Items !== undefined) {
            if (!models.length) {
              models = data.Items.map(toModel);
            } else {
              models = models.concat(data.Items.map(toModel));
            }

            if(options.one) {
              if (!models || models.length === 0) {
                return deferred.resolve();
              }
              return deferred.resolve(models[0]);
            }
            lastKey = data.LastEvaluatedKey;
          }
          totalCount += data.Count;
          scannedCount += data.ScannedCount;
          timesQueried++;

          if ((options.all.max === 0 || timesQueried < options.all.max) && lastKey) {
            // query.all need to query again
            queryReq.ExclusiveStartKey = lastKey;
            setTimeout(queryOne, options.all.delay * 1000);
          } else {
            models.lastKey = lastKey;
            models.count = totalCount;
            models.scannedCount = scannedCount;
            models.timesQueried = timesQueried;
            deferred.resolve(models);
          }
        } catch (err) {
          deferred.reject(err);
        }
      });
    }

    return deferred.promise.nodeify(next);
  }

  if(Model$.options.waitForActive) {
    return Model$.table.waitForActive().then(query).catch(query);
  }

  return query();
};

Query.prototype.where = function (rangeKey) {
  if (this.validationError) {
    return this;
  }
  if(this.buildState) {
    this.validationError = new errors.QueryError('Invalid Query state: where() must follow eq()');
    return this;
  }
  if(typeof rangeKey === 'string') {
    this.buildState = 'rangeKey';
    this.query.rangeKey = {name: rangeKey};
  } else {
    var rangeKeyName = Object.keys(rangeKey)[0];
    var rangeKeyVal = rangeKey[rangeKeyName];
    var rangeKeyComp = Object.keys(rangeKeyVal)[0];
    rangeKeyVal = rangeKeyVal[rangeKeyComp];
    this.query.rangeKey = {
      name: rangeKeyName,
      values: [rangeKeyVal],
      comparison: rangeKeyComp
    };
  }

  return this;
};

Query.prototype.filter = function (filter) {
  if (this.validationError) {
    return this;
  }
  if(this.buildState) {
    this.validationError = new errors.QueryError('Invalid Query state: filter() must follow comparison');
    return this;
  }
  if(typeof filter === 'string') {
    this.buildState = 'filter';
    this.currentFilter = filter;
    if(this.filters[filter]) {
      this.validationError =  new errors.QueryError('Invalid Query state: %s filter can only be used once', filter);
      return this;
    }
    this.filters[filter] = {name: filter};
  }

  return this;
};

var VALID_RANGE_KEYS = ['EQ', 'LE', 'LT', 'GE', 'GT', 'BEGINS_WITH', 'BETWEEN'];
Query.prototype.compVal = function (vals, comp) {
  if (this.validationError) {
    return this;
  }
  if(this.buildState === 'hashKey') {
    if(comp !== 'EQ') {
      this.validationError = new errors.QueryError('Invalid Query state: eq must follow query()');
      return this;
    }
    this.query.hashKey.value = vals[0];
  } else if (this.buildState === 'rangeKey'){
    if(VALID_RANGE_KEYS.indexOf(comp) < 0) {
      this.validationError =  new errors.QueryError('Invalid Query state: %s must follow filter()', comp);
      return this;
    }
    this.query.rangeKey.values = vals;
    this.query.rangeKey.comparison = comp;
  } else if (this.buildState === 'filter') {
    this.filters[this.currentFilter].values = vals;
    this.filters[this.currentFilter].comparison = comp;
  } else {
    this.validationError =  new errors.QueryError('Invalid Query state: %s must follow query(), where() or filter()', comp);
    return this;
  }

  this.buildState = false;
  this.notState = false;

  return this;
};

Query.prototype.and = function() {
  this.options.or = false;

  return this;
};

Query.prototype.or = function() {
  this.options.or = true;

  return this;
};

Query.prototype.not = function() {
  this.notState = true;
  return this;
};

Query.prototype.null = function() {
  if(this.notState) {
    return this.compVal(null, 'NOT_NULL');
  } else {
    return this.compVal(null, 'NULL');
  }
};

Query.prototype.eq = function (val) {
  if(this.notState) {
    return this.compVal([val], 'NE');
  } else {
    return this.compVal([val], 'EQ');
  }
};

Query.prototype.lt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GE');
  } else {
    return this.compVal([val], 'LT');
  }
};

Query.prototype.le = function (val) {
  if(this.notState) {
    return this.compVal([val], 'GT');
  } else {
    return this.compVal([val], 'LE');
  }
};

Query.prototype.ge = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LT');
  } else {
    return this.compVal([val], 'GE');
  }
};

Query.prototype.gt = function (val) {
  if(this.notState) {
    return this.compVal([val], 'LE');
  } else {
    return this.compVal([val], 'GT');
  }
};

Query.prototype.contains = function (val) {
  if(this.notState) {
    return this.compVal([val], 'NOT_CONTAINS');
  } else {
    return this.compVal([val], 'CONTAINS');
  }
};

Query.prototype.beginsWith = function (val) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError =  new errors.QueryError('Invalid Query state: beginsWith() cannot follow not()');
    return this;
  }
  return this.compVal([val], 'BEGINS_WITH');
};

Query.prototype.in = function (vals) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.QueryError('Invalid Query state: in() cannot follow not()');
    return this;
  }

  return this.compVal(vals, 'IN');
};

Query.prototype.between = function (a, b) {
  if (this.validationError) {
    return this;
  }
  if(this.notState) {
    this.validationError = new errors.QueryError('Invalid Query state: between() cannot follow not()');
    return this;
  }
  return this.compVal([a, b], 'BETWEEN');
};

Query.prototype.limit = function (limit) {
  this.options.limit = limit;
  return this;
};

Query.prototype.one = function () {
  this.options.one = true;
  return this;
};

Query.prototype.consistent = function () {
  this.options.consistent = true;
  return this;
};

Query.prototype.descending = function () {
  this.options.descending = true;
  return this;
};

Query.prototype.ascending = function () {
  this.options.descending = false;
  return this;
};

Query.prototype.startAt = function (key) {
  this.options.ExclusiveStartKey = key;
  return this;
};

Query.prototype.attributes = function (attributes) {
  this.options.attributes = attributes;
  return this;
};

Query.prototype.count = function () {
  this.options.count = true;
  this.options.select = 'COUNT';
  return this;
};

Query.prototype.counts = function () {
  this.options.counts = true;
  this.options.select = 'COUNT';
  return this;
};

Query.prototype.using = function (indexName) {
  this.options.indexName = indexName;
  return this;
};

Query.prototype.all = function (delay, max) {
  delay = delay || 1;
  max = max || 0;
  this.options.all = {'delay': delay, 'max': max};
  return this;
};

module.exports = Query;
<|endoftext|>


<commit_before>// models/todo.js
var Todo = DS.Model.extend({
  title: DS.attr('string'),
  isCompleted: DS.attr('boolean')
});

export default Todo;<commit_msg>Using Fixtures<commit_after>// models/todo.js
var Todo = DS.Model.extend({
  title: DS.attr('string'),
  isCompleted: DS.attr('boolean')
});

Todo.FIXTURES = [
  {
    id: "1",
    title: 'install EAK',
    isCompleted: true
  }, {
    id: "2",
    title: 'install additional dependencies',
    isCompleted: true
  }, {
    id: "3",
    title: 'develop amazing things',
    isCompleted: false
  }
];

export default Todo;<|endoftext|>


<commit_before>import React from 'react';
import cx from 'classnames';

const Popover = React.createClass({

  propTypes: {
    children: React.PropTypes.node.isRequired,
    popover: React.PropTypes.shape({
      content: React.PropTypes.node.isRequired,
      attachToBody: React.PropTypes.bool,
      position: React.PropTypes.oneOf(['top', 'bottom']),
      anchor: React.PropTypes.oneOf(['left', 'center', 'right']),
      event: React.PropTypes.oneOf(['click', 'hover']),
      onShow: React.PropTypes.func,
      onHide: React.PropTypes.func,
      dismissOnScroll: React.PropTypes.bool,
      className: React.PropTypes.string,
      id: React.PropTypes.string,
      maxWidth: React.PropTypes.oneOfType([
        React.PropTypes.number,
        React.PropTypes.string
      ]),
      distance: React.PropTypes.number,
      offsetX: React.PropTypes.number,
      offsetY: React.PropTypes.number
    }).isRequired,
    id: React.PropTypes.string,
    className: React.PropTypes.string,
    style: React.PropTypes.object
  },

  getDefaultProps() {
    return {
      id: '',
      className: '',
      style: {}
    };
  },

  getInitialState() {
    return {
      isOpen: false
    };
  },

  componentDidMount() {
    this.saveValuesFromNodeTree();
  },

  saveValuesFromNodeTree(cb) {
    const childrenNode = this.refs.children.getDOMNode();
    const popoverNode = this.isAbsolute() ? this.popoverNode : childrenNode.childNodes[1];

    if (popoverNode) {
      const { clientWidth: childWidth, clientHeight: childHeight } = childrenNode;
      const { clientHeight: popoverHeight, clientWidth: popoverWidth } = popoverNode;
      const { top: offsetTop, left: offsetLeft } = this.getOffsetRect();
      this.setState({
        childWidth,
        childHeight,
        popoverWidth,
        popoverHeight,
        offsetTop,
        offsetLeft
      }, cb);
    }
  },

  // extend with default values
  getPopoverProps() {
    return {
      type: 'relative',
      position: 'top',
      anchor: 'center',
      event: 'hover',
      onShow: () => {},
      onHide: () => {},
      dismissOnScroll: true,
      className: '',
      distance: 5,
      offsetX: 0,
      offsetY: 0,
      ...this.props.popover
    };
  },

  componentWillUnmount() {
    if (this.isAbsolute()) {
      this.removePopover();
    }
    this.removeOnScrollListener();
  },

  getOffsetRect() {
    const target = this.refs.children.getDOMNode();
    const box = target.getBoundingClientRect();

    const body = document.body;
    const docElem = document.documentElement;

    const scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
    const scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;

    const clientTop = docElem.clientTop || body.clientTop || 0;
    const clientLeft = docElem.clientLeft || body.clientLeft || 0;

    const top = Math.round(box.top + scrollTop - clientTop);
    const left = Math.round(box.left + scrollLeft - clientLeft);
    return {
      top,
      left
    };
  },

  popoverTemplate(style) {
    const { position, anchor, className, content, id } = this.getPopoverProps();
    const positionClass = `position-${position}`;
    const anchorClass = `anchor-${anchor}`;
    const _className = `popover-content ${positionClass} ${anchorClass} ${className}`;
    return (

        {content}

    );
  },

  getVisiblePopover() {
    return this.popoverTemplate(this.computePopoverStyle());
  },

  getHiddenPopover() {
    return this.popoverTemplate({position: 'absolute', visibility: 'hidden'});
  },

  appendPopover() {
    const hiddenPopover = this.getHiddenPopover();
    this.containerNode = document.createElement('div');
    this.containerNode.innerHTML = React.renderToString(hiddenPopover);
    this.popoverNode = this.containerNode.childNodes[0];
    document.body.appendChild(this.containerNode);

    this.saveValuesFromNodeTree(() => {
      const popover = this.getVisiblePopover();
      this.containerNode.innerHTML = React.renderToString(popover);
    });
  },

  removePopover() {
    if (this.containerNode) {
      document.body.removeChild(this.containerNode);
      this.containerNode = null;
    }
  },

  addOnScrollListener() {
    if (this.getPopoverProps().dismissOnScroll) {
      if (window.attachEvent) {
        //Internet Explorer
        window.attachEvent('onmousewheel', this.onScroll);
      } else if(window.addEventListener) {
        window.addEventListener('mousewheel', this.onScroll, false);
      }
    }
  },

  removeOnScrollListener() {
    if (this.getPopoverProps().dismissOnScroll) {
      if (window.detachEvent) {
        //Internet Explorer
        window.detachEvent('onmousewheel', this.onScroll);
      } else if(window.removeEventListener) {
        window.removeEventListener('mousewheel', this.onScroll, false);
      }
    }
  },

  onPopoverStateChange() {
    const { onShow, onHide } = this.getPopoverProps();
    const isAbsolute = this.isAbsolute();
    if (this.state.isOpen) {
      onShow();
      if (isAbsolute) {
        this.appendPopover();
      }
      this.addOnScrollListener();
    } else {
      onHide();
      if (isAbsolute) {
        this.removePopover();
      }
      this.removeOnScrollListener();
    }
  },

  showPopover() {
    this.setState(
      {isOpen: true},
      this.onPopoverStateChange
    );
  },

  hidePopover() {
    this.setState(
      {isOpen: false},
      this.onPopoverStateChange
    );
  },

  togglePopover() {
    this.setState(
      {isOpen: !this.state.isOpen},
      this.onPopoverStateChange
    );
  },

  onScroll() {
    this.hidePopover();
  },

  getEventCallbacks() {
    const { event } = this.getPopoverProps();
    const onHover = event === 'hover';
    const onClick = event === 'click';
    return {
      onMouseEnter: onHover ? this.showPopover : undefined,
      onMouseLeave: onHover ? this.hidePopover : undefined,
      onClick: onClick ? this.togglePopover : undefined
    };
  },

  isAbsolute() {
    return this.getPopoverProps().attachToBody === true;
  },

  computePopoverStyle() {
    const {
      childWidth,
      childHeight,
      popoverWidth,
      popoverHeight,
      offsetTop,
      offsetLeft
    } = this.state;
    const { anchor, position, maxWidth, offsetX, offsetY, distance } = this.getPopoverProps();
    const isAbsolute = this.isAbsolute();

    let deltaX;
    switch (anchor) {
      case 'left':
        deltaX = 0;
        break;
      case 'center':
        deltaX = (childWidth - popoverWidth) / 2;
        break;
      case 'right':
        deltaX = childWidth - popoverWidth;
        break;
    }

    let deltaY;
    switch (position) {
      case 'top':
        deltaY = isAbsolute ? -(popoverHeight + distance - offsetY) : (childHeight + distance - offsetY); //eslint-disable-line space-infix-ops
        break;
      case 'bottom':
        deltaY = childHeight + distance + offsetY;
        break;
    }

    const valueY = (isAbsolute ? offsetTop : 0) + deltaY;
    const valueX = (isAbsolute ? offsetLeft : 0) + deltaX + offsetX;
    return {
      position: 'absolute',
      top: isAbsolute || position === 'bottom' ? valueY : undefined,
      bottom: !isAbsolute && position === 'top' ? valueY : undefined,
      left: valueX,
      maxWidth: maxWidth
    };
  },

  getLocals() {
    const isRelative = !this.isAbsolute();
    const { isOpen } = this.state;
    const popover = isRelative && (isOpen ? this.getVisiblePopover() : this.getHiddenPopover());
    return {
      ...this.props,
      style: {
        display: 'inline-block',
        position: isRelative ? 'relative' : undefined,
        ...this.props.style
      },
      className: cx('react-popover', this.props.className),
      eventCallbacks: this.getEventCallbacks(),
      popover
    };
  },

  render() {
    const { children, style, className, id, eventCallbacks, popover } = this.getLocals();
    return (

        {children}
        {popover}

    );
  },

  componentWillReceiveProps() {
    this.saveValuesFromNodeTree();
  }

});

export default Popover;
<commit_msg>refactor logic to handle new props (position: top/bottom/left/right anchor: start/center/end)<commit_after>import React from 'react';
import cx from 'classnames';

const Popover = React.createClass({

  propTypes: {
    children: React.PropTypes.node.isRequired,
    popover: React.PropTypes.shape({
      content: React.PropTypes.node.isRequired,
      attachToBody: React.PropTypes.bool,
      position: React.PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
      anchor: React.PropTypes.oneOf(['start', 'center', 'end']),
      event: React.PropTypes.oneOf(['click', 'hover']),
      onShow: React.PropTypes.func,
      onHide: React.PropTypes.func,
      dismissOnScroll: React.PropTypes.bool,
      className: React.PropTypes.string,
      id: React.PropTypes.string,
      maxWidth: React.PropTypes.oneOfType([
        React.PropTypes.number,
        React.PropTypes.string
      ]),
      distance: React.PropTypes.number,
      offsetX: React.PropTypes.number,
      offsetY: React.PropTypes.number
    }).isRequired,
    id: React.PropTypes.string,
    className: React.PropTypes.string,
    style: React.PropTypes.object
  },

  getDefaultProps() {
    return {
      id: '',
      className: '',
      style: {}
    };
  },

  getInitialState() {
    return {
      isOpen: false
    };
  },

  componentDidMount() {
    this.saveValuesFromNodeTree();
  },

  saveValuesFromNodeTree(cb) {
    const childrenNode = this.refs.children.getDOMNode();
    const popoverNode = this.isAbsolute() ? this.popoverNode : childrenNode.childNodes[1];

    if (popoverNode) {
      const { clientWidth: childWidth, clientHeight: childHeight } = childrenNode;
      const { clientHeight: popoverHeight, clientWidth: popoverWidth } = popoverNode;
      const { top: offsetTop, left: offsetLeft } = this.getOffsetRect();
      this.setState({
        childWidth,
        childHeight,
        popoverWidth,
        popoverHeight,
        offsetTop,
        offsetLeft
      }, cb);
    }
  },

  // extend with default values
  getPopoverProps() {
    return {
      type: 'relative',
      position: 'top',
      anchor: 'center',
      event: 'hover',
      onShow: () => {},
      onHide: () => {},
      dismissOnScroll: true,
      className: '',
      distance: 5,
      offsetX: 0,
      offsetY: 0,
      ...this.props.popover
    };
  },

  componentWillUnmount() {
    if (this.isAbsolute()) {
      this.removePopover();
    }
    this.removeOnScrollListener();
  },

  getOffsetRect() {
    const target = this.refs.children.getDOMNode();
    const box = target.getBoundingClientRect();

    const body = document.body;
    const docElem = document.documentElement;

    const scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
    const scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;

    const clientTop = docElem.clientTop || body.clientTop || 0;
    const clientLeft = docElem.clientLeft || body.clientLeft || 0;

    const top = Math.round(box.top + scrollTop - clientTop);
    const left = Math.round(box.left + scrollLeft - clientLeft);
    return {
      top,
      left
    };
  },

  popoverTemplate(style) {
    const { position, anchor, className, content, id } = this.getPopoverProps();
    const positionClass = `position-${position}`;
    const anchorClass = `anchor-${anchor}`;
    const _className = `popover-content ${positionClass} ${anchorClass} ${className}`;
    return (

        {content}

    );
  },

  getVisiblePopover() {
    return this.popoverTemplate(this.computePopoverStyle());
  },

  getHiddenPopover() {
    return this.popoverTemplate({position: 'absolute', visibility: 'hidden'});
  },

  appendPopover() {
    const hiddenPopover = this.getHiddenPopover();
    this.containerNode = document.createElement('div');
    this.containerNode.innerHTML = React.renderToString(hiddenPopover);
    this.popoverNode = this.containerNode.childNodes[0];
    document.body.appendChild(this.containerNode);

    this.saveValuesFromNodeTree(() => {
      const popover = this.getVisiblePopover();
      this.containerNode.innerHTML = React.renderToString(popover);
    });
  },

  removePopover() {
    if (this.containerNode) {
      document.body.removeChild(this.containerNode);
      this.containerNode = null;
    }
  },

  addOnScrollListener() {
    if (this.getPopoverProps().dismissOnScroll) {
      if (window.attachEvent) {
        //Internet Explorer
        window.attachEvent('onmousewheel', this.onScroll);
      } else if(window.addEventListener) {
        window.addEventListener('mousewheel', this.onScroll, false);
      }
    }
  },

  removeOnScrollListener() {
    if (this.getPopoverProps().dismissOnScroll) {
      if (window.detachEvent) {
        //Internet Explorer
        window.detachEvent('onmousewheel', this.onScroll);
      } else if(window.removeEventListener) {
        window.removeEventListener('mousewheel', this.onScroll, false);
      }
    }
  },

  onPopoverStateChange() {
    const { onShow, onHide } = this.getPopoverProps();
    const isAbsolute = this.isAbsolute();
    if (this.state.isOpen) {
      onShow();
      if (isAbsolute) {
        this.appendPopover();
      }
      this.addOnScrollListener();
    } else {
      onHide();
      if (isAbsolute) {
        this.removePopover();
      }
      this.removeOnScrollListener();
    }
  },

  showPopover() {
    this.setState(
      {isOpen: true},
      this.onPopoverStateChange
    );
  },

  hidePopover() {
    this.setState(
      {isOpen: false},
      this.onPopoverStateChange
    );
  },

  togglePopover() {
    this.setState(
      {isOpen: !this.state.isOpen},
      this.onPopoverStateChange
    );
  },

  onScroll() {
    this.hidePopover();
  },

  getEventCallbacks() {
    const { event } = this.getPopoverProps();
    const onHover = event === 'hover';
    const onClick = event === 'click';
    return {
      onMouseEnter: onHover ? this.showPopover : undefined,
      onMouseLeave: onHover ? this.hidePopover : undefined,
      onClick: onClick ? this.togglePopover : undefined
    };
  },

  isAbsolute() {
    return this.getPopoverProps().attachToBody === true;
  },

  computePopoverStyle() {
    const {
      childWidth,
      childHeight,
      popoverWidth,
      popoverHeight,
      offsetTop,
      offsetLeft
    } = this.state;
    const { anchor, position, maxWidth, offsetX, offsetY, distance } = this.getPopoverProps();

    const isAbsolute = this.isAbsolute();
    const isHorizontal = position === 'top' || position === 'bottom';
    const isVertical = position === 'right' || position === 'left';

    const anchorOffset = { top: 0, left: 0 };
    const positionOffset = { top: 0, left: 0 };

    switch (anchor) {
      case 'start':
        // default -> { top: 0, left: 0 }
        break;
      case 'center':
        anchorOffset.left = isHorizontal ? (childWidth - popoverWidth) / 2 : 0;
        anchorOffset.top = isVertical ? (childHeight - popoverHeight) / 2 : 0;
        break;
      case 'end':
        anchorOffset.left = isHorizontal ? (childWidth - popoverWidth) : 0;
        anchorOffset.top = isVertical ? (childHeight - popoverHeight) : 0;
        break;
    }

    switch (position) {
      case 'top':
        positionOffset.top = -(popoverHeight + distance);
        break;
      case 'bottom':
        positionOffset.top = childHeight + distance;
        break;
      case 'left':
        positionOffset.left = -(popoverWidth + distance);
        break;
      case 'right':
        positionOffset.left = childWidth + distance;
        break;
    }

    return {
      position: 'absolute',
      top: (isAbsolute ? offsetTop : 0) + (positionOffset.top + anchorOffset.top + offsetY),
      left: (isAbsolute ? offsetLeft : 0) + (positionOffset.left + anchorOffset.left + offsetX),
      maxWidth
    };
  },

  getLocals() {
    const isRelative = !this.isAbsolute();
    const { isOpen } = this.state;
    const popover = isRelative && (isOpen ? this.getVisiblePopover() : this.getHiddenPopover());
    return {
      ...this.props,
      style: {
        display: 'inline-block',
        position: isRelative ? 'relative' : undefined,
        ...this.props.style
      },
      className: cx('react-popover', this.props.className),
      eventCallbacks: this.getEventCallbacks(),
      popover
    };
  },

  render() {
    const { children, style, className, id, eventCallbacks, popover } = this.getLocals();
    return (

        {children}
        {popover}

    );
  },

  componentWillReceiveProps() {
    this.saveValuesFromNodeTree();
  }

});

export default Popover;
<|endoftext|>


<commit_before>var _BOTKIT_STATS_API = 'http://192.168.33.20:3000';
var request = require('request');
var md5 = require('md5');

module.exports = function(botkit){
  var token = botkit.config.token;

  function generatebotHash(team, bot_id){
    var x = md5(team + '|' + bot_id);
    return x;
  }

  function recordStat(type, bot, data){
    var now = new Date();
    var stats_uri = null;
    if(bot.config.howdy_token){
      stats_uri = _BOTKIT_STATS_API + '/api/v1/stats?access_token=' + bot.config.howdy_token;
    }else{
      stats_uri = _BOTKIT_STATS_API + '/api/v1/stats';
    }
    var post_body = {
      botHash: generatebotHash(bot.team_info.id, bot.identity.id),
      team: md5(bot.team_info.id),
      channel: data.channel,
      user: data.user,
      type: type,
      time: now,
      meta: data
    };
    // console.log('stats record sent ----------------------------------------------------');
    // console.log(post_body);
    function req_cb(err, httpResponse, body) {
     if (err) {
       return console.error('stats not saved:', err);
     }
    }
    if(stats_uri !== null){
      request.post({url: stats_uri, form: post_body}, req_cb);
    }
  }

  botkit.on('message_received', function(bot, message) {
    // console.log('/////////////////////////////////////');
    // console.log(message);
    // var data = {
    //   user: md5(message.user),
    //   channel: md5(message.channel)
    // };
    // recordStat('message_received', bot, data);
    botkit.trigger('stats_message_received', message);
  });

  botkit.on('sent', function(bot, message) {
    console.log('sent /////////////////////////////////////');
    console.log(message);
    var data = {

      user: md5(message.user),
      channel: md5(message.channel)
    };
    recordStat('sent', bot, data);
    botkit.trigger('stats_sent', message);
  });

  botkit.on('spawned', function(bot) {
    var data = {
      user: md5(bot.identity.id),
      channel: null
    };
    recordStat('spawned', bot, data);
    botkit.trigger('stats_spawned', bot);
  });

  botkit.on('conversationStarted', function(bot, convo) {
    console.log('conversationStarted /////////////////////////////////////');
    // console.log(convo);
    var data = {
      user: md5(convo.source_message.user),
      channel: md5(convo.source_message.channel)
    };
    recordStat('conversationStart', bot, data);
    botkit.trigger('stats_conversationStarted', convo);
  });

  botkit.on('conversationEnded', function(bot, convo) {
    console.log('conversationEnded /////////////////////////////////////');
    // console.log(convo);
    var data = {
      user: md5(convo.source_message.user),
      channel: md5(convo.source_message.channel),
      conversation_length: convo.lastActive - convo.startTime
    };
    recordStat('conversationEnd', bot, data);
    botkit.trigger('stats_conversationEnded', convo);
  });

  botkit.on('command_triggered', function(bot, message, command) {
    console.log('command_triggered /////////////////////////////////////');
    // console.log(message);
    // console.log(command);
    var data = {
      user: md5(message.user),
      channel: md5(message.channel),
      command: command.command,
      timestamp: command.created
    };
    recordStat('command_triggered', bot, data);
    botkit.trigger('stats_command_triggered', message);
  });

  botkit.on('remote_command_end', function(bot, message, command, convo){
    console.log('remote_command_end /////////////////////////////////////////////////////');
    // console.log(bot);
    // console.log(message);
    // console.log(command);
    console.log(convo);
    var data = {
      user: md5(message.user),
      channel: md5(message.channel),
      command: command.command,
      timestamp: command.created,
      conversation_length: convo.lastActive - convo.startTime
    };
    console.log('----------------------------------------------');
    console.log(data);
    recordStat('remote_command_end', bot, data);
    botkit.trigger('stats_remote_command_end', message);
  });

  botkit.on('heard_trigger', function(bot, keywords, message) {
    // console.log('heard_trigger /////////////////////////////////////');
    // console.log(keywords);
    // console.log(message);
    var data = {
      user: md5(message.user),
      channel: md5(message.channel),
      meta: {
        command: 'command.command',
        timestamp: 'command.created'
      }
    };
    recordStat('heard_trigger', bot, data);
    botkit.trigger('stats_heard_trigger', message);
  });

};
<commit_msg>added status to remote command end stats call<commit_after>var _BOTKIT_STATS_API = 'http://192.168.33.20:3000';
var request = require('request');
var md5 = require('md5');

module.exports = function(botkit){
  var token = botkit.config.token;

  function generatebotHash(team, bot_id){
    var x = md5(team + '|' + bot_id);
    return x;
  }

  function recordStat(type, bot, data){
    var now = new Date();
    var stats_uri = null;
    if(bot.config.howdy_token){
      stats_uri = _BOTKIT_STATS_API + '/api/v1/stats?access_token=' + bot.config.howdy_token;
    }else{
      stats_uri = _BOTKIT_STATS_API + '/api/v1/stats';
    }
    var post_body = {
      botHash: generatebotHash(bot.team_info.id, bot.identity.id),
      team: md5(bot.team_info.id),
      channel: data.channel,
      user: data.user,
      type: type,
      time: now,
      meta: data
    };
    // console.log('stats record sent ----------------------------------------------------');
    // console.log(post_body);
    function req_cb(err, httpResponse, body) {
     if (err) {
       return console.error('stats not saved:', err);
     }
    }
    if(stats_uri !== null){
      request.post({url: stats_uri, form: post_body}, req_cb);
    }
  }

  botkit.on('message_received', function(bot, message) {
    // console.log('/////////////////////////////////////');
    // console.log(message);
    // var data = {
    //   user: md5(message.user),
    //   channel: md5(message.channel)
    // };
    // recordStat('message_received', bot, data);
    botkit.trigger('stats_message_received', message);
  });

  botkit.on('sent', function(bot, message) {
    console.log('sent /////////////////////////////////////');
    console.log(message);
    var data = {

      user: md5(message.user),
      channel: md5(message.channel)
    };
    recordStat('sent', bot, data);
    botkit.trigger('stats_sent', message);
  });

  botkit.on('spawned', function(bot) {
    var data = {
      user: md5(bot.identity.id),
      channel: null
    };
    recordStat('spawned', bot, data);
    botkit.trigger('stats_spawned', bot);
  });

  botkit.on('conversationStarted', function(bot, convo) {
    console.log('conversationStarted /////////////////////////////////////');
    // console.log(convo);
    var data = {
      user: md5(convo.source_message.user),
      channel: md5(convo.source_message.channel)
    };
    recordStat('conversationStart', bot, data);
    botkit.trigger('stats_conversationStarted', convo);
  });

  botkit.on('conversationEnded', function(bot, convo) {
    console.log('conversationEnded /////////////////////////////////////');
    // console.log(convo);
    var data = {
      user: md5(convo.source_message.user),
      channel: md5(convo.source_message.channel),
      conversation_length: convo.lastActive - convo.startTime
    };
    recordStat('conversationEnd', bot, data);
    botkit.trigger('stats_conversationEnded', convo);
  });

  botkit.on('command_triggered', function(bot, message, command) {
    console.log('command_triggered /////////////////////////////////////');
    // console.log(message);
    // console.log(command);
    var data = {
      user: md5(message.user),
      channel: md5(message.channel),
      command: command.command,
      timestamp: command.created
    };
    recordStat('command_triggered', bot, data);
    botkit.trigger('stats_command_triggered', message);
  });

  botkit.on('remote_command_end', function(bot, message, command, convo){
    console.log('remote_command_end /////////////////////////////////////////////////////');
    // console.log(bot);
    // console.log(message);
    // console.log(command);
    console.log(convo);
    var data = {
      user: md5(message.user),
      channel: md5(message.channel),
      command: command.command,
      timestamp: command.created,
      conversation_length: convo.lastActive - convo.startTime,
      status: convo.status
    };
    console.log('----------------------------------------------');
    console.log(data);
    recordStat('remote_command_end', bot, data);
    botkit.trigger('stats_remote_command_end', message);
  });

  botkit.on('heard_trigger', function(bot, keywords, message) {
    // console.log('heard_trigger /////////////////////////////////////');
    // console.log(keywords);
    // console.log(message);
    var data = {
      user: md5(message.user),
      channel: md5(message.channel),
      meta: {
        command: 'command.command',
        timestamp: 'command.created'
      }
    };
    recordStat('heard_trigger', bot, data);
    botkit.trigger('stats_heard_trigger', message);
  });

};
<|endoftext|>


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

const userSchema = new mongoose.Schema({
  id: { type: String, index: { unique: true } },
  twitter_id: String,
  twitter_credentials: mongoose.Schema.Types.Mixed,
  auth0_id: String,
  auth0_refesh_token: String,
  login_tokens: [ String ],
  data: mongoose.Schema.Types.Mixed,
  created_at: Date,
  updated_at: Date,
  last_street_id: Number
})

userSchema.pre('save', function (next) {
  let now = new Date()
  this.updated_at = now
  this.created_at = this.created_at || now
  next()
})

userSchema.methods.asJson = function (options, cb) {
  options = options || {}

  let json = {
    id: this.id
  }

  if (options.auth) {
    json.data = this.data
    json.createdAt = this.created_at
    json.updatedAt = this.updated_at
  }

  cb(null, json)
}
module.exports = mongoose.model('User', userSchema)
<commit_msg>Update user model to save profile image url<commit_after>const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
  id: { type: String, index: { unique: true } },
  twitter_id: String,
  twitter_credentials: mongoose.Schema.Types.Mixed,
  auth0_id: String,
  login_tokens: [ String ],
  profile_image_url: String,
  data: mongoose.Schema.Types.Mixed,
  created_at: Date,
  updated_at: Date,
  last_street_id: Number
})

userSchema.pre('save', function (next) {
  let now = new Date()
  this.updated_at = now
  this.created_at = this.created_at || now
  next()
})

userSchema.methods.asJson = function (options, cb) {
  options = options || {}

  let json = {
    id: this.id
  }

  if (options.auth) {
    json.data = this.data
    json.createdAt = this.created_at
    json.updatedAt = this.updated_at
  }

  cb(null, json)
}
module.exports = mongoose.model('User', userSchema)
<|endoftext|>


<commit_before>// This file is shared between the client and the server, in case "window" is defined we assume it is the client
if (typeof window !== 'undefined') {
	window.VYW = window.VYW || {};
	window.VYW.Protocol = {};
}

(function(Protocol) {
	// Hold some data characters (delimiters etc)
	var DATA_SEP = '#',
		OBJ_SEP = ',';

	Protocol.Direction = {
		Up: '8',
		Right: '6',
		Down: '2',
		Left: '4'
	};

	Protocol.GameOverReason = {
		PeerDisconnect: '1',
		Collision: '2',
		End: '3'
	};

	// Expose the messages enum
	Protocol.Messages = {
		Pending: '1',
		Ready: '2',
		Steady: '3',
		Go: '4',
		Update: '5',
		GameOver: '6'
	};

	// ------------- Model Classes -------------

	function Message(type) {
		this.type = type;
	}

	function GetReadyMessage() {
		Message.call(this, Protocol.Messages.Ready);
		this.playerIndex = 0;
		this.boardWidth = 0;
		this.boardHeight = 0;
		this.boardCellSize = 0;
	}

	function SteadyMessage() {
		Message.call(this, Protocol.Messages.Steady);
		this.timeToStart = 0;
	}

	function GameOverMessage(reason) {
		Message.call(this, Protocol.Messages.GameOver);
		this.reason = reason;
		this.winningPlayer = 0;
	}

	function UpdateMessage() {
		this.timeToEnd = -1;
		this.player1Direction = '';
		this.player2Direction = '';
		this.pellets = [];
		this.player1Score = 0;
		this.player2Score = 0;
	}

	// ------------- End Model Classes -------------

	// ------------- Encode Functions -------------

	Protocol.buildPending = function() {
		// Pending: 1
		return Protocol.Messages.Pending;
	};

	Protocol.buildReady = function(playerIndex, board) {
		// Ready: 2#playerIndex#boardWidth#boardHeight#cellSize
		return Protocol.Messages.Ready + DATA_SEP + playerIndex + DATA_SEP + board.rectangle.width + DATA_SEP + board.rectangle.height + DATA_SEP + board.boxSize;
	};

	Protocol.buildSteady = function(tts) {
		// Steady: 3#timeToStart
		return Protocol.Messages.Steady + DATA_SEP + tts;
	};

	Protocol.buildGo = function() {
		// Go: 4
		return Protocol.Messages.Go;
	};

	Protocol.buildUpdate = function(tte, snake1, snake2, pellets, board) {
		// Update: 5#timeToEnd#playersDirection#pellets#score
		// playersDirection - player1Direction,player2Direction
		// pellets - cellIndex,cellIndex,cellIndex...
		// score - player1Score,player2Score

		var msg = Protocol.Messages.Update + DATA_SEP + tte + DATA_SEP + snake1.direction + OBJ_SEP + snake2.direction + DATA_SEP;

		// Now add the pellets
		var currPellet;
		var delim;
		for (var i = 0; i < pellets.length; ++i) {
			currPellet = pellets[i];
			delim = (i === pellets.length - 1) ? '' : OBJ_SEP; // Don't add separator for the last element
			msg += board.toBoxIndex(currPellet.location.x, currPellet.location.y) + delim;
		}

		// Finally add the score
		msg += DATA_SEP + snake1.parts.length + OBJ_SEP + snake2.parts.length;

		return msg;
	};

	Protocol.buildGameOver = function(reason, winningPlayerIndex, snake1, snake2) {
		// GameOver: 6#Reason#ExtraData...
		//   PeerDisconnect - No extra data
		//   Collision - winningPlayerIndex
		//   End - player1Score#player2Score
		var msg;
		switch (reason) {
			case Protocol.GameOverReason.PeerDisconnect:
				msg = Protocol.Messages.GameOver + DATA_SEP + reason;
				break;
			case Protocol.GameOverReason.Collision:
				msg = Protocol.Messages.GameOver + DATA_SEP + reason + DATA_SEP + winningPlayerIndex;
				break;
			case Protocol.GameOverReason.End:
				msg = Protocol.Messages.GameOver + DATA_SEP + reason + DATA_SEP + snake1.parts.length + DATA_SEP + snake2.parts.length;
				break;
			default:
				msg = null;
		}

		return msg;
	};

	// ------------- End Encode Functions -------------

	// ------------- Decode Functions -------------

	Protocol.parseMessage = function(msg) {
		// Message: "CODE#DATA"
		if (!msg) {return null;}

		var parts = msg.split(DATA_SEP);
		var code = parts.shift(); // This also removes the code from the parts array
		switch (code) {
			case Protocol.Messages.Pending:
				// No specific data for this message type
				return new Message(code);
			case Protocol.Messages.Ready:
				return Protocol.parseGetReadyMessage(parts);
			case Protocol.Messages.Steady:
				return Protocol.parseSteadyMessage(parts);
			case Protocol.Messages.Go:
				// No specific data for this message type
				return new Message(code);
			case Protocol.Messages.Update:
				return Protocol.parseUpdateMessage(parts);
			case Protocol.Messages.GameOver:
				// No specific data for this message type
				return Protocol.parseGameOverMessage(parts);
			default:
				return null;
		}
	};

	Protocol.parseGetReadyMessage = function(data) {
		// GetReady message: playerIndex#boardWidth#boardHeight#cellSize
		// Remember that the message already got split, so we have all in the array.
		if (data.length < 4) {
			return null;
		}

		var res = new GetReadyMessage();
		res.playerIndex = parseInt(data[0]);
		res.boardWidth = parseInt(data[1]);
		res.boardHeight = parseInt(data[2]);
		res.boardCellSize = parseInt(data[3]);

		// Check that we got all positive values
		if (!res.playerIndex || !res.boardWidth || !res.boardHeight || !res.boardCellSize) {
			return null;
		} else {
			return res;
		}
	};

	Protocol.parseSteadyMessage = function(data) {
		// Steady message contains the time to start (e.g "5")
		// Remember that the message already got split, so we have all in the array.
		if (data.length < 1) {
			return null;
		}

		var res = new SteadyMessage();
		res.timeToStart = parseInt(data[0]);

		// Check that we got positive values
		if (!res.timeToStart) {
			return null;
		} else {
			return res;
		}
	};

	Protocol.parseGameOverMessage = function(data) {
		// GameOver message contains Reason#Extradata
		if (data.length < 1) {
			return null;
		}

		var reason = data[0];
		var res = new GameOverMessage(reason);
		switch (reason) {
			case Protocol.GameOverReason.PeerDisconnect:
				return res;
			case Protocol.GameOverReason.Collision:
				// In case of collision we expect another cell with the winning player index
				if (data.length < 2) {
					return null;
				}
				res.winningPlayer = parseInt(data[1]);
				if (!res.winningPlayer || res.winningPlayer < 1 || res.winningPlayer > 2) {
					return null;
				}
				return res;
			case Protocol.GameOverReason.End:
				// In case of game end we expect 2 more cells with the scores
				if (data.length < 3) {
					return null;
				}

				var player1Score = parseInt(data[1]);
				var player2Score = parseInt(data[2]);
				// The reason we check isNaN instead of (!player1Score) is that 0 is a valid value for this field
				if (isNaN(player1Score) || isNaN(player2Score)) {
					return null;
				}

				// For simplicity tie is not an option
				res.winningPlayer = player1Score > player2Score ? 1 : 2;
				return res;
			default:
				return null;
		}
	};

	Protocol.parseUpdateMessage = function(data) {
		// Update: [timeToEnd, playersDirection, pellets, score]
		// playersDirection - player1Direction,player2Direction
		// pellets - cellIndex,cellIndex,cellIndex...
		// score - player1Score,player2Score

		if (data.length < 4) {
			return null;
		}

		var res = new UpdateMessage();

		// Parse tte
		res.timeToEnd = parseInt(data[0]);
		if (isNaN(res.timeToEnd)) {
			return null;
		}

		// Parse players directions
		var dirs = data[1].split(OBJ_SEP);
		if (dirs.length < 2) {
			return null;
		}

		res.player1Direction = dirs[0];
		res.player2Direction = dirs[1];

		// Parse players scores
		var scores = data[3].split(OBJ_SEP);
		if (scores.length < 2) {
			return null;
		}

		res.player1Score = scores[0];
		res.player2Score = scores[1];

		// Parse pellets

		return null;
	};

	// ------------- End Decode Functions -------------

// Pass in the correct object (server vs client)
}(typeof window === 'undefined' ? module.exports : window.VYW.Protocol));
<commit_msg>Parse update message done<commit_after>// This file is shared between the client and the server, in case "window" is defined we assume it is the client
if (typeof window !== 'undefined') {
	window.VYW = window.VYW || {};
	window.VYW.Protocol = {};
}

(function(Protocol) {
	// Hold some data characters (delimiters etc)
	var DATA_SEP = '#',
		OBJ_SEP = ',';

	Protocol.Direction = {
		Up: '8',
		Right: '6',
		Down: '2',
		Left: '4'
	};

	Protocol.GameOverReason = {
		PeerDisconnect: '1',
		Collision: '2',
		End: '3'
	};

	// Expose the messages enum
	Protocol.Messages = {
		Pending: '1',
		Ready: '2',
		Steady: '3',
		Go: '4',
		Update: '5',
		GameOver: '6'
	};

	// ------------- Model Classes -------------

	function Message(type) {
		this.type = type;
	}

	function GetReadyMessage() {
		Message.call(this, Protocol.Messages.Ready);
		this.playerIndex = 0;
		this.boardWidth = 0;
		this.boardHeight = 0;
		this.boardCellSize = 0;
	}

	function SteadyMessage() {
		Message.call(this, Protocol.Messages.Steady);
		this.timeToStart = 0;
	}

	function GameOverMessage(reason) {
		Message.call(this, Protocol.Messages.GameOver);
		this.reason = reason;
		this.winningPlayer = 0;
	}

	function UpdateMessage() {
		this.timeToEnd = -1;
		this.player1Direction = '';
		this.player2Direction = '';
		this.pellets = [];
		this.player1Score = 0;
		this.player2Score = 0;
	}

	// ------------- End Model Classes -------------

	// ------------- Encode Functions -------------

	Protocol.buildPending = function() {
		// Pending: 1
		return Protocol.Messages.Pending;
	};

	Protocol.buildReady = function(playerIndex, board) {
		// Ready: 2#playerIndex#boardWidth#boardHeight#cellSize
		return Protocol.Messages.Ready + DATA_SEP + playerIndex + DATA_SEP + board.rectangle.width + DATA_SEP + board.rectangle.height + DATA_SEP + board.boxSize;
	};

	Protocol.buildSteady = function(tts) {
		// Steady: 3#timeToStart
		return Protocol.Messages.Steady + DATA_SEP + tts;
	};

	Protocol.buildGo = function() {
		// Go: 4
		return Protocol.Messages.Go;
	};

	Protocol.buildUpdate = function(tte, snake1, snake2, pellets, board) {
		// Update: 5#timeToEnd#playersDirection#pellets#score
		// playersDirection - player1Direction,player2Direction
		// pellets - cellIndex,cellIndex,cellIndex...
		// score - player1Score,player2Score

		var msg = Protocol.Messages.Update + DATA_SEP + tte + DATA_SEP + snake1.direction + OBJ_SEP + snake2.direction + DATA_SEP;

		// Now add the pellets
		var currPellet;
		var delim;
		for (var i = 0; i < pellets.length; ++i) {
			currPellet = pellets[i];
			delim = (i === pellets.length - 1) ? '' : OBJ_SEP; // Don't add separator for the last element
			msg += board.toBoxIndex(currPellet.location.x, currPellet.location.y) + delim;
		}

		// Finally add the score
		msg += DATA_SEP + snake1.parts.length + OBJ_SEP + snake2.parts.length;

		return msg;
	};

	Protocol.buildGameOver = function(reason, winningPlayerIndex, snake1, snake2) {
		// GameOver: 6#Reason#ExtraData...
		//   PeerDisconnect - No extra data
		//   Collision - winningPlayerIndex
		//   End - player1Score#player2Score
		var msg;
		switch (reason) {
			case Protocol.GameOverReason.PeerDisconnect:
				msg = Protocol.Messages.GameOver + DATA_SEP + reason;
				break;
			case Protocol.GameOverReason.Collision:
				msg = Protocol.Messages.GameOver + DATA_SEP + reason + DATA_SEP + winningPlayerIndex;
				break;
			case Protocol.GameOverReason.End:
				msg = Protocol.Messages.GameOver + DATA_SEP + reason + DATA_SEP + snake1.parts.length + DATA_SEP + snake2.parts.length;
				break;
			default:
				msg = null;
		}

		return msg;
	};

	// ------------- End Encode Functions -------------

	// ------------- Decode Functions -------------

	Protocol.parseMessage = function(msg) {
		// Message: "CODE#DATA"
		if (!msg) {return null;}

		var parts = msg.split(DATA_SEP);
		var code = parts.shift(); // This also removes the code from the parts array
		switch (code) {
			case Protocol.Messages.Pending:
				// No specific data for this message type
				return new Message(code);
			case Protocol.Messages.Ready:
				return Protocol.parseGetReadyMessage(parts);
			case Protocol.Messages.Steady:
				return Protocol.parseSteadyMessage(parts);
			case Protocol.Messages.Go:
				// No specific data for this message type
				return new Message(code);
			case Protocol.Messages.Update:
				return Protocol.parseUpdateMessage(parts);
			case Protocol.Messages.GameOver:
				// No specific data for this message type
				return Protocol.parseGameOverMessage(parts);
			default:
				return null;
		}
	};

	Protocol.parseGetReadyMessage = function(data) {
		// GetReady message: playerIndex#boardWidth#boardHeight#cellSize
		// Remember that the message already got split, so we have all in the array.
		if (data.length < 4) {
			return null;
		}

		var res = new GetReadyMessage();
		res.playerIndex = parseInt(data[0]);
		res.boardWidth = parseInt(data[1]);
		res.boardHeight = parseInt(data[2]);
		res.boardCellSize = parseInt(data[3]);

		// Check that we got all positive values
		if (!res.playerIndex || !res.boardWidth || !res.boardHeight || !res.boardCellSize) {
			return null;
		} else {
			return res;
		}
	};

	Protocol.parseSteadyMessage = function(data) {
		// Steady message contains the time to start (e.g "5")
		// Remember that the message already got split, so we have all in the array.
		if (data.length < 1) {
			return null;
		}

		var res = new SteadyMessage();
		res.timeToStart = parseInt(data[0]);

		// Check that we got positive values
		if (!res.timeToStart) {
			return null;
		} else {
			return res;
		}
	};

	Protocol.parseGameOverMessage = function(data) {
		// GameOver message contains Reason#Extradata
		if (data.length < 1) {
			return null;
		}

		var reason = data[0];
		var res = new GameOverMessage(reason);
		switch (reason) {
			case Protocol.GameOverReason.PeerDisconnect:
				return res;
			case Protocol.GameOverReason.Collision:
				// In case of collision we expect another cell with the winning player index
				if (data.length < 2) {
					return null;
				}
				res.winningPlayer = parseInt(data[1]);
				if (!res.winningPlayer || res.winningPlayer < 1 || res.winningPlayer > 2) {
					return null;
				}
				return res;
			case Protocol.GameOverReason.End:
				// In case of game end we expect 2 more cells with the scores
				if (data.length < 3) {
					return null;
				}

				var player1Score = parseInt(data[1]);
				var player2Score = parseInt(data[2]);
				// The reason we check isNaN instead of (!player1Score) is that 0 is a valid value for this field
				if (isNaN(player1Score) || isNaN(player2Score)) {
					return null;
				}

				// For simplicity tie is not an option
				res.winningPlayer = player1Score > player2Score ? 1 : 2;
				return res;
			default:
				return null;
		}
	};

	Protocol.parseUpdateMessage = function(data) {
		// Update: [timeToEnd, playersDirection, pellets, score]
		// playersDirection - player1Direction,player2Direction
		// pellets - cellIndex,cellIndex,cellIndex...
		// score - player1Score,player2Score

		if (data.length < 4) {
			return null;
		}

		var res = new UpdateMessage();

		// Parse tte
		res.timeToEnd = parseInt(data[0]);
		if (isNaN(res.timeToEnd)) {
			return null;
		}

		// Parse players directions
		var dirs = data[1].split(OBJ_SEP);
		if (dirs.length < 2) {
			return null;
		}

		res.player1Direction = dirs[0];
		res.player2Direction = dirs[1];

		// Parse pellets
		var pellets = data[2].split(OBJ_SEP);
		for (var i = 0; i < pellets.length; ++i) {
			res.pellets.push(pellets[i]);
		}

		// Parse players scores
		var scores = data[3].split(OBJ_SEP);
		if (scores.length < 2) {
			return null;
		}

		res.player1Score = scores[0];
		res.player2Score = scores[1];

		return res;
	};

	// ------------- End Decode Functions -------------

// Pass in the correct object (server vs client)
}(typeof window === 'undefined' ? module.exports : window.VYW.Protocol));
<|endoftext|>


<commit_before>/* global ciscospark */
import React, {Component} from 'react';
import classNames from 'classnames';
import {Cookies, withCookies} from 'react-cookie';
import {instanceOf} from 'prop-types';
import {autobind} from 'core-decorators';

import {Tabs, Tab} from 'material-ui/Tabs';
import RaisedButton from 'material-ui/RaisedButton';
import AppBar from 'material-ui/AppBar';
import Toggle from 'material-ui/Toggle';
import {RadioButtonGroup, RadioButton} from 'material-ui/RadioButton';
import {Card, CardActions, CardTitle, CardText} from 'material-ui/Card';

import SyntaxHighlighter from 'react-syntax-highlighter/prism';
import {coy} from 'react-syntax-highlighter/styles/prism';

import TokenInput from '../token-input';

import styles from './styles.css';

const MODE_ONE_ON_ONE = 'MODE_ONE_ON_ONE';
const MODE_SPACE = 'MODE_SPACE';

const widgetElementId = 'my-ciscospark-widget';

class DemoWidgetSpace extends Component {
  constructor(props) {
    super(props);
    const {cookies} = this.props;
    const toPersonEmail = cookies.get('toPersonEmail') || '';
    const spaceId = cookies.get('spaceId') || '';
    let mode;
    if (spaceId) {
      mode = MODE_SPACE;
    }
    else if (toPersonEmail) {
      mode = MODE_ONE_ON_ONE;
    }
    this.state = {
      accessToken: cookies.get('accessToken') || '',
      displayToken: false,
      mode,
      running: false,
      toPersonEmail,
      spaceId
    };
  }

  shouldComponentUpdate() {
    return true;
  }

  @autobind
  handleSubmit(e) {
    e.preventDefault();
    const {cookies} = this.props;
    cookies.set('accessToken', this.state.accessToken);
    if (this.state.mode === MODE_ONE_ON_ONE) {
      cookies.set('toPersonEmail', this.state.toPersonEmail);
    }
    else {
      cookies.set('spaceId', this.state.spaceId);
    }
    const toPerson = this.state.mode === MODE_ONE_ON_ONE ? this.state.toPersonEmail : '';
    const toSpace = this.state.mode === MODE_SPACE ? this.state.spaceId : '';
    const widgetEl = document.getElementById(widgetElementId);
    if (toPerson) {
      ciscospark.widget(widgetEl).spaceWidget({
        accessToken: this.state.accessToken,
        onEvent: (eventName, detail) => {
          window.ciscoSparkEvents.push({eventName, detail});
        },
        toPersonEmail: toPerson
      });
    }
    if (toSpace) {
      ciscospark.widget(widgetEl).spaceWidget({
        accessToken: this.state.accessToken,
        onEvent: (eventName, detail) => {
          window.ciscoSparkEvents.push({eventName, detail});
        },
        spaceId: toSpace
      });
    }
    this.setState({running: true});
  }

  @autobind
  handleRemove() {
    const widgetEl = document.getElementById(widgetElementId);
    ciscospark.widget(widgetEl).remove();
    this.setState({running: false});
  }

  @autobind
  handleAccessTokenChange(accessToken) {
    return this.setState({accessToken});
  }

  @autobind
  handleDisplayAccessTokenChange(e, displayToken) {
    return this.setState({displayToken});
  }

  @autobind
  handleEmailChange(e) {
    return this.setState({toPersonEmail: e.target.value});
  }

  @autobind
  handleModeChange(e, value) {
    return this.setState({mode: value});
  }

  @autobind
  handleSpaceChange(e) {
    return this.setState({spaceId: e.target.value});
  }

  generateExampleCode() {
    const {
      accessToken, displayToken, spaceId, toPersonEmail
    } = this.state;
    const displayedAccessToken = displayToken ? accessToken : 'YOUR_ACCESS_TOKEN';
    let globalToField, inlineToField;
    if (this.state.mode === MODE_ONE_ON_ONE) {
      inlineToField = `data-to-person-email="${toPersonEmail || 'TO_PERSON_EMAIL'}"`;
      globalToField = `toPersonEmail: '${toPersonEmail || 'TO_PERSON_EMAIL'}'`;
    }
    else {
      globalToField = `spaceId: '${spaceId || 'SPACE_ID'}'`;
      inlineToField = `data-space-id="${spaceId || 'SPACE_ID'}"`;
    }

    const inlineCode = `
`;
    const globalCode = `

`;
    return {
      globalCode,
      inlineCode
    };
  }

  render() {
    const hasToPerson = this.state.mode === MODE_ONE_ON_ONE && this.state.toPersonEmail;
    const hasToSpace = this.state.mode === MODE_SPACE && this.state.spaceId;
    const loadButtonEnabled = this.state.accessToken && (hasToPerson || hasToSpace);
    const {globalCode, inlineCode} = this.generateExampleCode(this.state);
    const componentContainerClassNames = [
      styles.widgetComponentContainer
    ];
    if (!this.state.running) {
      componentContainerClassNames.push(styles.hidden);
    }
    // eslint-disable-reason should not apply to jsx/html markup
    /* eslint-disable max-len */
    return (

        <AppBar title="Cisco Spark Space Widget" />

          <Card initiallyExpanded style={{margin: '10px'}}>
            <CardTitle
              actAsExpander
              showExpandableButton
              subtitle="The Spark Space widget allows developers to easily incorporate Cisco Spark Space messaging into an application."
              title="Cisco Spark Space Widget Demo"
            />
            <CardText expandable>
              This widget handles coordination between your application and the Spark APIs, and provides components of the Spark space experience without having to build all of the front end UI yourself.

              Our widget is built using React, Redux, and the Spark Javascript SDK .
            </CardText>
          </Card>
          <TokenInput onLogin={this.handleAccessTokenChange} token={this.state.accessToken} />
          <Card initiallyExpanded style={{margin: '10px'}}>
            <CardTitle
              actAsExpander
              showExpandableButton
              subtitle="Cisco Spark Space Widget can open to a space or person."
              title="Choose Widget Destination"
            />
            <CardText expandable>

                <RadioButtonGroup
                  aria-label="Widget 'To' Type"
                  name="toType"
                  onChange={this.handleModeChange}
                  valueSelected={this.state.mode}
                >
                  <RadioButton
                    aria-label="To Space"
                    label="To Space"
                    value={MODE_SPACE}
                  />
                  <RadioButton
                    aria-label="To Person"
                    label="To Person"
                    value={MODE_ONE_ON_ONE}
                  />
                </RadioButtonGroup>

              {
                this.state.mode === MODE_SPACE &&

                  <input
                    aria-label="To Space ID"
                    className={styles.textInput}
                    id="toSpaceId"
                    onChange={this.handleSpaceChange}
                    placeholder="Spark Space Id"
                    value={this.state.spaceId}
                  />

              }
              {
                this.state.mode === MODE_ONE_ON_ONE &&

                  <input
                    aria-label="To User Email"
                    className={styles.textInput}
                    id="toUserEmail"
                    onChange={this.handleEmailChange}
                    placeholder="Spark User Email (For 1:1)"
                    value={this.state.toPersonEmail}
                  />

              }
            </CardText>
            <CardActions expandable>
              <RaisedButton
                aria-label="Open Widget"
                disabled={!loadButtonEnabled}
                id="openWidgetButton"
                label="Open Embedded Widget"
                onClick={this.handleSubmit}
                primary
              />
              <RaisedButton
                aria-label="Remove Widget"
                disabled={!this.state.running}
                id="removeWidgetButton"
                label="Remove Embedded Widget"
                onClick={this.handleRemove}
                primary
              />
            </CardActions>
          </Card>
          <Card initiallyExpanded style={{margin: '10px'}}>
            <CardTitle
              actAsExpander
              showExpandableButton
              subtitle="Cisco Spark Space Widget can be implemented multiple ways."
              title="Widget Example Code"
            />
            <CardText expandable>

                <Tabs>
                  <Tab label="Inline Mode">

                      <SyntaxHighlighter language="jsx" style={coy}>
                        {inlineCode}
                      </SyntaxHighlighter>

                  </Tab>
                  <Tab label="Global Mode">

                      <SyntaxHighlighter language="jsx" style={coy}>
                        {globalCode}
                      </SyntaxHighlighter>

                  </Tab>
                </Tabs>

                    <Toggle
                      label="Display Access Token"
                      labelPosition="right"
                      onToggle={this.handleDisplayAccessTokenChange}
                    />

            </CardText>
          </Card>

    );
  }
}

DemoWidgetSpace.propTypes = {
  cookies: instanceOf(Cookies).isRequired
};

DemoWidgetSpace.title = 'Widget Space';
DemoWidgetSpace.path = '/widget-space-demo';

export default withCookies(DemoWidgetSpace);
<commit_msg>feat(widget-space-demo): add option to disable default activities<commit_after>/* global ciscospark */
import React, {Component} from 'react';
import classNames from 'classnames';
import {Cookies, withCookies} from 'react-cookie';
import {instanceOf} from 'prop-types';
import {autobind} from 'core-decorators';

import {Tabs, Tab} from 'material-ui/Tabs';
import RaisedButton from 'material-ui/RaisedButton';
import AppBar from 'material-ui/AppBar';
import Toggle from 'material-ui/Toggle';
import {RadioButtonGroup, RadioButton} from 'material-ui/RadioButton';
import {Card, CardActions, CardTitle, CardText} from 'material-ui/Card';
import Checkbox from 'material-ui/Checkbox';

import SyntaxHighlighter from 'react-syntax-highlighter/prism';
import {coy} from 'react-syntax-highlighter/styles/prism';

import TokenInput from '../token-input';

import styles from './styles.css';

const MODE_ONE_ON_ONE = 'MODE_ONE_ON_ONE';
const MODE_SPACE = 'MODE_SPACE';

const widgetElementId = 'my-ciscospark-widget';

class DemoWidgetSpace extends Component {
  constructor(props) {
    super(props);
    const {cookies} = this.props;
    const toPersonEmail = cookies.get('toPersonEmail') || '';
    const spaceId = cookies.get('spaceId') || '';
    let mode;
    if (spaceId) {
      mode = MODE_SPACE;
    }
    else if (toPersonEmail) {
      mode = MODE_ONE_ON_ONE;
    }
    this.state = {
      accessToken: cookies.get('accessToken') || '',
      activities: {
        files: true,
        meet: true,
        message: true,
        people: true
      },
      displayToken: false,
      mode,
      running: false,
      toPersonEmail,
      spaceId
    };
  }

  shouldComponentUpdate() {
    return true;
  }

  @autobind
  handleSubmit(e) {
    e.preventDefault();
    const {cookies} = this.props;
    cookies.set('accessToken', this.state.accessToken);
    if (this.state.mode === MODE_ONE_ON_ONE) {
      cookies.set('toPersonEmail', this.state.toPersonEmail);
    }
    else {
      cookies.set('spaceId', this.state.spaceId);
    }
    const toPerson = this.state.mode === MODE_ONE_ON_ONE ? this.state.toPersonEmail : '';
    const toSpace = this.state.mode === MODE_SPACE ? this.state.spaceId : '';
    const widgetEl = document.getElementById(widgetElementId);
    if (toPerson) {
      ciscospark.widget(widgetEl).spaceWidget({
        accessToken: this.state.accessToken,
        onEvent: (eventName, detail) => {
          window.ciscoSparkEvents.push({eventName, detail});
        },
        spaceActivities: this.state.activities,
        toPersonEmail: toPerson
      });
    }
    if (toSpace) {
      ciscospark.widget(widgetEl).spaceWidget({
        accessToken: this.state.accessToken,
        onEvent: (eventName, detail) => {
          window.ciscoSparkEvents.push({eventName, detail});
        },
        spaceActivities: this.state.activities,
        spaceId: toSpace
      });
    }
    this.setState({running: true});
  }

  @autobind
  handleRemove() {
    const widgetEl = document.getElementById(widgetElementId);
    ciscospark.widget(widgetEl).remove();
    this.setState({running: false});
  }

  @autobind
  handleAccessTokenChange(accessToken) {
    return this.setState({accessToken});
  }

  @autobind
  handleDisplayAccessTokenChange(e, displayToken) {
    return this.setState({displayToken});
  }

  @autobind
  handleEmailChange(e) {
    return this.setState({toPersonEmail: e.target.value});
  }

  @autobind
  handleModeChange(e, value) {
    return this.setState({mode: value});
  }

  @autobind
  handleActivitiesChange(event, isInputChecked) {
    const {activities} = this.state;
    activities[event.target.value] = isInputChecked;
    return this.setState({activities});
  }

  @autobind
  handleSpaceChange(e) {
    return this.setState({spaceId: e.target.value});
  }

  generateExampleCode() {
    const {
      accessToken, displayToken, spaceId, toPersonEmail
    } = this.state;
    const displayedAccessToken = displayToken ? accessToken : 'YOUR_ACCESS_TOKEN';
    let globalToField, inlineToField;
    if (this.state.mode === MODE_ONE_ON_ONE) {
      inlineToField = `data-to-person-email="${toPersonEmail || 'TO_PERSON_EMAIL'}"`;
      globalToField = `toPersonEmail: '${toPersonEmail || 'TO_PERSON_EMAIL'}'`;
    }
    else {
      globalToField = `spaceId: '${spaceId || 'SPACE_ID'}'`;
      inlineToField = `data-space-id="${spaceId || 'SPACE_ID'}"`;
    }

    const inlineCode = `
`;
    const globalCode = `

`;
    return {
      globalCode,
      inlineCode
    };
  }

  render() {
    const hasToPerson = this.state.mode === MODE_ONE_ON_ONE && this.state.toPersonEmail;
    const hasToSpace = this.state.mode === MODE_SPACE && this.state.spaceId;
    const loadButtonEnabled = this.state.accessToken && (hasToPerson || hasToSpace);
    const {globalCode, inlineCode} = this.generateExampleCode(this.state);
    const componentContainerClassNames = [
      styles.widgetComponentContainer
    ];
    if (!this.state.running) {
      componentContainerClassNames.push(styles.hidden);
    }
    // eslint-disable-reason should not apply to jsx/html markup
    /* eslint-disable max-len */
    return (

        <AppBar title="Cisco Spark Space Widget" />

          <Card initiallyExpanded style={{margin: '10px'}}>
            <CardTitle
              actAsExpander
              showExpandableButton
              subtitle="The Spark Space widget allows developers to easily incorporate Cisco Spark Space messaging into an application."
              title="Cisco Spark Space Widget Demo"
            />
            <CardText expandable>
              This widget handles coordination between your application and the Spark APIs, and provides components of the Spark space experience without having to build all of the front end UI yourself.

              Our widget is built using React, Redux, and the Spark Javascript SDK .
            </CardText>
          </Card>
          <TokenInput onLogin={this.handleAccessTokenChange} token={this.state.accessToken} />
          <Card initiallyExpanded style={{margin: '10px'}}>
            <CardTitle
              actAsExpander
              showExpandableButton
              subtitle="Cisco Spark Space Widget can open to a space or person."
              title="Choose Widget Destination"
            />
            <CardText expandable>

 Widget Destination Type

                <RadioButtonGroup
                  aria-label="Widget 'To' Type"
                  name="toType"
                  onChange={this.handleModeChange}
                  valueSelected={this.state.mode}
                >
                  <RadioButton
                    aria-label="To Space"
                    label="To Space"
                    value={MODE_SPACE}
                  />
                  <RadioButton
                    aria-label="To Person"
                    label="To Person"
                    value={MODE_ONE_ON_ONE}
                  />
                </RadioButtonGroup>

 Widget Activities

                <Checkbox
                  checked={this.state.activities.files}
                  label="Files"
                  onCheck={this.handleActivitiesChange}
                  value="files"
                />
                <Checkbox
                  checked={this.state.activities.meet}
                  label="Meet"
                  onCheck={this.handleActivitiesChange}
                  value="meet"
                />
                <Checkbox
                  checked={this.state.activities.message}
                  label="Message"
                  onCheck={this.handleActivitiesChange}
                  value="message"
                />
                <Checkbox
                  checked={this.state.activities.people}
                  label="People"
                  onCheck={this.handleActivitiesChange}
                  value="people"
                />

                {
                  this.state.mode === MODE_SPACE &&

                    <input
                      aria-label="To Space ID"
                      className={styles.textInput}
                      id="toSpaceId"
                      onChange={this.handleSpaceChange}
                      placeholder="Spark Space Id"
                      value={this.state.spaceId}
                    />

                }
                {
                  this.state.mode === MODE_ONE_ON_ONE &&

                    <input
                      aria-label="To User Email"
                      className={styles.textInput}
                      id="toUserEmail"
                      onChange={this.handleEmailChange}
                      placeholder="Spark User Email (For 1:1)"
                      value={this.state.toPersonEmail}
                    />

                }

            </CardText>
            <CardActions expandable>
              <RaisedButton
                aria-label="Open Widget"
                disabled={!loadButtonEnabled}
                id="openWidgetButton"
                label="Open Embedded Widget"
                onClick={this.handleSubmit}
                primary
              />
              <RaisedButton
                aria-label="Remove Widget"
                disabled={!this.state.running}
                id="removeWidgetButton"
                label="Remove Embedded Widget"
                onClick={this.handleRemove}
                primary
              />
            </CardActions>
          </Card>
          <Card initiallyExpanded style={{margin: '10px'}}>
            <CardTitle
              actAsExpander
              showExpandableButton
              subtitle="Cisco Spark Space Widget can be implemented multiple ways."
              title="Widget Example Code"
            />
            <CardText expandable>

                <Tabs>
                  <Tab label="Inline Mode">

                      <SyntaxHighlighter language="jsx" style={coy}>
                        {inlineCode}
                      </SyntaxHighlighter>

                  </Tab>
                  <Tab label="Global Mode">

                      <SyntaxHighlighter language="jsx" style={coy}>
                        {globalCode}
                      </SyntaxHighlighter>

                  </Tab>
                </Tabs>

                    <Toggle
                      label="Display Access Token"
                      labelPosition="right"
                      onToggle={this.handleDisplayAccessTokenChange}
                    />

            </CardText>
          </Card>

    );
  }
}

DemoWidgetSpace.propTypes = {
  cookies: instanceOf(Cookies).isRequired
};

DemoWidgetSpace.title = 'Widget Space';
DemoWidgetSpace.path = '/widget-space-demo';

export default withCookies(DemoWidgetSpace);
<|endoftext|>