<commit_before>(function(window, app, Backbone, jQuery, _){
    "use strict";

    var $ = jQuery;

    app.GroupView = Backbone.View.extend({
        tagName: 'li',
        className: 'group',
        template: _.template(app.templates.group),

        initialize: function(){
            Backbone.View.prototype.initialize.apply(this, arguments);

            _.bindAll(this, 'updateCount', 'updateUsersSeen', 'updateLastSeen',
                'updateResolved', 'renderSparkline', 'render');

            this.model.on({
                'change:count': this.updateCount,
                'change:usersSeen': this.updateUsersSeen,
                'change:lastSeen': this.updateLastSeen,
                'change:isBookmarked': this.render,
                'change:isResolved': this.updateResolved,
                'change:historicalData': this.renderSparkline
            }, this);
        },

        render: function(){
            var data = this.model.toJSON();
            data.loggerUrl = app.config.urlPrefix + '/' + app.config.teamId +
                '/' + app.config.projectId + '/?logger=' + data.logger;

            this.$el.html(this.template(data));
            this.$el.attr('data-id', this.model.id);
            this.$el.addClass(this.getLevelClassName());
            this.$el.find('a[data-action=resolve]').click(_.bind(function(e){
                e.preventDefault();
                if (this.model.get('isResolved')) {
                    this.unresolve();
                } else {
                    this.resolve();
                }
            }, this));
            this.$el.find('a[data-action=bookmark]').click(_.bind(function(e){
                e.preventDefault();
                this.bookmark();
            }, this));
            this.renderSparkline();
            this.updateResolved();
        },

        updateResolved: function(){
            if (this.model.get('isResolved')) {
                this.$el.addClass('resolved');
            } else {
                this.$el.removeClass('resolved');
            }
        },

        renderSparkline: function(obj){
            var data = this.model.get('historicalData');
            if (!data || !data.length)
                return;

            this.$el.addClass('with-sparkline');

            app.charts.createSparkline(this.$el.find('.sparkline'), data);
        },

        resolve: function(){
            $.ajax({
                url: this.getResolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version);
                    this.model.set('isResolved', true);
                }, this)
            });
        },

        unresolve: function(){
            $.ajax({
                url: this.getUnresolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version);
                    this.model.set('isResolved', false);
                }, this)
            });
        },

        getResolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/resolved/';
        },

        getUnresolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/unresolved/';
        },

        getBookmarkUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' + app.config.projectId + '/bookmark/';
        },

        bookmark: function(){
            $.ajax({
                url: this.getBookmarkUrl(),
                type: 'post',
                dataType: 'json',
                data: {
                    gid: this.model.get('id')
                },
                success: _.bind(function(response){
                    this.model.set('version', response.version);
                    this.model.set('isBookmarked', response.isBookmarked);
                }, this)
            });
        },

        getLevelClassName: function(){
            return 'level-' + this.model.get('levelName');
        },

        updateLastSeen: function(){
            this.$el.find('.last-seen')
                .text(app.utils.prettyDate(this.model.get('lastSeen')))
                .attr('title', this.model.get('lastSeen'));
        },

        updateCount: function(){
            var new_count = app.utils.formatNumber(this.model.get('count'));
            var counter = this.$el.find('.count');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        },

        updateUsersSeen: function(){
            var value = this.model.get('usersSeen')
            if (value === null)
                return;
            var new_count = app.utils.formatNumber(value);
            var counter = this.$el.find('.tag-users');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        }

    });

    app.OrderedElementsView = Backbone.View.extend({

        emptyMessage: '

No events to show.

We\'ll notify you if that changes. In the meantime why not take a moment to become more familiar with Sentry.

Installation instructions Project settings

',
        loadingMessage: '
Loading...
',
        model: app.models.Group,

        defaults: {
            maxItems: 50,
            view: Backbone.View
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            var members = data.members;

            Backbone.View.prototype.initialize.apply(this, arguments);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.$wrapper = $('#' + this.id);
            this.$parent = $('

');
            this.$empty = $('

');
            this.$wrapper.html(this.$parent);

            if (this.options.className)
                this.$parent.addClass(this.options.className);

            _.bindAll(this, 'renderMemberInContainer', 'unrenderMember', 'reSortMembers');

            this.collection = new app.ScoredList([], {
                model: data.model
            });
            this.collection.on('add', this.renderMemberInContainer, this);
            this.collection.on('remove', this.unrenderMember, this);
            this.collection.on('reset', this.reSortMembers, this);

            delete data.members;

            this.reset(members);
        },

        reset: function(members){
            this.$parent.empty();
            this.setEmpty();

            if (members === undefined) {
                this.$empty.html(this.loadingMessage);
                this.collection.reset();
                this.setEmpty();
                this.loaded = false;
            } else {
                this.$empty.html(this.emptyMessage);
                this.collection.reset(members);
                this.loaded = true;
            }
        },

        setEmpty: function(){
            this.$parent.html(this.$empty);
        },

        extend: function(data){
            for (var i=0; i<data.length; i++) {
                this.addMember(data[i]);
            }
        },

        addMember: function(member){
            if (!this.hasMember(member)) {
                if (this.collection.length >= this.options.maxItems) {
                    // bail early if the score is too low
                    if (member.score < this.collection.last().get('score'))
                        return;

                    // make sure we limit the number shown
                    while (this.collection.length >= this.options.maxItems)
                        this.collection.pop();
                }
            }
            this.collection.add(member, {merge: true});
        },

        reSortMembers: function(){
            this.collection.each(_.bind(function(member){
                this.renderMemberInContainer(member);
            }, this));
        },

        updateMember: function(member, options){
            if (_.isUndefined(options))
                options = {};

            var existing = this.collection.get(member.id);
            if (existing.get('version') > member.get('version'))
                return;

            this.collection.add(member, {
                merge: true,
                sort: options.sort !== false ? true : false
            });

        },

        hasMember: function(member){
            return (this.collection.get(member.id) ? true : false);
        },

        removeMember: function(member){
            this.collection.remove(member);
        },

        renderMemberInContainer: function(member){
            var new_pos = this.collection.indexOf(member),
                $el, $rel;

            this.$parent.find('li.empty').remove();

            $el = $('#' + this.id + member.id);

            if (!$el.length) {
                // create the element if it does not yet exist
                $el = this.renderMember(member);
            } else if ($el.index() === new_pos) {
                // if the row was already present, ensure it moved
                return;
            }

            // top item
            if (new_pos === 0) {
                this.$parent.prepend($el);
            } else {
                // find existing item at new position
                $rel = $('#' + this.id + this.collection.at(new_pos).id);
                if (!$rel.length) {
                    this.$parent.append($el);
                } else if ($el.id !== $rel.id) {
                    // TODO: why do we get here?
                    $el.insertBefore($rel);
                } else {

                    return;
                }
            }

            if (this.loaded)
                $el.css('background-color', '#eee').animate({backgroundColor: '#fff'}, 300);
        },

        renderMember: function(member){
            var view = new this.options.view({
                model: member,
                id: this.id + member.id
            });
            view.render();
            return view.$el;
        },

        unrenderMember: function(member){
            this.$parent.find('#' + this.id + member.id).remove();
            if (!this.$parent.find('li').length)
                this.setEmpty();
        }

    });

    app.GroupListView = app.OrderedElementsView.extend({

        defaults: {
            realtime: false,
            stream: false,
            pollUrl: null,
            pollTime: 1000,
            tickTime: 100
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            data.model = app.models.Group;
            data.view = app.GroupView;

            app.OrderedElementsView.prototype.initialize.call(this, data);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.queue = new app.ScoredList([], {
                model: data.model
            });

            this.cursor = null;

            _.bindAll(this, 'poll', 'pollSuccess', 'pollFailure', 'tick');

            this.poll();

            window.setInterval(this.tick, this.options.tickTime);
        },

        tick: function(){
            if (!this.queue.length)
                return;

            var item = this.queue.pop();
            if (this.options.canStream){
                this.addMember(item);
            } else if (this.hasMember(item)) {
                this.updateMember(item, {
                    sort: false
                });
            }
        },

        pollSuccess: function(groups){
            if (!groups.length)
                return window.setTimeout(this.poll, this.options.pollTime * 5);

            this.cursor = groups[groups.length - 1].score;

            this.queue.add(groups, {merge: true});

            window.setTimeout(this.poll, this.options.pollTime);
        },

        pollFailure: function(jqXHR, textStatus, errorThrown){
            // if an error happened lets give the server a bit of time before we poll again
            window.setTimeout(this.poll, this.options.pollTime * 10);
        },

        poll: function(){
            var data;

            if (!this.options.realtime || !this.options.pollUrl)
                return window.setTimeout(this.poll, this.options.pollTime);

            data = app.utils.getQueryParams();
            data.cursor = this.cursor || undefined;

            $.ajax({
                url: this.options.pollUrl,
                type: 'GET',
                dataType: 'json',
                data: data,
                success: this.pollSuccess,
                error: this.pollFailure
            });
        }

    });

}(window, app, Backbone, jQuery, _));
<commit_msg>lint error<commit_after>(function(window, app, Backbone, jQuery, _){
    "use strict";

    var $ = jQuery;

    app.GroupView = Backbone.View.extend({
        tagName: 'li',
        className: 'group',
        template: _.template(app.templates.group),

        initialize: function(){
            Backbone.View.prototype.initialize.apply(this, arguments);

            _.bindAll(this, 'updateCount', 'updateUsersSeen', 'updateLastSeen',
                'updateResolved', 'renderSparkline', 'render');

            this.model.on({
                'change:count': this.updateCount,
                'change:usersSeen': this.updateUsersSeen,
                'change:lastSeen': this.updateLastSeen,
                'change:isBookmarked': this.render,
                'change:isResolved': this.updateResolved,
                'change:historicalData': this.renderSparkline
            }, this);
        },

        render: function(){
            var data = this.model.toJSON();
            data.loggerUrl = app.config.urlPrefix + '/' + app.config.teamId +
                '/' + app.config.projectId + '/?logger=' + data.logger;

            this.$el.html(this.template(data));
            this.$el.attr('data-id', this.model.id);
            this.$el.addClass(this.getLevelClassName());
            this.$el.find('a[data-action=resolve]').click(_.bind(function(e){
                e.preventDefault();
                if (this.model.get('isResolved')) {
                    this.unresolve();
                } else {
                    this.resolve();
                }
            }, this));
            this.$el.find('a[data-action=bookmark]').click(_.bind(function(e){
                e.preventDefault();
                this.bookmark();
            }, this));
            this.renderSparkline();
            this.updateResolved();
        },

        updateResolved: function(){
            if (this.model.get('isResolved')) {
                this.$el.addClass('resolved');
            } else {
                this.$el.removeClass('resolved');
            }
        },

        renderSparkline: function(obj){
            var data = this.model.get('historicalData');
            if (!data || !data.length)
                return;

            this.$el.addClass('with-sparkline');

            app.charts.createSparkline(this.$el.find('.sparkline'), data);
        },

        resolve: function(){
            $.ajax({
                url: this.getResolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version);
                    this.model.set('isResolved', true);
                }, this)
            });
        },

        unresolve: function(){
            $.ajax({
                url: this.getUnresolveUrl(),
                type: 'post',
                dataType: 'json',
                success: _.bind(function(response) {
                    this.model.set('version', response.version);
                    this.model.set('isResolved', false);
                }, this)
            });
        },

        getResolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/resolved/';
        },

        getUnresolveUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' +
                    app.config.projectId + '/group/' + this.model.get('id') +
                    '/set/unresolved/';
        },

        getBookmarkUrl: function(){
            return app.config.urlPrefix + '/api/' + app.config.teamId + '/' + app.config.projectId + '/bookmark/';
        },

        bookmark: function(){
            $.ajax({
                url: this.getBookmarkUrl(),
                type: 'post',
                dataType: 'json',
                data: {
                    gid: this.model.get('id')
                },
                success: _.bind(function(response){
                    this.model.set('version', response.version);
                    this.model.set('isBookmarked', response.isBookmarked);
                }, this)
            });
        },

        getLevelClassName: function(){
            return 'level-' + this.model.get('levelName');
        },

        updateLastSeen: function(){
            this.$el.find('.last-seen')
                .text(app.utils.prettyDate(this.model.get('lastSeen')))
                .attr('title', this.model.get('lastSeen'));
        },

        updateCount: function(){
            var new_count = app.utils.formatNumber(this.model.get('count'));
            var counter = this.$el.find('.count');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        },

        updateUsersSeen: function(){
            var value = this.model.get('usersSeen');
            if (value === null)
                return;
            var new_count = app.utils.formatNumber(value);
            var counter = this.$el.find('.tag-users');
            var digit = counter.find('span');

            if (digit.is(':animated'))
                return false;

            if (counter.data('count') == new_count) {
                // We are already showing this number
                return false;
            }

            counter.data('count', new_count);

            var replacement = $('', {
                css: {
                    top: '-2.1em',
                    opacity: 0
                },
                text: new_count
            });

            // The .static class is added when the animation
            // completes. This makes it run smoother.

            digit.before(replacement).animate({
                top: '2.5em',
                opacity: 0
            }, 'fast', function(){
                digit.remove();
            });

            replacement.delay(100).animate({
                top: 0,
                opacity: 1
            }, 'fast');
        }

    });

    app.OrderedElementsView = Backbone.View.extend({

        emptyMessage: '

No events to show.

We\'ll notify you if that changes. In the meantime why not take a moment to become more familiar with Sentry.

Installation instructions Project settings

',
        loadingMessage: '
Loading...
',
        model: app.models.Group,

        defaults: {
            maxItems: 50,
            view: Backbone.View
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            var members = data.members;

            Backbone.View.prototype.initialize.apply(this, arguments);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.$wrapper = $('#' + this.id);
            this.$parent = $('

');
            this.$empty = $('

');
            this.$wrapper.html(this.$parent);

            if (this.options.className)
                this.$parent.addClass(this.options.className);

            _.bindAll(this, 'renderMemberInContainer', 'unrenderMember', 'reSortMembers');

            this.collection = new app.ScoredList([], {
                model: data.model
            });
            this.collection.on('add', this.renderMemberInContainer, this);
            this.collection.on('remove', this.unrenderMember, this);
            this.collection.on('reset', this.reSortMembers, this);

            delete data.members;

            this.reset(members);
        },

        reset: function(members){
            this.$parent.empty();
            this.setEmpty();

            if (members === undefined) {
                this.$empty.html(this.loadingMessage);
                this.collection.reset();
                this.setEmpty();
                this.loaded = false;
            } else {
                this.$empty.html(this.emptyMessage);
                this.collection.reset(members);
                this.loaded = true;
            }
        },

        setEmpty: function(){
            this.$parent.html(this.$empty);
        },

        extend: function(data){
            for (var i=0; i<data.length; i++) {
                this.addMember(data[i]);
            }
        },

        addMember: function(member){
            if (!this.hasMember(member)) {
                if (this.collection.length >= this.options.maxItems) {
                    // bail early if the score is too low
                    if (member.score < this.collection.last().get('score'))
                        return;

                    // make sure we limit the number shown
                    while (this.collection.length >= this.options.maxItems)
                        this.collection.pop();
                }
            }
            this.collection.add(member, {merge: true});
        },

        reSortMembers: function(){
            this.collection.each(_.bind(function(member){
                this.renderMemberInContainer(member);
            }, this));
        },

        updateMember: function(member, options){
            if (_.isUndefined(options))
                options = {};

            var existing = this.collection.get(member.id);
            if (existing.get('version') > member.get('version'))
                return;

            this.collection.add(member, {
                merge: true,
                sort: options.sort !== false ? true : false
            });

        },

        hasMember: function(member){
            return (this.collection.get(member.id) ? true : false);
        },

        removeMember: function(member){
            this.collection.remove(member);
        },

        renderMemberInContainer: function(member){
            var new_pos = this.collection.indexOf(member),
                $el, $rel;

            this.$parent.find('li.empty').remove();

            $el = $('#' + this.id + member.id);

            if (!$el.length) {
                // create the element if it does not yet exist
                $el = this.renderMember(member);
            } else if ($el.index() === new_pos) {
                // if the row was already present, ensure it moved
                return;
            }

            // top item
            if (new_pos === 0) {
                this.$parent.prepend($el);
            } else {
                // find existing item at new position
                $rel = $('#' + this.id + this.collection.at(new_pos).id);
                if (!$rel.length) {
                    this.$parent.append($el);
                } else if ($el.id !== $rel.id) {
                    // TODO: why do we get here?
                    $el.insertBefore($rel);
                } else {

                    return;
                }
            }

            if (this.loaded)
                $el.css('background-color', '#eee').animate({backgroundColor: '#fff'}, 300);
        },

        renderMember: function(member){
            var view = new this.options.view({
                model: member,
                id: this.id + member.id
            });
            view.render();
            return view.$el;
        },

        unrenderMember: function(member){
            this.$parent.find('#' + this.id + member.id).remove();
            if (!this.$parent.find('li').length)
                this.setEmpty();
        }

    });

    app.GroupListView = app.OrderedElementsView.extend({

        defaults: {
            realtime: false,
            stream: false,
            pollUrl: null,
            pollTime: 1000,
            tickTime: 100
        },

        initialize: function(data){
            if (_.isUndefined(data))
                data = {};

            data.model = app.models.Group;
            data.view = app.GroupView;

            app.OrderedElementsView.prototype.initialize.call(this, data);

            this.options = $.extend({}, this.defaults, this.options, data);

            this.queue = new app.ScoredList([], {
                model: data.model
            });

            this.cursor = null;

            _.bindAll(this, 'poll', 'pollSuccess', 'pollFailure', 'tick');

            this.poll();

            window.setInterval(this.tick, this.options.tickTime);
        },

        tick: function(){
            if (!this.queue.length)
                return;

            var item = this.queue.pop();
            if (this.options.canStream){
                this.addMember(item);
            } else if (this.hasMember(item)) {
                this.updateMember(item, {
                    sort: false
                });
            }
        },

        pollSuccess: function(groups){
            if (!groups.length)
                return window.setTimeout(this.poll, this.options.pollTime * 5);

            this.cursor = groups[groups.length - 1].score;

            this.queue.add(groups, {merge: true});

            window.setTimeout(this.poll, this.options.pollTime);
        },

        pollFailure: function(jqXHR, textStatus, errorThrown){
            // if an error happened lets give the server a bit of time before we poll again
            window.setTimeout(this.poll, this.options.pollTime * 10);
        },

        poll: function(){
            var data;

            if (!this.options.realtime || !this.options.pollUrl)
                return window.setTimeout(this.poll, this.options.pollTime);

            data = app.utils.getQueryParams();
            data.cursor = this.cursor || undefined;

            $.ajax({
                url: this.options.pollUrl,
                type: 'GET',
                dataType: 'json',
                data: data,
                success: this.pollSuccess,
                error: this.pollFailure
            });
        }

    });

}(window, app, Backbone, jQuery, _));
<|endoftext|>


<commit_before>var AWS = require('aws-sdk');
var _ = require('lodash');
var base64 = require('base-64');
var AWS = require('aws-sdk');
var s3 = new AWS.S3();

var cloudwatch = new AWS.CloudWatch();
var dynamodb = new AWS.DynamoDB();

/**
 * cleanJSONString
 *
 * @param {any} string string that requires cleaning
 * @returns string cleaned string
 */
function cleanJSONString(string) {
  // preserve newlines, etc - use valid JSON
  string = string.replace(/\\n/g, '\\n')
               .replace(/\\'/g, '\\\'')
               .replace(/\\"/g, '\\"')
               .replace(/\\&/g, '\\&')
               .replace(/\\r/g, '\\r')
               .replace(/\\t/g, '\\t')
               .replace(/\\b/g, '\\b')
               .replace(/\\f/g, '\\f');
  // remove non-printable and other non-valid JSON chars
  return string.replace(/[\u0000-\u0019]+/g, '');
}

function writeToDynamo(name, value) {
  var params = {
    Item: {
      'source': {S: name},
      'value': {S: value},
      'units': {S: units},
      'description': {S: description}
    },
    ReturnConsumedCapacity: 'TOTAL',
    TableName: 'simhub'
  };

  dynamodb.putItem(params, function(err, data) {
    if (err)
      console.log(err, err.stack);  // an error occurred
    else
      console.log(data);  // successful response
  });
}

function putCloudwatchMetric(metricName, dimName, dimValue, ts, value) {
  var params = {
    MetricData: [
      /* required */
      {
        MetricName: metricName,
        Dimensions: [
          {Name: dimName, Value: dimValue},
        ],
        StorageResolution: 1,
        Timestamp: new Date(ts).toISOString(),
        Unit: 'None',
        Value: parseFloat(value)
      },

    ],
    Namespace: 'simhub' /* required */
  };

  cloudwatch.putMetricData(params, function(err, data) {
    if (err)
      console.log(err, err.stack);  // an error occurred
    else
      console.log(data);  // successful response
  });
}

/**
 * Main lambda handler and entry point
 */
exports.index = function(event, context, callback) {
  // find the number of records being given to us by kinesis
  var recordCount = event.Records.length;

  // iterate through the records
  for (i = 0; i < recordCount; i++) {
    // get the kinesis payload
    var kinesis = event.Records[i].kinesis;

    // convert from base64, clean up and "funny" characters and parse into
    // JSON
    data = JSON.parse(cleanJSONString(base64.decode(kinesis.data)));

    putCloudwatchMetric(data.s, data.s, 'On/Off', data.ts, data.val);
    writeToDynamo(data.s, data.val);
  }

  callback(null, 'Complete');
}
<commit_msg>update<commit_after>var AWS = require('aws-sdk');
var _ = require('lodash');
var base64 = require('base-64');
var AWS = require('aws-sdk');
var s3 = new AWS.S3();

var cloudwatch = new AWS.CloudWatch();
var dynamodb = new AWS.DynamoDB();

/**
 * cleanJSONString
 *
 * @param {any} string string that requires cleaning
 * @returns string cleaned string
 */
function cleanJSONString(string) {
  // preserve newlines, etc - use valid JSON
  string = string.replace(/\\n/g, '\\n')
               .replace(/\\'/g, '\\\'')
               .replace(/\\"/g, '\\"')
               .replace(/\\&/g, '\\&')
               .replace(/\\r/g, '\\r')
               .replace(/\\t/g, '\\t')
               .replace(/\\b/g, '\\b')
               .replace(/\\f/g, '\\f');
  // remove non-printable and other non-valid JSON chars
  return string.replace(/[\u0000-\u0019]+/g, '');
}

function writeToDynamo(name, value, units, description) {
  var params = {
    Item: {
      'source': {S: name},
      'value': {S: value},
      'units': {S: units},
      'description': {S: description}
    },
    ReturnConsumedCapacity: 'TOTAL',
    TableName: 'simhub'
  };

  dynamodb.putItem(params, function(err, data) {
    if (err)
      console.log(err, err.stack);  // an error occurred
    else
      console.log(data);  // successful response
  });
}

function putCloudwatchMetric(metricName, dimName, dimValue, ts, value) {
  var params = {
    MetricData: [
      /* required */
      {
        MetricName: metricName,
        Dimensions: [
          {Name: dimName, Value: dimValue},
        ],
        StorageResolution: 1,
        Timestamp: new Date(ts).toISOString(),
        Unit: 'None',
        Value: parseFloat(value)
      },

    ],
    Namespace: 'simhub' /* required */
  };

  cloudwatch.putMetricData(params, function(err, data) {
    if (err)
      console.log(err, err.stack);  // an error occurred
    else
      console.log(data);  // successful response
  });
}

/**
 * Main lambda handler and entry point
 */
exports.index = function(event, context, callback) {
  // find the number of records being given to us by kinesis
  var recordCount = event.Records.length;

  // iterate through the records
  for (i = 0; i < recordCount; i++) {
    // get the kinesis payload
    var kinesis = event.Records[i].kinesis;

    // convert from base64, clean up and "funny" characters and parse into
    // JSON
    data = JSON.parse(cleanJSONString(base64.decode(kinesis.data)));
    console.log(data);
    putCloudwatchMetric(data.s, data.s, 'On/Off', parseInt(data.ts), data.val);
    writeToDynamo(data.s, data.val, data.units, data.description);
  }

  callback(null, 'Complete');
}
<|endoftext|>


<commit_before>define(['dojo/_base/declare',
        'dojo/_base/array',
        'dojo/_base/lang',
        'dojo/dom-construct',
        'dojo/query',
        'dojo/on',
        'dojo/json',

        'dijit/TitlePane',
        'dijit/layout/ContentPane',

        'JBrowse/Util',
        './_TextFilterMixin'
       ],
       function(
           declare,
           array,
           lang,
           dom,
           query,
           on,
           JSON,

           TitlePane,
           ContentPane,

           Util,
           _TextFilterMixin
       ) {

return declare(
    'JBrowse.View.TrackList.Hierarchical',
    [ ContentPane, _TextFilterMixin ],
    {

    region: 'left',
    splitter: true,
    style: 'width: 25%',

    id: 'hierarchicalTrackPane',
    baseClass: 'jbrowseHierarchicalTrackSelector',

    categoryFacet: 'category',

    constructor: function( args ) {
        this.categories = {};
        this.config=
            lang.mixin({
                "sortHierarchical": true
            },
            args);

        this._loadState();
    },
    postCreate: function() {
        this.placeAt( this.browser.container );

        // subscribe to commands coming from the the controller
        this.browser.subscribe( '/jbrowse/v1/c/tracks/show',
                                lang.hitch( this, 'setTracksActive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/hide',
                                lang.hitch( this, 'setTracksInactive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/new',
                                lang.hitch( this, 'addTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/replace',
                                lang.hitch( this, 'replaceTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/delete',
                                lang.hitch( this, 'deleteTracks' ));
    },

    buildRendering: function() {
        this.inherited('buildRendering',arguments);

        var topPane = new ContentPane({ className: 'header' });
        this.addChild( topPane );
        dom.create(
            'h2',
            { className: 'title',
              innerHTML: 'Available Tracks'
            },
            topPane.containerNode );

        this._makeTextFilterNodes(
            dom.create('div',
                       { className: 'textfilterContainer' },
                       topPane.containerNode )
        );
        this._updateTextFilterControl();
    },

    startup: function() {
        this.inherited('startup', arguments );

        var tracks = [];
        var categoryFacet = this.get('categoryFacet');
        var sorter;
        if(this.config.sortHierarchical) {
            sorter = [
                { attribute: categoryFacet.toLowerCase() },
                { attribute: 'key' },
                { attribute: 'label' }
            ];
        }

        // add initally collapsed categories to the local storage
        var arr = (this.get('collapsedCategories') || "").split(",");
        for(var i = 0; i < arr.length; i++) {
            lang.setObject('collapsed.' + arr[i], true, this.state);
        }
        this._saveState();

        this.get('trackMetaData').fetch({
            onItem: function(i) {
                if( i.conf )
                    tracks.push( i );
            },
            onComplete: () => {
                // make a pane at the top to hold uncategorized tracks
                this.categories.Uncategorized = {
                    pane: new ContentPane({ className: 'uncategorized' }).placeAt( this.containerNode ),
                    tracks: {},
                    categories: {}
                };
                if(this.config.categoryOrder) {
                    const order = this.config.categoryOrder.split(",").map(s => s.trim())
                    tracks.sort((a, b) => {
                        if(order.indexOf(a.category) == -1 || order.indexOf(b.category) == -1) return 1;
                        return order.indexOf(a.category) - order.indexOf(b.category);
                    });
                }

                this.addTracks( tracks, true );

                // hide the uncategorized pane if it is empty
                if( ! this.categories.Uncategorized.pane.containerNode.children.length ) {
                    this.categories.Uncategorized.pane.domNode.style.display = 'none';
                }
            },
            sort: sorter
        });
    },

    addTracks: function( tracks, inStartup ) {
        this.pane = this;
        var thisB = this;

        array.forEach( tracks, function( track ) {
            var trackConf = track.conf || track;

            var categoryFacet = this.get('categoryFacet');
            var categoryNames = (
                trackConf.metadata && trackConf.metadata[ categoryFacet ]
                    || trackConf[ categoryFacet ]
                    || track[ categoryFacet ]
                    || 'Uncategorized'
            ).split(/\s*\/\s*/);

            var category = _findCategory( this, categoryNames, [] );

            function _findCategory( obj, names, path ) {
                var categoryName = names.shift();
                path = path.concat(categoryName);
                var categoryPath = path.join('/');

                var cat = obj.categories[categoryName] || ( obj.categories[categoryName] = function() {
                    var isCollapsed = lang.getObject( 'collapsed.'+categoryPath, false, thisB.state );
                    var c = new TitlePane(
                        { title: ''+categoryName+''
                          + ' 0',
                          open: ! isCollapsed
                        });
                    // save our open/collapsed state in local storage
                    c.watch( 'open', function( attr, oldval, newval ) {
                                 lang.setObject( 'collapsed.'+categoryPath, !newval, thisB.state );
                                 thisB._saveState();
                             });
                    obj.pane.addChild(c, inStartup ? undefined : 1 );
                    return { parent: obj, pane: c, categories: {}, tracks: {} };
                }.call(thisB));

                return names.length ? _findCategory( cat, names, path ) : cat;
            };

            category.pane.domNode.style.display = 'block';

            // note: sometimes trackConf.description is defined as numeric, so in this case, ignore it
            var labelNode = dom.create(
                'label', {
                    className: 'tracklist-label shown',
                    title: Util.escapeHTML( trackConf.shortDescription || track.shortDescription ||  (trackConf.description===1?undefined:trackConf.description) || track.description || trackConf.Description || track.Description || trackConf.metadata && ( trackConf.metadata.shortDescription || trackConf.metadata.description || trackConf.metadata.Description ) || track.key || trackConf.key || trackConf.label )
                }, category.pane.containerNode );

            var checkBoxProps = { type: 'checkbox', className: 'check' };

            // hook point
            if (typeof thisB.extendCheckbox === 'function')
                var checkBoxProps = thisB.extendCheckbox(checkBoxProps,trackConf);

            var checkbox = dom.create('input', checkBoxProps, labelNode );
            var trackLabel = trackConf.label;
            var checkListener;
            this.own( checkListener = on( checkbox, 'click', function() {
                    thisB.itemClick(this,trackConf);
            }));
            dom.create('span', { className: 'key', innerHTML: trackConf.key || trackConf.label }, labelNode );

            category.tracks[ trackLabel ] = { checkbox: checkbox, checkListener: checkListener, labelNode: labelNode };

            this._updateTitles( category );
        }, this );
    },
    // called when item checkbox is clicked.
    itemClick: function(checkbox,trackConf) {

        this.browser.publish( '/jbrowse/v1/v/tracks/'+(checkbox.checked ? 'show' : 'hide'), [trackConf] );
    },

    _loadState: function() {
        this.state = {};
        try {
            this.state = JSON.parse( localStorage.getItem( 'JBrowse-Hierarchical-Track-Selector' ) || '{}' );
        } catch(e) {}
        return this.state;
    },
    _saveState: function( state ) {
        try {
            localStorage.setItem( 'JBrowse-Hierarchical-Track-Selector', JSON.stringify( this.state ) );
        } catch(e) {}
    },

    // depth-first traverse and update the titles of all the categories
    _updateAllTitles: function(r) {
        var root = r || this;
        for( var c in root.categories ) {
            this._updateTitle( root.categories[c] );
            this._updateAllTitles( root.categories[c] );
        }
    },

    _updateTitle: function( category ) {
        category.pane.set( 'title', category.pane.get('title')
                           .replace( />\s*\d+\s*</, '>'+query('label.shown', category.pane.containerNode ).length+'<' )
                         );
    },

    // update the titles of the given category and its parents
    _updateTitles: function( category ) {
        this._updateTitle( category );
        if( category.parent )
            this._updateTitles( category.parent );
    },

    _findTrack: function _findTrack( trackLabel, callback, r ) {
        var root = r || this;
        for( var c in root.categories ) {
            var category = root.categories[c];
            if( category.tracks[ trackLabel ] ) {
                callback( category.tracks[ trackLabel ], category );
                return true;
            }
            else {
                if( this._findTrack( trackLabel, callback, category ) )
                    return true;
            }
        }
        return false;
    },
    //  hook point
    replaceTracks: function( trackConfigs ) {   // notification
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned on.
     */
    setTracksActive: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = true;
            });
        },this);
    },

    deleteTracks: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.labelNode.parentNode.removeChild( trackRecord.labelNode );
                trackRecord.checkListener.remove();
                delete category.tracks[conf.label];
            });
        },this);
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned off.
     */
    setTracksInactive: function( /**Array[Object]*/ trackConfigs ) {
          array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = false;
            });
        },this);
    },

    _textFilter: function() {
        this.inherited(arguments);
        this._updateAllTitles();
    },

    /**
     * Make the track selector visible.
     * This does nothing for this track selector, since it is always visible.
     */
    show: function() {
    },

    /**
     * Make the track selector invisible.
     * This does nothing for this track selector, since it is always visible.
     */
    hide: function() {
    },

    /**
     * Toggle visibility of this track selector.
     * This does nothing for this track selector, since it is always visible.
     */
    toggle: function() {
    }

});
});
<commit_msg>Relegate unordered tracks to the bottom of the hierarchical selector<commit_after>define(['dojo/_base/declare',
        'dojo/_base/array',
        'dojo/_base/lang',
        'dojo/dom-construct',
        'dojo/query',
        'dojo/on',
        'dojo/json',
        'dijit/TitlePane',
        'dijit/layout/ContentPane',
        'JBrowse/Util',
        './_TextFilterMixin'
       ],
       function(
           declare,
           array,
           lang,
           dom,
           query,
           on,
           JSON,
           TitlePane,
           ContentPane,
           Util,
           _TextFilterMixin
       ) {

return declare(
    'JBrowse.View.TrackList.Hierarchical',
    [ ContentPane, _TextFilterMixin ],
    {

    region: 'left',
    splitter: true,
    style: 'width: 25%',

    id: 'hierarchicalTrackPane',
    baseClass: 'jbrowseHierarchicalTrackSelector',

    categoryFacet: 'category',

    constructor: function( args ) {
        this.categories = {};
        this.config=
            lang.mixin({
                "sortHierarchical": true
            },
            args);

        this._loadState();
    },
    postCreate: function() {
        this.placeAt( this.browser.container );

        // subscribe to commands coming from the the controller
        this.browser.subscribe( '/jbrowse/v1/c/tracks/show',
                                lang.hitch( this, 'setTracksActive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/hide',
                                lang.hitch( this, 'setTracksInactive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/new',
                                lang.hitch( this, 'addTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/replace',
                                lang.hitch( this, 'replaceTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/delete',
                                lang.hitch( this, 'deleteTracks' ));
    },

    buildRendering: function() {
        this.inherited('buildRendering',arguments);

        var topPane = new ContentPane({ className: 'header' });
        this.addChild( topPane );
        dom.create(
            'h2',
            { className: 'title',
              innerHTML: 'Available Tracks'
            },
            topPane.containerNode );

        this._makeTextFilterNodes(
            dom.create('div',
                       { className: 'textfilterContainer' },
                       topPane.containerNode )
        );
        this._updateTextFilterControl();
    },

    startup: function() {
        this.inherited('startup', arguments );

        var tracks = [];
        var categoryFacet = this.get('categoryFacet');
        var sorter;
        if(this.config.sortHierarchical) {
            sorter = [
                { attribute: categoryFacet.toLowerCase() },
                { attribute: 'key' },
                { attribute: 'label' }
            ];
        }

        // add initally collapsed categories to the local storage
        var arr = (this.get('collapsedCategories') || "").split(",");
        for(var i = 0; i < arr.length; i++) {
            lang.setObject('collapsed.' + arr[i], true, this.state);
        }
        this._saveState();

        this.get('trackMetaData').fetch({
            onItem: function(i) {
                if( i.conf )
                    tracks.push( i );
            },
            onComplete: () => {
                // make a pane at the top to hold uncategorized tracks
                this.categories.Uncategorized = {
                    pane: new ContentPane({ className: 'uncategorized' }).placeAt( this.containerNode ),
                    tracks: {},
                    categories: {}
                };
                if( this.config.categoryOrder ) {
                    const order = this.config.categoryOrder.split(",").map(s => s.trim())
                    var unordered = tracks.filter(t => order.indexOf(t.category) === -1);
                    var ordered = tracks.filter(t => order.indexOf(t.category) !== -1);
                    ordered.sort((a, b) => {
                        return order.indexOf(a.category) - order.indexOf(b.category);
                    });
                    tracks = ordered.concat(unordered)
                }

                this.addTracks( tracks, true );

                // hide the uncategorized pane if it is empty
                if( ! this.categories.Uncategorized.pane.containerNode.children.length ) {
                    this.categories.Uncategorized.pane.domNode.style.display = 'none';
                }
            },
            sort: sorter
        });
    },

    addTracks: function( tracks, inStartup ) {
        this.pane = this;
        var thisB = this;

        array.forEach( tracks, function( track ) {
            var trackConf = track.conf || track;

            var categoryFacet = this.get('categoryFacet');
            var categoryNames = (
                trackConf.metadata && trackConf.metadata[ categoryFacet ]
                    || trackConf[ categoryFacet ]
                    || track[ categoryFacet ]
                    || 'Uncategorized'
            ).split(/\s*\/\s*/);

            var category = _findCategory( this, categoryNames, [] );

            function _findCategory( obj, names, path ) {
                var categoryName = names.shift();
                path = path.concat(categoryName);
                var categoryPath = path.join('/');

                var cat = obj.categories[categoryName] || ( obj.categories[categoryName] = function() {
                    var isCollapsed = lang.getObject( 'collapsed.'+categoryPath, false, thisB.state );
                    var c = new TitlePane(
                        { title: ''+categoryName+''
                          + ' 0',
                          open: ! isCollapsed
                        });
                    // save our open/collapsed state in local storage
                    c.watch( 'open', function( attr, oldval, newval ) {
                                 lang.setObject( 'collapsed.'+categoryPath, !newval, thisB.state );
                                 thisB._saveState();
                             });
                    obj.pane.addChild(c, inStartup ? undefined : 1 );
                    return { parent: obj, pane: c, categories: {}, tracks: {} };
                }.call(thisB));

                return names.length ? _findCategory( cat, names, path ) : cat;
            };

            category.pane.domNode.style.display = 'block';

            // note: sometimes trackConf.description is defined as numeric, so in this case, ignore it
            var labelNode = dom.create(
                'label', {
                    className: 'tracklist-label shown',
                    title: Util.escapeHTML( trackConf.shortDescription || track.shortDescription ||  (trackConf.description===1?undefined:trackConf.description) || track.description || trackConf.Description || track.Description || trackConf.metadata && ( trackConf.metadata.shortDescription || trackConf.metadata.description || trackConf.metadata.Description ) || track.key || trackConf.key || trackConf.label )
                }, category.pane.containerNode );

            var checkBoxProps = { type: 'checkbox', className: 'check' };

            // hook point
            if (typeof thisB.extendCheckbox === 'function')
                var checkBoxProps = thisB.extendCheckbox(checkBoxProps,trackConf);

            var checkbox = dom.create('input', checkBoxProps, labelNode );
            var trackLabel = trackConf.label;
            var checkListener;
            this.own( checkListener = on( checkbox, 'click', function() {
                    thisB.itemClick(this,trackConf);
            }));
            dom.create('span', { className: 'key', innerHTML: trackConf.key || trackConf.label }, labelNode );

            category.tracks[ trackLabel ] = { checkbox: checkbox, checkListener: checkListener, labelNode: labelNode };

            this._updateTitles( category );
        }, this );
    },
    // called when item checkbox is clicked.
    itemClick: function(checkbox,trackConf) {

        this.browser.publish( '/jbrowse/v1/v/tracks/'+(checkbox.checked ? 'show' : 'hide'), [trackConf] );
    },

    _loadState: function() {
        this.state = {};
        try {
            this.state = JSON.parse( localStorage.getItem( 'JBrowse-Hierarchical-Track-Selector' ) || '{}' );
        } catch(e) {}
        return this.state;
    },
    _saveState: function( state ) {
        try {
            localStorage.setItem( 'JBrowse-Hierarchical-Track-Selector', JSON.stringify( this.state ) );
        } catch(e) {}
    },

    // depth-first traverse and update the titles of all the categories
    _updateAllTitles: function(r) {
        var root = r || this;
        for( var c in root.categories ) {
            this._updateTitle( root.categories[c] );
            this._updateAllTitles( root.categories[c] );
        }
    },

    _updateTitle: function( category ) {
        category.pane.set( 'title', category.pane.get('title')
                           .replace( />\s*\d+\s*</, '>'+query('label.shown', category.pane.containerNode ).length+'<' )
                         );
    },

    // update the titles of the given category and its parents
    _updateTitles: function( category ) {
        this._updateTitle( category );
        if( category.parent )
            this._updateTitles( category.parent );
    },

    _findTrack: function _findTrack( trackLabel, callback, r ) {
        var root = r || this;
        for( var c in root.categories ) {
            var category = root.categories[c];
            if( category.tracks[ trackLabel ] ) {
                callback( category.tracks[ trackLabel ], category );
                return true;
            }
            else {
                if( this._findTrack( trackLabel, callback, category ) )
                    return true;
            }
        }
        return false;
    },
    //  hook point
    replaceTracks: function( trackConfigs ) {   // notification
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned on.
     */
    setTracksActive: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = true;
            });
        },this);
    },

    deleteTracks: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.labelNode.parentNode.removeChild( trackRecord.labelNode );
                trackRecord.checkListener.remove();
                delete category.tracks[conf.label];
            });
        },this);
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned off.
     */
    setTracksInactive: function( /**Array[Object]*/ trackConfigs ) {
          array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = false;
            });
        },this);
    },

    _textFilter: function() {
        this.inherited(arguments);
        this._updateAllTitles();
    },

    /**
     * Make the track selector visible.
     * This does nothing for this track selector, since it is always visible.
     */
    show: function() {
    },

    /**
     * Make the track selector invisible.
     * This does nothing for this track selector, since it is always visible.
     */
    hide: function() {
    },

    /**
     * Toggle visibility of this track selector.
     * This does nothing for this track selector, since it is always visible.
     */
    toggle: function() {
    }

});
});
<|endoftext|>


<commit_before>define(['dojo/_base/declare',
        'dojo/_base/array',
        'dojo/_base/lang',
        'dojo/dom-construct',
        'dojo/query',
        'dojo/on',
        'dojo/json',

        'dijit/TitlePane',
        'dijit/layout/ContentPane',

        './_TextFilterMixin'
       ],
       function(
           declare,
           array,
           lang,
           dom,
           query,
           on,
           JSON,

           TitlePane,
           ContentPane,

           _TextFilterMixin
       ) {

return declare(
    'JBrowse.View.TrackList.Hierarchical',
    [ ContentPane, _TextFilterMixin ],
    {

    region: 'left',
    splitter: true,
    style: 'width: 25%',

    id: 'hierarchicalTrackPane',
    baseClass: 'jbrowseHierarchicalTrackSelector',

    categoryFacet: 'category',

    constructor: function( args ) {
        this.categories = {};

        this._loadState();
    },
    postCreate: function() {
        this.placeAt( this.browser.container );

        // subscribe to commands coming from the the controller
        this.browser.subscribe( '/jbrowse/v1/c/tracks/show',
                                lang.hitch( this, 'setTracksActive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/hide',
                                lang.hitch( this, 'setTracksInactive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/new',
                                lang.hitch( this, 'addTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/replace',
                                lang.hitch( this, 'replaceTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/delete',
                                lang.hitch( this, 'deleteTracks' ));
    },

    buildRendering: function() {
        this.inherited(arguments);

        var topPane = new ContentPane({ className: 'header' });
        this.addChild( topPane );
        dom.create(
            'h2',
            { className: 'title',
              innerHTML: 'Available Tracks'
            },
            topPane.containerNode );

        this._makeTextFilterNodes(
            dom.create('div',
                       { className: 'textfilterContainer' },
                       topPane.containerNode )
        );
        this._updateTextFilterControl();
    },

    startup: function() {
        this.inherited( arguments );

        var tracks = [];
        var thisB = this;
        var categoryFacet = this.get('categoryFacet');
        this.get('trackMetaData').fetch(
            { onItem: function(i) {
                  if( i.conf )
                      tracks.push( i.conf );
              },
              onComplete: function() {
                  // make a pane at the top to hold uncategorized tracks
                  thisB.categories.Uncategorized =
                      { pane: new ContentPane({ className: 'uncategorized' }).placeAt( thisB.containerNode ),
                        tracks: {},
                        categories: {}
                      };

                  thisB.addTracks( tracks, true );

                  // remove the uncategorized pane if it is empty
                  if( ! thisB.categories.Uncategorized.pane.containerNode.children.length ) {
                      thisB.removeChild( thisB.categories.Uncategorized.pane );
                  }
              },
              sort: [ { attribute: this.get('categoryFacet').toLowerCase()},
                      { attribute: 'key' },
                      { attribute: 'label' }
                    ]
            });
    },

    addTracks: function( trackConfigs, inStartup ) {
        this.pane = this;
        var thisB = this;

        array.forEach( trackConfigs, function( trackConf ) {

            var categoryFacet = this.get('categoryFacet');
            var categoryNames = (
                trackConf.metadata && trackConf.metadata[ categoryFacet ]
                    || trackConf[ categoryFacet ]
                    || 'Uncategorized'
            ).split(/\s*\/\s*/);

            var category = _findCategory( this, categoryNames, [] );

            function _findCategory( obj, names, path ) {
                var categoryName = names.shift();
                path = path.concat(categoryName);
                var categoryPath = path.join('/');

                var cat = obj.categories[categoryName] || ( obj.categories[categoryName] = function() {
                    var isCollapsed = lang.getObject( 'collapsed.'+categoryPath, false, thisB.state );
                    var c = new TitlePane(
                        { title: ''+categoryName+''
                          + ' 0',
                          open: ! isCollapsed
                        });
                    // save our open/collapsed state in local storage
                    c.watch( 'open', function( attr, oldval, newval ) {
                                 lang.setObject( 'collapsed.'+categoryPath, !newval, thisB.state );
                                 thisB._saveState();
                             });
                    obj.pane.addChild(c, inStartup ? undefined : 1 );
                    return { parent: obj, pane: c, categories: {}, tracks: {} };
                }.call(thisB));

                return names.length ? _findCategory( cat, names, path ) : cat;
            };

            var labelNode = dom.create( 'label', { className: 'tracklist-label shown' }, category.pane.containerNode );
            var checkbox = dom.create('input', { type: 'checkbox', className: 'check' }, labelNode );
            var trackLabel = trackConf.label;
            var checkListener;
            this.own( checkListener = on( checkbox, 'click', function() {
                thisB.browser.publish( '/jbrowse/v1/v/tracks/'+(this.checked ? 'show' : 'hide'), [trackConf] );
            }));
            dom.create('span', { className: 'key', innerHTML: trackConf.key || trackConf.label }, labelNode );

            category.tracks[ trackLabel ] = { checkbox: checkbox, checkListener: checkListener, labelNode: labelNode };

            this._updateTitles( category );
        }, this );
    },

    _loadState: function() {
        this.state = {};
        try {
            this.state = JSON.parse( localStorage.getItem( 'JBrowse-Hierarchical-Track-Selector' ) || '{}' );
        } catch(e) {}
        return this.state;
    },
    _saveState: function( state ) {
        try {
            localStorage.setItem( 'JBrowse-Hierarchical-Track-Selector', JSON.stringify( this.state ) );
        } catch(e) {}
    },

    // depth-first traverse and update the titles of all the categories
    _updateAllTitles: function(r) {
        var root = r || this;
        for( var c in root.categories ) {
            this._updateTitle( root.categories[c] );
            this._updateAllTitles( root.categories[c] );
        }
    },

    _updateTitle: function( category ) {
        category.pane.set( 'title', category.pane.get('title')
                           .replace( />\s*\d+\s*\</, '>'+query('label.shown', category.pane.containerNode ).length+'<' )
                         );
    },

    // update the titles of the given category and its parents
    _updateTitles: function( category ) {
        this._updateTitle( category );
        if( category.parent )
            this._updateTitles( category.parent );
    },

    _findTrack: function _findTrack( trackLabel, callback, r ) {
        var root = r || this;
        for( var c in root.categories ) {
            var category = root.categories[c];
            if( category.tracks[ trackLabel ] ) {
                callback( category.tracks[ trackLabel ], category );
                return true;
            }
            else {
                if( this._findTrack( trackLabel, callback, category ) )
                    return true;
            }
        }
        return false;
    },

    replaceTracks: function( trackConfigs ) {
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned on.
     */
    setTracksActive: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = true;
            });
        },this);
    },

    deleteTracks: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.labelNode.parentNode.removeChild( trackRecord.labelNode );
                trackRecord.checkListener.remove();
                delete category.tracks[conf.label];
            });
        },this);
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned off.
     */
    setTracksInactive: function( /**Array[Object]*/ trackConfigs ) {
          array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = false;
            });
        },this);
    },

    _textFilter: function() {
        this.inherited(arguments);
        this._updateAllTitles();
    },

    /**
     * Make the track selector visible.
     * This does nothing for this track selector, since it is always visible.
     */
    show: function() {
    },

    /**
     * Make the track selector invisible.
     * This does nothing for this track selector, since it is always visible.
     */
    hide: function() {
    },

    /**
     * Toggle visibility of this track selector.
     * This does nothing for this track selector, since it is always visible.
     */
    toggle: function() {
    }

});
});<commit_msg>re-show the uncategorized pane in the hierarchical track sel if something is added to it later<commit_after>define(['dojo/_base/declare',
        'dojo/_base/array',
        'dojo/_base/lang',
        'dojo/dom-construct',
        'dojo/query',
        'dojo/on',
        'dojo/json',

        'dijit/TitlePane',
        'dijit/layout/ContentPane',

        './_TextFilterMixin'
       ],
       function(
           declare,
           array,
           lang,
           dom,
           query,
           on,
           JSON,

           TitlePane,
           ContentPane,

           _TextFilterMixin
       ) {

return declare(
    'JBrowse.View.TrackList.Hierarchical',
    [ ContentPane, _TextFilterMixin ],
    {

    region: 'left',
    splitter: true,
    style: 'width: 25%',

    id: 'hierarchicalTrackPane',
    baseClass: 'jbrowseHierarchicalTrackSelector',

    categoryFacet: 'category',

    constructor: function( args ) {
        this.categories = {};

        this._loadState();
    },
    postCreate: function() {
        this.placeAt( this.browser.container );

        // subscribe to commands coming from the the controller
        this.browser.subscribe( '/jbrowse/v1/c/tracks/show',
                                lang.hitch( this, 'setTracksActive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/hide',
                                lang.hitch( this, 'setTracksInactive' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/new',
                                lang.hitch( this, 'addTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/replace',
                                lang.hitch( this, 'replaceTracks' ));
        this.browser.subscribe( '/jbrowse/v1/c/tracks/delete',
                                lang.hitch( this, 'deleteTracks' ));
    },

    buildRendering: function() {
        this.inherited(arguments);

        var topPane = new ContentPane({ className: 'header' });
        this.addChild( topPane );
        dom.create(
            'h2',
            { className: 'title',
              innerHTML: 'Available Tracks'
            },
            topPane.containerNode );

        this._makeTextFilterNodes(
            dom.create('div',
                       { className: 'textfilterContainer' },
                       topPane.containerNode )
        );
        this._updateTextFilterControl();
    },

    startup: function() {
        this.inherited( arguments );

        var tracks = [];
        var thisB = this;
        var categoryFacet = this.get('categoryFacet');
        this.get('trackMetaData').fetch(
            { onItem: function(i) {
                  if( i.conf )
                      tracks.push( i.conf );
              },
              onComplete: function() {
                  // make a pane at the top to hold uncategorized tracks
                  thisB.categories.Uncategorized =
                      { pane: new ContentPane({ className: 'uncategorized' }).placeAt( thisB.containerNode ),
                        tracks: {},
                        categories: {}
                      };

                  thisB.addTracks( tracks, true );

                  // hide the uncategorized pane if it is empty
                  if( ! thisB.categories.Uncategorized.pane.containerNode.children.length ) {
                      //thisB.removeChild( thisB.categories.Uncategorized.pane );
                      thisB.categories.Uncategorized.pane.domNode.style.display = 'none';
                  }
              },
              sort: [ { attribute: this.get('categoryFacet').toLowerCase()},
                      { attribute: 'key' },
                      { attribute: 'label' }
                    ]
            });
    },

    addTracks: function( trackConfigs, inStartup ) {
        this.pane = this;
        var thisB = this;

        array.forEach( trackConfigs, function( trackConf ) {

            var categoryFacet = this.get('categoryFacet');
            var categoryNames = (
                trackConf.metadata && trackConf.metadata[ categoryFacet ]
                    || trackConf[ categoryFacet ]
                    || 'Uncategorized'
            ).split(/\s*\/\s*/);

            var category = _findCategory( this, categoryNames, [] );

            function _findCategory( obj, names, path ) {
                var categoryName = names.shift();
                path = path.concat(categoryName);
                var categoryPath = path.join('/');

                var cat = obj.categories[categoryName] || ( obj.categories[categoryName] = function() {
                    var isCollapsed = lang.getObject( 'collapsed.'+categoryPath, false, thisB.state );
                    var c = new TitlePane(
                        { title: ''+categoryName+''
                          + ' 0',
                          open: ! isCollapsed
                        });
                    // save our open/collapsed state in local storage
                    c.watch( 'open', function( attr, oldval, newval ) {
                                 lang.setObject( 'collapsed.'+categoryPath, !newval, thisB.state );
                                 thisB._saveState();
                             });
                    obj.pane.addChild(c, inStartup ? undefined : 1 );
                    return { parent: obj, pane: c, categories: {}, tracks: {} };
                }.call(thisB));

                return names.length ? _findCategory( cat, names, path ) : cat;
            };

            category.pane.domNode.style.display = 'block';
            var labelNode = dom.create( 'label', { className: 'tracklist-label shown' }, category.pane.containerNode );
            var checkbox = dom.create('input', { type: 'checkbox', className: 'check' }, labelNode );
            var trackLabel = trackConf.label;
            var checkListener;
            this.own( checkListener = on( checkbox, 'click', function() {
                thisB.browser.publish( '/jbrowse/v1/v/tracks/'+(this.checked ? 'show' : 'hide'), [trackConf] );
            }));
            dom.create('span', { className: 'key', innerHTML: trackConf.key || trackConf.label }, labelNode );

            category.tracks[ trackLabel ] = { checkbox: checkbox, checkListener: checkListener, labelNode: labelNode };

            this._updateTitles( category );
        }, this );
    },

    _loadState: function() {
        this.state = {};
        try {
            this.state = JSON.parse( localStorage.getItem( 'JBrowse-Hierarchical-Track-Selector' ) || '{}' );
        } catch(e) {}
        return this.state;
    },
    _saveState: function( state ) {
        try {
            localStorage.setItem( 'JBrowse-Hierarchical-Track-Selector', JSON.stringify( this.state ) );
        } catch(e) {}
    },

    // depth-first traverse and update the titles of all the categories
    _updateAllTitles: function(r) {
        var root = r || this;
        for( var c in root.categories ) {
            this._updateTitle( root.categories[c] );
            this._updateAllTitles( root.categories[c] );
        }
    },

    _updateTitle: function( category ) {
        category.pane.set( 'title', category.pane.get('title')
                           .replace( />\s*\d+\s*\</, '>'+query('label.shown', category.pane.containerNode ).length+'<' )
                         );
    },

    // update the titles of the given category and its parents
    _updateTitles: function( category ) {
        this._updateTitle( category );
        if( category.parent )
            this._updateTitles( category.parent );
    },

    _findTrack: function _findTrack( trackLabel, callback, r ) {
        var root = r || this;
        for( var c in root.categories ) {
            var category = root.categories[c];
            if( category.tracks[ trackLabel ] ) {
                callback( category.tracks[ trackLabel ], category );
                return true;
            }
            else {
                if( this._findTrack( trackLabel, callback, category ) )
                    return true;
            }
        }
        return false;
    },

    replaceTracks: function( trackConfigs ) {
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned on.
     */
    setTracksActive: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = true;
            });
        },this);
    },

    deleteTracks: function( /**Array[Object]*/ trackConfigs ) {
        array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.labelNode.parentNode.removeChild( trackRecord.labelNode );
                trackRecord.checkListener.remove();
                delete category.tracks[conf.label];
            });
        },this);
    },

    /**
     * Given an array of track configs, update the track list to show
     * that they are turned off.
     */
    setTracksInactive: function( /**Array[Object]*/ trackConfigs ) {
          array.forEach( trackConfigs, function(conf) {
            this._findTrack( conf.label, function( trackRecord, category ) {
                trackRecord.checkbox.checked = false;
            });
        },this);
    },

    _textFilter: function() {
        this.inherited(arguments);
        this._updateAllTitles();
    },

    /**
     * Make the track selector visible.
     * This does nothing for this track selector, since it is always visible.
     */
    show: function() {
    },

    /**
     * Make the track selector invisible.
     * This does nothing for this track selector, since it is always visible.
     */
    hide: function() {
    },

    /**
     * Toggle visibility of this track selector.
     * This does nothing for this track selector, since it is always visible.
     */
    toggle: function() {
    }

});
});<|endoftext|>


<commit_before>import React from 'react';
import Backbone from 'backbone';
import Router from 'react-router';
import stores from 'stores';
import Glyphicon from 'components/common/Glyphicon.react';
import actions from 'actions';
import ResourceActions from 'actions/ResourceActions';

let RouteHandler = Router.RouteHandler;

let ResourceRequest = React.createClass({
    mixins: [Router.State],

    getInitialState: function(){
      return {
        AUSearch: null,
        currentAllocation: null,
        currentQuota: null,
        quotaSearch: null,
        delta: 525600,
        expire: true,
        response: "",
        request: null,
        canSubmit: false,
        displayAdmin: false
      };
    },

    componentDidMount: function(){
        stores.ResourceRequestStore.addChangeListener(this.updateState);
        stores.AllocationStore.addChangeListener(this.updateState);
        stores.QuotaStore.addChangeListener(this.updateState);

        stores.AllocationStore.getAll();
        stores.QuotaStore.getAll();

        // In case all stores have already been loaded, call updateState manually once.
        this.updateState();
    },

    componentWillUnmount: function(){
        stores.ResourceRequestStore.removeChangeListener(this.updateState);
        stores.AllocationStore.removeChangeListener(this.updateState);
        stores.QuotaStore.removeChangeListener(this.updateState);
    },

    updateState: function(){
        var request = this.state.request || stores.ResourceRequestStore.get(this.getParams().id);
        var quotaId = request.get('current_quota');
        var allocationId = request.get('current_allocation');
        var currentQuota = this.state.currentQuota || stores.QuotaStore.get(quotaId);
        var currentAllocation = this.state.currentAllocation || stores.AllocationStore.get(allocationId);

        // Wait until all data has been fetched from the stores
        if(!request || !currentQuota || !currentAllocation){
            return;
        }

        // Set quota search to current quota if state's quota doesn't exist yet
        function setQuota(){
            return{
                cpu: currentQuota.get('cpu'),
                memory: currentQuota.get('memory'),
                storage: currentQuota.get('storage'),
                storage_count: currentQuota.get('storage_count'),
                suspended_count: currentQuota.get('suspended_count')
            };
        };

        var quotaSearch = this.state.quotaSearch || (currentQuota && setQuota());
        var AUSearch = this.state.AUSearch || currentAllocation.get('threshold') / 60;

        this.setState({
            request: request,
            currentQuota: currentQuota,
            currentAllocation: currentAllocation,
            AUSearch: AUSearch,
            quotaSearch: quotaSearch
        });
    },

    canSubmit: function(){
        var allocationExists = stores.AllocationStore.findWhere({
            threshold: this.state.AUSearch * 60,
            delta: this.state.delta
        }).length > 0;
        var quotaExists = stores.QuotaStore.findWhere(this.state.quotaSearch).length > 0;
        return this.state.response && allocationExists && quotaExists
    },

    handleDisplayChange: function (event){
      this.setState({displayAdmin: !this.state.displayAdmin});
    },

    handleResponseChange: function (event) {
      var response = event.target.value;
      if (response) this.setState({response: response});
    },

    handleQuotaChange: function (event) {
      var quota = event.target.value;
      if (quota) this.setState({quota: quota});
    },

    handleAllocationChange: function (event) {
      var allocation = event.target.value;
      if (allocation) this.setState({allocation: allocation});
    },

    handleApproval: function(e){
        e.preventDefault();
        var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
            quota = stores.QuotaStore.findWhere(this.state.quotaSearch).models[0].id,
            allocation = stores.AllocationStore.findWhere({
                threshold: this.state.AUSearch * 60,
                delta: this.state.delta
            }).models[0].id,
            status = stores.StatusStore.findOne({name: "approved"});

        ResourceActions.update({
          request: resourceRequest,
          response: this.state.response,
          quota: quota,
          allocation: allocation,
          status: status.id
        });

    },

    handleDenial: function(e){
      e.preventDefault();
      var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
        status = stores.StatusStore.findOne({name: "denied"});

      ResourceActions.update({
        request: resourceRequest,
        response: this.state.response,
        status: status.id
      });
    },

    handleThresholdSearchChange: function(e){
      this.setState({
        AUSearch: e.target.value
      });
    },

    makeNewQuota: function(e){
        actions.QuotaActions.create(this.state.quotaSearch);
    },

    makeNewAllocation: function(e){
        actions.AllocationActions.create({"threshold": this.state.AUSearch * 60, "delta": this.state.delta});
    },

    onExpireClick: function(expire){
      this.setState({
          delta: expire ? 525600: -1,
          expire,
      });
    },

    renderResourceStatus: function(){
        let allocation = stores.AllocationStore.findWhere({"threshold": parseInt(this.state.AUSearch) * 60, "delta": this.state.delta});
        let allocationExists = allocation && allocation.length > 0;

        let quota = stores.QuotaStore.findWhere(this.state.quotaSearch);
        let quotaExists = quota && quota.length > 0;

        if (!quotaExists || !allocationExists) {
            return(

                        These resources do not exist. Click   {
                            e.preventDefault();
                            quotaExists || this.makeNewQuota();
                            allocationExists || this.makeNewAllocation();
                        } }>here to create them.

            );
        }
    },

    onQuotaSearchChange: function(e){
        var currentQuotaSearch = this.state.quotaSearch;
        currentQuotaSearch[e.target.id] = Number(e.target.value);
        this.setState({
            quotaSearch: currentQuotaSearch
        });
    },

    renderAdminDetails: function(){
        var request = this.state.request,
            currentQuota = this.state.currentQuota,
            currentAllocation = this.state.currentAllocation,
            quotaSearch = this.state.quotaSearch,
            statuses = stores.StatusStore.getAll(),
            currentQuotaString = 'N/A',
            currentAllocationString = 'N/A';

        if (!request || !currentQuota || !currentAllocation || !statuses || !quotaSearch) return
;

        var currentCPU = "  CPU: " + currentQuota.get('cpu'),
          currentMemory = "  Memory: " + currentQuota.get('memory'),
          currentStorage = "  Storage: " + currentQuota.get('storage'),
          currentStorageCount = "  Storage Count: " + currentQuota.get('storage_count'),
          currentSuspendedCount = "  Suspended: " + currentQuota.get('suspended_count'),
          currentThreshold = "  Threshold: " + currentAllocation.get('threshold') + " (" + (currentAllocation.get('threshold') / 60) + " AU)",
          currentDelta = "  Delta: " + currentAllocation.get('delta'),
          currentQuotaString = currentCPU + currentMemory + currentStorage + currentStorageCount + currentSuspendedCount,
          currentAllocationString = currentThreshold + currentDelta;

        return(

For user:

{request.get('user').username}

Created by:

{request.get('created_by').username}

Request:
{request.get('request')}

Description:
{request.get('description')}

Admin message:
{request.get('admin_message')}

Current quota:
{currentQuotaString}

New quota:

                CPU:
                <input className="form-control" id="cpu" type="number" value={this.state.quotaSearch.cpu} onChange={this.onQuotaSearchChange} />
                Memory (GB):
                <input className="form-control" id="memory" type="number" value={this.state.quotaSearch.memory} onChange={this.onQuotaSearchChange} />
                Storage:
                <input className="form-control" id="storage" type="number" value={this.state.quotaSearch.storage} onChange={this.onQuotaSearchChange} />
                Storage Count:
                <input className="form-control" id="storage_count" type="number" value={this.state.quotaSearch.storage_count} onChange={this.onQuotaSearchChange} />
                Suspended Count:
                <input className="form-control" id="suspended_count" type="number" value={this.state.quotaSearch.suspended_count} onChange={this.onQuotaSearchChange} />

Current allocation:
{currentAllocationString}

New allocation:

                    AU:
                    <input id="au" className="form-control" type="number" value={this.state.AUSearch} onChange={this.handleThresholdSearchChange} />

Expiring allocation?

                  <input id="expire-enabled" type="radio" name="expire" defaultChecked onClick={this.onExpireClick.bind(this, true)}/>
                  Yes

                  <input id="expire-disabled" type="radio" name="expire" onClick={this.onExpireClick.bind(this, false)}/>
                  No

Response:

                <textarea type="text" form="admin" value={this.state.value} onChange={this.handleResponseChange}/>

              {this.renderResourceStatus()}

                <button disabled={!this.canSubmit()} onClick={this.handleApproval} type="button" className="btn btn-default btn-sm">Approve</button>
                <button onClick={this.handleDenial} type="button" className="btn btn-default btn-sm">Deny</button>

        );
    },

    render: function () {
      return (

          {this.renderAdminDetails()}
          <RouteHandler />

      );
    }
});

export default ResourceRequest;
<commit_msg>Prefer conversion to number earlier<commit_after>import React from 'react';
import Backbone from 'backbone';
import Router from 'react-router';
import stores from 'stores';
import Glyphicon from 'components/common/Glyphicon.react';
import actions from 'actions';
import ResourceActions from 'actions/ResourceActions';

let RouteHandler = Router.RouteHandler;

let ResourceRequest = React.createClass({
    mixins: [Router.State],

    getInitialState: function(){
      return {
        AUSearch: null,
        currentAllocation: null,
        currentQuota: null,
        quotaSearch: null,
        delta: 525600,
        expire: true,
        response: "",
        request: null,
        canSubmit: false,
        displayAdmin: false
      };
    },

    componentDidMount: function(){
        stores.ResourceRequestStore.addChangeListener(this.updateState);
        stores.AllocationStore.addChangeListener(this.updateState);
        stores.QuotaStore.addChangeListener(this.updateState);

        stores.AllocationStore.getAll();
        stores.QuotaStore.getAll();

        // In case all stores have already been loaded, call updateState manually once.
        this.updateState();
    },

    componentWillUnmount: function(){
        stores.ResourceRequestStore.removeChangeListener(this.updateState);
        stores.AllocationStore.removeChangeListener(this.updateState);
        stores.QuotaStore.removeChangeListener(this.updateState);
    },

    updateState: function(){
        var request = this.state.request || stores.ResourceRequestStore.get(this.getParams().id);
        var quotaId = request.get('current_quota');
        var allocationId = request.get('current_allocation');
        var currentQuota = this.state.currentQuota || stores.QuotaStore.get(quotaId);
        var currentAllocation = this.state.currentAllocation || stores.AllocationStore.get(allocationId);

        // Wait until all data has been fetched from the stores
        if(!request || !currentQuota || !currentAllocation){
            return;
        }

        // Set quota search to current quota if state's quota doesn't exist yet
        function setQuota(){
            return{
                cpu: currentQuota.get('cpu'),
                memory: currentQuota.get('memory'),
                storage: currentQuota.get('storage'),
                storage_count: currentQuota.get('storage_count'),
                suspended_count: currentQuota.get('suspended_count')
            };
        };

        var quotaSearch = this.state.quotaSearch || (currentQuota && setQuota());
        var AUSearch = this.state.AUSearch || currentAllocation.get('threshold') / 60;

        this.setState({
            request: request,
            currentQuota: currentQuota,
            currentAllocation: currentAllocation,
            AUSearch: AUSearch,
            quotaSearch: quotaSearch
        });
    },

    canSubmit: function(){
        var allocationExists = stores.AllocationStore.findWhere({
            threshold: this.state.AUSearch * 60,
            delta: this.state.delta
        }).length > 0;
        var quotaExists = stores.QuotaStore.findWhere(this.state.quotaSearch).length > 0;
        return this.state.response && allocationExists && quotaExists
    },

    handleDisplayChange: function (event){
      this.setState({displayAdmin: !this.state.displayAdmin});
    },

    handleResponseChange: function (event) {
      var response = event.target.value;
      if (response) this.setState({response: response});
    },

    handleQuotaChange: function (event) {
      var quota = event.target.value;
      if (quota) this.setState({quota: quota});
    },

    handleAllocationChange: function (event) {
      var allocation = event.target.value;
      if (allocation) this.setState({allocation: allocation});
    },

    handleApproval: function(e){
        e.preventDefault();
        var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
            quota = stores.QuotaStore.findWhere(this.state.quotaSearch).models[0].id,
            allocation = stores.AllocationStore.findWhere({
                threshold: this.state.AUSearch * 60,
                delta: this.state.delta
            }).models[0].id,
            status = stores.StatusStore.findOne({name: "approved"});

        ResourceActions.update({
          request: resourceRequest,
          response: this.state.response,
          quota: quota,
          allocation: allocation,
          status: status.id
        });

    },

    handleDenial: function(e){
      e.preventDefault();
      var resourceRequest = stores.ResourceRequestStore.get(this.getParams().id),
        status = stores.StatusStore.findOne({name: "denied"});

      ResourceActions.update({
        request: resourceRequest,
        response: this.state.response,
        status: status.id
      });
    },

    handleThresholdSearchChange: function(e){
      this.setState({
        AUSearch: Number(e.target.value)
      });
    },

    makeNewQuota: function(e){
        actions.QuotaActions.create(this.state.quotaSearch);
    },

    makeNewAllocation: function(e){
        actions.AllocationActions.create({"threshold": this.state.AUSearch * 60, "delta": this.state.delta});
    },

    onExpireClick: function(expire){
      this.setState({
          delta: expire ? 525600: -1,
          expire,
      });
    },

    renderResourceStatus: function(){
        let allocation = stores.AllocationStore.findWhere({"threshold": this.state.AUSearch * 60, "delta": this.state.delta});
        let allocationExists = allocation && allocation.length > 0;

        let quota = stores.QuotaStore.findWhere(this.state.quotaSearch);
        let quotaExists = quota && quota.length > 0;

        if (!quotaExists || !allocationExists) {
            return(

                        These resources do not exist. Click   {
                            e.preventDefault();
                            quotaExists || this.makeNewQuota();
                            allocationExists || this.makeNewAllocation();
                        } }>here to create them.

            );
        }
    },

    onQuotaSearchChange: function(e){
        var currentQuotaSearch = this.state.quotaSearch;
        currentQuotaSearch[e.target.id] = Number(e.target.value);
        this.setState({
            quotaSearch: currentQuotaSearch
        });
    },

    renderAdminDetails: function(){
        var request = this.state.request,
            currentQuota = this.state.currentQuota,
            currentAllocation = this.state.currentAllocation,
            quotaSearch = this.state.quotaSearch,
            statuses = stores.StatusStore.getAll(),
            currentQuotaString = 'N/A',
            currentAllocationString = 'N/A';

        if (!request || !currentQuota || !currentAllocation || !statuses || !quotaSearch) return
;

        var currentCPU = "  CPU: " + currentQuota.get('cpu'),
          currentMemory = "  Memory: " + currentQuota.get('memory'),
          currentStorage = "  Storage: " + currentQuota.get('storage'),
          currentStorageCount = "  Storage Count: " + currentQuota.get('storage_count'),
          currentSuspendedCount = "  Suspended: " + currentQuota.get('suspended_count'),
          currentThreshold = "  Threshold: " + currentAllocation.get('threshold') + " (" + (currentAllocation.get('threshold') / 60) + " AU)",
          currentDelta = "  Delta: " + currentAllocation.get('delta'),
          currentQuotaString = currentCPU + currentMemory + currentStorage + currentStorageCount + currentSuspendedCount,
          currentAllocationString = currentThreshold + currentDelta;

        return(

For user:

{request.get('user').username}

Created by:

{request.get('created_by').username}

Request:
{request.get('request')}

Description:
{request.get('description')}

Admin message:
{request.get('admin_message')}

Current quota:
{currentQuotaString}

New quota:

                CPU:
                <input className="form-control" id="cpu" type="number" value={this.state.quotaSearch.cpu} onChange={this.onQuotaSearchChange} />
                Memory (GB):
                <input className="form-control" id="memory" type="number" value={this.state.quotaSearch.memory} onChange={this.onQuotaSearchChange} />
                Storage:
                <input className="form-control" id="storage" type="number" value={this.state.quotaSearch.storage} onChange={this.onQuotaSearchChange} />
                Storage Count:
                <input className="form-control" id="storage_count" type="number" value={this.state.quotaSearch.storage_count} onChange={this.onQuotaSearchChange} />
                Suspended Count:
                <input className="form-control" id="suspended_count" type="number" value={this.state.quotaSearch.suspended_count} onChange={this.onQuotaSearchChange} />

Current allocation:
{currentAllocationString}

New allocation:

                    AU:
                    <input id="au" className="form-control" type="number" value={this.state.AUSearch} onChange={this.handleThresholdSearchChange} />

Expiring allocation?

                  <input id="expire-enabled" type="radio" name="expire" defaultChecked onClick={this.onExpireClick.bind(this, true)}/>
                  Yes

                  <input id="expire-disabled" type="radio" name="expire" onClick={this.onExpireClick.bind(this, false)}/>
                  No

Response:

                <textarea type="text" form="admin" value={this.state.value} onChange={this.handleResponseChange}/>

              {this.renderResourceStatus()}

                <button disabled={!this.canSubmit()} onClick={this.handleApproval} type="button" className="btn btn-default btn-sm">Approve</button>
                <button onClick={this.handleDenial} type="button" className="btn btn-default btn-sm">Deny</button>

        );
    },

    render: function () {
      return (

          {this.renderAdminDetails()}
          <RouteHandler />

      );
    }
});

export default ResourceRequest;
<|endoftext|>