<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var parallel = require('run-parallel');

var errors = require('./errors');
var OutArgStream = require('./argstream').OutArgStream;

var emptyBuffer = Buffer(0);

var States = Object.create(null);
States.Initial = 0;
States.Streaming = 1;
States.Done = 2;
States.Error = 3;

function TChannelOutgoingRequest(id, options) {
    options = options || {};
    if (!options.sendFrame) {
        throw new Error('missing sendFrame');
    }
    var self = this;
    EventEmitter.call(self);
    self.logger = options.logger;
    self.random = options.random;
    self.timers = options.timers;

    self.remoteAddr = options.remoteAddr;
    self.state = States.Initial;
    self.id = id || 0;
    self.ttl = options.ttl || 0;
    self.tracing = options.tracing || null;
    self.service = options.service || '';
    self.headers = options.headers || {};
    self.checksumType = options.checksumType || 0;
    self.checksum = options.checksum || null;

    self.sendFrame = options.sendFrame;
    if (options.streamed) {
        self.streamed = true;
        self._argstream = OutArgStream();
        self.arg1 = self._argstream.arg1;
        self.arg2 = self._argstream.arg2;
        self.arg3 = self._argstream.arg3;
        self._argstream.on('error', function passError(err) {
            self.emit('error', err);
        });
        self._argstream.on('frame', function onFrame(parts, isLast) {
            self.sendParts(parts, isLast);
        });
        self._argstream.on('finish', function onFinish() {
            self.emit('finish');
        });
    } else {
        self.streamed = false;
        self._argstream = null;
        self.arg1 = null;
        self.arg2 = null;
        self.arg3 = null;
    }

    if (options.tracer) {
        // new span with new ids
        self.span = options.tracer.setupNewSpan({
            outgoing: true,
            parentSpan: options.parentSpan,
            topLevelRequest: options.topLevelRequest,
            spanid: null,
            traceid: null,
            parentid: null,
            flags: options.trace? 1 : 0,
            hostPort: self.remoteAddr,
            serviceName: self.service,
            name: '' // fill this in later
        });

        self.tracing = self.span.getTracing();
    } else {
        self.span = null;
    }

    self.err = null;
    self.res = null;
    self.start = self.timers.now();
    self.timedOut = false;
}

inherits(TChannelOutgoingRequest, EventEmitter);

TChannelOutgoingRequest.prototype.type = 'tchannel.outgoing-request';

TChannelOutgoingRequest.prototype.sendParts = function sendParts(parts, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.sendCallRequestFrame(parts, isLast);
            break;
        case States.Streaming:
            self.sendCallRequestContFrame(parts, isLast);
            break;
        case States.Done:
            // TODO: could probably happen normally, like say if a
            // streaming request is canceled
            throw new Error('got frame in done state'); // TODO: typed error
        case States.Error:
            // TODO: log warn
            break;
    }
};

TChannelOutgoingRequest.prototype.sendCallRequestFrame = function sendCallRequestFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            if (self.span) {
                self.span.annotate('cs');
            }
            self.sendFrame.callRequest(args, isLast);
            if (isLast) self.state = States.Done;
            else self.state = States.Streaming;
            break;
        case States.Streaming:
            throw new Error('first request frame already sent'); // TODO: typed error
        case States.Done:
            throw new Error('request already done'); // TODO: typed error
    }
};

TChannelOutgoingRequest.prototype.sendCallRequestContFrame = function sendCallRequestContFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            throw new Error('first request frame not sent'); // TODO: typed error
        case States.Streaming:
            self.sendFrame.callRequestCont(args, isLast);
            if (isLast) self.state = States.Done;
            break;
        case States.Done:
            throw new Error('request already done'); // TODO: typed error
    }
};

TChannelOutgoingRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;

    if (self.span) {
        self.span.name = String(arg1);
    }
    if (callback) self.hookupCallback(callback);
    if (self.streamed) {
        self.arg1.end(arg1);
        self.arg2.end(arg2);
        self.arg3.end(arg3);
    } else {
        self.sendCallRequestFrame([
            arg1 ? Buffer(arg1) : emptyBuffer,
            arg2 ? Buffer(arg2) : emptyBuffer,
            arg3 ? Buffer(arg3) : emptyBuffer
        ], true);
        self.emit('finish');
    }
    return self;
};

TChannelOutgoingRequest.prototype.hookupStreamCallback = function hookupCallback(callback) {
    var self = this;
    var called = false;
    self.on('error', onError);
    self.on('response', onResponse);

    function onError(err) {
        if (called) return;
        called = true;
        self.err = err;
        callback(err, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        self.res = res;
        callback(null, self, res);
    }

    return self;
};

TChannelOutgoingRequest.prototype.hookupCallback = function hookupCallback(callback) {
    var self = this;
    if (callback.canStream) {
        return self.hookupStreamCallback(callback);
    }
    var called = false;

    self.on('error', onError);
    self.on('response', onResponse);

    function onError(err) {
        if (called) return;
        called = true;
        self.err = err;
        callback(err, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        self.res = res;
        if (!res.streamed) {
            callback(null, res, res.arg2, res.arg3);
            return;
        }
        parallel({
            arg2: res.arg2.onValueReady,
            arg3: res.arg3.onValueReady
        }, compatCall);
        function compatCall(err, args) {
            callback(err, res, args.arg2, args.arg3);
        }
    }

    return self;
};

TChannelOutgoingRequest.prototype.checkTimeout = function checkTimeout() {
    var self = this;
    if (!self.timedOut) {
        var elapsed = self.timers.now() - self.start;
        if (elapsed > self.ttl) {
            self.timedOut = true;
            process.nextTick(function deferOutReqTimeoutErrorEmit() {
                self.emit('error', errors.TimeoutError({
                    id: self.id,
                    start: self.start,
                    elapsed: elapsed,
                    timeout: self.ttl
                }));
            });
        }
    }
    return self.timedOut;
};

TChannelOutgoingRequest.States = States;

module.exports = TChannelOutgoingRequest;
<commit_msg>TChannelOutgoingRequest: add start/end timing<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var parallel = require('run-parallel');

var errors = require('./errors');
var OutArgStream = require('./argstream').OutArgStream;

var emptyBuffer = Buffer(0);

var States = Object.create(null);
States.Initial = 0;
States.Streaming = 1;
States.Done = 2;
States.Error = 3;

function TChannelOutgoingRequest(id, options) {
    options = options || {};
    if (!options.sendFrame) {
        throw new Error('missing sendFrame');
    }
    var self = this;
    EventEmitter.call(self);
    self.logger = options.logger;
    self.random = options.random;
    self.timers = options.timers;

    self.start = 0;
    self.end = 0;
    self.remoteAddr = options.remoteAddr;
    self.state = States.Initial;
    self.id = id || 0;
    self.ttl = options.ttl || 0;
    self.tracing = options.tracing || null;
    self.service = options.service || '';
    self.headers = options.headers || {};
    self.checksumType = options.checksumType || 0;
    self.checksum = options.checksum || null;

    self.sendFrame = options.sendFrame;
    if (options.streamed) {
        self.streamed = true;
        self._argstream = OutArgStream();
        self.arg1 = self._argstream.arg1;
        self.arg2 = self._argstream.arg2;
        self.arg3 = self._argstream.arg3;
        self._argstream.on('error', function passError(err) {
            self.emit('error', err);
        });
        self._argstream.on('frame', function onFrame(parts, isLast) {
            self.sendParts(parts, isLast);
        });
        self._argstream.on('finish', function onFinish() {
            self.emit('finish');
        });
    } else {
        self.streamed = false;
        self._argstream = null;
        self.arg1 = null;
        self.arg2 = null;
        self.arg3 = null;
    }

    if (options.tracer) {
        // new span with new ids
        self.span = options.tracer.setupNewSpan({
            outgoing: true,
            parentSpan: options.parentSpan,
            topLevelRequest: options.topLevelRequest,
            spanid: null,
            traceid: null,
            parentid: null,
            flags: options.trace? 1 : 0,
            hostPort: self.remoteAddr,
            serviceName: self.service,
            name: '' // fill this in later
        });

        self.tracing = self.span.getTracing();
    } else {
        self.span = null;
    }

    self.err = null;
    self.res = null;
    self.start = self.timers.now();
    self.timedOut = false;
}

inherits(TChannelOutgoingRequest, EventEmitter);

TChannelOutgoingRequest.prototype.type = 'tchannel.outgoing-request';

TChannelOutgoingRequest.prototype.sendParts = function sendParts(parts, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            self.start = self.timers.now();
            self.sendCallRequestFrame(parts, isLast);
            break;
        case States.Streaming:
            self.sendCallRequestContFrame(parts, isLast);
            break;
        case States.Done:
            // TODO: could probably happen normally, like say if a
            // streaming request is canceled
            throw new Error('got frame in done state'); // TODO: typed error
        case States.Error:
            // TODO: log warn
            break;
    }
};

TChannelOutgoingRequest.prototype.sendCallRequestFrame = function sendCallRequestFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            if (self.span) {
                self.span.annotate('cs');
            }
            self.sendFrame.callRequest(args, isLast);
            if (isLast) self.state = States.Done;
            else self.state = States.Streaming;
            break;
        case States.Streaming:
            throw new Error('first request frame already sent'); // TODO: typed error
        case States.Done:
            throw new Error('request already done'); // TODO: typed error
    }
};

TChannelOutgoingRequest.prototype.sendCallRequestContFrame = function sendCallRequestContFrame(args, isLast) {
    var self = this;
    switch (self.state) {
        case States.Initial:
            throw new Error('first request frame not sent'); // TODO: typed error
        case States.Streaming:
            self.sendFrame.callRequestCont(args, isLast);
            if (isLast) self.state = States.Done;
            break;
        case States.Done:
            throw new Error('request already done'); // TODO: typed error
    }
};

TChannelOutgoingRequest.prototype.send = function send(arg1, arg2, arg3, callback) {
    var self = this;

    if (self.span) {
        self.span.name = String(arg1);
    }
    if (callback) self.hookupCallback(callback);
    if (self.streamed) {
        self.arg1.end(arg1);
        self.arg2.end(arg2);
        self.arg3.end(arg3);
    } else {
        self.sendCallRequestFrame([
            arg1 ? Buffer(arg1) : emptyBuffer,
            arg2 ? Buffer(arg2) : emptyBuffer,
            arg3 ? Buffer(arg3) : emptyBuffer
        ], true);
        self.emit('finish');
    }
    return self;
};

TChannelOutgoingRequest.prototype.hookupStreamCallback = function hookupCallback(callback) {
    var self = this;
    var called = false;
    self.on('error', onError);
    self.on('response', onResponse);

    function onError(err) {
        if (called) return;
        called = true;
        self.err = err;
        if (!self.end) self.end = self.timers.now();
        callback(err, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        self.res = res;
        if (!self.end) self.end = self.timers.now();
        callback(null, self, res);
    }

    return self;
};

TChannelOutgoingRequest.prototype.hookupCallback = function hookupCallback(callback) {
    var self = this;
    if (callback.canStream) {
        return self.hookupStreamCallback(callback);
    }
    var called = false;

    self.on('error', onError);
    self.on('response', onResponse);

    function onError(err) {
        if (called) return;
        called = true;
        self.err = err;
        if (!self.end) self.end = self.timers.now();
        callback(err, null, null);
    }

    function onResponse(res) {
        if (called) return;
        called = true;
        self.res = res;
        if (!res.streamed) {
            if (!self.end) self.end = self.timers.now();
            callback(null, res, res.arg2, res.arg3);
            return;
        }
        parallel({
            arg2: res.arg2.onValueReady,
            arg3: res.arg3.onValueReady
        }, compatCall);
        function compatCall(err, args) {
            if (!self.end) self.end = self.timers.now();
            callback(err, res, args.arg2, args.arg3);
        }
    }

    return self;
};

TChannelOutgoingRequest.prototype.checkTimeout = function checkTimeout() {
    var self = this;
    if (!self.timedOut) {
        var now = self.timers.now();
        var elapsed = now - self.start;
        if (elapsed > self.ttl) {
            self.end = now;
            self.timedOut = true;
            process.nextTick(function deferOutReqTimeoutErrorEmit() {
                self.emit('error', errors.TimeoutError({
                    id: self.id,
                    start: self.start,
                    elapsed: elapsed,
                    timeout: self.ttl
                }));
            });
        }
    }
    return self.timedOut;
};

TChannelOutgoingRequest.States = States;

module.exports = TChannelOutgoingRequest;
<|endoftext|>


<commit_before>/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
/** @module */

var Stream = require('../Stream');
var Scheduler = require('../Scheduler');
var promise = require('../promises');
var step = require('../step');

var when = promise.when;
var delayed = promise.delay;
var never = promise.never;
var raceIndex = promise.raceIndex;
var Yield = step.Yield;
var Pair = step.Pair;
var yieldPair = step.yieldPair;
var ensureScheduler = Scheduler.ensure;

exports.periodic = periodic;
exports.periodicOn = periodicOn;
exports.delay = delay;
exports.delayOn = delayOn;
exports.debounce = debounce;
exports.debounceOn = debounceOn;
exports.throttle = throttle;
exports.throttleOn = throttleOn;

/**
 * Create a stream that emits the current time periodically
 * @param {Number} period
 * @returns {Stream} new stream that emits the current time every period
 */
function periodic(period) {
	return periodicOn(Scheduler.getDefault(), period);
}

/**
 * Create a stream that emits the current time periodically using
 * the provided scheduler
 * @param {Scheduler} scheduler
 * @param {Number} period
 * @returns {Stream} new stream that emits the current time every period
 */
function periodicOn(scheduler, period) {
	return new Stream(function(p) {
		var now = p.state.now();
		return delayed(p.value, new Yield(now, p), p.state);
	}, new Pair(Math.max(1, period), ensureScheduler(scheduler)));
}

/**
 * @param {Number} delayTime milliseconds to delay each item using
 * the provided scheduler
 * @param {Stream} stream
 * @returns {Stream} new stream containing the same items, but delayed by ms
 */
function delay(delayTime, stream) {
	return delayOn(Scheduler.getDefault(), delayTime, stream);
}

/**
 * @param {Scheduler} scheduler
 * @param {Number} delayTime milliseconds to delay each item
 * @param {Stream} stream
 * @returns {Stream} new stream containing the same items, but delayed by ms
 */
function delayOn(scheduler, delayTime, stream) {
	var stepper = stream.step;
	return new Stream(function(s) {
		return when(function(i) {
			return i.done ? i
				: delayed(s.value, yieldPair(i, s.value), scheduler);
		}, when(stepper, s.state));
	}, new Pair(Math.max(0, delayTime), stream.state));
}

function throttle(period, stream) {
	return throttleOn(Scheduler.getDefault(), period, stream);
}

/**
 * Skip events for period time after the most recent event using
 * the provided scheduler
 * @param {Scheduler} scheduler
 * @param {Number} period time to suppress events
 * @param {Stream} stream
 * @returns {Stream} new stream that skips events for period
 */
function throttleOn(scheduler, period, stream) {
	scheduler = ensureScheduler(scheduler);

	var stepper = stream.step;
	return new Stream(function(s) {
		return throttleNext(stepper, s, period, scheduler);
	}, new Pair(-1, stream.state));
}

function throttleNext(stepper, s, period, scheduler) {
	return when(function(i) {
		if(i.done) {
			return i;
		}

		var now = scheduler.now();
		var end = s.value;
		return now > end ? yieldPair(i, now + period)
			: throttleNext(stepper, new Pair(end, i.state), period, scheduler);
	}, when(stepper, s.state));
}

/**
 * Wait for a burst of events to subside and emit only the last event in the burst
 * stream:              abcd----abcd----
 * debounce(2, stream): -----d-------d--
 * @param {Number} period events occuring more frequently than this
 *  will be suppressed
 * @param {Stream} stream stream to debounce
 * @returns {Stream} new debounced stream
 */
function debounce(period, stream) {
	return debounceOn(Scheduler.getDefault(), period, stream);
}

/**
 * Wait for a burst of events to subside and emit only the last event in the burst
 * stream:              abcd----abcd----
 * debounce(2, stream): -----d-------d--
 * @param {Scheduler} scheduler
 * @param {Number} period events occuring more frequently than this
 *  on the provided scheduler will be suppressed
 * @param {Stream} stream stream to debounce
 * @returns {Stream} new debounced stream
 */
function debounceOn(scheduler, period, stream) {
	var stepper = stream.step;
	return new Stream(function(s) {
		return stepDebounce(scheduler, period, stepper, s);
	}, { timer: void 0, prev: void 0, next: void 0, state: stream.state });
}

function stepDebounce(scheduler, period, step, s) {
	return stepD(scheduler, period, step, s.timer === void 0 ? initState(step, s) : s);
}

function stepD(scheduler, period, step, s) {
	return raceIndex(function(winner, index) {
		return index === 0 ? yieldDebounced(s)
			: winner.done ? winner
			: stepD(scheduler, period, step, nextState(scheduler, period, step, winner));
	}, [s.timer, s.next]);
}

function initState(step, s) {
	return { timer: never(), prev: s.prev, next: when(step, s.state), state: s.state };
}

function nextState (scheduler, period, step, winner) {
	return { timer: delayed(period, 'timer', scheduler), prev: winner, next: when(step, winner.state), state: winner.state };
}

function yieldDebounced(s) {
	return when(function (prev) {
		return new Yield(prev.value, { timer: never(), next: s.next, prev: void 0, state: prev.state });
	}, s.prev);
}<commit_msg>Rename stepD to stepEarliest, clean up formatting<commit_after>/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
/** @module */

var Stream = require('../Stream');
var Scheduler = require('../Scheduler');
var promise = require('../promises');
var step = require('../step');

var when = promise.when;
var delayed = promise.delay;
var never = promise.never;
var raceIndex = promise.raceIndex;
var Yield = step.Yield;
var Pair = step.Pair;
var yieldPair = step.yieldPair;
var ensureScheduler = Scheduler.ensure;

exports.periodic = periodic;
exports.periodicOn = periodicOn;
exports.delay = delay;
exports.delayOn = delayOn;
exports.debounce = debounce;
exports.debounceOn = debounceOn;
exports.throttle = throttle;
exports.throttleOn = throttleOn;

/**
 * Create a stream that emits the current time periodically
 * @param {Number} period
 * @returns {Stream} new stream that emits the current time every period
 */
function periodic(period) {
	return periodicOn(Scheduler.getDefault(), period);
}

/**
 * Create a stream that emits the current time periodically using
 * the provided scheduler
 * @param {Scheduler} scheduler
 * @param {Number} period
 * @returns {Stream} new stream that emits the current time every period
 */
function periodicOn(scheduler, period) {
	return new Stream(function(p) {
		var now = p.state.now();
		return delayed(p.value, new Yield(now, p), p.state);
	}, new Pair(Math.max(1, period), ensureScheduler(scheduler)));
}

/**
 * @param {Number} delayTime milliseconds to delay each item using
 * the provided scheduler
 * @param {Stream} stream
 * @returns {Stream} new stream containing the same items, but delayed by ms
 */
function delay(delayTime, stream) {
	return delayOn(Scheduler.getDefault(), delayTime, stream);
}

/**
 * @param {Scheduler} scheduler
 * @param {Number} delayTime milliseconds to delay each item
 * @param {Stream} stream
 * @returns {Stream} new stream containing the same items, but delayed by ms
 */
function delayOn(scheduler, delayTime, stream) {
	var stepper = stream.step;
	return new Stream(function(s) {
		return when(function(i) {
			return i.done ? i
				: delayed(s.value, yieldPair(i, s.value), scheduler);
		}, when(stepper, s.state));
	}, new Pair(Math.max(0, delayTime), stream.state));
}

function throttle(period, stream) {
	return throttleOn(Scheduler.getDefault(), period, stream);
}

/**
 * Skip events for period time after the most recent event using
 * the provided scheduler
 * @param {Scheduler} scheduler
 * @param {Number} period time to suppress events
 * @param {Stream} stream
 * @returns {Stream} new stream that skips events for period
 */
function throttleOn(scheduler, period, stream) {
	scheduler = ensureScheduler(scheduler);

	var stepper = stream.step;
	return new Stream(function(s) {
		return throttleNext(stepper, s, period, scheduler);
	}, new Pair(-1, stream.state));
}

function throttleNext(stepper, s, period, scheduler) {
	return when(function(i) {
		if(i.done) {
			return i;
		}

		var now = scheduler.now();
		var end = s.value;
		return now > end ? yieldPair(i, now + period)
			: throttleNext(stepper, new Pair(end, i.state), period, scheduler);
	}, when(stepper, s.state));
}

/**
 * Wait for a burst of events to subside and emit only the last event in the burst
 * stream:              abcd----abcd----
 * debounce(2, stream): -----d-------d--
 * @param {Number} period events occuring more frequently than this
 *  will be suppressed
 * @param {Stream} stream stream to debounce
 * @returns {Stream} new debounced stream
 */
function debounce(period, stream) {
	return debounceOn(Scheduler.getDefault(), period, stream);
}

/**
 * Wait for a burst of events to subside and emit only the last event in the burst
 * stream:              abcd----abcd----
 * debounce(2, stream): -----d-------d--
 * @param {Scheduler} scheduler
 * @param {Number} period events occuring more frequently than this
 *  on the provided scheduler will be suppressed
 * @param {Stream} stream stream to debounce
 * @returns {Stream} new debounced stream
 */
function debounceOn(scheduler, period, stream) {
	var stepper = stream.step;
	return new Stream(function(s) {
		return stepDebounce(scheduler, period, stepper, s);
	}, { timer: void 0, prev: void 0, next: void 0, state: stream.state });
}

function stepDebounce(scheduler, period, step, s) {
	return stepEarliest(scheduler, period, step, s.timer === void 0 ? initState(step, s) : s);
}

function stepEarliest(scheduler, period, step, s) {
	return raceIndex(function(winner, index) {
		return index === 0 ? yieldDebounced(s)
			: winner.done ? winner
			: stepEarliest(scheduler, period, step, nextState(scheduler, period, step, winner));
	}, [s.timer, s.next]);
}

function initState(step, s) {
	return { timer: never(), prev: s.prev, next: when(step, s.state), state: s.state };
}

function nextState (scheduler, period, step, winner) {
	return { timer: delayed(period, 'timer', scheduler), prev: winner,
		next: when(step, winner.state), state: winner.state };
}

function yieldDebounced(s) {
	return when(function (prev) {
		return new Yield(prev.value, { timer: never(), prev: void 0,
			next: s.next, state: prev.state });
	}, s.prev);
}<|endoftext|>


<commit_before>import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'
import {connect} from 'react-redux'

import {MultiGrid, ColumnSizer} from 'react-virtualized'
import {bindActionCreators} from 'redux'
import moment from 'moment'
import {reduce} from 'fast.js'

import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers'
import {
  computeFieldOptions,
  transformTableData,
} from 'src/dashboards/utils/tableGraph'
import {updateFieldOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {DEFAULT_TIME_FIELD} from 'src/dashboards/constants'
import {
  ASCENDING,
  DESCENDING,
  NULL_HOVER_TIME,
  NULL_ARRAY_INDEX,
  DEFAULT_FIX_FIRST_COLUMN,
  DEFAULT_VERTICAL_TIME_AXIS,
  DEFAULT_SORT_DIRECTION,
} from 'src/shared/constants/tableGraph'
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
import {colorsStringSchema} from 'shared/schemas'
import {ErrorHandling} from 'src/shared/decorators/errors'

@ErrorHandling
class TableGraph extends Component {
  constructor(props) {
    super(props)

    const sortField = _.get(
      this.props,
      ['tableOptions', 'sortBy', 'internalName'],
      DEFAULT_TIME_FIELD.internalName
    )

    this.state = {
      data: [[]],
      transformedData: [[]],
      sortedTimeVals: [],
      sortedLabels: [],
      hoveredColumnIndex: NULL_ARRAY_INDEX,
      hoveredRowIndex: NULL_ARRAY_INDEX,
      sort: {field: sortField, direction: DEFAULT_SORT_DIRECTION},
      columnWidths: {},
      totalColumnWidths: 0,
    }
  }

  componentDidMount() {
    const sortField = _.get(
      this.props,
      ['tableOptions', 'sortBy', 'internalName'],
      DEFAULT_TIME_FIELD.internalName
    )
    const sort = {field: sortField, direction: DEFAULT_SORT_DIRECTION}
    const {
      data,
      tableOptions,
      timeFormat,
      fieldOptions,
      decimalPlaces,
    } = this.props
    const result = timeSeriesToTableGraph(data)
    const sortedLabels = result.sortedLabels

    const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels)
    const {
      transformedData,
      sortedTimeVals,
      columnWidths,
      totalWidths,
    } = transformTableData(
      result.data,
      sort,
      computedFieldOptions,
      tableOptions,
      timeFormat,
      decimalPlaces
    )

    this.setState({
      transformedData,
      sortedTimeVals,
      columnWidths,
      data: result.data,
      sortedLabels,
      totalColumnWidths: totalWidths,
      hoveredColumnIndex: NULL_ARRAY_INDEX,
      hoveredRowIndex: NULL_ARRAY_INDEX,
      sort,
    })
  }

  handleUpdateFieldOptions = fieldOptions => {
    const {isInCEO} = this.props
    if (!isInCEO) {
      return
    }
    this.props.handleUpdateFieldOptions(fieldOptions)
  }

  componentWillReceiveProps(nextProps) {
    const updatedProps = _.keys(nextProps).filter(
      k => !_.isEqual(this.props[k], nextProps[k])
    )
    const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = nextProps

    let result = {}

    if (_.includes(updatedProps, 'data')) {
      result = timeSeriesToTableGraph(nextProps.data)
    }

    const data = _.get(result, 'data', this.state.data)
    const sortedLabels = _.get(result, 'sortedLabels', this.state.sortedLabels)

    const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels)

    if (_.includes(updatedProps, 'data')) {
      this.handleUpdateFieldOptions(computedFieldOptions)
    }

    if (_.isEmpty(data[0])) {
      return
    }

    const {sort} = this.state
    const internalName = _.get(
      nextProps,
      ['tableOptions', 'sortBy', 'internalName'],
      ''
    )
    if (
      !_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
      internalName
    ) {
      sort.direction = DEFAULT_SORT_DIRECTION
      sort.field = internalName
    }

    if (
      _.includes(updatedProps, 'data') ||
      _.includes(updatedProps, 'tableOptions') ||
      _.includes(updatedProps, 'fieldOptions') ||
      _.includes(updatedProps, 'timeFormat')
    ) {
      const {
        transformedData,
        sortedTimeVals,
        columnWidths,
        totalWidths,
      } = transformTableData(
        data,
        sort,
        computedFieldOptions,
        tableOptions,
        timeFormat,
        decimalPlaces
      )

      this.setState({
        data,
        sortedLabels,
        transformedData,
        sortedTimeVals,
        sort,
        columnWidths,
        totalColumnWidths: totalWidths,
      })
    }
  }

  calcScrollToColRow = () => {
    const {data, sortedTimeVals, hoveredColumnIndex} = this.state
    const {hoverTime, tableOptions} = this.props
    const hoveringThisTable = hoveredColumnIndex !== NULL_ARRAY_INDEX
    const notHovering = hoverTime === NULL_HOVER_TIME
    if (_.isEmpty(data[0]) || notHovering || hoveringThisTable) {
      return {scrollToColumn: undefined, scrollToRow: undefined}
    }

    const firstDiff = Math.abs(hoverTime - sortedTimeVals[1]) // sortedTimeVals[0] is "time"
    const hoverTimeFound = reduce(
      sortedTimeVals,
      (acc, currentTime, index) => {
        const thisDiff = Math.abs(hoverTime - currentTime)
        if (thisDiff < acc.diff) {
          return {index, diff: thisDiff}
        }
        return acc
      },
      {index: 1, diff: firstDiff}
    )

    const {verticalTimeAxis} = tableOptions
    const scrollToColumn = verticalTimeAxis ? undefined : hoverTimeFound.index
    const scrollToRow = verticalTimeAxis ? hoverTimeFound.index : undefined
    return {scrollToRow, scrollToColumn}
  }

  handleHover = (columnIndex, rowIndex) => () => {
    const {
      handleSetHoverTime,
      tableOptions: {verticalTimeAxis},
    } = this.props
    const {sortedTimeVals} = this.state
    if (verticalTimeAxis && rowIndex === 0) {
      return
    }
    if (handleSetHoverTime) {
      const hoverTime = verticalTimeAxis
        ? sortedTimeVals[rowIndex]
        : sortedTimeVals[columnIndex]
      handleSetHoverTime(hoverTime.toString())
    }
    this.setState({
      hoveredColumnIndex: columnIndex,
      hoveredRowIndex: rowIndex,
    })
  }

  handleMouseLeave = () => {
    if (this.props.handleSetHoverTime) {
      this.props.handleSetHoverTime(NULL_HOVER_TIME)
      this.setState({
        hoveredColumnIndex: NULL_ARRAY_INDEX,
        hoveredRowIndex: NULL_ARRAY_INDEX,
      })
    }
  }

  handleClickFieldName = clickedFieldName => () => {
    const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = this.props
    const {data, sort} = this.state

    if (clickedFieldName === sort.field) {
      sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING
    } else {
      sort.field = clickedFieldName
      sort.direction = DEFAULT_SORT_DIRECTION
    }

    const {transformedData, sortedTimeVals} = transformTableData(
      data,
      sort,
      fieldOptions,
      tableOptions,
      timeFormat,
      decimalPlaces
    )

    this.setState({
      transformedData,
      sortedTimeVals,
      sort,
    })
  }

  calculateColumnWidth = columnSizerWidth => column => {
    const {index} = column
    const {
      tableOptions: {fixFirstColumn},
    } = this.props
    const {transformedData, columnWidths, totalColumnWidths} = this.state
    const columnCount = _.get(transformedData, ['0', 'length'], 0)
    const columnLabel = transformedData[0][index]

    let adjustedColumnSizerWidth = columnWidths[columnLabel]

    const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
    if (tableWidth > totalColumnWidths) {
      if (columnCount === 1) {
        return columnSizerWidth
      }
      const difference = tableWidth - totalColumnWidths
      const distributeOver =
        fixFirstColumn && columnCount > 1 ? columnCount - 1 : columnCount
      const increment = difference / distributeOver
      adjustedColumnSizerWidth =
        fixFirstColumn && index === 0
          ? columnWidths[columnLabel]
          : columnWidths[columnLabel] + increment
    }

    return adjustedColumnSizerWidth
  }

  createCellContents = (
    cellData,
    fieldName,
    isTimeData,
    isFieldName,
    isNumerical
  ) => {
    const {timeFormat, decimalPlaces} = this.props
    if (isTimeData) {
      return `${moment(cellData).format(timeFormat)}`
    }
    if (isFieldName) {
      return fieldName
    }
    if (isNumerical && decimalPlaces.isEnforced) {
      return cellData.toFixed(decimalPlaces.digits)
    }
    return `${cellData}`
  }

  cellRenderer = ({columnIndex, rowIndex, key, parent, style}) => {
    const {
      hoveredColumnIndex,
      hoveredRowIndex,
      transformedData,
      sort,
    } = this.state

    const {
      fieldOptions = [DEFAULT_TIME_FIELD],
      tableOptions,
      colors,
    } = parent.props

    const {
      verticalTimeAxis = DEFAULT_VERTICAL_TIME_AXIS,
      fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN,
    } = tableOptions

    const cellData = transformedData[rowIndex][columnIndex]

    const timeFieldIndex = fieldOptions.findIndex(
      field => field.internalName === DEFAULT_TIME_FIELD.internalName
    )

    const visibleTime = _.get(fieldOptions, [timeFieldIndex, 'visible'], true)

    const isFixedRow = rowIndex === 0 && columnIndex > 0
    const isFixedColumn = fixFirstColumn && rowIndex > 0 && columnIndex === 0
    const isTimeData =
      visibleTime &&
      (verticalTimeAxis
        ? rowIndex !== 0 && columnIndex === timeFieldIndex
        : rowIndex === timeFieldIndex && columnIndex !== 0)
    const isFieldName = verticalTimeAxis ? rowIndex === 0 : columnIndex === 0
    const isFixedCorner = rowIndex === 0 && columnIndex === 0
    const isNumerical = _.isNumber(cellData)
    const isHighlightedRow =
      rowIndex === parent.props.scrollToRow ||
      (rowIndex === hoveredRowIndex && hoveredRowIndex !== 0)
    const isHighlightedColumn =
      columnIndex === parent.props.scrollToColumn ||
      (columnIndex === hoveredColumnIndex && hoveredColumnIndex !== 0)

    let cellStyle = style

    if (!isFixedRow && !isFixedColumn && !isFixedCorner && !isTimeData) {
      const {bgColor, textColor} = generateThresholdsListHexs({
        colors,
        lastValue: cellData,
        cellType: 'table',
      })

      cellStyle = {
        ...style,
        backgroundColor: bgColor,
        color: textColor,
      }
    }

    const foundField =
      isFieldName && fieldOptions.find(field => field.internalName === cellData)
    const fieldName =
      foundField && (foundField.displayName || foundField.internalName)

    const cellClass = classnames('table-graph-cell', {
      'table-graph-cell__fixed-row': isFixedRow,
      'table-graph-cell__fixed-column': isFixedColumn,
      'table-graph-cell__fixed-corner': isFixedCorner,
      'table-graph-cell__highlight-row': isHighlightedRow,
      'table-graph-cell__highlight-column': isHighlightedColumn,
      'table-graph-cell__numerical': isNumerical,
      'table-graph-cell__field-name': isFieldName,
      'table-graph-cell__sort-asc':
        isFieldName && sort.field === cellData && sort.direction === ASCENDING,
      'table-graph-cell__sort-desc':
        isFieldName && sort.field === cellData && sort.direction === DESCENDING,
    })

    const cellContents = this.createCellContents(
      cellData,
      fieldName,
      isTimeData,
      isFieldName,
      isNumerical
    )

    return (
      <div
        key={key}
        style={cellStyle}
        className={cellClass}
        onClick={isFieldName ? this.handleClickFieldName(cellData) : null}
        onMouseOver={_.throttle(this.handleHover(columnIndex, rowIndex), 100)}
        title={cellContents}
      >
        {cellContents}
      </div>
    )
  }

  getMultiGridRef = (r, registerChild) => {
    this.multiGridRef = r
    return registerChild(r)
  }

  render() {
    const {
      hoveredColumnIndex,
      hoveredRowIndex,
      timeColumnWidth,
      sort,
      transformedData,
    } = this.state
    const {
      hoverTime,
      tableOptions,
      colors,
      fieldOptions,
      timeFormat,
      decimalPlaces,
    } = this.props
    const {fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN} = tableOptions
    const columnCount = _.get(transformedData, ['0', 'length'], 0)
    const rowCount = columnCount === 0 ? 0 : transformedData.length

    const COLUMN_MIN_WIDTH = 100
    const COLUMN_MAX_WIDTH = 1000
    const ROW_HEIGHT = 30

    const fixedColumnCount = fixFirstColumn && columnCount > 1 ? 1 : undefined

    const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
    const tableHeight = _.get(this, ['gridContainer', 'clientHeight'], 0)
    const {scrollToColumn, scrollToRow} = this.calcScrollToColRow()
    return (
      <div
        className="table-graph-container"
        ref={gridContainer => (this.gridContainer = gridContainer)}
        onMouseLeave={this.handleMouseLeave}
      >
        {rowCount > 0 && (
          <ColumnSizer
            columnCount={columnCount}
            columnMaxWidth={COLUMN_MAX_WIDTH}
            columnMinWidth={COLUMN_MIN_WIDTH}
            width={tableWidth}
          >
            {({columnWidth, registerChild}) => (
              <MultiGrid
                ref={r => this.getMultiGridRef(r, registerChild)}
                columnCount={columnCount}
                columnWidth={this.calculateColumnWidth(columnWidth)}
                rowCount={rowCount}
                rowHeight={ROW_HEIGHT}
                height={tableHeight}
                width={tableWidth}
                fixedColumnCount={fixedColumnCount}
                fixedRowCount={1}
                enableFixedColumnScroll={true}
                enableFixedRowScroll={true}
                scrollToRow={scrollToRow}
                scrollToColumn={scrollToColumn}
                sort={sort}
                cellRenderer={this.cellRenderer}
                hoveredColumnIndex={hoveredColumnIndex}
                hoveredRowIndex={hoveredRowIndex}
                hoverTime={hoverTime}
                colors={colors}
                fieldOptions={fieldOptions}
                tableOptions={tableOptions}
                timeFormat={timeFormat}
                decimalPlaces={decimalPlaces}
                timeColumnWidth={timeColumnWidth}
                classNameBottomRightGrid="table-graph--scroll-window"
              />
            )}
          </ColumnSizer>
        )}
      </div>
    )
  }
}
const {arrayOf, bool, number, shape, string, func} = PropTypes

TableGraph.propTypes = {
  data: arrayOf(shape()),
  tableOptions: shape({
    verticalTimeAxis: bool.isRequired,
    sortBy: shape({
      internalName: string.isRequired,
      displayName: string.isRequired,
      visible: bool.isRequired,
    }).isRequired,
    wrapping: string.isRequired,
    fixFirstColumn: bool.isRequired,
  }),
  timeFormat: string.isRequired,
  decimalPlaces: shape({
    isEnforced: bool.isRequired,
    digits: number.isRequired,
  }).isRequired,
  fieldOptions: arrayOf(
    shape({
      internalName: string.isRequired,
      displayName: string.isRequired,
      visible: bool.isRequired,
    })
  ).isRequired,
  hoverTime: string,
  handleUpdateFieldOptions: func,
  handleSetHoverTime: func,
  colors: colorsStringSchema,
  isInCEO: bool,
}

const mapDispatchToProps = dispatch => ({
  handleUpdateFieldOptions: bindActionCreators(updateFieldOptions, dispatch),
})

export default connect(null, mapDispatchToProps)(TableGraph)
<commit_msg>Set redux field options at table mount<commit_after>import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'
import {connect} from 'react-redux'

import {MultiGrid, ColumnSizer} from 'react-virtualized'
import {bindActionCreators} from 'redux'
import moment from 'moment'
import {reduce} from 'fast.js'

import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers'
import {
  computeFieldOptions,
  transformTableData,
} from 'src/dashboards/utils/tableGraph'
import {updateFieldOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {DEFAULT_TIME_FIELD} from 'src/dashboards/constants'
import {
  ASCENDING,
  DESCENDING,
  NULL_HOVER_TIME,
  NULL_ARRAY_INDEX,
  DEFAULT_FIX_FIRST_COLUMN,
  DEFAULT_VERTICAL_TIME_AXIS,
  DEFAULT_SORT_DIRECTION,
} from 'src/shared/constants/tableGraph'
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
import {colorsStringSchema} from 'shared/schemas'
import {ErrorHandling} from 'src/shared/decorators/errors'

@ErrorHandling
class TableGraph extends Component {
  constructor(props) {
    super(props)

    const sortField = _.get(
      this.props,
      ['tableOptions', 'sortBy', 'internalName'],
      DEFAULT_TIME_FIELD.internalName
    )

    this.state = {
      data: [[]],
      transformedData: [[]],
      sortedTimeVals: [],
      sortedLabels: [],
      hoveredColumnIndex: NULL_ARRAY_INDEX,
      hoveredRowIndex: NULL_ARRAY_INDEX,
      sort: {field: sortField, direction: DEFAULT_SORT_DIRECTION},
      columnWidths: {},
      totalColumnWidths: 0,
    }
  }

  componentDidMount() {
    const sortField = _.get(
      this.props,
      ['tableOptions', 'sortBy', 'internalName'],
      DEFAULT_TIME_FIELD.internalName
    )
    const sort = {field: sortField, direction: DEFAULT_SORT_DIRECTION}
    const {
      data,
      tableOptions,
      timeFormat,
      fieldOptions,
      decimalPlaces,
    } = this.props
    const result = timeSeriesToTableGraph(data)
    const sortedLabels = result.sortedLabels

    const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels)

    if (!_.isEqual(computedFieldOptions, fieldOptions)) {
      this.handleUpdateFieldOptions(computedFieldOptions)
    }

    const {
      transformedData,
      sortedTimeVals,
      columnWidths,
      totalWidths,
    } = transformTableData(
      result.data,
      sort,
      computedFieldOptions,
      tableOptions,
      timeFormat,
      decimalPlaces
    )

    this.setState({
      transformedData,
      sortedTimeVals,
      columnWidths,
      data: result.data,
      sortedLabels,
      totalColumnWidths: totalWidths,
      hoveredColumnIndex: NULL_ARRAY_INDEX,
      hoveredRowIndex: NULL_ARRAY_INDEX,
      sort,
    })
  }

  handleUpdateFieldOptions = fieldOptions => {
    const {isInCEO} = this.props
    if (!isInCEO) {
      return
    }
    this.props.handleUpdateFieldOptions(fieldOptions)
  }

  componentWillReceiveProps(nextProps) {
    const updatedProps = _.keys(nextProps).filter(
      k => !_.isEqual(this.props[k], nextProps[k])
    )
    const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = nextProps

    let result = {}

    if (_.includes(updatedProps, 'data')) {
      result = timeSeriesToTableGraph(nextProps.data)
    }

    const data = _.get(result, 'data', this.state.data)
    const sortedLabels = _.get(result, 'sortedLabels', this.state.sortedLabels)

    const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels)

    if (_.includes(updatedProps, 'data')) {
      this.handleUpdateFieldOptions(computedFieldOptions)
    }

    if (_.isEmpty(data[0])) {
      return
    }

    const {sort} = this.state
    const internalName = _.get(
      nextProps,
      ['tableOptions', 'sortBy', 'internalName'],
      ''
    )
    if (
      !_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
      internalName
    ) {
      sort.direction = DEFAULT_SORT_DIRECTION
      sort.field = internalName
    }

    if (
      _.includes(updatedProps, 'data') ||
      _.includes(updatedProps, 'tableOptions') ||
      _.includes(updatedProps, 'fieldOptions') ||
      _.includes(updatedProps, 'timeFormat')
    ) {
      const {
        transformedData,
        sortedTimeVals,
        columnWidths,
        totalWidths,
      } = transformTableData(
        data,
        sort,
        computedFieldOptions,
        tableOptions,
        timeFormat,
        decimalPlaces
      )

      this.setState({
        data,
        sortedLabels,
        transformedData,
        sortedTimeVals,
        sort,
        columnWidths,
        totalColumnWidths: totalWidths,
      })
    }
  }

  calcScrollToColRow = () => {
    const {data, sortedTimeVals, hoveredColumnIndex} = this.state
    const {hoverTime, tableOptions} = this.props
    const hoveringThisTable = hoveredColumnIndex !== NULL_ARRAY_INDEX
    const notHovering = hoverTime === NULL_HOVER_TIME
    if (_.isEmpty(data[0]) || notHovering || hoveringThisTable) {
      return {scrollToColumn: undefined, scrollToRow: undefined}
    }

    const firstDiff = Math.abs(hoverTime - sortedTimeVals[1]) // sortedTimeVals[0] is "time"
    const hoverTimeFound = reduce(
      sortedTimeVals,
      (acc, currentTime, index) => {
        const thisDiff = Math.abs(hoverTime - currentTime)
        if (thisDiff < acc.diff) {
          return {index, diff: thisDiff}
        }
        return acc
      },
      {index: 1, diff: firstDiff}
    )

    const {verticalTimeAxis} = tableOptions
    const scrollToColumn = verticalTimeAxis ? undefined : hoverTimeFound.index
    const scrollToRow = verticalTimeAxis ? hoverTimeFound.index : undefined
    return {scrollToRow, scrollToColumn}
  }

  handleHover = (columnIndex, rowIndex) => () => {
    const {
      handleSetHoverTime,
      tableOptions: {verticalTimeAxis},
    } = this.props
    const {sortedTimeVals} = this.state
    if (verticalTimeAxis && rowIndex === 0) {
      return
    }
    if (handleSetHoverTime) {
      const hoverTime = verticalTimeAxis
        ? sortedTimeVals[rowIndex]
        : sortedTimeVals[columnIndex]
      handleSetHoverTime(hoverTime.toString())
    }
    this.setState({
      hoveredColumnIndex: columnIndex,
      hoveredRowIndex: rowIndex,
    })
  }

  handleMouseLeave = () => {
    if (this.props.handleSetHoverTime) {
      this.props.handleSetHoverTime(NULL_HOVER_TIME)
      this.setState({
        hoveredColumnIndex: NULL_ARRAY_INDEX,
        hoveredRowIndex: NULL_ARRAY_INDEX,
      })
    }
  }

  handleClickFieldName = clickedFieldName => () => {
    const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = this.props
    const {data, sort} = this.state

    if (clickedFieldName === sort.field) {
      sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING
    } else {
      sort.field = clickedFieldName
      sort.direction = DEFAULT_SORT_DIRECTION
    }

    const {transformedData, sortedTimeVals} = transformTableData(
      data,
      sort,
      fieldOptions,
      tableOptions,
      timeFormat,
      decimalPlaces
    )

    this.setState({
      transformedData,
      sortedTimeVals,
      sort,
    })
  }

  calculateColumnWidth = columnSizerWidth => column => {
    const {index} = column
    const {
      tableOptions: {fixFirstColumn},
    } = this.props
    const {transformedData, columnWidths, totalColumnWidths} = this.state
    const columnCount = _.get(transformedData, ['0', 'length'], 0)
    const columnLabel = transformedData[0][index]

    let adjustedColumnSizerWidth = columnWidths[columnLabel]

    const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
    if (tableWidth > totalColumnWidths) {
      if (columnCount === 1) {
        return columnSizerWidth
      }
      const difference = tableWidth - totalColumnWidths
      const distributeOver =
        fixFirstColumn && columnCount > 1 ? columnCount - 1 : columnCount
      const increment = difference / distributeOver
      adjustedColumnSizerWidth =
        fixFirstColumn && index === 0
          ? columnWidths[columnLabel]
          : columnWidths[columnLabel] + increment
    }

    return adjustedColumnSizerWidth
  }

  createCellContents = (
    cellData,
    fieldName,
    isTimeData,
    isFieldName,
    isNumerical
  ) => {
    const {timeFormat, decimalPlaces} = this.props
    if (isTimeData) {
      return `${moment(cellData).format(timeFormat)}`
    }
    if (isFieldName) {
      return fieldName
    }
    if (isNumerical && decimalPlaces.isEnforced) {
      return cellData.toFixed(decimalPlaces.digits)
    }
    return `${cellData}`
  }

  cellRenderer = ({columnIndex, rowIndex, key, parent, style}) => {
    const {
      hoveredColumnIndex,
      hoveredRowIndex,
      transformedData,
      sort,
    } = this.state

    const {
      fieldOptions = [DEFAULT_TIME_FIELD],
      tableOptions,
      colors,
    } = parent.props

    const {
      verticalTimeAxis = DEFAULT_VERTICAL_TIME_AXIS,
      fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN,
    } = tableOptions

    const cellData = transformedData[rowIndex][columnIndex]

    const timeFieldIndex = fieldOptions.findIndex(
      field => field.internalName === DEFAULT_TIME_FIELD.internalName
    )

    const visibleTime = _.get(fieldOptions, [timeFieldIndex, 'visible'], true)

    const isFixedRow = rowIndex === 0 && columnIndex > 0
    const isFixedColumn = fixFirstColumn && rowIndex > 0 && columnIndex === 0
    const isTimeData =
      visibleTime &&
      (verticalTimeAxis
        ? rowIndex !== 0 && columnIndex === timeFieldIndex
        : rowIndex === timeFieldIndex && columnIndex !== 0)
    const isFieldName = verticalTimeAxis ? rowIndex === 0 : columnIndex === 0
    const isFixedCorner = rowIndex === 0 && columnIndex === 0
    const isNumerical = _.isNumber(cellData)
    const isHighlightedRow =
      rowIndex === parent.props.scrollToRow ||
      (rowIndex === hoveredRowIndex && hoveredRowIndex !== 0)
    const isHighlightedColumn =
      columnIndex === parent.props.scrollToColumn ||
      (columnIndex === hoveredColumnIndex && hoveredColumnIndex !== 0)

    let cellStyle = style

    if (!isFixedRow && !isFixedColumn && !isFixedCorner && !isTimeData) {
      const {bgColor, textColor} = generateThresholdsListHexs({
        colors,
        lastValue: cellData,
        cellType: 'table',
      })

      cellStyle = {
        ...style,
        backgroundColor: bgColor,
        color: textColor,
      }
    }

    const foundField =
      isFieldName && fieldOptions.find(field => field.internalName === cellData)
    const fieldName =
      foundField && (foundField.displayName || foundField.internalName)

    const cellClass = classnames('table-graph-cell', {
      'table-graph-cell__fixed-row': isFixedRow,
      'table-graph-cell__fixed-column': isFixedColumn,
      'table-graph-cell__fixed-corner': isFixedCorner,
      'table-graph-cell__highlight-row': isHighlightedRow,
      'table-graph-cell__highlight-column': isHighlightedColumn,
      'table-graph-cell__numerical': isNumerical,
      'table-graph-cell__field-name': isFieldName,
      'table-graph-cell__sort-asc':
        isFieldName && sort.field === cellData && sort.direction === ASCENDING,
      'table-graph-cell__sort-desc':
        isFieldName && sort.field === cellData && sort.direction === DESCENDING,
    })

    const cellContents = this.createCellContents(
      cellData,
      fieldName,
      isTimeData,
      isFieldName,
      isNumerical
    )

    return (
      <div
        key={key}
        style={cellStyle}
        className={cellClass}
        onClick={isFieldName ? this.handleClickFieldName(cellData) : null}
        onMouseOver={_.throttle(this.handleHover(columnIndex, rowIndex), 100)}
        title={cellContents}
      >
        {cellContents}
      </div>
    )
  }

  getMultiGridRef = (r, registerChild) => {
    this.multiGridRef = r
    return registerChild(r)
  }

  render() {
    const {
      hoveredColumnIndex,
      hoveredRowIndex,
      timeColumnWidth,
      sort,
      transformedData,
    } = this.state
    const {
      hoverTime,
      tableOptions,
      colors,
      fieldOptions,
      timeFormat,
      decimalPlaces,
    } = this.props
    const {fixFirstColumn = DEFAULT_FIX_FIRST_COLUMN} = tableOptions
    const columnCount = _.get(transformedData, ['0', 'length'], 0)
    const rowCount = columnCount === 0 ? 0 : transformedData.length

    const COLUMN_MIN_WIDTH = 100
    const COLUMN_MAX_WIDTH = 1000
    const ROW_HEIGHT = 30

    const fixedColumnCount = fixFirstColumn && columnCount > 1 ? 1 : undefined

    const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
    const tableHeight = _.get(this, ['gridContainer', 'clientHeight'], 0)
    const {scrollToColumn, scrollToRow} = this.calcScrollToColRow()
    return (
      <div
        className="table-graph-container"
        ref={gridContainer => (this.gridContainer = gridContainer)}
        onMouseLeave={this.handleMouseLeave}
      >
        {rowCount > 0 && (
          <ColumnSizer
            columnCount={columnCount}
            columnMaxWidth={COLUMN_MAX_WIDTH}
            columnMinWidth={COLUMN_MIN_WIDTH}
            width={tableWidth}
          >
            {({columnWidth, registerChild}) => (
              <MultiGrid
                ref={r => this.getMultiGridRef(r, registerChild)}
                columnCount={columnCount}
                columnWidth={this.calculateColumnWidth(columnWidth)}
                rowCount={rowCount}
                rowHeight={ROW_HEIGHT}
                height={tableHeight}
                width={tableWidth}
                fixedColumnCount={fixedColumnCount}
                fixedRowCount={1}
                enableFixedColumnScroll={true}
                enableFixedRowScroll={true}
                scrollToRow={scrollToRow}
                scrollToColumn={scrollToColumn}
                sort={sort}
                cellRenderer={this.cellRenderer}
                hoveredColumnIndex={hoveredColumnIndex}
                hoveredRowIndex={hoveredRowIndex}
                hoverTime={hoverTime}
                colors={colors}
                fieldOptions={fieldOptions}
                tableOptions={tableOptions}
                timeFormat={timeFormat}
                decimalPlaces={decimalPlaces}
                timeColumnWidth={timeColumnWidth}
                classNameBottomRightGrid="table-graph--scroll-window"
              />
            )}
          </ColumnSizer>
        )}
      </div>
    )
  }
}
const {arrayOf, bool, number, shape, string, func} = PropTypes

TableGraph.propTypes = {
  data: arrayOf(shape()),
  tableOptions: shape({
    verticalTimeAxis: bool.isRequired,
    sortBy: shape({
      internalName: string.isRequired,
      displayName: string.isRequired,
      visible: bool.isRequired,
    }).isRequired,
    wrapping: string.isRequired,
    fixFirstColumn: bool.isRequired,
  }),
  timeFormat: string.isRequired,
  decimalPlaces: shape({
    isEnforced: bool.isRequired,
    digits: number.isRequired,
  }).isRequired,
  fieldOptions: arrayOf(
    shape({
      internalName: string.isRequired,
      displayName: string.isRequired,
      visible: bool.isRequired,
    })
  ).isRequired,
  hoverTime: string,
  handleUpdateFieldOptions: func,
  handleSetHoverTime: func,
  colors: colorsStringSchema,
  isInCEO: bool,
}

const mapDispatchToProps = dispatch => ({
  handleUpdateFieldOptions: bindActionCreators(updateFieldOptions, dispatch),
})

export default connect(null, mapDispatchToProps)(TableGraph)
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var mout = require('mout');
var Q = require('q');
var promptly = require('promptly');
var RegistryClient = require('bower-registry-client');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('../core/Logger');
var cli = require('../util/cli');
var createError = require('../util/createError');
var defaultConfig = require('../config');

function register(name, url, config) {
    var repository;
    var registryClient;
    var emitter = new EventEmitter();
    var logger = new Logger();
    var force;

    config = mout.object.deepMixIn(config || {}, defaultConfig);
    force = config.force;

    // Bypass any cache
    config.offline = false;
    config.force = true;

    // Ensure the URL starts with git://
    // TODO: After the registry server is rewritten this might change
    if (!mout.string.startsWith(url, 'git://')) {
        process.nextTick(function () {
            emitter.emit('error', createError('The registry only accepts URLs starting with git://', 'EINVFORMAT'));
        });

        return emitter;
    }

    // Attempt to resolve the package referenced by the URL to ensure
    // everything is ok before registering
    repository = new PackageRepository(config, logger);
    repository.fetch({ name: '', source: url, target: '*' })
    .then(function () {
        // If non interactive or use run with force, bypass confirmation
        if (!config.interactive || force) {
            return true;
        }

        // Confirm if the user really wants to register
        return Q.nfcall(promptly.confirm, 'Registering a package will make it visible and installable via the registry, continue? (y/n)');
    })
    .then(function (result) {
        // If user response was negative, abort
        if (!result) {
            return;
        }

        // Register
        logger.action('register', url, {
            name: name,
            url: url
        });

        config.cache = config.storage.registry;
        registryClient = new RegistryClient(config);

        return Q.nfcall(registryClient.register.bind(registryClient), name, url);
    })
    .then(function (result) {
        emitter.emit('end', result);
    })
    .fail(function (error) {
        emitter.emit('error', error);
    });

    return logger.pipe(emitter);
}

// -------------------

register.line = function (argv) {
    var options = register.options(argv);
    var name = options.argv.remain[1];
    var url = options.argv.remain[2];

    if (!name || !url) {
        return null;
    }

    return register(name, url);
};

register.options = function (argv) {
    return cli.readOptions(argv);
};

register.completion = function () {
    // TODO:
};

module.exports = register;
<commit_msg>Minor doc typo.<commit_after>var EventEmitter = require('events').EventEmitter;
var mout = require('mout');
var Q = require('q');
var promptly = require('promptly');
var RegistryClient = require('bower-registry-client');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('../core/Logger');
var cli = require('../util/cli');
var createError = require('../util/createError');
var defaultConfig = require('../config');

function register(name, url, config) {
    var repository;
    var registryClient;
    var emitter = new EventEmitter();
    var logger = new Logger();
    var force;

    config = mout.object.deepMixIn(config || {}, defaultConfig);
    force = config.force;

    // Bypass any cache
    config.offline = false;
    config.force = true;

    // Ensure the URL starts with git://
    // TODO: After the registry server is rewritten this might change
    if (!mout.string.startsWith(url, 'git://')) {
        process.nextTick(function () {
            emitter.emit('error', createError('The registry only accepts URLs starting with git://', 'EINVFORMAT'));
        });

        return emitter;
    }

    // Attempt to resolve the package referenced by the URL to ensure
    // everything is ok before registering
    repository = new PackageRepository(config, logger);
    repository.fetch({ name: '', source: url, target: '*' })
    .then(function () {
        // If non interactive or user forced, bypass confirmation
        if (!config.interactive || force) {
            return true;
        }

        // Confirm if the user really wants to register
        return Q.nfcall(promptly.confirm, 'Registering a package will make it visible and installable via the registry, continue? (y/n)');
    })
    .then(function (result) {
        // If user response was negative, abort
        if (!result) {
            return;
        }

        // Register
        logger.action('register', url, {
            name: name,
            url: url
        });

        config.cache = config.storage.registry;
        registryClient = new RegistryClient(config);

        return Q.nfcall(registryClient.register.bind(registryClient), name, url);
    })
    .then(function (result) {
        emitter.emit('end', result);
    })
    .fail(function (error) {
        emitter.emit('error', error);
    });

    return logger.pipe(emitter);
}

// -------------------

register.line = function (argv) {
    var options = register.options(argv);
    var name = options.argv.remain[1];
    var url = options.argv.remain[2];

    if (!name || !url) {
        return null;
    }

    return register(name, url);
};

register.options = function (argv) {
    return cli.readOptions(argv);
};

register.completion = function () {
    // TODO:
};

module.exports = register;
<|endoftext|>


<commit_before>var ComputeSummaries = {

  stock_buys: {},
  stock_sells: {},
  stock_summary: {},
  stock_events: {},

  stock_details: {},
  total_portfolio_value: 0.0,

  today: new Date(),

  init: function(stocks, trades, dividends) {
    this.stock_buys = {};
    this.stock_sells = {};
    this.stock_summary = {};
    this.stock_events = {};

    this.stock_details = {};
    this.total_portfolio_value = 0.0;

    for (var i = 0; i < stocks.length; i++) {
      var stock = stocks[i];
      var stock_symbol = stock.symbol;
      this.stock_details[stock.symbol] = stock;
      this.stock_events[stock_symbol] = [];
    }

    for (var i = 0; i < dividends.length; i++){
      var dividend = dividends[i];
      if ( dividend.symbol in this.stock_events) {
        this.stock_events[dividend.symbol].push(dividend);
      } else {
        console.error(dividend.symbol + 'was not found in the portfolio!');
      }
    }

    for (var i = 0; i < trades.length; i++) {
      var trade = trades[i];
      if (trade.action == Constants.BUY) {
        this.add_amount(trade.symbol, this.stock_buys, trade.amount, trade.price, trade.fees, trade.date);
      } else if (trade.action == Constants.SELL) {
        this.add_amount(trade.symbol, this.stock_sells, trade.amount, trade.price, -1 * trade.fees, trade.date);
      } else {
        console.error('Unknown trade action: ' + trade.action);
      }

      if (trade.symbol in this.stock_events){
        this.stock_events[trade.symbol].push(trade);
      }
    }

    for (var symbol in this.stock_events){
      this.stock_events[symbol].sort(this.sortEvents);
      var stock_amount = 0;
      var dividends_total = 0;
      var buy_events = [];
      var events = this.stock_events[symbol];
      for (var i = 0; i < events.length; i++){
        if (events[i].hasOwnProperty('action')){
          if (events[i].action == Constants.BUY) {
            stock_amount += events[i].amount;
            buy_events.push(events[i]);
          } else if (events[i].action == Constants.SELL){
            stock_amount -= events[i].amount;
          }
        } else {
          if (events[i].hasOwnProperty('dividend_amount')){
            dividends_total += stock_amount * events[i].dividend_amount;
          }
        }

        days_passed = (this.today - buy_events[0].date) / (1000 * 60 * 60 * 24);
        years = days_passed / 365;
        if (years == 0 || years < 1 || dividends_total == 0) {
          average_return = 'N/A';
        } else {
          average_return = dividends_total / years;
        }
      }
      this.stock_events[symbol] = {dividends_total: dividends_total};
      this.stock_events[symbol].average_return = average_return;
    }

    for (var symbol in this.stock_details) {
      var buy_price = this.getStockBuyPrice(symbol);
      var buy_amount = this.getStockBuyAmount(symbol);
      var buy_price_days = this.getStockBuyPriceDays(symbol);
      var sell_price = this.getStockSellPrice(symbol);
      var sell_amount = this.getStockSellAmount(symbol);
      var sell_price_days = this.getStockSellPriceDays(symbol);
      if (buy_price == 0) {
        continue;
      }

      var avg_buy_price = buy_price / buy_amount;
      var avg_sell_price = sell_price / sell_amount;
      var realized_pl = (avg_sell_price - avg_buy_price) * sell_amount;
      var realized_pl_perc = realized_pl * 100 / (sell_amount * avg_buy_price);

      var left_amount = buy_amount - sell_amount;
      var unrealized_pl = left_amount * (this.stock_details[symbol].price - avg_buy_price);
      var unrealized_pl_perc = unrealized_pl * 100 / (left_amount * avg_buy_price);

      var total_value = left_amount * this.stock_details[symbol].price;
      var weighted_cost = (buy_price_days - sell_price_days) / 365.0;
      var weighted_pl = unrealized_pl * 100 / weighted_cost;

      this.stock_summary[symbol] = {
        description: this.stock_details[symbol].description,
        current_price: this.stock_details[symbol].price,
        avg_buy_price: avg_buy_price,
        avg_sell_price: avg_sell_price,
        realized_pl: realized_pl,
        realized_pl_perc: realized_pl_perc,
        unrealized_pl: unrealized_pl,
        unrealized_pl_perc: unrealized_pl_perc,
        current_amount: left_amount,
        total_value: total_value,
        value_date: this.stock_details[symbol].price_date,
        weighted_cost: weighted_cost,
        weighted_pl: weighted_pl,
        dividends_total: this.stock_events[symbol].dividends_total,
        average_return: this.stock_events[symbol].average_return
      }

      this.total_portfolio_value += this.stock_summary[symbol].total_value;
    }

    for (var symbol in this.stock_summary) {
      this.stock_summary[symbol].portion = this.stock_summary[symbol].total_value * 100 / this.total_portfolio_value;
    }
  },

  add_amount: function(symbol, collection, amount, price, fees, date) {
    days_passed = (this.today - date) / (1000 * 60 * 60 * 24);
    if (symbol in collection) {
      collection[symbol].amount += amount;
      collection[symbol].price += amount * price + fees;
      collection[symbol].price_days += (amount * price + fees) * days_passed;
    } else {
      collection[symbol] = {
        amount: amount,
        price: amount * price + fees,
        price_days: (amount * price + fees) * days_passed
      };
    }
  },

  getStockBuyAmount: function(symbol) {
    if (symbol in this.stock_buys) {
      return this.stock_buys[symbol].amount;
    } else {
      return 0;
    }
  },

  getStockBuyPrice: function(symbol) {
    if (symbol in this.stock_buys) {
      return this.stock_buys[symbol].price;
    } else {
      return 0;
    }
  },

  getStockBuyPriceDays: function(symbol) {
    if (symbol in this.stock_buys) {
      return this.stock_buys[symbol].price_days;
    } else {
      return 0;
    }
  },

  getStockSellAmount: function(symbol) {
    if (symbol in this.stock_sells) {
      return this.stock_sells[symbol].amount;
    } else {
      return 0;
    }
  },

  getStockSellPrice: function(symbol) {
    if (symbol in this.stock_sells) {
      return this.stock_sells[symbol].price;
    } else {
      return 0;
    }
  },

  getStockSellPriceDays: function(symbol) {
    if (symbol in this.stock_sells) {
      return this.stock_sells[symbol].price_days;
    } else {
      return 0;
    }
  },

  getStockSummaries: function() {
    return this.stock_summary;
  },

  sortEvents: function(a,b) {
    return a.date - b.date;
  }
};
<commit_msg>Moved parts of the code into separated functions that are then called inside the init function.<commit_after>var ComputeSummaries = {

  stock_buys: {},
  stock_sells: {},
  stock_summary: {},
  stock_events: {},

  stock_details: {},
  total_portfolio_value: 0.0,

  today: new Date(),

  init: function(stocks, trades, dividends) {
    this.stock_buys = {};
    this.stock_sells = {};
    this.stock_summary = {};
    this.stock_events = {};

    this.stock_details = {};
    this.total_portfolio_value = 0.0;

    this.collectStockDetails(stocks);
    this.collectStockEvents(dividends);

    for (var i = 0; i < trades.length; i++) {
      var trade = trades[i];
      if (trade.action == Constants.BUY) {
        this.add_amount(trade.symbol, this.stock_buys, trade.amount, trade.price, trade.fees, trade.date);
      } else if (trade.action == Constants.SELL) {
        this.add_amount(trade.symbol, this.stock_sells, trade.amount, trade.price, -1 * trade.fees, trade.date);
      } else {
        console.error('Unknown trade action: ' + trade.action);
      }

      if (trade.symbol in this.stock_events){
        this.stock_events[trade.symbol].push(trade);
      }
    }

    this.computeDividendStats();

    for (var symbol in this.stock_details) {
      var buy_price = this.getStockBuyPrice(symbol);
      var buy_amount = this.getStockBuyAmount(symbol);
      var buy_price_days = this.getStockBuyPriceDays(symbol);
      var sell_price = this.getStockSellPrice(symbol);
      var sell_amount = this.getStockSellAmount(symbol);
      var sell_price_days = this.getStockSellPriceDays(symbol);
      if (buy_price == 0) {
        continue;
      }

      var avg_buy_price = buy_price / buy_amount;
      var avg_sell_price = sell_price / sell_amount;
      var realized_pl = (avg_sell_price - avg_buy_price) * sell_amount;
      var realized_pl_perc = realized_pl * 100 / (sell_amount * avg_buy_price);

      var left_amount = buy_amount - sell_amount;
      var unrealized_pl = left_amount * (this.stock_details[symbol].price - avg_buy_price);
      var unrealized_pl_perc = unrealized_pl * 100 / (left_amount * avg_buy_price);

      var total_value = left_amount * this.stock_details[symbol].price;
      var weighted_cost = (buy_price_days - sell_price_days) / 365.0;
      var weighted_pl = unrealized_pl * 100 / weighted_cost;

      this.stock_summary[symbol] = {
        description: this.stock_details[symbol].description,
        current_price: this.stock_details[symbol].price,
        avg_buy_price: avg_buy_price,
        avg_sell_price: avg_sell_price,
        realized_pl: realized_pl,
        realized_pl_perc: realized_pl_perc,
        unrealized_pl: unrealized_pl,
        unrealized_pl_perc: unrealized_pl_perc,
        current_amount: left_amount,
        total_value: total_value,
        value_date: this.stock_details[symbol].price_date,
        weighted_cost: weighted_cost,
        weighted_pl: weighted_pl,
        dividends_total: this.stock_events[symbol].dividends_total,
        average_return: this.stock_events[symbol].average_return
      }

      this.total_portfolio_value += this.stock_summary[symbol].total_value;
    }

    for (var symbol in this.stock_summary) {
      this.stock_summary[symbol].portion = this.stock_summary[symbol].total_value * 100 / this.total_portfolio_value;
    }
  },

  add_amount: function(symbol, collection, amount, price, fees, date) {
    days_passed = (this.today - date) / (1000 * 60 * 60 * 24);
    if (symbol in collection) {
      collection[symbol].amount += amount;
      collection[symbol].price += amount * price + fees;
      collection[symbol].price_days += (amount * price + fees) * days_passed;
    } else {
      collection[symbol] = {
        amount: amount,
        price: amount * price + fees,
        price_days: (amount * price + fees) * days_passed
      };
    }
  },

  getStockBuyAmount: function(symbol) {
    if (symbol in this.stock_buys) {
      return this.stock_buys[symbol].amount;
    } else {
      return 0;
    }
  },

  getStockBuyPrice: function(symbol) {
    if (symbol in this.stock_buys) {
      return this.stock_buys[symbol].price;
    } else {
      return 0;
    }
  },

  getStockBuyPriceDays: function(symbol) {
    if (symbol in this.stock_buys) {
      return this.stock_buys[symbol].price_days;
    } else {
      return 0;
    }
  },

  getStockSellAmount: function(symbol) {
    if (symbol in this.stock_sells) {
      return this.stock_sells[symbol].amount;
    } else {
      return 0;
    }
  },

  getStockSellPrice: function(symbol) {
    if (symbol in this.stock_sells) {
      return this.stock_sells[symbol].price;
    } else {
      return 0;
    }
  },

  getStockSellPriceDays: function(symbol) {
    if (symbol in this.stock_sells) {
      return this.stock_sells[symbol].price_days;
    } else {
      return 0;
    }
  },

  getStockSummaries: function() {
    return this.stock_summary;
  },

  sortEvents: function(a,b) {
    return a.date - b.date;
  },

  collectStockEvents: function(dividends) {
    for (var i = 0; i < dividends.length; i++){
      var dividend = dividends[i];
      if ( dividend.symbol in this.stock_events) {
        this.stock_events[dividend.symbol].push(dividend);
      } else {
        console.error(dividend.symbol + 'was not found in the portfolio!');
      }
    }
  },

  collectStockDetails: function(stocks) {
    for (var i = 0; i < stocks.length; i++) {
      var stock = stocks[i];
      this.stock_details[stock.symbol] = stock;
      this.stock_events[stock.symbol] = [];
    }
  },

  computeDividendStats: function() {
    for (var symbol in this.stock_events){
      this.stock_events[symbol].sort(this.sortEvents);
      var stock_amount = 0;
      var dividends_total = 0;
      var buy_events = [];
      var today = this.today;
      var events = this.stock_events[symbol];

      for (var i = 0; i < events.length; i++){
        if (events[i].hasOwnProperty('action')){
          if (events[i].action == Constants.BUY) {
            stock_amount += events[i].amount;
            buy_events.push(events[i]);
          } else if (events[i].action == Constants.SELL){
            stock_amount -= events[i].amount;
          }
        } else {
          if (events[i].hasOwnProperty('dividend_amount')){
            dividends_total += stock_amount * events[i].dividend_amount;
          }
        }

        this.computeAverageReturn(today, buy_events, dividends_total);
      }
      this.stock_events[symbol] = {dividends_total: dividends_total};
      this.stock_events[symbol].average_return = average_return;
    }
  },

  computeAverageReturn: function(today, buy_events, dividends_total) {
    days_passed = (today - buy_events[0].date) / (1000 * 60 * 60 * 24);
    years = days_passed / 365;
    if (years == 0 || years < 1 || dividends_total == 0) {
      average_return = 'N/A';
    } else {
      average_return = dividends_total / years;
    }
  }
};
<|endoftext|>


<commit_before>var utils = require('keystone-utils');

module.exports = function initDatabase () {
	if (!this.get('mongo')) {
		var dbName = this.get('db name')
			|| utils.slug(this.get('name'));
		var dbUrl = process.env.MONGO_URI
			|| process.env.MONGO_URL
			|| process.env.MONGODB_URL
			|| process.env.MONGOLAB_URI
			|| process.env.MONGOLAB_URL
			|| (process.env.OPENSHIFT_MONGODB_DB_URL
			|| 'mongodb://localhost/') + dbName;
		this.set('mongo', dbUrl);
	}
	return this;
};
<commit_msg>Added support for heroku's mLab connection string <commit_after>var utils = require('keystone-utils');

module.exports = function initDatabase () {
	if (!this.get('mongo')) {
		var dbName = this.get('db name')
			|| utils.slug(this.get('name'));
		var dbUrl = process.env.MONGO_URI
			|| process.env.MONGODB_URI
			|| process.env.MONGO_URL
			|| process.env.MONGODB_URL
			|| process.env.MONGOLAB_URI
			|| process.env.MONGOLAB_URL
			|| (process.env.OPENSHIFT_MONGODB_DB_URL
			|| 'mongodb://localhost/') + dbName;
		this.set('mongo', dbUrl);
	}
	return this;
};
<|endoftext|>


<commit_before>import Octokat from 'octokat';
import { decryptData } from '../utils/cryptData';
import { fetchAll } from '../utils/octokat-helper';
export const TYPE_PULL = 'TYPE_PULL';
export const TYPE_ISSUE = 'TYPE_ISSUE';

function mixRepos(...repos) {
  return Object.assign([], ...repos);
}

function mixWatchedAndNotUserRepos(...repos) {// eslint-disable-line no-unused-vars
  return [];
}

function fetchUserReposPromise({octo, since} = {}) {
  return fetchAll(octo.user.repos.fetch, {
    per_page: 100,
    sort: 'pushed',
    direction: 'desc',
  }).then((value) => {
    return value.filter((repo) => {
      return (repo.pushedAt && repo.pushedAt >= since) || (repo.updatedAt && repo.updatedAt >= since);
    });
  });
}

function fetchWatchedReposPromise({octo, since} = {}) {// eslint-disable-line no-unused-vars
  return Promise.resolve([]);
}

function fetchUserIssuesPromise({octo, since} = {}) {
  return fetchAll(octo.issues.fetch, {
    per_page: 100,
    filter: 'all',
    state: 'all',
    sort: 'updated',
    since: since.toISOString(),
  });
}

function fetchReposIssuesPromise({repos, since} = {}) {// eslint-disable-line no-unused-vars
  return Promise.resolve([]);
}

function fetchReposBranchesPromise({repos} = {}) {
  return Promise.all(
    repos.map((repo) => {
      return fetchAll(repo.branches.fetch, {
        per_page: 100,
      }).then((value) => {
        return {
          branches: value,
          repo: repo,
        };
      });
    })
  );
}

function appendPullRequestPromiseArray(data = []) {
  return data.map((story) => {
    if (!story.issue.pullRequest) {
      return Promise.resolve(Object.assign({}, story, { type: TYPE_ISSUE }));
    }
    return story.issue.pullRequest.fetch({
      per_page: 100,
    }).then((pull) => {
      return Object.assign({}, story, { pull: pull, type: TYPE_PULL });
    });
  });
}

function appendStatusPromiseArray(data = []) {
  return data.map((story) => {
    if (!story.pull) {
      return Promise.resolve(story);
    }
    return story.repo.commits(story.pull.head.sha).status.fetch({
      per_page: 100,
    }).then((status) => {
      return Object.assign({}, story, { status: status });
    });
  });
}

function appendBranchPromiseArray({stories, reposBranches} = {}) {
  return stories.map((story) => {
    if (!story.pull) {
      return Promise.resolve(story);
    }
    const repoBranches = Array.find(reposBranches, (repo) => {
      return repo.repo.id === story.repo.id;
    });
    if (!repoBranches) {
      return Promise.resolve(story);
    }
    const targetBranch = Array.find(repoBranches.branches, (branch) => {
      return branch.name === story.pull.head.ref;
    });
    if (!targetBranch) {
      return Promise.resolve(story);
    }
    return Promise.resolve(
      Object.assign({}, story, { branch: targetBranch })
    );
  });
}

export function fetchCraftedStories(settings = {}) {
  return new Promise((resolve1) => {
    const now = new Date();
    const since = new Date(now - 5 * 24 * 60 * 60 * 1000);
    const token = decryptData(settings.token);
    const octo = new Octokat({
      token: token,
      rootURL: settings.apiendpoint,
      acceptHeader: 'application/vnd.github.v3.text+json',
    });
    let repos;
    let userRepos;
    let watchedRepos;
    let watchedAndNotUserRepos;
    let reposBranches;
    Promise.all([
      fetchUserReposPromise({octo: octo, since: since}),
      fetchWatchedReposPromise({octo: octo, since: since}),
    ]).then((value) => {
      [ userRepos, watchedRepos ] = value;
      repos = mixRepos(userRepos, watchedRepos);
      watchedAndNotUserRepos = mixWatchedAndNotUserRepos(watchedRepos, userRepos);
      return Promise.all([
        fetchUserIssuesPromise({octo: octo, since: since}),
        fetchReposIssuesPromise({repos: watchedAndNotUserRepos, since: since}),
        fetchReposBranchesPromise({repos: repos}),
      ]).then((value2) => {
        const [userIssues, reposIssues, branches] = value2;
        reposBranches = branches;
        // FIXME: single issue does not have repository
        return userIssues.concat(reposIssues).map((issue) => {
          return {
            id: `issue:${issue.id}`,
            issue: issue,
            repo: issue.repository,
          };
        });
      });
    }).then((value) => {
      return Promise.all(appendPullRequestPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendStatusPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendBranchPromiseArray({
        stories: value,
        reposBranches: reposBranches,
      }));
    }).then((value) => {
      resolve1(value);
    });
  });
}

function fetchReposPromiseByIdentifiers({octo, identifiers = []} = {}) {
  return Promise.all(
    identifiers.map((identifier) => {
      return octo.repos(identifier.owner, identifier.repo).fetch();
    })
  );
}

function fetchIssuesPromiseByIdentifiers({octo, identifiers = []} = {}) {
  return Promise.all(
    identifiers.map((identifier) => {
      return Promise.all([
        octo.repos(identifier.owner, identifier.repo)
          .issues(identifier.number)
          .fetch(),
        octo.repos(identifier.owner, identifier.repo).fetch(),
      ]);
    })
  );
}

function toggleIssueState(state) {
  return (state === 'open') ? 'closed' : 'open';
}

function toggleIssuesStatePromiseByIdentifiers({octo, identifiers = []} = {}) {
  return Promise.all(
    identifiers.map((identifier) => {
      return octo.repos(identifier.owner, identifier.repo)
        .issues(identifier.number)
        .fetch()
        .then((value) => {
          const issue = value;
          return issue.update({
            state: toggleIssueState(issue.state),
          });
        });
    })
  );
}

export function fetchCraftedStoriesByIssue({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    const token = decryptData(settings.token);
    const octo = new Octokat({
      token: token,
      rootURL: settings.apiendpoint,
      acceptHeader: 'application/vnd.github.v3.text+json',
    });
    let repos;
    let reposBranches;
    fetchReposPromiseByIdentifiers({
      octo: octo,
      identifiers: identifiers,
    }).then((value) => {
      repos = value;
      return fetchReposBranchesPromise({
        repos: repos,
      });
    }).then((value) => {
      reposBranches = value;
      return fetchIssuesPromiseByIdentifiers({
        octo: octo,
        identifiers: identifiers,
      });
    }).then((value) => {
      return value.map((value2) => {
        const [issue, repository] = value2;
        return {
          id: `issue:${issue.id}`,
          issue: issue,
          repo: repository,
        };
      });
    }).then((value) => {
      return Promise.all(appendPullRequestPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendStatusPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendBranchPromiseArray({
        stories: value,
        reposBranches: reposBranches,
      }));
    }).then((value) => {
      resolve1(value);
    });
  });
}

export function toggleIssuesState({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    const token = decryptData(settings.token);
    const octo = new Octokat({
      token: token,
      rootURL: settings.apiendpoint,
      acceptHeader: 'application/vnd.github.v3.text+json',
    });
    toggleIssuesStatePromiseByIdentifiers({
      octo: octo,
      identifiers: identifiers,
    }).then(() => {
      return fetchCraftedStoriesByIssue({
        settings: settings,
        identifiers: identifiers,
      });
    }).then((value) => {
      resolve1(value);
    });
  });
}

export function deletePullRequestsBranch({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    fetchCraftedStoriesByIssue({
      settings: settings,
      identifiers: identifiers,
    }).then((value) => {
      const targets = value.filter((story) => {
        return story.pull && story.pull.head && story.branch;
      });
      return Promise.all(
        targets.map((story) => {
          return story.pull.head.repo.git.refs.heads(story.pull.head.ref).remove();
        })
      );
    }).then(() => {
      return fetchCraftedStoriesByIssue({
        settings: settings,
        identifiers: identifiers,
      });
    }).then((value) => {
      resolve1(value);
    });
  });
}

export function mergePullRequests({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    fetchCraftedStoriesByIssue({
      settings: settings,
      identifiers: identifiers,
    }).then((value) => {
      resolve1(value);
    });
  });
}
<commit_msg>feat(merge): merge implementation<commit_after>import Octokat from 'octokat';
import { decryptData } from '../utils/cryptData';
import { fetchAll } from '../utils/octokat-helper';
export const TYPE_PULL = 'TYPE_PULL';
export const TYPE_ISSUE = 'TYPE_ISSUE';

function mixRepos(...repos) {
  return Object.assign([], ...repos);
}

function mixWatchedAndNotUserRepos(...repos) {// eslint-disable-line no-unused-vars
  return [];
}

function fetchUserReposPromise({octo, since} = {}) {
  return fetchAll(octo.user.repos.fetch, {
    per_page: 100,
    sort: 'pushed',
    direction: 'desc',
  }).then((value) => {
    return value.filter((repo) => {
      return (repo.pushedAt && repo.pushedAt >= since) || (repo.updatedAt && repo.updatedAt >= since);
    });
  });
}

function fetchWatchedReposPromise({octo, since} = {}) {// eslint-disable-line no-unused-vars
  return Promise.resolve([]);
}

function fetchUserIssuesPromise({octo, since} = {}) {
  return fetchAll(octo.issues.fetch, {
    per_page: 100,
    filter: 'all',
    state: 'all',
    sort: 'updated',
    since: since.toISOString(),
  });
}

function fetchReposIssuesPromise({repos, since} = {}) {// eslint-disable-line no-unused-vars
  return Promise.resolve([]);
}

function fetchReposBranchesPromise({repos} = {}) {
  return Promise.all(
    repos.map((repo) => {
      return fetchAll(repo.branches.fetch, {
        per_page: 100,
      }).then((value) => {
        return {
          branches: value,
          repo: repo,
        };
      });
    })
  );
}

function appendPullRequestPromiseArray(data = []) {
  return data.map((story) => {
    if (!story.issue.pullRequest) {
      return Promise.resolve(Object.assign({}, story, { type: TYPE_ISSUE }));
    }
    return story.issue.pullRequest.fetch({
      per_page: 100,
    }).then((pull) => {
      return Object.assign({}, story, { pull: pull, type: TYPE_PULL });
    });
  });
}

function appendStatusPromiseArray(data = []) {
  return data.map((story) => {
    if (!story.pull) {
      return Promise.resolve(story);
    }
    return story.repo.commits(story.pull.head.sha).status.fetch({
      per_page: 100,
    }).then((status) => {
      return Object.assign({}, story, { status: status });
    });
  });
}

function appendBranchPromiseArray({stories, reposBranches} = {}) {
  return stories.map((story) => {
    if (!story.pull) {
      return Promise.resolve(story);
    }
    const repoBranches = Array.find(reposBranches, (repo) => {
      return repo.repo.id === story.repo.id;
    });
    if (!repoBranches) {
      return Promise.resolve(story);
    }
    const targetBranch = Array.find(repoBranches.branches, (branch) => {
      return branch.name === story.pull.head.ref;
    });
    if (!targetBranch) {
      return Promise.resolve(story);
    }
    return Promise.resolve(
      Object.assign({}, story, { branch: targetBranch })
    );
  });
}

export function fetchCraftedStories(settings = {}) {
  return new Promise((resolve1) => {
    const now = new Date();
    const since = new Date(now - 5 * 24 * 60 * 60 * 1000);
    const token = decryptData(settings.token);
    const octo = new Octokat({
      token: token,
      rootURL: settings.apiendpoint,
      acceptHeader: 'application/vnd.github.v3.text+json',
    });
    let repos;
    let userRepos;
    let watchedRepos;
    let watchedAndNotUserRepos;
    let reposBranches;
    Promise.all([
      fetchUserReposPromise({octo: octo, since: since}),
      fetchWatchedReposPromise({octo: octo, since: since}),
    ]).then((value) => {
      [ userRepos, watchedRepos ] = value;
      repos = mixRepos(userRepos, watchedRepos);
      watchedAndNotUserRepos = mixWatchedAndNotUserRepos(watchedRepos, userRepos);
      return Promise.all([
        fetchUserIssuesPromise({octo: octo, since: since}),
        fetchReposIssuesPromise({repos: watchedAndNotUserRepos, since: since}),
        fetchReposBranchesPromise({repos: repos}),
      ]).then((value2) => {
        const [userIssues, reposIssues, branches] = value2;
        reposBranches = branches;
        // FIXME: single issue does not have repository
        return userIssues.concat(reposIssues).map((issue) => {
          return {
            id: `issue:${issue.id}`,
            issue: issue,
            repo: issue.repository,
          };
        });
      });
    }).then((value) => {
      return Promise.all(appendPullRequestPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendStatusPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendBranchPromiseArray({
        stories: value,
        reposBranches: reposBranches,
      }));
    }).then((value) => {
      resolve1(value);
    });
  });
}

function fetchReposPromiseByIdentifiers({octo, identifiers = []} = {}) {
  return Promise.all(
    identifiers.map((identifier) => {
      return octo.repos(identifier.owner, identifier.repo).fetch();
    })
  );
}

function fetchIssuesPromiseByIdentifiers({octo, identifiers = []} = {}) {
  return Promise.all(
    identifiers.map((identifier) => {
      return Promise.all([
        octo.repos(identifier.owner, identifier.repo)
          .issues(identifier.number)
          .fetch(),
        octo.repos(identifier.owner, identifier.repo).fetch(),
      ]);
    })
  );
}

function toggleIssueState(state) {
  return (state === 'open') ? 'closed' : 'open';
}

function toggleIssuesStatePromiseByIdentifiers({octo, identifiers = []} = {}) {
  return Promise.all(
    identifiers.map((identifier) => {
      return octo.repos(identifier.owner, identifier.repo)
        .issues(identifier.number)
        .fetch()
        .then((value) => {
          const issue = value;
          return issue.update({
            state: toggleIssueState(issue.state),
          });
        });
    })
  );
}

function mergePullRequestPromiseByIdentifiers({octo, identifiers = []} = {}) {
  return Promise.all(
    identifiers.map((identifier) => {
      return octo.repos(identifier.owner, identifier.repo)
        .pulls(identifier.number)
        .merge
        .add({});
    })
  );
}

export function fetchCraftedStoriesByIssue({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    const token = decryptData(settings.token);
    const octo = new Octokat({
      token: token,
      rootURL: settings.apiendpoint,
      acceptHeader: 'application/vnd.github.v3.text+json',
    });
    let repos;
    let reposBranches;
    fetchReposPromiseByIdentifiers({
      octo: octo,
      identifiers: identifiers,
    }).then((value) => {
      repos = value;
      return fetchReposBranchesPromise({
        repos: repos,
      });
    }).then((value) => {
      reposBranches = value;
      return fetchIssuesPromiseByIdentifiers({
        octo: octo,
        identifiers: identifiers,
      });
    }).then((value) => {
      return value.map((value2) => {
        const [issue, repository] = value2;
        return {
          id: `issue:${issue.id}`,
          issue: issue,
          repo: repository,
        };
      });
    }).then((value) => {
      return Promise.all(appendPullRequestPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendStatusPromiseArray(value));
    }).then((value) => {
      return Promise.all(appendBranchPromiseArray({
        stories: value,
        reposBranches: reposBranches,
      }));
    }).then((value) => {
      resolve1(value);
    });
  });
}

export function toggleIssuesState({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    const token = decryptData(settings.token);
    const octo = new Octokat({
      token: token,
      rootURL: settings.apiendpoint,
      acceptHeader: 'application/vnd.github.v3.text+json',
    });
    toggleIssuesStatePromiseByIdentifiers({
      octo: octo,
      identifiers: identifiers,
    }).then(() => {
      return fetchCraftedStoriesByIssue({
        settings: settings,
        identifiers: identifiers,
      });
    }).then((value) => {
      resolve1(value);
    });
  });
}

export function deletePullRequestsBranch({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    fetchCraftedStoriesByIssue({
      settings: settings,
      identifiers: identifiers,
    }).then((value) => {
      const targets = value.filter((story) => {
        return story.pull && story.pull.head && story.branch;
      });
      return Promise.all(
        targets.map((story) => {
          return story.pull.head.repo.git.refs.heads(story.pull.head.ref).remove();
        })
      );
    }).then(() => {
      return fetchCraftedStoriesByIssue({
        settings: settings,
        identifiers: identifiers,
      });
    }).then((value) => {
      resolve1(value);
    });
  });
}

export function mergePullRequests({settings = {}, identifiers = []} = {}) {
  return new Promise((resolve1) => {
    const token = decryptData(settings.token);
    const octo = new Octokat({
      token: token,
      rootURL: settings.apiendpoint,
      acceptHeader: 'application/vnd.github.v3.text+json',
    });
    return mergePullRequestPromiseByIdentifiers({
      octo: octo,
      identifiers: identifiers,
    }).then(() => {
      return fetchCraftedStoriesByIssue({
        settings: settings,
        identifiers: identifiers,
      });
    }).then((value) => {
      resolve1(value);
    });
  });
}
<|endoftext|>