<commit_before>//---------------------------------------------------------------------
//
// This source file is part of the Folktale project.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//---------------------------------------------------------------------

// --[ Dependencies ]--------------------------------------------------
const define = require('folktale/helpers/define');
const provideAliases = require('folktale/helpers/provide-fantasy-land-aliases');
const Deferred = require('./deferred');
const { Pending, Resolved, Rejected } = require('./_execution-state');

// --[ Implementation ]------------------------------------------------

/*~
 * Represents an eventual value, like Promise, but without a recursive
 * `.then`.
 */
class Future {
  constructor() {
    define(this, '_state', Pending());
    define(this, '_listeners', []);
  }

  // ---[ State and configuration ]------------------------------------
  /*~
   * The current state of the future.
   *
   * ---
   * isRequired: true
   * category: State and configuration
   * type: |
   *   get (Future 'f 's) => ExecutionState 'f 's
   */
  get _state() {
    throw new TypeError('Future.prototype._state should be implemented in an inherited object.');
  }

  /*~
   * A list of listeners to notify when the future is resolved.
   *
   * ---
   * isRequired: true
   * category: State and configuration
   * type: |
   *   get (Future 'f 's) => Array (DeferredListener 'f 's)
   */
  get _listeners() {
    throw new TypeError('Future.prototype._listeners should be implemented in an inherited object.');
  }

  // ---[ Reacting to Future events ]----------------------------------
  /*~
   * Attaches a listener to be invoked when the Future resolves.
   *
   * ---
   * category: Reacting to Future events
   * type: |
   *   (Future 'f 's).(DeferredListener 'f 's) => Void
   */
  listen(pattern) {
    this._state.matchWith({
      Pending:   ()           => this._listeners.push(pattern),
      Cancelled: ()           => pattern.onCancelled(),
      Resolved:  ({ value })  => pattern.onResolved(value),
      Rejected:  ({ reason }) => pattern.onRejected(reason)
    });
  }

  // --[ Transforming Futures ]----------------------------------------
  /*~
   * Transforms the value inside a future with a monadic function.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(('s) => Future 's2) => Future 'f 's2
   */
  chain(transformation) {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onRejected:  reason => deferred.reject(reason),
      onResolved:  value  => {
        transformation(value).listen({
          onCancelled: ()     => deferred.cancel(),
          onRejected:  reason => deferred.reject(reason),
          onResolved:  value2 => deferred.resolve(value2)
        });
      }
    });

    return deferred.future();
  }

  /*~
   * Transforms the value inside a future with a simple function.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(('s) => 's2) => Future 'f 's2
   */
  map(transformation) {
    return this.chain(value => Future.of(transformation(value)));
  }

  /*~
   * Transforms the value inside a future with a function contained in
   * another future.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(Future 'f (('s) => 's2)) => Future 'f 's2
   */
  apply(future) {
    return future.chain(fn => this.map(fn));
  }

  /*~
   * Transforms successes and failures in a future.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(('f) => 'f2, ('s) => 's2) => Future 'f2 's2
   */
  bimap(rejectionTransformation, successTransformation) {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onRejected:  reason => deferred.reject(rejectionTransformation(reason)),
      onResolved:  value  => deferred.resolve(successTransformation(value))
    });

    return deferred.future();
  }

  /*~
   * Transform the values of rejected futures.
   *
   * ---
   * category: Transforming values
   * type: |
   *   (Future 'f 's).(('f) => 'f2) => Future 'f2 's
   */
  mapRejection(transformation) {
    return this.bimap(transformation, x => x);
  }

  // ---[ Recovering from errors ]-------------------------------------
  /*~
   * Transforms a rejected future into a new future.
   *
   * ---
   * category: Recovering from errors
   * type: |
   *   (Future 'f 's).(('f) => Future 'f2 's2) => Future 'f2 's
   */
  recover(handler) {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onResolved:  value  => deferred.resolve(value),
      onRejected:  reason => {
        handler(reason).listen({
          onCancelled: ()        => deferred.cancel(),
          onResolved:  value     => deferred.resolve(value),
          onRejected:  newReason => deferred.reject(newReason)
        });
      }
    });

    return deferred.future();
  }

  willMatchWith(pattern) {
    let deferred = new Deferred();
    const resolve = (handler) => (value) => handler(value).listen({
      onCancelled: ()       => deferred.cancel(),
      onResolved:  (value)  => deferred.resolve(value),
      onRejected:  (reason) => deferred.reject(reason)
    });
    this.listen({
      onCancelled: resolve(pattern.Cancelled),
      onResolved:  resolve(pattern.Resolved),
      onRejected:  resolve(pattern.Rejected)
    });

    return deferred.future();
  }

  /*~
   * Transforms rejected futures in successes, and vice-versa.
   *
   * ---
   * category: Recovering from errors
   * type: |
   *   (Future 'f 's).() => Future 's 'f
   */
  swap() {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onRejected:  reason => deferred.resolve(reason),
      onResolved:  value  => deferred.reject(value)
    });

    return deferred.future();
  }

  // ---[ Debugging ]--------------------------------------------------
  /*~
   * Returns a textual representation of this object for debugging.
   *
   * ---
   * category: Debugging
   * type: |
   *   (Future 'f 's).() => String
   */
  toString() {
    const listeners = this._listeners.length;
    const state     = this._state;

    return `folktale:Future(${state}, ${listeners} listeners)`;
  }

  /*~
   * Returns a textual representation of this object for Node's REPL.
   *
   * ---
   * category: Debugging
   * type: |
   *   (Future 'f 's).() => String
   */
  inspect() {
    return this.toString();
  }
}

// ---[ Constructing futures ]-----------------------------------------
Object.assign(Future, {
  of(value) {
    let result = new Future();
    result._state = Resolved(value);
    return result;
  },

  /*~
   * Constructs a future containing a single rejected value.
   *
   * ---
   * category: Constructing futures
   * type: |
   *   (Future).('f) => Future 'f 's
   */
  rejected(reason) {
    let result = new Future();
    result._state = Rejected(reason);
    return result;
  }
});

provideAliases(Future);
provideAliases(Future.prototype);

// --[ Exports ]-------------------------------------------------------
module.exports = Future;
<commit_msg>(fix) Fixes Future.apply's incorrect type<commit_after>//---------------------------------------------------------------------
//
// This source file is part of the Folktale project.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//---------------------------------------------------------------------

// --[ Dependencies ]--------------------------------------------------
const define = require('folktale/helpers/define');
const provideAliases = require('folktale/helpers/provide-fantasy-land-aliases');
const Deferred = require('./deferred');
const { Pending, Resolved, Rejected } = require('./_execution-state');

// --[ Implementation ]------------------------------------------------

/*~
 * Represents an eventual value, like Promise, but without a recursive
 * `.then`.
 */
class Future {
  constructor() {
    define(this, '_state', Pending());
    define(this, '_listeners', []);
  }

  // ---[ State and configuration ]------------------------------------
  /*~
   * The current state of the future.
   *
   * ---
   * isRequired: true
   * category: State and configuration
   * type: |
   *   get (Future 'f 's) => ExecutionState 'f 's
   */
  get _state() {
    throw new TypeError('Future.prototype._state should be implemented in an inherited object.');
  }

  /*~
   * A list of listeners to notify when the future is resolved.
   *
   * ---
   * isRequired: true
   * category: State and configuration
   * type: |
   *   get (Future 'f 's) => Array (DeferredListener 'f 's)
   */
  get _listeners() {
    throw new TypeError('Future.prototype._listeners should be implemented in an inherited object.');
  }

  // ---[ Reacting to Future events ]----------------------------------
  /*~
   * Attaches a listener to be invoked when the Future resolves.
   *
   * ---
   * category: Reacting to Future events
   * type: |
   *   (Future 'f 's).(DeferredListener 'f 's) => Void
   */
  listen(pattern) {
    this._state.matchWith({
      Pending:   ()           => this._listeners.push(pattern),
      Cancelled: ()           => pattern.onCancelled(),
      Resolved:  ({ value })  => pattern.onResolved(value),
      Rejected:  ({ reason }) => pattern.onRejected(reason)
    });
  }

  // --[ Transforming Futures ]----------------------------------------
  /*~
   * Transforms the value inside a future with a monadic function.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(('s) => Future 's2) => Future 'f 's2
   */
  chain(transformation) {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onRejected:  reason => deferred.reject(reason),
      onResolved:  value  => {
        transformation(value).listen({
          onCancelled: ()     => deferred.cancel(),
          onRejected:  reason => deferred.reject(reason),
          onResolved:  value2 => deferred.resolve(value2)
        });
      }
    });

    return deferred.future();
  }

  /*~
   * Transforms the value inside a future with a simple function.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(('s) => 's2) => Future 'f 's2
   */
  map(transformation) {
    return this.chain(value => Future.of(transformation(value)));
  }

  /*~
   * Transforms the value inside a future with a function contained in
   * another future.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(Future 'f (('s) => 's2)) => Future 'f 's2
   */
  apply(future) {
    return this.chain(fn => future.map(fn));
  }

  /*~
   * Transforms successes and failures in a future.
   *
   * ---
   * category: Transforming futures
   * type: |
   *   (Future 'f 's).(('f) => 'f2, ('s) => 's2) => Future 'f2 's2
   */
  bimap(rejectionTransformation, successTransformation) {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onRejected:  reason => deferred.reject(rejectionTransformation(reason)),
      onResolved:  value  => deferred.resolve(successTransformation(value))
    });

    return deferred.future();
  }

  /*~
   * Transform the values of rejected futures.
   *
   * ---
   * category: Transforming values
   * type: |
   *   (Future 'f 's).(('f) => 'f2) => Future 'f2 's
   */
  mapRejection(transformation) {
    return this.bimap(transformation, x => x);
  }

  // ---[ Recovering from errors ]-------------------------------------
  /*~
   * Transforms a rejected future into a new future.
   *
   * ---
   * category: Recovering from errors
   * type: |
   *   (Future 'f 's).(('f) => Future 'f2 's2) => Future 'f2 's
   */
  recover(handler) {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onResolved:  value  => deferred.resolve(value),
      onRejected:  reason => {
        handler(reason).listen({
          onCancelled: ()        => deferred.cancel(),
          onResolved:  value     => deferred.resolve(value),
          onRejected:  newReason => deferred.reject(newReason)
        });
      }
    });

    return deferred.future();
  }

  willMatchWith(pattern) {
    let deferred = new Deferred();
    const resolve = (handler) => (value) => handler(value).listen({
      onCancelled: ()       => deferred.cancel(),
      onResolved:  (value)  => deferred.resolve(value),
      onRejected:  (reason) => deferred.reject(reason)
    });
    this.listen({
      onCancelled: resolve(pattern.Cancelled),
      onResolved:  resolve(pattern.Resolved),
      onRejected:  resolve(pattern.Rejected)
    });

    return deferred.future();
  }

  /*~
   * Transforms rejected futures in successes, and vice-versa.
   *
   * ---
   * category: Recovering from errors
   * type: |
   *   (Future 'f 's).() => Future 's 'f
   */
  swap() {
    let deferred = new Deferred();
    this.listen({
      onCancelled: ()     => deferred.cancel(),
      onRejected:  reason => deferred.resolve(reason),
      onResolved:  value  => deferred.reject(value)
    });

    return deferred.future();
  }

  // ---[ Debugging ]--------------------------------------------------
  /*~
   * Returns a textual representation of this object for debugging.
   *
   * ---
   * category: Debugging
   * type: |
   *   (Future 'f 's).() => String
   */
  toString() {
    const listeners = this._listeners.length;
    const state     = this._state;

    return `folktale:Future(${state}, ${listeners} listeners)`;
  }

  /*~
   * Returns a textual representation of this object for Node's REPL.
   *
   * ---
   * category: Debugging
   * type: |
   *   (Future 'f 's).() => String
   */
  inspect() {
    return this.toString();
  }
}

// ---[ Constructing futures ]-----------------------------------------
Object.assign(Future, {
  of(value) {
    let result = new Future();
    result._state = Resolved(value);
    return result;
  },

  /*~
   * Constructs a future containing a single rejected value.
   *
   * ---
   * category: Constructing futures
   * type: |
   *   (Future).('f) => Future 'f 's
   */
  rejected(reason) {
    let result = new Future();
    result._state = Rejected(reason);
    return result;
  }
});

provideAliases(Future);
provideAliases(Future.prototype);

// --[ Exports ]-------------------------------------------------------
module.exports = Future;
<|endoftext|>


<commit_before>var aws = require('aws-sdk');
var _ = require('underscore');
var util = require('util');
var SimpleFileWriter = require('simple-file-writer');
var log4js = require('log4js');
var path = require('path');
var fs = require('fs');
var events = require('events');

var EventEmitter = events.EventEmitter;

var logger = log4js.getLogger('redshift-bulk-insert');

module.exports = RedshiftBulkInsert;

var DELIMITER = '|';
var ESCAPE = '\\';
var NEWLINE = '\n';
var NULL = '\\N';
var SUFFIX = '.log';

util.inherits(RedshiftBulkInsert, EventEmitter);

function RedshiftBulkInsert(options) {

	var pathToLogs = options.pathToLogs;

	this._datastore = options.datastore;
	this._tableName = options.tableName;
	this._fields = options.fields;
	this._pathToLogs = pathToLogs;

	var idleFlushPeriod = options.idleFlushPeriod;
	this._idleFlushPeriod = _.isNumber(idleFlushPeriod) ? idleFlushPeriod : 10000;

	var threshold = options.threshold;
	this._threshold = _.isNumber(threshold) ? threshold : 1000;

	this._awsBucketName = options.awsBucketName;
	this._awsAccessKeyId = options.awsAccessKeyId;
	this._awsSecretAccessKey = options.awsSecretAccessKey;
	this._awsrRegion = options.awsRegion;

	this._numberOfEventsInFile = 0;
	this._activeFlushOps = 0;

	var fileName = this._getLogFileName();
	var pathToFile = path.join(pathToLogs, fileName);

	this._fileName = fileName;
	this._file = new SimpleFileWriter(pathToFile);
	this._s3 = this._createS3();

	this.startIdleFlushMonitor();
}

RedshiftBulkInsert.prototype._getLogFileName = function() {
	return this._tableName + '_' + Date.now() + SUFFIX;
}

RedshiftBulkInsert.prototype._createS3 = function() {
	var options = {
		region: this._awsRegion,
		accessKeyId: this._awsAccessKeyId,
		secretAccessKey: this._awsSecretAccessKey
	};
	return new aws.S3(options);
}

RedshiftBulkInsert.prototype.flush = function() {

	if (this._numberOfEventsInFile > 0) {

		this.activeFlushOps++;

		var oldFileName = this._fileName;
		var pathToLogs = this._pathToLogs;

		var newFileName = this._getLogFileName();
		this._fileName = newFileName;
		var newPathToFile = path.join(pathToLogs, newFileName);
		this._file.setupFile(newPathToFile);

		var sendToS3 = _.bind(this._sendToS3, this, oldFileName, Date.now());
		var oldPathToFile = path.join(pathToLogs, oldFileName);
		fs.readFile(oldPathToFile, sendToS3);

		this._numberOfEventsInFile = 0;
	}

	this.startIdleFlushMonitor();
};

RedshiftBulkInsert.prototype._sendToS3 = function(fileName, start, err, body) {
	if (err) {
		logger.error(err);
		return;
	}
	var params = {
		Body: body,
		Key: fileName,
		Bucket: this._awsBucketName
	};
	var onSentToS3 = _.bind(this._onSentToS3, this, fileName, start);
	this._s3.putObject(params, onSentToS3);
};

RedshiftBulkInsert.prototype._onSentToS3 = function(fileName, start, err, data) {
	if (err) {
		logger.error(err);
		return;
	}
	var onSentCopyToRedshift = _.bind(this._onSentCopyToRedshift, this, fileName, start);
	var query = this._getCopyQuery(fileName);
	this._datastore.query(query, onSentCopyToRedshift);
};

RedshiftBulkInsert.prototype._getCopyQuery = function(fileName) {
	return 'COPY '
		+ this._tableName
		+ ' ('
		+ this._fields.join(', ')
		+ ')'
		+ ' FROM '
		+ "'"
		+ 's3://'
		+ this._awsBucketName
		+ '/'
		+ fileName
		+ "'"
		+ ' CREDENTIALS '
		+ "'aws_access_key_id="
		+ this._awsAccessKeyId
		+ ';'
		+ 'aws_secret_access_key='
		+ this._awsSecretAccessKey
		+ "' ";
		// + ' DELIMITER '
		// + "'"
		// + DELIMITER
		// + "'"
		// + ' NULL AS '
		// + "'"
		// + NULL
		// + "'"
		// + ' ESCAPE '
		// + "'"
		// + ESCAPE
		// + "'";
};

RedshiftBulkInsert.prototype._onSentCopyToRedshift = function(fileName, start, err, result) {
	if (err) {
		logger.error(err);
	}

	var pathToFile = path.join(this._pathToLogs, fileName);
	fs.unlink(pathToFile, _.bind(this._onLogFileRemoved, this, start, err, result));
};

RedshiftBulkInsert.prototype._onLogFileRemoved = function(start, err, result) {
	this.activeFlushOps--;
	this.emit('flush', err, result, '', Date.now() - start, this);
};

RedshiftBulkInsert.prototype.insert = function(row) {

	var line = this._rowToLine(row);
	this._file.write(line + NEWLINE);
	this._numberOfEventsInFile++;

	if (this._numberOfEventsInFile > this._threshold) {
		clearTimeout(this.ref);
		this.flush();
		return;
	}
};

RedshiftBulkInsert.prototype._rowToLine = function(row) {
	var lineBuff = [];
	for (var i = 0, l = row.length; i < l; i++) {
		var value = row[i];
		value = this._escapeValue(value);
		lineBuff.push(value);
	}
	var line = lineBuff.join(DELIMITER);
	return line;
};

RedshiftBulkInsert.prototype._escapeValue = function(value) {
	if (value === null || value === undefined) {
		return NULL;
	}
	if (_.isString(value)) {
		return value
			.replace(ESCAPE, ESCAPE + ESCAPE)
			.replace(DELIMITER, ESCAPE + DELIMITER);
	}
	return value + '';
};

RedshiftBulkInsert.prototype.close = function() {
	clearTimeout(this.ref);
};

RedshiftBulkInsert.prototype.startIdleFlushMonitor = function() {
	logger.info('startIdleFlushMonitor');

	var flush = _.bind(this.flush, this);
	this.ref = setTimeout(flush, this._idleFlushPeriod);
};<commit_msg>Remove verbosity<commit_after>var aws = require('aws-sdk');
var _ = require('underscore');
var util = require('util');
var SimpleFileWriter = require('simple-file-writer');
var log4js = require('log4js');
var path = require('path');
var fs = require('fs');
var events = require('events');

var EventEmitter = events.EventEmitter;

var logger = log4js.getLogger('redshift-bulk-insert');

module.exports = RedshiftBulkInsert;

var DELIMITER = '|';
var ESCAPE = '\\';
var NEWLINE = '\n';
var NULL = '\\N';
var SUFFIX = '.log';

util.inherits(RedshiftBulkInsert, EventEmitter);

function RedshiftBulkInsert(options) {

	var pathToLogs = options.pathToLogs;

	this._datastore = options.datastore;
	this._tableName = options.tableName;
	this._fields = options.fields;
	this._pathToLogs = pathToLogs;

	var idleFlushPeriod = options.idleFlushPeriod;
	this._idleFlushPeriod = _.isNumber(idleFlushPeriod) ? idleFlushPeriod : 10000;

	var threshold = options.threshold;
	this._threshold = _.isNumber(threshold) ? threshold : 1000;

	this._awsBucketName = options.awsBucketName;
	this._awsAccessKeyId = options.awsAccessKeyId;
	this._awsSecretAccessKey = options.awsSecretAccessKey;
	this._awsrRegion = options.awsRegion;

	this._numberOfEventsInFile = 0;
	this._activeFlushOps = 0;

	var fileName = this._getLogFileName();
	var pathToFile = path.join(pathToLogs, fileName);

	this._fileName = fileName;
	this._file = new SimpleFileWriter(pathToFile);
	this._s3 = this._createS3();

	this.startIdleFlushMonitor();
}

RedshiftBulkInsert.prototype._getLogFileName = function() {
	return this._tableName + '_' + Date.now() + SUFFIX;
}

RedshiftBulkInsert.prototype._createS3 = function() {
	var options = {
		region: this._awsRegion,
		accessKeyId: this._awsAccessKeyId,
		secretAccessKey: this._awsSecretAccessKey
	};
	return new aws.S3(options);
}

RedshiftBulkInsert.prototype.flush = function() {

	if (this._numberOfEventsInFile > 0) {

		this.activeFlushOps++;

		var oldFileName = this._fileName;
		var pathToLogs = this._pathToLogs;

		var newFileName = this._getLogFileName();
		this._fileName = newFileName;
		var newPathToFile = path.join(pathToLogs, newFileName);
		this._file.setupFile(newPathToFile);

		var sendToS3 = _.bind(this._sendToS3, this, oldFileName, Date.now());
		var oldPathToFile = path.join(pathToLogs, oldFileName);
		fs.readFile(oldPathToFile, sendToS3);

		this._numberOfEventsInFile = 0;
	}

	this.startIdleFlushMonitor();
};

RedshiftBulkInsert.prototype._sendToS3 = function(fileName, start, err, body) {
	if (err) {
		logger.error(err);
		return;
	}
	var params = {
		Body: body,
		Key: fileName,
		Bucket: this._awsBucketName
	};
	var onSentToS3 = _.bind(this._onSentToS3, this, fileName, start);
	this._s3.putObject(params, onSentToS3);
};

RedshiftBulkInsert.prototype._onSentToS3 = function(fileName, start, err, data) {
	if (err) {
		logger.error(err);
		return;
	}
	var onSentCopyToRedshift = _.bind(this._onSentCopyToRedshift, this, fileName, start);
	var query = this._getCopyQuery(fileName);
	this._datastore.query(query, onSentCopyToRedshift);
};

RedshiftBulkInsert.prototype._getCopyQuery = function(fileName) {
	return 'COPY '
		+ this._tableName
		+ ' ('
		+ this._fields.join(', ')
		+ ')'
		+ ' FROM '
		+ "'"
		+ 's3://'
		+ this._awsBucketName
		+ '/'
		+ fileName
		+ "'"
		+ ' CREDENTIALS '
		+ "'aws_access_key_id="
		+ this._awsAccessKeyId
		+ ';'
		+ 'aws_secret_access_key='
		+ this._awsSecretAccessKey
		+ "' ";
		// + ' DELIMITER '
		// + "'"
		// + DELIMITER
		// + "'"
		// + ' NULL AS '
		// + "'"
		// + NULL
		// + "'"
		// + ' ESCAPE '
		// + "'"
		// + ESCAPE
		// + "'";
};

RedshiftBulkInsert.prototype._onSentCopyToRedshift = function(fileName, start, err, result) {
	if (err) {
		logger.error(err);
	}
	var pathToFile = path.join(this._pathToLogs, fileName);
	var onLogFileRemoved = _.bind(this._onLogFileRemoved, this, start, err, result);
	fs.unlink(pathToFile, onLogFileRemoved);
};

RedshiftBulkInsert.prototype._onLogFileRemoved = function(start, err, result) {
	this.activeFlushOps--;
	this.emit('flush', err, result, '', Date.now() - start, this);
};

RedshiftBulkInsert.prototype.insert = function(row) {

	var line = this._rowToLine(row);
	this._file.write(line + NEWLINE);
	this._numberOfEventsInFile++;

	if (this._numberOfEventsInFile > this._threshold) {
		clearTimeout(this.ref);
		this.flush();
		return;
	}
};

RedshiftBulkInsert.prototype._rowToLine = function(row) {
	var lineBuff = [];
	for (var i = 0, l = row.length; i < l; i++) {
		var value = row[i];
		value = this._escapeValue(value);
		lineBuff.push(value);
	}
	var line = lineBuff.join(DELIMITER);
	return line;
};

RedshiftBulkInsert.prototype._escapeValue = function(value) {
	if (value === null || value === undefined) {
		return NULL;
	}
	if (_.isString(value)) {
		return value
			.replace(ESCAPE, ESCAPE + ESCAPE)
			.replace(DELIMITER, ESCAPE + DELIMITER);
	}
	return value + '';
};

RedshiftBulkInsert.prototype.close = function() {
	clearTimeout(this.ref);
};

RedshiftBulkInsert.prototype.startIdleFlushMonitor = function() {
	var flush = _.bind(this.flush, this);
	this.ref = setTimeout(flush, this._idleFlushPeriod);
};<|endoftext|>


<commit_before>
var ReportModule = (function () {

    function Config(dict) {
        var self = this;

        dict = dict || {};

        self.keyValuePairs = ko.observableArray();
        for (var key in dict) {
            self.keyValuePairs.push([ko.observable(key), ko.observable(dict[key])]);
        }

        self.addConfig = function() {
            self.keyValuePairs.push([ko.observable(), ko.observable()]);
        };
    }

    function GraphConfig(report_id, reportId, availableReportIds, reportCharts, graph_configs) {
        var self = this;

        graph_configs = graph_configs || {};

        this.graphConfigs = {};
        for (var i = 0; i < availableReportIds.length; i++) {
            var currentReportId = availableReportIds[i];
            self.graphConfigs[currentReportId] = {};
            for (var j = 0; j < reportCharts[currentReportId].length; j++) {
                var currentChart = reportCharts[currentReportId][j];
                var graph_config = graph_configs[currentChart.chart_id] || {};
                var series_configs = {};
                var chart_series = [];
                for(var k = 0; k < currentChart.y_axis_columns.length; k++) {
                    var series = currentChart.y_axis_columns[k];
                    chart_series.push(series);
                    series_configs[series] = new Config(
                        currentReportId == report_id ? (graph_config.series_configs || {})[series] || {} : {}
                    );
                }

                self.graphConfigs[currentReportId][currentChart.chart_id] = {
                    graph_type: ko.observable(currentReportId == report_id ? graph_config.graph_type || 'bar' : 'bar'),
                    series_configs: series_configs,
                    chart_series: chart_series,
                    config: new Config(
                        currentReportId == report_id ? graph_config.config || {} : {}
                    )
                };
            }
        }

        this.currentGraphConfigs = ko.computed(function() {
            return self.graphConfigs[reportId()];
        });

        this.currentCharts = ko.computed(function() {
            return reportCharts[reportId()];
        });

        this.getCurrentGraphConfig = function(chart_id) {
            return self.currentGraphConfigs()[chart_id] || {config: {keyValuePairs: []}};
        };

        this.toJSON = function () {
            function configToDict(config) {
                var dict = {};
                var keyValuePairs = config.keyValuePairs();
                for (var i = 0; i < keyValuePairs.length; i++) {
                    dict[keyValuePairs[i][0]()] = keyValuePairs[i][1]();
                }
                return dict;
            }

            var chartsToConfigs = {};
            var currentChartsToConfigs = self.currentGraphConfigs();
            _.each(currentChartsToConfigs, function(graph_config, chart_id) {
                chartsToConfigs[chart_id] = {
                    series_configs: {}
                };
                for (var series in graph_config.series_configs) {
                    chartsToConfigs[chart_id].series_configs[series] = configToDict(graph_config.series_configs[series]);
                }
                chartsToConfigs[chart_id].graph_type = graph_config.graph_type();
                chartsToConfigs[chart_id].config = configToDict(graph_config.config);
            });
            return chartsToConfigs;
        };

        this.allGraphTypes = ['bar', 'time', 'xy'];
    }

    function FilterConfig(report_id, reportId, filterValues, reportFilters, changeSaveButton) {
        var self = this;

        this.reportFilters = ko.observable(JSON.parse(JSON.stringify(reportFilters)) || {});
        for (var _id in this.reportFilters()) {
            for (var i = 0; i < this.reportFilters()[_id].length; i++) {
                var filter = this.reportFilters()[_id][i];
                if (_id == report_id && filterValues.hasOwnProperty(filter.slug)) {
                    filter.selectedValue = filterValues[filter.slug];
                    filter.selectedValue.doc_type = ko.observable(filter.selectedValue.doc_type);
                } else {
                    filter.selectedValue = {
                        doc_type: ko.observable(null)
                    };
                }
                filter.selectedValue.filter_type = ko.observable(filter.selectedValue.filter_type || '');
                filter.selectedValue.date_range = ko.observable(filter.selectedValue.date_range || '');
                filter.selectedValue.custom_data_property = ko.observable(filter.selectedValue.custom_data_property || '');
                filter.selectedValue.value = ko.observable(filter.selectedValue.value ? filter.selectedValue.value.join("\u001F") : '');
                filter.selectedValue.select_value = ko.observable(filter.selectedValue.select_value || '');

                filter.dynamicFilterName = ko.computed(function () {
                    return reportId() + '/' + filter.slug;
                });

                if(filter.choices != undefined && filter.show_all) {
                    filter.choices.unshift({value: "_all", display: "Show All"}); // TODO: translate
                }
            }
        }

        this.selectedFilterStructure = ko.computed(function () { // for the chosen report
            return self.reportFilters[reportId()];
        });

        this.toJSON = function () {
            var selectedFilterStructure = self.selectedFilterStructure();
            var selectedFilterValues = {};
            for (var i = 0; i < selectedFilterStructure.length; i++) {
                var filter = selectedFilterStructure[i];
                if (filter.selectedValue.doc_type()) {
                    selectedFilterValues[filter.slug] = {};
                    selectedFilterValues[filter.slug]['doc_type'] = filter.selectedValue.doc_type();
                    // Depending on doc_type, pull the correct observables' values
                    if (filter.selectedValue.doc_type() == 'AutoFilter') {
                        selectedFilterValues[filter.slug]['filter_type'] = filter.selectedValue.filter_type();
                    } else if(filter.selectedValue.doc_type() == 'StaticDatespanFilter') {
                        selectedFilterValues[filter.slug]['date_range'] = filter.selectedValue.date_range();
                    } else if(filter.selectedValue.doc_type() == 'CustomDataAutoFilter') {
                        selectedFilterValues[filter.slug]['custom_data_property'] = filter.selectedValue.custom_data_property();
                    } else if (filter.selectedValue.doc_type() == 'StaticChoiceListFilter') {
                        selectedFilterValues[filter.slug]['value'] = filter.selectedValue.value().split("\u001F");
                    } else if(filter.selectedValue.doc_type() == 'StaticChoiceFilter') {
                        selectedFilterValues[filter.slug]['select_value'] = filter.selectedValue.select_value();
                    }
                }
            }
            return selectedFilterValues;
        }

        this.addSubscribersToSaveButton = function() {
            for(var _id in this.reportFilters) {
                for (var i = 0; i < this.reportFilters[_id].length; i++) {
                    var filter = this.reportFilters[_id][i];
                    for (var key in filter.selectedValue) {
                        filter.selectedValue[key].subscribe(changeSaveButton);
                    }
                }
            }
        };

        // TODO - add user-friendly text
        this.filterDocTypes = [null, 'AutoFilter', 'StaticDatespanFilter', 'CustomDataAutoFilter', 'StaticChoiceListFilter', 'StaticChoiceFilter'];
        this.autoFilterTypes = ['case_sharing_group', 'location_id', 'username', 'user_id']
        this.date_range_options = ['last7', 'last30', 'lastmonth'];
    }

    function ReportConfig(report_id, display, availableReportIds,
                          reportCharts, graph_configs,
                          filterValues, reportFilters,
                          language, changeSaveButton) {
        var self = this;
        this.lang = language;
        this.fullDisplay = display || {};
        this.availableReportIds = availableReportIds;
        this.display = ko.observable(this.fullDisplay[this.lang]);
        this.reportId = ko.observable(report_id);
        this.graphConfig = new GraphConfig(report_id, this.reportId, availableReportIds, reportCharts, graph_configs);
        this.filterConfig = new FilterConfig(report_id, this.reportId, filterValues, reportFilters, changeSaveButton);

        this.toJSON = function () {
            self.fullDisplay[self.lang] = self.display();
            return {
                report_id: self.reportId(),
                graph_configs: self.graphConfig.toJSON(),
                filters: self.filterConfig.toJSON(),
                header: self.fullDisplay
            };
        };
    }

    function ReportModule(options) {
        var self = this;
        var currentReports = options.currentReports || [];
        var availableReports = options.availableReports || [];
        var saveURL = options.saveURL;
        self.lang = options.lang;
        self.moduleName = options.moduleName;
        self.currentModuleName = ko.observable(options.moduleName[self.lang]);
        self.reportTitles = {};
        self.reportCharts = {};
        self.reportFilters = {};
        self.reports = ko.observableArray([]);
        for (var i = 0; i < availableReports.length; i++) {
            var report = availableReports[i];
            var report_id = report.report_id;
            self.reportTitles[report_id] = report.title;
            self.reportCharts[report_id] = report.charts;
            self.reportFilters[report_id] = report.filter_structure;
        }

        self.availableReportIds = _.map(options.availableReports, function (r) { return r.report_id; });

        self.defaultReportTitle = function (reportId) {
            return self.reportTitles[reportId];
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved changes in your report list module",
            save: function () {
                // validate that all reports have valid data
                var reports = self.reports();
                for (var i = 0; i < reports.length; i++) {
                    if (!reports[i].reportId() || !reports[i].display()) {
                        alert('Reports must have all properties set!');
                    }
                }
                self.moduleName[self.lang] = self.currentModuleName();
                self.saveButton.ajax({
                    url: saveURL,
                    type: 'post',
                    dataType: 'json',
                    data: {
                        name: JSON.stringify(self.moduleName),
                        reports: JSON.stringify(_.map(self.reports(), function (r) { return r.toJSON(); }))
                    }
                });
            }
        });

        var changeSaveButton = function () {
            self.saveButton.fire('change');
        };

        self.currentModuleName.subscribe(changeSaveButton);

        function newReport(options) {
            options = options || {};
            var report = new ReportConfig(
                options.report_id,
                options.header,
                self.availableReportIds,
                self.reportCharts,
                options.graph_configs,
                options.filters,
                self.reportFilters,
                self.lang,
                changeSaveButton
            );
            report.display.subscribe(changeSaveButton);
            report.reportId.subscribe(changeSaveButton);
            report.reportId.subscribe(function (reportId) {
                report.display(self.defaultReportTitle(reportId));
            });

            return report;
        }
        this.addReport = function () {
            self.reports.push(newReport());
        };
        this.removeReport = function (report) {
            self.reports.remove(report);
            changeSaveButton();
        };

        // add existing reports to UI
        for (i = 0; i < currentReports.length; i += 1) {
            var report = newReport(currentReports[i]);
            self.reports.push(report);
        }
    }

    return ReportModule;
}());
<commit_msg>js lint<commit_after>
var ReportModule = (function () {

    function Config(dict) {
        var self = this;

        dict = dict || {};

        self.keyValuePairs = ko.observableArray();
        for (var key in dict) {
            self.keyValuePairs.push([ko.observable(key), ko.observable(dict[key])]);
        }

        self.addConfig = function() {
            self.keyValuePairs.push([ko.observable(), ko.observable()]);
        };
    }

    function GraphConfig(report_id, reportId, availableReportIds, reportCharts, graph_configs) {
        var self = this;

        graph_configs = graph_configs || {};

        this.graphConfigs = {};
        for (var i = 0; i < availableReportIds.length; i++) {
            var currentReportId = availableReportIds[i];
            self.graphConfigs[currentReportId] = {};
            for (var j = 0; j < reportCharts[currentReportId].length; j++) {
                var currentChart = reportCharts[currentReportId][j];
                var graph_config = graph_configs[currentChart.chart_id] || {};
                var series_configs = {};
                var chart_series = [];
                for(var k = 0; k < currentChart.y_axis_columns.length; k++) {
                    var series = currentChart.y_axis_columns[k];
                    chart_series.push(series);
                    series_configs[series] = new Config(
                        currentReportId == report_id ? (graph_config.series_configs || {})[series] || {} : {}
                    );
                }

                self.graphConfigs[currentReportId][currentChart.chart_id] = {
                    graph_type: ko.observable(currentReportId == report_id ? graph_config.graph_type || 'bar' : 'bar'),
                    series_configs: series_configs,
                    chart_series: chart_series,
                    config: new Config(
                        currentReportId == report_id ? graph_config.config || {} : {}
                    )
                };
            }
        }

        this.currentGraphConfigs = ko.computed(function() {
            return self.graphConfigs[reportId()];
        });

        this.currentCharts = ko.computed(function() {
            return reportCharts[reportId()];
        });

        this.getCurrentGraphConfig = function(chart_id) {
            return self.currentGraphConfigs()[chart_id] || {config: {keyValuePairs: []}};
        };

        this.toJSON = function () {
            function configToDict(config) {
                var dict = {};
                var keyValuePairs = config.keyValuePairs();
                for (var i = 0; i < keyValuePairs.length; i++) {
                    dict[keyValuePairs[i][0]()] = keyValuePairs[i][1]();
                }
                return dict;
            }

            var chartsToConfigs = {};
            var currentChartsToConfigs = self.currentGraphConfigs();
            _.each(currentChartsToConfigs, function(graph_config, chart_id) {
                chartsToConfigs[chart_id] = {
                    series_configs: {}
                };
                for (var series in graph_config.series_configs) {
                    chartsToConfigs[chart_id].series_configs[series] = configToDict(graph_config.series_configs[series]);
                }
                chartsToConfigs[chart_id].graph_type = graph_config.graph_type();
                chartsToConfigs[chart_id].config = configToDict(graph_config.config);
            });
            return chartsToConfigs;
        };

        this.allGraphTypes = ['bar', 'time', 'xy'];
    }

    function FilterConfig(report_id, reportId, filterValues, reportFilters, changeSaveButton) {
        var self = this;

        this.reportFilters = ko.observable(JSON.parse(JSON.stringify(reportFilters)) || {});
        for (var _id in this.reportFilters()) {
            for (var i = 0; i < this.reportFilters()[_id].length; i++) {
                var filter = this.reportFilters()[_id][i];
                if (_id == report_id && filterValues.hasOwnProperty(filter.slug)) {
                    filter.selectedValue = filterValues[filter.slug];
                    filter.selectedValue.doc_type = ko.observable(filter.selectedValue.doc_type);
                } else {
                    filter.selectedValue = {
                        doc_type: ko.observable(null)
                    };
                }
                filter.selectedValue.filter_type = ko.observable(filter.selectedValue.filter_type || '');
                filter.selectedValue.date_range = ko.observable(filter.selectedValue.date_range || '');
                filter.selectedValue.custom_data_property = ko.observable(filter.selectedValue.custom_data_property || '');
                filter.selectedValue.value = ko.observable(filter.selectedValue.value ? filter.selectedValue.value.join("\u001F") : '');
                filter.selectedValue.select_value = ko.observable(filter.selectedValue.select_value || '');

                filter.dynamicFilterName = ko.computed(function () {
                    return reportId() + '/' + filter.slug;
                });

                if(filter.choices !== undefined && filter.show_all) {
                    filter.choices.unshift({value: "_all", display: "Show All"}); // TODO: translate
                }
            }
        }

        this.selectedFilterStructure = ko.computed(function () { // for the chosen report
            return self.reportFilters[reportId()];
        });

        this.toJSON = function () {
            var selectedFilterStructure = self.selectedFilterStructure();
            var selectedFilterValues = {};
            for (var i = 0; i < selectedFilterStructure.length; i++) {
                var filter = selectedFilterStructure[i];
                if (filter.selectedValue.doc_type()) {
                    selectedFilterValues[filter.slug] = {};
                    selectedFilterValues[filter.slug].doc_type = filter.selectedValue.doc_type();
                    // Depending on doc_type, pull the correct observables' values
                    if(filter.selectedValue.doc_type() == 'AutoFilter') {
                        selectedFilterValues[filter.slug].filter_type = filter.selectedValue.filter_type();
                    } else if(filter.selectedValue.doc_type() == 'StaticDatespanFilter') {
                        selectedFilterValues[filter.slug].date_range = filter.selectedValue.date_range();
                    } else if(filter.selectedValue.doc_type() == 'CustomDataAutoFilter') {
                        selectedFilterValues[filter.slug].custom_data_property = filter.selectedValue.custom_data_property();
                    } else if(filter.selectedValue.doc_type() == 'StaticChoiceListFilter') {
                        selectedFilterValues[filter.slug].value = filter.selectedValue.value().split("\u001F");
                    } else if(filter.selectedValue.doc_type() == 'StaticChoiceFilter') {
                        selectedFilterValues[filter.slug].select_value = filter.selectedValue.select_value();
                    }
                }
            }
            return selectedFilterValues;
        }

        this.addSubscribersToSaveButton = function() {
            for(var _id in this.reportFilters) {
                for (var i = 0; i < this.reportFilters[_id].length; i++) {
                    var filter = this.reportFilters[_id][i];
                    for (var key in filter.selectedValue) {
                        filter.selectedValue[key].subscribe(changeSaveButton);
                    }
                }
            }
        };

        // TODO - add user-friendly text
        this.filterDocTypes = [null, 'AutoFilter', 'StaticDatespanFilter', 'CustomDataAutoFilter', 'StaticChoiceListFilter', 'StaticChoiceFilter'];
        this.autoFilterTypes = ['case_sharing_group', 'location_id', 'username', 'user_id'];
        this.date_range_options = ['last7', 'last30', 'lastmonth'];
    }

    function ReportConfig(report_id, display, availableReportIds,
                          reportCharts, graph_configs,
                          filterValues, reportFilters,
                          language, changeSaveButton) {
        var self = this;
        this.lang = language;
        this.fullDisplay = display || {};
        this.availableReportIds = availableReportIds;
        this.display = ko.observable(this.fullDisplay[this.lang]);
        this.reportId = ko.observable(report_id);
        this.graphConfig = new GraphConfig(report_id, this.reportId, availableReportIds, reportCharts, graph_configs);
        this.filterConfig = new FilterConfig(report_id, this.reportId, filterValues, reportFilters, changeSaveButton);

        this.toJSON = function () {
            self.fullDisplay[self.lang] = self.display();
            return {
                report_id: self.reportId(),
                graph_configs: self.graphConfig.toJSON(),
                filters: self.filterConfig.toJSON(),
                header: self.fullDisplay
            };
        };
    }

    function ReportModule(options) {
        var self = this;
        var currentReports = options.currentReports || [];
        var availableReports = options.availableReports || [];
        var saveURL = options.saveURL;
        self.lang = options.lang;
        self.moduleName = options.moduleName;
        self.currentModuleName = ko.observable(options.moduleName[self.lang]);
        self.reportTitles = {};
        self.reportCharts = {};
        self.reportFilters = {};
        self.reports = ko.observableArray([]);
        for (var i = 0; i < availableReports.length; i++) {
            var report = availableReports[i];
            var report_id = report.report_id;
            self.reportTitles[report_id] = report.title;
            self.reportCharts[report_id] = report.charts;
            self.reportFilters[report_id] = report.filter_structure;
        }

        self.availableReportIds = _.map(options.availableReports, function (r) { return r.report_id; });

        self.defaultReportTitle = function (reportId) {
            return self.reportTitles[reportId];
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved changes in your report list module",
            save: function () {
                // validate that all reports have valid data
                var reports = self.reports();
                for (var i = 0; i < reports.length; i++) {
                    if (!reports[i].reportId() || !reports[i].display()) {
                        alert('Reports must have all properties set!');
                    }
                }
                self.moduleName[self.lang] = self.currentModuleName();
                self.saveButton.ajax({
                    url: saveURL,
                    type: 'post',
                    dataType: 'json',
                    data: {
                        name: JSON.stringify(self.moduleName),
                        reports: JSON.stringify(_.map(self.reports(), function (r) { return r.toJSON(); }))
                    }
                });
            }
        });

        var changeSaveButton = function () {
            self.saveButton.fire('change');
        };

        self.currentModuleName.subscribe(changeSaveButton);

        function newReport(options) {
            options = options || {};
            var report = new ReportConfig(
                options.report_id,
                options.header,
                self.availableReportIds,
                self.reportCharts,
                options.graph_configs,
                options.filters,
                self.reportFilters,
                self.lang,
                changeSaveButton
            );
            report.display.subscribe(changeSaveButton);
            report.reportId.subscribe(changeSaveButton);
            report.reportId.subscribe(function (reportId) {
                report.display(self.defaultReportTitle(reportId));
            });

            return report;
        }
        this.addReport = function () {
            self.reports.push(newReport());
        };
        this.removeReport = function (report) {
            self.reports.remove(report);
            changeSaveButton();
        };

        // add existing reports to UI
        for (i = 0; i < currentReports.length; i += 1) {
            var report = newReport(currentReports[i]);
            self.reports.push(report);
        }
    }

    return ReportModule;
}());
<|endoftext|>


<commit_before>// TODO: Ideally the separator would be defined in one place. Right now it is
//       also defined corehq.apps.userreports.reports.filters.CHOICE_DELIMITER
var select2Separator = "\u001F";

var ReportModule = (function () {

    function KeyValuePair(key, value, config) {
        var self = this;

        self.key = ko.observable(key);
        self.value = ko.observable(value);
        self.config = config;

        self.remove = function() {
            config.keyValuePairs.remove(self);
        };
    }

    function Config(dict) {
        var self = this;

        dict = dict || {};

        self.keyValuePairs = ko.observableArray();
        _.each(dict, function(value, key) {
            self.keyValuePairs.push(new KeyValuePair(key, value, self));
        });

        self.addConfig = function() {
            self.keyValuePairs.push(new KeyValuePair('', '', self));
        };
    }

    function GraphConfig(report_id, reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton) {
        var self = this;

        graph_configs = graph_configs || {};

        this.graphConfigs = {};
        for (var i = 0; i < availableReportIds.length; i++) {
            var currentReportId = availableReportIds[i];
            self.graphConfigs[currentReportId] = {};
            for (var j = 0; j < reportCharts[currentReportId].length; j++) {
                var currentChart = reportCharts[currentReportId][j];
                var graph_config = graph_configs[currentChart.chart_id] || {};
                var series_configs = {};
                var chart_series = [];
                for(var k = 0; k < currentChart.y_axis_columns.length; k++) {
                    var series = currentChart.y_axis_columns[k].column_id;
                    chart_series.push(series);
                    series_configs[series] = new Config(
                        currentReportId === report_id ? (graph_config.series_configs || {})[series] || {} : {}
                    );
                }

                self.graphConfigs[currentReportId][currentChart.chart_id] = {
                    graph_type: ko.observable(currentReportId === report_id ? graph_config.graph_type || 'bar' : 'bar'),
                    series_configs: series_configs,
                    chart_series: chart_series,
                    config: new Config(
                        currentReportId === report_id ? graph_config.config || {} : {}
                    )
                };
            }
        }

        this.currentGraphConfigs = ko.computed(function() {
            return self.graphConfigs[reportId()];
        });

        this.currentCharts = ko.computed(function() {
            return reportCharts[reportId()];
        });

        this.getCurrentGraphConfig = function(chart_id) {
            return self.currentGraphConfigs()[chart_id] || {config: {keyValuePairs: []}};
        };

        this.toJSON = function () {
            function configToDict(config) {
                var dict = {};
                var keyValuePairs = config.keyValuePairs();
                for (var i = 0; i < keyValuePairs.length; i++) {
                    dict[keyValuePairs[i].key()] = keyValuePairs[i].value();
                }
                return dict;
            }

            var chartsToConfigs = {};
            var currentChartsToConfigs = self.currentGraphConfigs();
            _.each(currentChartsToConfigs, function(graph_config, chart_id) {
                chartsToConfigs[chart_id] = {
                    series_configs: {}
                };
                _.each(graph_config.series_configs, function(series_config, series) {
                    chartsToConfigs[chart_id].series_configs[series] = configToDict(series_config);
                });
                chartsToConfigs[chart_id].graph_type = graph_config.graph_type();
                chartsToConfigs[chart_id].config = configToDict(graph_config.config);
            });
            return chartsToConfigs;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            var addConfigToSaveButton = function(config) {
                addSubscriberToSaveButton(config.keyValuePairs);
                _.each(config.keyValuePairs(), function(keyValuePair) {
                    addSubscriberToSaveButton(keyValuePair.key);
                    addSubscriberToSaveButton(keyValuePair.value);
                });
            };
            _.each(self.graphConfigs, function(reportGraphConfigs) {
                _.each(reportGraphConfigs, function(graphConfig) {
                    addSubscriberToSaveButton(graphConfig.graph_type);
                    addConfigToSaveButton(graphConfig.config);
                    _.each(graphConfig.series_configs, addConfigToSaveButton);
                });
            });
        };

        this.allGraphTypes = ['bar', 'time', 'xy'];
    }

    /**
     * View-model for the filters of a mobile UCR.
     *
     * @param savedReportId - the id of the report, currently saved. Can be undefined for unsaved report.
     * @param selectedReportId - KO observable for the id of the currently selected report
     * @param filterValues - { slug : saved filter data } for each saved filter
     * @param reportFilters - { report id --> [ { slug: filter slug } for each filter in report ] for each report }
     * @param changeSaveButton - function that enables the "Save" button
     */
    function FilterConfig(savedReportId, selectedReportId, filterValues, reportFilters, changeSaveButton) {
        var self = this;

        this.reportFilters = JSON.parse(JSON.stringify(reportFilters || {}));
        _.each(this.reportFilters, function(filtersInReport, id) {
            for (var i = 0; i < filtersInReport.length; i++) {
                var filter = filtersInReport[i];
                if (id === savedReportId && filterValues.hasOwnProperty(filter.slug)) {
                    filter.selectedValue = filterValues[filter.slug];
                    filter.selectedValue.doc_type = ko.observable(filter.selectedValue.doc_type);
                } else {
                    filter.selectedValue = {
                        doc_type: ko.observable(null)
                    };
                }
                var filterFields = [
                    'custom_data_property',
                    'date_range',
                    'filter_type',
                    'select_value',
                    'operator',
                    'date_number',
                    'date_number2'
                ];
                for(var filterFieldsIndex = 0; filterFieldsIndex < filterFields.length; filterFieldsIndex++) {
                    filter.selectedValue[filterFields[filterFieldsIndex]] = ko.observable(filter.selectedValue[filterFields[filterFieldsIndex]] || '');
                }
                filter.selectedValue.value = ko.observable(filter.selectedValue.value ? filter.selectedValue.value.join(select2Separator) : '');

                filter.dynamicFilterName = ko.computed(function () {
                    return selectedReportId() + '/' + filter.slug;
                });

                if(filter.choices !== undefined && filter.show_all) {
                    filter.choices.unshift({value: "_all", display: "Show All"}); // TODO: translate
                }
            }
        });

        this.selectedFilterStructure = ko.computed(function () { // for the chosen report
            return self.reportFilters[selectedReportId()];
        });

        this.toJSON = function () {
            var selectedFilterStructure = self.selectedFilterStructure();
            var selectedFilterValues = {};
            for (var i = 0; i < selectedFilterStructure.length; i++) {
                var filter = selectedFilterStructure[i];
                if (filter.selectedValue.doc_type()) {
                    selectedFilterValues[filter.slug] = {};
                    selectedFilterValues[filter.slug].doc_type = filter.selectedValue.doc_type();
                    // Depending on doc_type, pull the correct observables' values
                    var docTypeToField = {
                        AutoFilter: ['filter_type'],
                        CustomDataAutoFilter: ['custom_data_property'],
                        StaticChoiceFilter: ['select_value'],
                        StaticDatespanFilter: ['date_range'],
                        CustomDatespanFilter: ['operator', 'date_number', 'date_number2']
                    };
                    _.each(docTypeToField, function(field, docType) {
                        if(filter.selectedValue.doc_type() === docType) {
                            _.each(field, function(value) {
                                selectedFilterValues[filter.slug][value] = filter.selectedValue[value]();
                            });
                        }
                    });
                    if(filter.selectedValue.doc_type() === 'StaticChoiceListFilter') {
                        selectedFilterValues[filter.slug].value = filter.selectedValue.value().split(select2Separator);
                    }
                }
            }
            return selectedFilterValues;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            _.each(this.reportFilters, function(filtersInReport) {
                for (var i = 0; i < filtersInReport.length; i++) {
                    var filter = filtersInReport[i];
                    _.each(filter.selectedValue, addSubscriberToSaveButton);
                }
            });
        };

        // TODO - add user-friendly text
        this.filterDocTypes = [null, 'AutoFilter', 'StaticDatespanFilter', 'CustomDatespanFilter', 'CustomDataAutoFilter', 'StaticChoiceListFilter', 'StaticChoiceFilter', 'MobileSelectFilter'];
        this.autoFilterTypes = ['case_sharing_group', 'location_id', 'username', 'user_id'];
        this.date_range_options = ['last7', 'last30', 'lastmonth', 'lastyear'];
        this.date_operators = ['=', '<', '<=', '>', '>=', 'between'];
    }

    function ReportConfig(report_id, display,
                          localizedDescription, xpathDescription, useXpathDescription,
                          uuid, availableReportIds,
                          reportCharts, graph_configs,
                          filterValues, reportFilters,
                          language, changeSaveButton) {
        var self = this;
        this.lang = language;
        this.fullDisplay = display || {};
        this.fullLocalizedDescription = localizedDescription || {};
        this.uuid = uuid;
        this.availableReportIds = availableReportIds;

        this.reportId = ko.observable(report_id);
        this.display = ko.observable(this.fullDisplay[this.lang]);
        this.localizedDescription = ko.observable(this.fullLocalizedDescription[this.lang]);
        this.xpathDescription = ko.observable(xpathDescription);
        this.useXpathDescription = ko.observable(useXpathDescription);

        this.reportId.subscribe(changeSaveButton);
        this.display.subscribe(changeSaveButton);
        this.localizedDescription.subscribe(changeSaveButton);
        this.xpathDescription.subscribe(changeSaveButton);
        this.useXpathDescription.subscribe(changeSaveButton);

        this.graphConfig = new GraphConfig(report_id, this.reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton);
        this.filterConfig = new FilterConfig(report_id, this.reportId, filterValues, reportFilters, changeSaveButton);

        this.toJSON = function () {
            self.fullDisplay[self.lang] = self.display();
            self.fullLocalizedDescription[self.lang] = self.localizedDescription();
            return {
                report_id: self.reportId(),
                graph_configs: self.graphConfig.toJSON(),
                filters: self.filterConfig.toJSON(),
                header: self.fullDisplay,
                localized_description: self.fullLocalizedDescription,
                xpath_description: self.xpathDescription(),
                use_xpath_description: self.useXpathDescription(),
                uuid: self.uuid
            };
        };
    }

    function ReportModule(options) {
        var self = this;
        var currentReports = options.currentReports || [];
        var availableReports = options.availableReports || [];
        var saveURL = options.saveURL;
        self.lang = options.lang;
        self.moduleName = options.moduleName;
        self.moduleFilter = options.moduleFilter === "None" ? "" : options.moduleFilter;
        self.currentModuleName = ko.observable(options.moduleName[self.lang]);
        self.currentModuleFilter = ko.observable(self.moduleFilter);
        self.menuImage = options.menuImage;
        self.menuAudio = options.menuAudio;
        self.reportTitles = {};
        self.reportDescriptions = {};
        self.reportCharts = {};
        self.reportFilters = {};
        self.reports = ko.observableArray([]);
        for (var i = 0; i < availableReports.length; i++) {
            var report = availableReports[i];
            var report_id = report.report_id;
            self.reportTitles[report_id] = report.title;
            self.reportDescriptions[report_id] = report.description;
            self.reportCharts[report_id] = report.charts;
            self.reportFilters[report_id] = report.filter_structure;
        }

        self.availableReportIds = _.map(options.availableReports, function (r) { return r.report_id; });

        self.defaultReportTitle = function (reportId) {
            return self.reportTitles[reportId];
        };
        self.defaultReportDescription = function (reportId) {
            return self.reportDescriptions[reportId];
        };

        self.multimedia = function () {
            var multimedia = {};
            multimedia.mediaImage = {};
            multimedia.mediaImage[self.lang] = self.menuImage.ref().path;
            multimedia.mediaAudio = {};
            multimedia.mediaAudio[self.lang] = self.menuAudio.ref().path;
            return multimedia;
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved changes in your report list module",
            save: function () {
                // validate that all reports have valid data
                var reports = self.reports();
                for (var i = 0; i < reports.length; i++) {
                    if (!reports[i].reportId() || !reports[i].display()) {
                        alert('Reports must have all properties set!');
                    }
                }
                self.moduleName[self.lang] = self.currentModuleName();

                var filter = self.currentModuleFilter().trim();
                self.moduleFilter = filter === '' ? undefined : filter;

                self.saveButton.ajax({
                    url: saveURL,
                    type: 'post',
                    dataType: 'json',
                    data: {
                        name: JSON.stringify(self.moduleName),
                        module_filter: self.moduleFilter,
                        reports: JSON.stringify(_.map(self.reports(), function (r) { return r.toJSON(); })),
                        multimedia: JSON.stringify(self.multimedia())
                    }
                });
            }
        });

        self.changeSaveButton = function () {
            self.saveButton.fire('change');
        };

        self.currentModuleName.subscribe(self.changeSaveButton);
        self.currentModuleFilter.subscribe(self.changeSaveButton);
        $(options.containerId + ' input').on('textchange', self.changeSaveButton);

        function newReport(options) {
            options = options || {};
            var report = new ReportConfig(
                options.report_id,
                options.header,
                options.localized_description,
                options.xpath_description,
                options.use_xpath_description,
                options.uuid,
                self.availableReportIds,
                self.reportCharts,
                options.graph_configs,
                options.filters,
                self.reportFilters,
                self.lang,
                self.changeSaveButton
            );
            report.reportId.subscribe(function (reportId) {
                report.display(self.defaultReportTitle(reportId));
            });
            report.reportId.subscribe(function (reportId) {
                report.localizedDescription(self.defaultReportDescription(reportId));
            });

            return report;
        }
        this.addReport = function () {
            self.reports.push(newReport());
        };
        this.removeReport = function (report) {
            self.reports.remove(report);
            self.changeSaveButton();
        };

        // add existing reports to UI
        for (i = 0; i < currentReports.length; i += 1) {
            var report = newReport(currentReports[i]);
            self.reports.push(report);
        }
    }

    return ReportModule;
}());
<commit_msg>Access the path the right way<commit_after>// TODO: Ideally the separator would be defined in one place. Right now it is
//       also defined corehq.apps.userreports.reports.filters.CHOICE_DELIMITER
var select2Separator = "\u001F";

var ReportModule = (function () {

    function KeyValuePair(key, value, config) {
        var self = this;

        self.key = ko.observable(key);
        self.value = ko.observable(value);
        self.config = config;

        self.remove = function() {
            config.keyValuePairs.remove(self);
        };
    }

    function Config(dict) {
        var self = this;

        dict = dict || {};

        self.keyValuePairs = ko.observableArray();
        _.each(dict, function(value, key) {
            self.keyValuePairs.push(new KeyValuePair(key, value, self));
        });

        self.addConfig = function() {
            self.keyValuePairs.push(new KeyValuePair('', '', self));
        };
    }

    function GraphConfig(report_id, reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton) {
        var self = this;

        graph_configs = graph_configs || {};

        this.graphConfigs = {};
        for (var i = 0; i < availableReportIds.length; i++) {
            var currentReportId = availableReportIds[i];
            self.graphConfigs[currentReportId] = {};
            for (var j = 0; j < reportCharts[currentReportId].length; j++) {
                var currentChart = reportCharts[currentReportId][j];
                var graph_config = graph_configs[currentChart.chart_id] || {};
                var series_configs = {};
                var chart_series = [];
                for(var k = 0; k < currentChart.y_axis_columns.length; k++) {
                    var series = currentChart.y_axis_columns[k].column_id;
                    chart_series.push(series);
                    series_configs[series] = new Config(
                        currentReportId === report_id ? (graph_config.series_configs || {})[series] || {} : {}
                    );
                }

                self.graphConfigs[currentReportId][currentChart.chart_id] = {
                    graph_type: ko.observable(currentReportId === report_id ? graph_config.graph_type || 'bar' : 'bar'),
                    series_configs: series_configs,
                    chart_series: chart_series,
                    config: new Config(
                        currentReportId === report_id ? graph_config.config || {} : {}
                    )
                };
            }
        }

        this.currentGraphConfigs = ko.computed(function() {
            return self.graphConfigs[reportId()];
        });

        this.currentCharts = ko.computed(function() {
            return reportCharts[reportId()];
        });

        this.getCurrentGraphConfig = function(chart_id) {
            return self.currentGraphConfigs()[chart_id] || {config: {keyValuePairs: []}};
        };

        this.toJSON = function () {
            function configToDict(config) {
                var dict = {};
                var keyValuePairs = config.keyValuePairs();
                for (var i = 0; i < keyValuePairs.length; i++) {
                    dict[keyValuePairs[i].key()] = keyValuePairs[i].value();
                }
                return dict;
            }

            var chartsToConfigs = {};
            var currentChartsToConfigs = self.currentGraphConfigs();
            _.each(currentChartsToConfigs, function(graph_config, chart_id) {
                chartsToConfigs[chart_id] = {
                    series_configs: {}
                };
                _.each(graph_config.series_configs, function(series_config, series) {
                    chartsToConfigs[chart_id].series_configs[series] = configToDict(series_config);
                });
                chartsToConfigs[chart_id].graph_type = graph_config.graph_type();
                chartsToConfigs[chart_id].config = configToDict(graph_config.config);
            });
            return chartsToConfigs;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            var addConfigToSaveButton = function(config) {
                addSubscriberToSaveButton(config.keyValuePairs);
                _.each(config.keyValuePairs(), function(keyValuePair) {
                    addSubscriberToSaveButton(keyValuePair.key);
                    addSubscriberToSaveButton(keyValuePair.value);
                });
            };
            _.each(self.graphConfigs, function(reportGraphConfigs) {
                _.each(reportGraphConfigs, function(graphConfig) {
                    addSubscriberToSaveButton(graphConfig.graph_type);
                    addConfigToSaveButton(graphConfig.config);
                    _.each(graphConfig.series_configs, addConfigToSaveButton);
                });
            });
        };

        this.allGraphTypes = ['bar', 'time', 'xy'];
    }

    /**
     * View-model for the filters of a mobile UCR.
     *
     * @param savedReportId - the id of the report, currently saved. Can be undefined for unsaved report.
     * @param selectedReportId - KO observable for the id of the currently selected report
     * @param filterValues - { slug : saved filter data } for each saved filter
     * @param reportFilters - { report id --> [ { slug: filter slug } for each filter in report ] for each report }
     * @param changeSaveButton - function that enables the "Save" button
     */
    function FilterConfig(savedReportId, selectedReportId, filterValues, reportFilters, changeSaveButton) {
        var self = this;

        this.reportFilters = JSON.parse(JSON.stringify(reportFilters || {}));
        _.each(this.reportFilters, function(filtersInReport, id) {
            for (var i = 0; i < filtersInReport.length; i++) {
                var filter = filtersInReport[i];
                if (id === savedReportId && filterValues.hasOwnProperty(filter.slug)) {
                    filter.selectedValue = filterValues[filter.slug];
                    filter.selectedValue.doc_type = ko.observable(filter.selectedValue.doc_type);
                } else {
                    filter.selectedValue = {
                        doc_type: ko.observable(null)
                    };
                }
                var filterFields = [
                    'custom_data_property',
                    'date_range',
                    'filter_type',
                    'select_value',
                    'operator',
                    'date_number',
                    'date_number2'
                ];
                for(var filterFieldsIndex = 0; filterFieldsIndex < filterFields.length; filterFieldsIndex++) {
                    filter.selectedValue[filterFields[filterFieldsIndex]] = ko.observable(filter.selectedValue[filterFields[filterFieldsIndex]] || '');
                }
                filter.selectedValue.value = ko.observable(filter.selectedValue.value ? filter.selectedValue.value.join(select2Separator) : '');

                filter.dynamicFilterName = ko.computed(function () {
                    return selectedReportId() + '/' + filter.slug;
                });

                if(filter.choices !== undefined && filter.show_all) {
                    filter.choices.unshift({value: "_all", display: "Show All"}); // TODO: translate
                }
            }
        });

        this.selectedFilterStructure = ko.computed(function () { // for the chosen report
            return self.reportFilters[selectedReportId()];
        });

        this.toJSON = function () {
            var selectedFilterStructure = self.selectedFilterStructure();
            var selectedFilterValues = {};
            for (var i = 0; i < selectedFilterStructure.length; i++) {
                var filter = selectedFilterStructure[i];
                if (filter.selectedValue.doc_type()) {
                    selectedFilterValues[filter.slug] = {};
                    selectedFilterValues[filter.slug].doc_type = filter.selectedValue.doc_type();
                    // Depending on doc_type, pull the correct observables' values
                    var docTypeToField = {
                        AutoFilter: ['filter_type'],
                        CustomDataAutoFilter: ['custom_data_property'],
                        StaticChoiceFilter: ['select_value'],
                        StaticDatespanFilter: ['date_range'],
                        CustomDatespanFilter: ['operator', 'date_number', 'date_number2']
                    };
                    _.each(docTypeToField, function(field, docType) {
                        if(filter.selectedValue.doc_type() === docType) {
                            _.each(field, function(value) {
                                selectedFilterValues[filter.slug][value] = filter.selectedValue[value]();
                            });
                        }
                    });
                    if(filter.selectedValue.doc_type() === 'StaticChoiceListFilter') {
                        selectedFilterValues[filter.slug].value = filter.selectedValue.value().split(select2Separator);
                    }
                }
            }
            return selectedFilterValues;
        };

        this.addSubscribersToSaveButton = function() {
            var addSubscriberToSaveButton = function(observable) {
                observable.subscribe(changeSaveButton);
            };
            _.each(this.reportFilters, function(filtersInReport) {
                for (var i = 0; i < filtersInReport.length; i++) {
                    var filter = filtersInReport[i];
                    _.each(filter.selectedValue, addSubscriberToSaveButton);
                }
            });
        };

        // TODO - add user-friendly text
        this.filterDocTypes = [null, 'AutoFilter', 'StaticDatespanFilter', 'CustomDatespanFilter', 'CustomDataAutoFilter', 'StaticChoiceListFilter', 'StaticChoiceFilter', 'MobileSelectFilter'];
        this.autoFilterTypes = ['case_sharing_group', 'location_id', 'username', 'user_id'];
        this.date_range_options = ['last7', 'last30', 'lastmonth', 'lastyear'];
        this.date_operators = ['=', '<', '<=', '>', '>=', 'between'];
    }

    function ReportConfig(report_id, display,
                          localizedDescription, xpathDescription, useXpathDescription,
                          uuid, availableReportIds,
                          reportCharts, graph_configs,
                          filterValues, reportFilters,
                          language, changeSaveButton) {
        var self = this;
        this.lang = language;
        this.fullDisplay = display || {};
        this.fullLocalizedDescription = localizedDescription || {};
        this.uuid = uuid;
        this.availableReportIds = availableReportIds;

        this.reportId = ko.observable(report_id);
        this.display = ko.observable(this.fullDisplay[this.lang]);
        this.localizedDescription = ko.observable(this.fullLocalizedDescription[this.lang]);
        this.xpathDescription = ko.observable(xpathDescription);
        this.useXpathDescription = ko.observable(useXpathDescription);

        this.reportId.subscribe(changeSaveButton);
        this.display.subscribe(changeSaveButton);
        this.localizedDescription.subscribe(changeSaveButton);
        this.xpathDescription.subscribe(changeSaveButton);
        this.useXpathDescription.subscribe(changeSaveButton);

        this.graphConfig = new GraphConfig(report_id, this.reportId, availableReportIds, reportCharts, graph_configs, changeSaveButton);
        this.filterConfig = new FilterConfig(report_id, this.reportId, filterValues, reportFilters, changeSaveButton);

        this.toJSON = function () {
            self.fullDisplay[self.lang] = self.display();
            self.fullLocalizedDescription[self.lang] = self.localizedDescription();
            return {
                report_id: self.reportId(),
                graph_configs: self.graphConfig.toJSON(),
                filters: self.filterConfig.toJSON(),
                header: self.fullDisplay,
                localized_description: self.fullLocalizedDescription,
                xpath_description: self.xpathDescription(),
                use_xpath_description: self.useXpathDescription(),
                uuid: self.uuid
            };
        };
    }

    function ReportModule(options) {
        var self = this;
        var currentReports = options.currentReports || [];
        var availableReports = options.availableReports || [];
        var saveURL = options.saveURL;
        self.lang = options.lang;
        self.moduleName = options.moduleName;
        self.moduleFilter = options.moduleFilter === "None" ? "" : options.moduleFilter;
        self.currentModuleName = ko.observable(options.moduleName[self.lang]);
        self.currentModuleFilter = ko.observable(self.moduleFilter);
        self.menuImage = options.menuImage;
        self.menuAudio = options.menuAudio;
        self.reportTitles = {};
        self.reportDescriptions = {};
        self.reportCharts = {};
        self.reportFilters = {};
        self.reports = ko.observableArray([]);
        for (var i = 0; i < availableReports.length; i++) {
            var report = availableReports[i];
            var report_id = report.report_id;
            self.reportTitles[report_id] = report.title;
            self.reportDescriptions[report_id] = report.description;
            self.reportCharts[report_id] = report.charts;
            self.reportFilters[report_id] = report.filter_structure;
        }

        self.availableReportIds = _.map(options.availableReports, function (r) { return r.report_id; });

        self.defaultReportTitle = function (reportId) {
            return self.reportTitles[reportId];
        };
        self.defaultReportDescription = function (reportId) {
            return self.reportDescriptions[reportId];
        };

        self.multimedia = function () {
            var multimedia = {};
            multimedia.mediaImage = {};
            multimedia.mediaImage[self.lang] = self.menuImage.currentPath();
            multimedia.mediaAudio = {};
            multimedia.mediaAudio[self.lang] = self.menuAudio.currentPath();
            return multimedia;
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved changes in your report list module",
            save: function () {
                // validate that all reports have valid data
                var reports = self.reports();
                for (var i = 0; i < reports.length; i++) {
                    if (!reports[i].reportId() || !reports[i].display()) {
                        alert('Reports must have all properties set!');
                    }
                }
                self.moduleName[self.lang] = self.currentModuleName();

                var filter = self.currentModuleFilter().trim();
                self.moduleFilter = filter === '' ? undefined : filter;

                self.saveButton.ajax({
                    url: saveURL,
                    type: 'post',
                    dataType: 'json',
                    data: {
                        name: JSON.stringify(self.moduleName),
                        module_filter: self.moduleFilter,
                        reports: JSON.stringify(_.map(self.reports(), function (r) { return r.toJSON(); })),
                        multimedia: JSON.stringify(self.multimedia())
                    }
                });
            }
        });

        self.changeSaveButton = function () {
            self.saveButton.fire('change');
        };

        self.currentModuleName.subscribe(self.changeSaveButton);
        self.currentModuleFilter.subscribe(self.changeSaveButton);
        $(options.containerId + ' input').on('textchange', self.changeSaveButton);

        function newReport(options) {
            options = options || {};
            var report = new ReportConfig(
                options.report_id,
                options.header,
                options.localized_description,
                options.xpath_description,
                options.use_xpath_description,
                options.uuid,
                self.availableReportIds,
                self.reportCharts,
                options.graph_configs,
                options.filters,
                self.reportFilters,
                self.lang,
                self.changeSaveButton
            );
            report.reportId.subscribe(function (reportId) {
                report.display(self.defaultReportTitle(reportId));
            });
            report.reportId.subscribe(function (reportId) {
                report.localizedDescription(self.defaultReportDescription(reportId));
            });

            return report;
        }
        this.addReport = function () {
            self.reports.push(newReport());
        };
        this.removeReport = function (report) {
            self.reports.remove(report);
            self.changeSaveButton();
        };

        // add existing reports to UI
        for (i = 0; i < currentReports.length; i += 1) {
            var report = newReport(currentReports[i]);
            self.reports.push(report);
        }
    }

    return ReportModule;
}());
<|endoftext|>


<commit_before>module.exports = keys;

function keys(interface) {
  this.interface = interface;
}

keys.prototype.listen = function() {
  this.interface.screen.key(['C-c'], function(ch, key) {
    return process.exit(0);
  }.bind(this));

  this.interface.inputBar.key(['pageup', 'M-v'], function(ch, key) {
    this.interface.interpretCommand('scrollb');
  }.bind(this));
  this.interface.inputBar.key(['pagedown', 'C-v'], function(ch, key) {
    this.interface.interpretCommand('scrollf');
  }.bind(this));
  this.interface.inputBar.key(['M-w'], function(ch, key) {
    this.interface.interpretCommand('part');
  }.bind(this))

  this.interface.inputBar.key(['M-1'], function(ch, key) {
    this.interface.interpretCommand('w 1');
  }.bind(this));
  this.interface.inputBar.key(['M-2'], function(ch, key) {
    this.interface.interpretCommand('w 2');
  }.bind(this));
  this.interface.inputBar.key(['M-3'], function(ch, key) {
    this.interface.interpretCommand('w 3');
  }.bind(this));
  this.interface.inputBar.key(['M-4'], function(ch, key) {
    this.interface.interpretCommand('w 4');
  }.bind(this));
  this.interface.inputBar.key(['M-5'], function(ch, key) {
    this.interface.interpretCommand('w 5');
  }.bind(this));
  this.interface.inputBar.key(['M-6'], function(ch, key) {
    this.interface.interpretCommand('w 6');
  }.bind(this));
  this.interface.inputBar.key(['M-7'], function(ch, key) {
    this.interface.interpretCommand('w 7');
  }.bind(this));
  this.interface.inputBar.key(['M-8'], function(ch, key) {
    this.interface.interpretCommand('w 8');
  }.bind(this));
  this.interface.inputBar.key(['M-9'], function(ch, key) {
    this.interface.interpretCommand('w 9');
  }.bind(this));

  this.interface.inputBar.on('keypress', function() {
    this.interface.idle();
  }.bind(this));
};
<commit_msg>Added some editing keybinds.<commit_after>module.exports = keys;

function keys(interface) {
  this.interface = interface;
}

keys.prototype.listen = function() {
  this.interface.screen.key(['C-c'], function(ch, key) {
    return process.exit(0);
  }.bind(this));

  this.interface.inputBar.key(['pageup', 'M-v'], function(ch, key) {
    this.interface.interpretCommand('scrollb');
  }.bind(this));
  this.interface.inputBar.key(['pagedown', 'C-v'], function(ch, key) {
    this.interface.interpretCommand('scrollf');
  }.bind(this));
  this.interface.inputBar.key(['M-w'], function(ch, key) {
    this.interface.interpretCommand('part');
  }.bind(this))

  this.interface.inputBar.key(['M-1'], function(ch, key) {
    this.interface.interpretCommand('w 1');
  }.bind(this));
  this.interface.inputBar.key(['M-2'], function(ch, key) {
    this.interface.interpretCommand('w 2');
  }.bind(this));
  this.interface.inputBar.key(['M-3'], function(ch, key) {
    this.interface.interpretCommand('w 3');
  }.bind(this));
  this.interface.inputBar.key(['M-4'], function(ch, key) {
    this.interface.interpretCommand('w 4');
  }.bind(this));
  this.interface.inputBar.key(['M-5'], function(ch, key) {
    this.interface.interpretCommand('w 5');
  }.bind(this));
  this.interface.inputBar.key(['M-6'], function(ch, key) {
    this.interface.interpretCommand('w 6');
  }.bind(this));
  this.interface.inputBar.key(['M-7'], function(ch, key) {
    this.interface.interpretCommand('w 7');
  }.bind(this));
  this.interface.inputBar.key(['M-8'], function(ch, key) {
    this.interface.interpretCommand('w 8');
  }.bind(this));
  this.interface.inputBar.key(['M-9'], function(ch, key) {
    this.interface.interpretCommand('w 9');
  }.bind(this));

  this.interface.inputBar.on('keypress', function() {
    this.interface.idle();
  }.bind(this));

  // Text editing

  this.interface.inputBar.key(['C-u'], function(ch, key) {
    this.interface.session.yankBuffer = this.interface.inputBar.getValue();
    this.interface.inputBar.clearValue();
    this.interface.screen.render();
  }.bind(this));

  this.interface.inputBar.key(['C-y'], function(ch, key) {
    this.interface.inputBar.setValue(this.interface.inputBar.getValue() + this.interface.session.yankBuffer);
    this.interface.screen.render();
  }.bind(this));

  this.interface.inputBar.key(['C-w'], function(ch, key) {
    var text = this.interface.inputBar.getValue();
    text = text.replace(/\s+$/gm,''); // remove trailing whitespace
    var yank = text.substring(text.lastIndexOf(' '));
    text = text.substring(0, text.lastIndexOf(' '));
    this.interface.session.yankBuffer = yank;
    this.interface.inputBar.setValue(text);
    this.interface.screen.render();
  }.bind(this));

  /* FIX: Moves cursor but not input.
  this.interface.inputBar.key(['C-b'], function(ch, key) {
    this.interface.program.back();
    this.interface.screen.render();
  }.bind(this));
  */
};
<|endoftext|>