<commit_before>/**
 * @module Foundation
 * @submodule Declaration
 * @for p5
 */

/**
 * Creates and names a new variable. A variable is a container for a value.
 * Variables that are declared with let will have block-scope.
 * This means that the variable only exists within the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block"
 * block that it is created within.
 *
 *
 * From the MDN entry:
 * Declares a block scope local variable, optionally initializing it to a value.
 *
 * @property let
 *
 * @example
 *

 *
 * let x = 2;
 * console.log(x); // prints 2 to the console
 * x = 1;
 * console.log(x); // prints 1 to the console
 *
 *

 */

/**
 * Creates and names a new constant variable. A variable is a container for a value.
 * Variables that are declared with const will have block-scope.
 * This means that the variable only exists within the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block"
 * block that it is created within.
 * const is different from let in that
 * variables declared with const cannot be reassigned or redeclared.
 *
 * From the MDN entry:
 * Declares a read-only named constant.
 * Constants are block-scoped, much like variables defined using the 'let' statement.
 * The value of a constant can't be changed through reassignment, and it can't be redeclared.
 * @property const
 *
 * @example
 *

 *
 * // define myFavNumber as a constant and give it the value 7
 * const myFavNumber = 7;
 * console.log('my favorite number is: ' + myFavNumber);
 *
 *

 */

/**
 * @submodule Control Statement
 */

/**
 * The if-else statement helps control the flow of your code.
 * A condition is placed between the parenthesis following 'if'.
 * When that condition evalues to < href="https://developer.mozilla.org/en-US/docs/Glossary/truthy">truthy,
 * the code between the preceding curly braces is run.
 * Alternatively, when the condition evaluates to falsy,
 * the code between the curly braces that precedes 'else' is run instead.
 *
 * From the MDN entry:
 * The 'if' statement executes a statement if a specified condition is truthy.
 * If the condition is falsy, another statement can be executed
 * @property if-else
 *
 * @example
 *

 *
 * let a = 4;
 * if (a > 0) {
 *   console.log('positive');
 * } else {
 *   console.log('negative');
 * }
 *
 *

 */

/**
 * @submodule Function
 */

/**
 * Creates and names a function.
 * A function is a set of statements that perform a task.
 * Optionally, functions can have parameters. Parameters
 * are variables that are scoped to the function, that can be assigned a value when calling the function.
 *
 * From the MDN entry:
 * Declares a function with the specified parameters.
 * @property function
 *
 * @example
 *

 *
 * function calculateRectArea(width, height) {
 *   return width * height;
 * }
 * calculateRectArea(4, 5); // calling the function
 *
 *

 */

/**
 * Specifies the value to be returned by a function.
 * For more info checkout
 * the MDN entry for return.
 * @property return
 *
 * @example
 *

 *
 * function calculateSquare(x) {
 *   return x * x;
 * }
 * calculateSquare(4); // returns 16
 *
 *

 */

/**
 * @submodule Types
 */

/**
 * boolean is one of the 7 primitive data types in Javascript.
 * A boolean can only be `true` or `false`.
 *
 * From the MDN entry:
 * Boolean represents a logical entity and can have two values: true, and false.
 *
 * @property boolean
 *
 * @example
 *

 *
 * const myBoolean = false;
 * console.log(typeof myBoolean); // prints 'boolean' to the console
 *
 *

 */

/**
 * string is one of the 7 primitive data types in Javascript.
 * A string is a series of text characters. In Javascript, a string value must be surrounded by either single-quotation marks(') or double-quotation marks(").
 *
 * From the MDN entry:
 * A string is a sequence of characters used to represent text.
 *
 * @property string
 *
 * @example
 *

 *
 * const mood = 'chill';
 * console.log(typeof mood); // prints 'string' to the console
 *
 *

 */

/**
 * number is one of the 7 primitive data types in Javascript.
 * A number can be a whole number or a decimal number.
 *
 * The MDN entry for number
 *
 * @property number
 *
 * @example
 *

 *
 * const num = 46.5;
 * console.log(typeof num); // prints 'number' to the console
 *
 *

 */

/**
 *
 * From MDN's object basics:
 * An object is a collection of related data and/or functionality (which usually consists of several variables and functions —
 * which are called properties and methods when they are inside objects.)
 * @property object
 *
 * @example
 *

 *
 * const author = {
 *   name: 'Ursula K Le Guin',
 *   books: [
 *     'The Left Hand of Darkness',
 *     'The Dispossessed',
 *     'A Wizard of Earthsea'
 *   ]
 * };
 * console.log(author.name); // prints 'Ursula K Le Guin' to the console
 *
 *

 */

/**
 * @submodule Classes
 */

/**
 * A class is template for the creation of objects.
 *
 * From the MDN entry:
 * The class declaration creates a new Class with a given name using prototype-based inheritance.
 * @property class
 *
 * @example
 *

 *
 * class Polygon {
 *   constructor(name, height, width) {
 *     this.name = name;
 *     this.height = height;
 *     this.width = width;
 *   }
 * }
 * const square = new Polygon('square', 1, 1); // creating new instance of Polygon Class.
 * console.log(square.width); // prints '1' to the console
 *
 *

 */

/**
 * @submodule Iterative Statement
 */

/**
 * for creates a loop that is useful for executing the code multiple times.
 *
 * A 'for loop' consists of three different expressions inside of a parenthesis, all of which are optional.
 * These expressions are used to control the number of times the loop is run.
 * The first expression is a statement that is used to set the initial state for the loop.
 * The second expression is a condition that you would like to check before each loop. If this expression returns
 * false then the loop will exit.
 * The third expression is executed at the end of each loop.
 *
 * The code inside of the loop body (in between the curly braces) is executed between the evaluation of the second
 * and third expression.
 *
 * From the MDN entry:
 * Creates a loop that executes a specified statement until the test condition evaluates to false.
 * The condition is evaluated after executing the statement, resulting in the specified statement executing at least once.
 * @property for
 *
 * @example
 *

 *
 * for (let i = 0; i < 9; i++) {
 *   console.log(i);
 * }
 *
 *

 */

/**
 * From the MDN entry:
 * Iterates over the enumerable properties of an object, in arbitrary order.
 * For each distinct property, statements can be executed.
 * @property for-in
 *
 * @example
 *

 *
 * const person = { fname: 'John', lname: 'Doe', age: 25 };
 * let text = '';
 * let x;
 * for (x in person) {
 *   text += person[x] + '';
 * }
 * console.log(text); //'John Doe 25 '
 *
 *

 */

/**
 * From the MDN entry:
 * Iterates over iterable objects (including arrays, array-like objects, iterators and generators),
 * invoking a custom iteration hook with statements to be executed for the value of each distinct property.
 * @property for-of
 *
 * @example
 *

 *
 * const word = 'boo';
 * for (const value of word) {
 *   console.log(value);
 * }
 *
 *

 */

/**
 * @submodule JS Method
 */

/**
 * From the MDN entry:
 * The parseInt() function parses a string argument and
 * returns an integer of the specified radix (the base in mathematical numeral systems).
 * @method parseInt
 *
 * @example
 *

 *
 * parseInt('F', 16);
 * parseInt(21, 8);
 *
 *

 */

/**
 * The JSON.stringify() method converts a JavaScript object or value to a JSON string.
 * @method stringify
 * @for JSON
 * @param {Object} object :Javascript object that you would like to convert to JSON
 *
 * @example
 *

 *
 * JSON.stringify('foo'); // '"foo"'
 * JSON.stringify([1, 'false', false]); //'[1,"false",false]'
 * JSON.stringify([NaN, null, Infinity]); //'[null,null,null]'
 *
 *

 */
<commit_msg>JSON.stringify first pass<commit_after>/**
 * @module Foundation
 * @submodule Declaration
 * @for p5
 */

/**
 * Creates and names a new variable. A variable is a container for a value.
 * Variables that are declared with let will have block-scope.
 * This means that the variable only exists within the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block"
 * block that it is created within.
 *
 *
 * From the MDN entry:
 * Declares a block scope local variable, optionally initializing it to a value.
 *
 * @property let
 *
 * @example
 *

 *
 * let x = 2;
 * console.log(x); // prints 2 to the console
 * x = 1;
 * console.log(x); // prints 1 to the console
 *
 *

 */

/**
 * Creates and names a new constant variable. A variable is a container for a value.
 * Variables that are declared with const will have block-scope.
 * This means that the variable only exists within the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block"
 * block that it is created within.
 * const is different from let in that
 * variables declared with const cannot be reassigned or redeclared.
 *
 * From the MDN entry:
 * Declares a read-only named constant.
 * Constants are block-scoped, much like variables defined using the 'let' statement.
 * The value of a constant can't be changed through reassignment, and it can't be redeclared.
 * @property const
 *
 * @example
 *

 *
 * // define myFavNumber as a constant and give it the value 7
 * const myFavNumber = 7;
 * console.log('my favorite number is: ' + myFavNumber);
 *
 *

 */

/**
 * @submodule Control Statement
 */

/**
 * The if-else statement helps control the flow of your code.
 * A condition is placed between the parenthesis following 'if'.
 * When that condition evalues to < href="https://developer.mozilla.org/en-US/docs/Glossary/truthy">truthy,
 * the code between the preceding curly braces is run.
 * Alternatively, when the condition evaluates to falsy,
 * the code between the curly braces that precedes 'else' is run instead.
 *
 * From the MDN entry:
 * The 'if' statement executes a statement if a specified condition is truthy.
 * If the condition is falsy, another statement can be executed
 * @property if-else
 *
 * @example
 *

 *
 * let a = 4;
 * if (a > 0) {
 *   console.log('positive');
 * } else {
 *   console.log('negative');
 * }
 *
 *

 */

/**
 * @submodule Function
 */

/**
 * Creates and names a function.
 * A function is a set of statements that perform a task.
 * Optionally, functions can have parameters. Parameters
 * are variables that are scoped to the function, that can be assigned a value when calling the function.
 *
 * From the MDN entry:
 * Declares a function with the specified parameters.
 * @property function
 *
 * @example
 *

 *
 * function calculateRectArea(width, height) {
 *   return width * height;
 * }
 * calculateRectArea(4, 5); // calling the function
 *
 *

 */

/**
 * Specifies the value to be returned by a function.
 * For more info checkout
 * the MDN entry for return.
 * @property return
 *
 * @example
 *

 *
 * function calculateSquare(x) {
 *   return x * x;
 * }
 * calculateSquare(4); // returns 16
 *
 *

 */

/**
 * @submodule Types
 */

/**
 * boolean is one of the 7 primitive data types in Javascript.
 * A boolean can only be `true` or `false`.
 *
 * From the MDN entry:
 * Boolean represents a logical entity and can have two values: true, and false.
 *
 * @property boolean
 *
 * @example
 *

 *
 * const myBoolean = false;
 * console.log(typeof myBoolean); // prints 'boolean' to the console
 *
 *

 */

/**
 * string is one of the 7 primitive data types in Javascript.
 * A string is a series of text characters. In Javascript, a string value must be surrounded by either single-quotation marks(') or double-quotation marks(").
 *
 * From the MDN entry:
 * A string is a sequence of characters used to represent text.
 *
 * @property string
 *
 * @example
 *

 *
 * const mood = 'chill';
 * console.log(typeof mood); // prints 'string' to the console
 *
 *

 */

/**
 * number is one of the 7 primitive data types in Javascript.
 * A number can be a whole number or a decimal number.
 *
 * The MDN entry for number
 *
 * @property number
 *
 * @example
 *

 *
 * const num = 46.5;
 * console.log(typeof num); // prints 'number' to the console
 *
 *

 */

/**
 *
 * From MDN's object basics:
 * An object is a collection of related data and/or functionality (which usually consists of several variables and functions —
 * which are called properties and methods when they are inside objects.)
 * @property object
 *
 * @example
 *

 *
 * const author = {
 *   name: 'Ursula K Le Guin',
 *   books: [
 *     'The Left Hand of Darkness',
 *     'The Dispossessed',
 *     'A Wizard of Earthsea'
 *   ]
 * };
 * console.log(author.name); // prints 'Ursula K Le Guin' to the console
 *
 *

 */

/**
 * @submodule Classes
 */

/**
 * A class is template for the creation of objects.
 *
 * From the MDN entry:
 * The class declaration creates a new Class with a given name using prototype-based inheritance.
 * @property class
 *
 * @example
 *

 *
 * class Polygon {
 *   constructor(name, height, width) {
 *     this.name = name;
 *     this.height = height;
 *     this.width = width;
 *   }
 * }
 * const square = new Polygon('square', 1, 1); // creating new instance of Polygon Class.
 * console.log(square.width); // prints '1' to the console
 *
 *

 */

/**
 * @submodule Iterative Statement
 */

/**
 * for creates a loop that is useful for executing the code multiple times.
 *
 * A 'for loop' consists of three different expressions inside of a parenthesis, all of which are optional.
 * These expressions are used to control the number of times the loop is run.
 * The first expression is a statement that is used to set the initial state for the loop.
 * The second expression is a condition that you would like to check before each loop. If this expression returns
 * false then the loop will exit.
 * The third expression is executed at the end of each loop.
 *
 * The code inside of the loop body (in between the curly braces) is executed between the evaluation of the second
 * and third expression.
 *
 * From the MDN entry:
 * Creates a loop that executes a specified statement until the test condition evaluates to false.
 * The condition is evaluated after executing the statement, resulting in the specified statement executing at least once.
 * @property for
 *
 * @example
 *

 *
 * for (let i = 0; i < 9; i++) {
 *   console.log(i);
 * }
 *
 *

 */

/**
 * From the MDN entry:
 * Iterates over the enumerable properties of an object, in arbitrary order.
 * For each distinct property, statements can be executed.
 * @property for-in
 *
 * @example
 *

 *
 * const person = { fname: 'John', lname: 'Doe', age: 25 };
 * let text = '';
 * let x;
 * for (x in person) {
 *   text += person[x] + '';
 * }
 * console.log(text); //'John Doe 25 '
 *
 *

 */

/**
 * From the MDN entry:
 * Iterates over iterable objects (including arrays, array-like objects, iterators and generators),
 * invoking a custom iteration hook with statements to be executed for the value of each distinct property.
 * @property for-of
 *
 * @example
 *

 *
 * const word = 'boo';
 * for (const value of word) {
 *   console.log(value);
 * }
 *
 *

 */

/**
 * @submodule JS Method
 */

/**
 * From the MDN entry:
 * The parseInt() function parses a string argument and
 * returns an integer of the specified radix (the base in mathematical numeral systems).
 * @method parseInt
 *
 * @example
 *

 *
 * parseInt('F', 16);
 * parseInt(21, 8);
 *
 *

 */

/**
 * From the MDN entry:
 * The JSON.stringify() method converts a JavaScript object or value to a JSON string.
 * @method stringify
 * @for JSON
 * @param {Object} object :Javascript object that you would like to convert to JSON
 *
 * @example
 *

 *
 * const myObject = { x: 5, y: 6 };
 * const myObjectAsString = JSON.stringify(myObject);
 * console.log(myObjectAsString); // prints "{"x":5,"y":6}" to the console
 * console.log(typeof myObjectAsString); // prints 'string' to the console
 *
 *

 */
<|endoftext|>


<commit_before><commit_msg>BUG: Negative test results are 'attempted', see app/src/constants.js<commit_after><|endoftext|>


<commit_before>/*global define*/
/* jshint -W106, -W069*/
define(['jquery', 'underscore', 'backbone', 'templates', 'humanize', 'helpers/animation', 'marionette'], function($, _, Backbone, JST, humanize, animation) {
    'use strict';

    return Backbone.Marionette.ItemView.extend({
        className: 'gauge card span6 status',
        template: JST['app/scripts/templates/status.ejs'],
        statusTemplate: JST['app/scripts/templates/status-icon.ejs'],
        title: 'status',
        timer: null,
        ui: {
            cardTitle: '.card-title',
            subText: '.subtext',
            monstatus: '.mon-status',
            pgstate: '.pg-status',
            okosd: '.ok-osd',
            warnosd: '.warn-osd',
            failosd: '.fail-osd',
            okpool: '.ok-pool',
            warnpool: '.warn-pool',
            failpool: '.fail-pool',
            okmds: '.ok-mds',
            warnmds: '.warn-mds',
            failmds: '.fail-mds',
            okpg: '.ok-pg',
            warnpg: '.warn-pg',
            failpg: '.fail-pg',
            okmon: '.ok-mon',
            warnmon: '.warn-mon',
            failmon: '.fail-mon',
            spinner: '.icon-spinner'
        },
        modelEvents: {
            'change': 'updateView'
        },
        serializeData: function() {
            return {
                title: this.title,
                relTime: humanize.relativeTime(Date.now() / 1000)
            };
        },
        initialize: function(options) {
            this.disappearAnimation = animation.single('fadeOutUpAnim');
            this.reappearAnimation = animation.single('fadeInDownAnim');
            _.bindAll(this, 'updateView', 'set', 'updateTimer', 'disappear', 'disappearAnimation', 'reappearAnimation', 'reappear');

            // The are defaults for Gauge.js and can be overidden from the contructor
            this.App = Backbone.Marionette.getOption(this, 'App');
            if (this.App) {
                this.listenTo(this.App.vent, 'status:update', this.set);
                this.listenTo(this.App.vent, 'gauges:disappear', this.disappear);
                this.listenTo(this.App.vent, 'gauges:reappear', this.reappear);
                this.listenTo(this.App.vent, 'gauges:expand', this.expand);
                this.listenTo(this.App.vent, 'gauges:collapse', this.collapse);
            }
            if (options && options.App !== undefined) {
                this.App = options.App;
            }
            if (this.App && !this.App.Config['offline']) {
                /* Placeholder */
            }
            var self = this;
            this.listenToOnce(this, 'render', function() {
                self.listenTo(self.App.vent, 'status:request', function() {
                    self.ui.spinner.css('visibility', 'visible');
                });
                self.listenTo(self.App.vent, 'status:sync status:error', function() {
                    self.ui.spinner.css('visibility', 'hidden');
                });
                if (self.timer === null) {
                    self.updateTimer();
                }
            });
            this.formatPGWarn = this.formatStatus('PG Warn Status', 'icon-info warn');
            this.formatPGStates = this.formatStates('PGs');
            this.formatPGCrit = this.formatStatus('PG Critical Status', 'icon-info fail');
            this.formatPGStates = this.formatStates('PGs');
        },
        set: function(model) {
            this.model.set(model.attributes);
        },
        updateTimer: function() {
            this.ui.subText.text(humanize.relativeTime(this.model.get('added_ms') / 1000));
            this.timer = setTimeout(this.updateTimer, 1000);
        },
        updateView: function(model) {
            var attr = model.attributes;
            this.ui.okosd.text(attr.osd['up_in']);
            this.ui.warnosd.text(attr.osd['up_not_in']);
            this.ui.failosd.text(attr.osd['not_up_not_in']);
            this.ui.okmon.text(attr.mon['in_quorum']);
            this.ui.failmon.text(attr.mon['not_in_quorum']);
            var pg = model.getPGCounts();
            this.ui.okpg.text(pg.ok);
            this.ui.warnpg.text(pg.warn);
            this.ui.failpg.text(pg.critical);
            this.ui.subText.text(humanize.relativeTime(attr.added_ms / 1000));
            (function(self) {
                // yield this it's not urgent
                setTimeout(function() {
                    self.addPGStateInfo.call(self, model.getPGStates());
                }, 0);
            })(this);

        },
        // add pgstate info to ui
        addPGStateInfo: function(pgstate) {
            var pgstateHtml = [],
                pgSelectors = [];
            if (_.keys(pgstate.warn).length) {
                pgstateHtml.push(this.formatPGWarn(this.formatPGStates(pgstate.warn)));
                pgSelectors.push('.warn');
            }
            if (_.keys(pgstate.crit).length) {
                pgstateHtml.push(this.formatPGCrit(this.formatPGStates(pgstate.crit)));
                pgSelectors.push('.fail');
            }
            this.ui.pgstate.html(pgstateHtml.join(''));
            var ui = this.ui;
            _.each(pgSelectors, function(selector) {
                ui.pgstate.find(selector).popover();
            });
        },
        // uses partial application to format state strings for display
        formatStates: function(entity) {
            return function(states) {
                return _.reduce(_.map(states, function(value, key) {
                    return value + ' ' + entity + ' ' + key;
                }), function(memo, string) {
                    return memo + ', ' + string;
                });
            };
        },
        // uses partial application function to create
        // markup/configuration for state popover
        formatStatus: function(title, iconClass) {
            return function(status) {
                return this.statusTemplate({
                    iconClass: iconClass,
                    content: status,
                    title: title
                });
            };
        },
        disappear: function(callback) {
            return this.disappearAnimation(this.$el, function() {
                this.$el.css('visibility', 'hidden');
            }).then(function() {
                if (callback) {
                    callback.apply(this);
                }
            });
        },
        reappear: function(callback) {
            this.$el.css('visibility', 'visible');
            return this.reappearAnimation(this.$el, callback);
        },
        expand: function(callback) {
            this.$el.css('display', 'block');
            if (callback) {
                callback.apply(this);
            }
        },
        collapse: function(callback) {
            this.$el.css('display', 'none');
            if (callback) {
                callback.apply(this);
            }
        }
    });
});
<commit_msg>Remove duplicate<commit_after>/*global define*/
/* jshint -W106, -W069*/
define(['jquery', 'underscore', 'backbone', 'templates', 'humanize', 'helpers/animation', 'marionette'], function($, _, Backbone, JST, humanize, animation) {
    'use strict';

    return Backbone.Marionette.ItemView.extend({
        className: 'gauge card span6 status',
        template: JST['app/scripts/templates/status.ejs'],
        statusTemplate: JST['app/scripts/templates/status-icon.ejs'],
        title: 'status',
        timer: null,
        ui: {
            cardTitle: '.card-title',
            subText: '.subtext',
            monstatus: '.mon-status',
            pgstate: '.pg-status',
            okosd: '.ok-osd',
            warnosd: '.warn-osd',
            failosd: '.fail-osd',
            okpool: '.ok-pool',
            warnpool: '.warn-pool',
            failpool: '.fail-pool',
            okmds: '.ok-mds',
            warnmds: '.warn-mds',
            failmds: '.fail-mds',
            okpg: '.ok-pg',
            warnpg: '.warn-pg',
            failpg: '.fail-pg',
            okmon: '.ok-mon',
            warnmon: '.warn-mon',
            failmon: '.fail-mon',
            spinner: '.icon-spinner'
        },
        modelEvents: {
            'change': 'updateView'
        },
        serializeData: function() {
            return {
                title: this.title,
                relTime: humanize.relativeTime(Date.now() / 1000)
            };
        },
        initialize: function(options) {
            this.disappearAnimation = animation.single('fadeOutUpAnim');
            this.reappearAnimation = animation.single('fadeInDownAnim');
            _.bindAll(this, 'updateView', 'set', 'updateTimer', 'disappear', 'disappearAnimation', 'reappearAnimation', 'reappear');

            // The are defaults for Gauge.js and can be overidden from the contructor
            this.App = Backbone.Marionette.getOption(this, 'App');
            if (this.App) {
                this.listenTo(this.App.vent, 'status:update', this.set);
                this.listenTo(this.App.vent, 'gauges:disappear', this.disappear);
                this.listenTo(this.App.vent, 'gauges:reappear', this.reappear);
                this.listenTo(this.App.vent, 'gauges:expand', this.expand);
                this.listenTo(this.App.vent, 'gauges:collapse', this.collapse);
            }
            if (options && options.App !== undefined) {
                this.App = options.App;
            }
            if (this.App && !this.App.Config['offline']) {
                /* Placeholder */
            }
            var self = this;
            this.listenToOnce(this, 'render', function() {
                self.listenTo(self.App.vent, 'status:request', function() {
                    self.ui.spinner.css('visibility', 'visible');
                });
                self.listenTo(self.App.vent, 'status:sync status:error', function() {
                    self.ui.spinner.css('visibility', 'hidden');
                });
                if (self.timer === null) {
                    self.updateTimer();
                }
            });
            this.formatPGWarn = this.formatStatus('PG Warn Status', 'icon-info warn');
            this.formatPGStates = this.formatStates('PGs');
            this.formatPGCrit = this.formatStatus('PG Critical Status', 'icon-info fail');
        },
        set: function(model) {
            this.model.set(model.attributes);
        },
        updateTimer: function() {
            this.ui.subText.text(humanize.relativeTime(this.model.get('added_ms') / 1000));
            this.timer = setTimeout(this.updateTimer, 1000);
        },
        updateView: function(model) {
            var attr = model.attributes;
            this.ui.okosd.text(attr.osd['up_in']);
            this.ui.warnosd.text(attr.osd['up_not_in']);
            this.ui.failosd.text(attr.osd['not_up_not_in']);
            this.ui.okmon.text(attr.mon['in_quorum']);
            this.ui.failmon.text(attr.mon['not_in_quorum']);
            var pg = model.getPGCounts();
            this.ui.okpg.text(pg.ok);
            this.ui.warnpg.text(pg.warn);
            this.ui.failpg.text(pg.critical);
            this.ui.subText.text(humanize.relativeTime(attr.added_ms / 1000));
            (function(self) {
                // yield this it's not urgent
                setTimeout(function() {
                    self.addPGStateInfo.call(self, model.getPGStates());
                }, 0);
            })(this);

        },
        // add pgstate info to ui
        addPGStateInfo: function(pgstate) {
            var pgstateHtml = [],
                pgSelectors = [];
            if (_.keys(pgstate.warn).length) {
                pgstateHtml.push(this.formatPGWarn(this.formatPGStates(pgstate.warn)));
                pgSelectors.push('.warn');
            }
            if (_.keys(pgstate.crit).length) {
                pgstateHtml.push(this.formatPGCrit(this.formatPGStates(pgstate.crit)));
                pgSelectors.push('.fail');
            }
            this.ui.pgstate.html(pgstateHtml.join(''));
            var ui = this.ui;
            _.each(pgSelectors, function(selector) {
                ui.pgstate.find(selector).popover();
            });
        },
        // uses partial application to format state strings for display
        formatStates: function(entity) {
            return function(states) {
                return _.reduce(_.map(states, function(value, key) {
                    return value + ' ' + entity + ' ' + key;
                }), function(memo, string) {
                    return memo + ', ' + string;
                });
            };
        },
        // uses partial application function to create
        // markup/configuration for state popover
        formatStatus: function(title, iconClass) {
            return function(status) {
                return this.statusTemplate({
                    iconClass: iconClass,
                    content: status,
                    title: title
                });
            };
        },
        disappear: function(callback) {
            return this.disappearAnimation(this.$el, function() {
                this.$el.css('visibility', 'hidden');
            }).then(function() {
                if (callback) {
                    callback.apply(this);
                }
            });
        },
        reappear: function(callback) {
            this.$el.css('visibility', 'visible');
            return this.reappearAnimation(this.$el, callback);
        },
        expand: function(callback) {
            this.$el.css('display', 'block');
            if (callback) {
                callback.apply(this);
            }
        },
        collapse: function(callback) {
            this.$el.css('display', 'none');
            if (callback) {
                callback.apply(this);
            }
        }
    });
});
<|endoftext|>


<commit_before>"use strict";

// import databaseModel
const CustomCommand = require('../dbModels/customCommand.js');

const moment = require('moment');
const util = require('util');
const admins = require('../admins.json').admins;
const _ = require('lodash');
const ytdl = require('ytdl-core');
const fs = require('fs');
const ffmpeg = require('ffmpeg');
const exec = require('child_process').exec;
const path = require('path');

const joinAndPlay = require('./sounds.js').joinVoiceChannelAndPlay;

const chalk = require("chalk"),
    c = new chalk.constructor({
        enabled: true
    });

let serverC = c.black.bold,
    channelC = c.green.bold,
    userC = c.cyan.bold,
    warningC = c.yellow.bold,
    errorC = c.red.bold,
    botC = c.magenta.bold;

let removeFile = (file) => {
    fs.unlink(file);
};

let newSoundCommand = (bot, msg, commandInfo, customCmd) => {
    let cmdName = commandInfo[0];
    let cmdNoTrigger = cmdName.slice(1);
    let ytUrl = commandInfo[2];
    let startTime = commandInfo[3];
    let seconds = commandInfo[4];

    // download the video via ytdl, then write that to a video file.  After that is done, then we run that video file through the ffmpeg
    // lib to create the small snippet video we need, save that to a new file and delete the original video downloaded via fs.unlink()

    //todo: move resources path into a config file or something
    let resourcesPath = path.resolve('resources/');

    let stream = ytdl(ytUrl, {
        filter: (format) => {
            return format.container === 'mp4';
        }
    }).pipe(fs.createWriteStream(`${__dirname}/temp.mp4`));

    let tempFileDir = `${__dirname}/temp.mp4`;

    stream.on('finish', () => {
        bot.reply(msg, `Converting command from video -> audio`);

        let duration = `00:00:${seconds}`;
        // create a child process to run the ffmpeg command to convert the downloaded file to an mp3 and store it on the server
        //todo: possibly change \\ to / for windows vs linux directory navigation
        let childProcess = exec(`ffmpeg -i ${__dirname}\\temp.mp4 -acodec libmp3lame -ac 2 -ab 160k -ar 48000 -ss ${startTime} -t ${duration} ${resourcesPath}\\${cmdNoTrigger}.mp3`,
            (error, stdout, stderr) => {
                if (error !== null) {
                    console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${errorC(`There was an error trying to encode the command: ${cmdName}`)}`);
                    console.log(`${errorC(`error: ${error}`)}`);

                    removeFile(tempFileDir);
                }
            });

        // event to catch when ffmpeg is finished converting
        childProcess.on('exit', (code) => {
            if (code != 0) {
                console.log(`${errorC(`ffmpeg exited with an error. Uh oh.`)}`);
                bot.reply(msg, `Shit hit the fan when trying to convert the video to an audio file`);

                removeFile(tempFileDir);
                removeFile(`${resourcesPath}\\${cmdNoTrigger}.mp3`);

                return;
            }

            // making sure the file was created successfully
            fs.stat(`${resourcesPath}/${cmdNoTrigger}.mp3`, (err, stats) => {
                if (err || !stats.isFile()) {
                    console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${errorC(`The file cannot be found after ffmpeg conversion: ${cmdNoTrigger}.mp3`)}`);
                    console.log(`Removing temp file downloaded from ytdl-core.`);

                    removeFile(`${__dirname}/temp.mp4`);
                    bot.reply(msg, 'Something happened when trying to find the converted audio file.');

                    return; // return because we don't want the database command to be created if the sound file to play cannot be found
                } else {

                    // remove the temp file that was originally downloaded via ytdl
                    fs.unlink(`${__dirname}/temp.mp4`, () => {
                        console.log(`${channelC(`File has been created, sliced, and copied successfully. Removal of temporary file was also successful. Storing command in database`)}`);
                    });
                }

                customCmd.save().then((result) => {
                    console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${warningC(cmdName)} was created by ${userC(msg.author.username)}`);

                    bot.reply(msg, `New command '${cmdName}' was successfully created! Commands must be reloaded before the new commands may be used!`);
                });
            });
        });
    });
};

let customCommands = {
    "createcommand": {
        usage: "~createcommand ~[commandName]|[type ('text', 'sound', 'image')]|[(imageUrl, textResponse, or youtubeUrl)]|[startTime (ex:00:00:00)]|[duration (ex: 15)}]",
        delete: true,
        type: "customCommand",
        process: (bot, msg, suffix) => {
            let adminsArray = Array.from(admins);

            if (!_.includes(adminsArray, msg.author.id.toString())) {
                bot.reply(`Sorry, you don't have permissions to execute this command!`);
                return;
            }

            let commandInfo = suffix.split('|');
            if (commandInfo.length < 3) {
                bot.reply(`Incorrect number of parameters passed into the command. Correct usage: ${this.usage}`);
                return;
            }

            let trigger = commandInfo[0];
            if (!trigger.startsWith('~')) {
                bot.reply(`The new command must start with a prefix of '~'`);
                return;
            }

            let commandType = commandInfo[1];
            if (commandType != 'text' || commandType != 'sound' || commandType != 'image') {
                bot.reply(`Incorrect custom command type was passed in. Types accepted: 'text', 'image', 'sound'`);
            }

            let customCmd = new CustomCommand({
                serverId: msg.server.id,
                commandText: trigger,
                createDate: moment().format("MM/DD/YYYY hh:mm:ss"),
                createUser: msg.author.username
            });

            console.log(`commandInfo: ${commandInfo}`);

            switch (commandType){
                case 'text':
                    customCmd.commandType = 'text';
                    customCmd.commandResponse = commandInfo[2];

                    customCmd.save().then((result) => {
                        console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${warningC(trigger)} was created by ${userC(msg.author.username)}`);

                        bot.reply(msg, `New command '${trigger}' was successfully created! Commands must be reloaded before the new commands may be used!`);
                    });

                    break;

                case 'sound':
                    customCmd.commandType = 'sound';
                    let duration = commandInfo[4];

                    if (Date.parse(`01/01/2016 ${duration.toString()}`) > Date.parse("01/01/2016 00:00:07")) {
                        bot.reply(msg, `The maximum duration for a sound command is 7 seconds!`);
                        return;
                    }

                    newSoundCommand(bot, msg, commandInfo, customCmd);

                    break;

                case 'image':
                    customCmd.commandType = 'image';
                    customCmd.imageUrl = commandInfo[2];

                    customCmd.save().then(() => {
                        console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${warningC(trigger)} was created by ${userC(msg.author.username)}`);

                        bot.reply(msg, `New command '${trigger}' was successfully created! Commands must be reloaded before the new commands may be used!`);
                    });

                    break;
            }
        }
    },
    "deletecommand": {
        usage: "~deletecommand [commandname]",
        delete: true,
        type: "customCommand",
        process: (bot, msg, suffix) => {
            let adminsArray = Array.from(admins);

            if (!_.includes(adminsArray, msg.author.id.toString())) {
                bot.reply(`Sorry, you don't have permissions to execute this command!`);
                return;
            }

            CustomCommand.filter({ serverId: msg.server.id, commandText: suffix}).run({readMode: 'majority'}).then((result) => {
                result[0].delete().then((result) => {
                    if (result.type == 'sound')
                        removeFile(path.resolve('resources/', `${suffix.slice(1)}.mp3`));

                    bot.reply(msg, `Custom command '${suffix}' was successfully deleted`);
                }).catch((err) => {
                    console.log(`error: ${err}`);
                });
            });
        }
    },
    "dbimagecommand": {
        usage: "~command",
        delete: true,
        type: "dbCommand",
        process: (bot, msg, dbCommand) => {
            bot.sendFile(msg.channel, dbCommand.imageUrl).catch(err => {
                console.log(`Error sending \'${dbCommand.commandText}\' from database: ${err}`);
            });
        }
    },
    "dbtextcommand": {
        usage: "~command",
        delete: true,
        type: "dbCommand",
        process: (bot, msg, dbCommand) => {
            bot.sendMessage(msg.channel, dbCommand.commandResponse).catch(err => {
                console.log(`Error sending \'${dbCommand.commandText}\' from database: ${err}`);
            })
        }
    },
    "dbsoundcommand": {
        usage: "~command",
        delete: true,
        type: "dbCommand",
        process: (bot, msg, dbCommand) => {
            let options = {
                volume: 0.5
            };

            let filename = `${dbCommand.commandText.substring(1)}.mp3`;

            let file = path.resolve('resources/', filename);

            joinAndPlay(bot, msg, file, options);
        }
    }
};

exports.customCommands = customCommands;
<commit_msg>sound files associated with custom commands are now unique per server. appends the server id to the filename.<commit_after>"use strict";

// import databaseModel
const CustomCommand = require('../dbModels/customCommand.js');

const moment = require('moment');
const util = require('util');
const admins = require('../admins.json').admins;
const _ = require('lodash');
const ytdl = require('ytdl-core');
const fs = require('fs');
const ffmpeg = require('ffmpeg');
const exec = require('child_process').exec;
const path = require('path');

const joinAndPlay = require('./sounds.js').joinVoiceChannelAndPlay;

const chalk = require("chalk"),
    c = new chalk.constructor({
        enabled: true
    });

let serverC = c.black.bold,
    channelC = c.green.bold,
    userC = c.cyan.bold,
    warningC = c.yellow.bold,
    errorC = c.red.bold,
    botC = c.magenta.bold;

let removeFile = (file) => {
    console.log(`file to remove: ${file}`);
    fs.unlink(file);
};

let commandExists = (trigger, serverId) => {
    CustomCommand.filter({ serverId, commandText: trigger }).run({readMode: 'majority'}).then((result) => {
        return result.length > 0;
    });
};

let newSoundCommand = (bot, msg, commandInfo, customCmd) => {
    let cmdName = commandInfo[0];
    let cmdNoTrigger = cmdName.slice(1);
    let ytUrl = commandInfo[2];
    let startTime = commandInfo[3];
    let seconds = commandInfo[4];

    // download the video via ytdl, then write that to a video file.  After that is done, then we run that video file through the ffmpeg
    // lib to create the small snippet video we need, save that to a new file and delete the original video downloaded via fs.unlink()

    //todo: move resources path into a config file or something
    let resourcesPath = path.resolve('resources/');

    let stream = ytdl(ytUrl, {
        filter: (format) => {
            return format.container === 'mp4';
        }
    }).pipe(fs.createWriteStream(`${__dirname}/temp.mp4`));

    let tempFileDir = `${__dirname}/temp.mp4`;

    stream.on('finish', () => {
        bot.reply(msg, `Converting command from video -> audio`);

        let duration = `00:00:${seconds}`;
        // create a child process to run the ffmpeg command to convert the downloaded file to an mp3 and store it on the server
        //todo: possibly change \\ to / for windows vs linux directory navigation
        let childProcess = exec(`ffmpeg -i ${__dirname}/temp.mp4 -acodec libmp3lame -ac 2 -ab 160k -ar 48000 -ss ${startTime} -t ${duration} ${resourcesPath}/${cmdNoTrigger}${msg.server.id}.mp3`,
            (error, stdout, stderr) => {
                if (error !== null) {
                    console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${errorC(`There was an error trying to encode the command: ${cmdName}`)}`);
                    console.log(`${errorC(`error: ${error}`)}`);

                    removeFile(tempFileDir);
                }
            });

        // event to catch when ffmpeg is finished converting
        childProcess.on('exit', (code) => {
            if (code != 0) {
                console.log(`${errorC(`ffmpeg exited with an error. Uh oh.`)}`);
                bot.reply(msg, `Shit hit the fan when trying to convert the video to an audio file`);

                removeFile(tempFileDir);
                removeFile(`${resourcesPath}\\${cmdNoTrigger}.mp3`);

                return;
            }

            // making sure the file was created successfully
            fs.stat(`${resourcesPath}/${cmdNoTrigger}${msg.server.id}.mp3`, (err, stats) => {
                if (err || !stats.isFile()) {
                    console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${errorC(`The file cannot be found after ffmpeg conversion: ${cmdNoTrigger}${msg.server.id}.mp3`)}`);
                    console.log(`Removing temp file downloaded from ytdl-core.`);

                    removeFile(`${__dirname}/temp.mp4`);
                    bot.reply(msg, 'Something happened when trying to find the converted audio file.');

                    return; // return because we don't want the database command to be created if the sound file to play cannot be found
                } else {

                    // remove the temp file that was originally downloaded via ytdl
                    fs.unlink(`${__dirname}/temp.mp4`, () => {
                        console.log(`${channelC(`File has been created, sliced, and copied successfully. Removal of temporary file was also successful. Storing command in database`)}`);
                    });
                }

                customCmd.save().then((result) => {
                    console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${warningC(cmdName)} was created by ${userC(msg.author.username)}`);

                    bot.reply(msg, `New command '${cmdName}' was successfully created! Commands must be reloaded before the new commands may be used!`);
                });
            });
        });
    });
};

let customCommands = {
    "createcommand": {
        usage: "~createcommand ~[commandName]|[type ('text', 'sound', 'image')]|[(imageUrl, textResponse, or youtubeUrl)]|[startTime (ex:00:00:00)]|[duration (ex: 07)}]",
        delete: true,
        type: "customCommand",
        process: (bot, msg, suffix) => {
            let adminsArray = Array.from(admins);

            let commandInfo = suffix.split('|');
            let trigger = commandInfo[0];

            if (!_.includes(adminsArray, msg.author.id.toString())) {
                bot.reply(`Sorry, you don't have permissions to execute this command!`);
                return;
            }

            if(commandExists(trigger, msg.server.id)) {
                bot.reply(msg, `Command '${trigger}' already exists!`);
                return;
            }

            if (commandInfo.length < 3) {
                bot.reply(`Incorrect number of parameters passed into the command. Correct usage: ${this.usage}`);
                return;
            }

            if (!trigger.startsWith('~')) {
                bot.reply(`The new command must start with a prefix of '~'`);
                return;
            }

            let commandType = commandInfo[1];
            if (commandType != 'text' || commandType != 'sound' || commandType != 'image') {
                bot.reply(`Incorrect custom command type was passed in. Types accepted: 'text', 'image', 'sound'`);
            }

            let customCmd = new CustomCommand({
                serverId: msg.server.id,
                commandText: trigger,
                createDate: moment().format("MM/DD/YYYY hh:mm:ss"),
                createUser: msg.author.username
            });

            console.log(`commandInfo: ${commandInfo}`);

            switch (commandType){
                case 'text':
                    customCmd.commandType = 'text';
                    customCmd.commandResponse = commandInfo[2];

                    customCmd.save().then((result) => {
                        console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${warningC(trigger)} was created by ${userC(msg.author.username)}`);

                        bot.reply(msg, `New command '${trigger}' was successfully created! Commands must be reloaded before the new commands may be used!`);
                    });

                    break;

                case 'sound':
                    customCmd.commandType = 'sound';
                    let duration = commandInfo[4];

                    if (Date.parse(`01/01/2016 ${duration.toString()}`) > Date.parse("01/01/2016 00:00:07")) {
                        bot.reply(msg, `The maximum duration for a sound command is 7 seconds!`);
                        return;
                    }

                    newSoundCommand(bot, msg, commandInfo, customCmd);

                    break;

                case 'image':
                    customCmd.commandType = 'image';
                    customCmd.imageUrl = commandInfo[2];

                    customCmd.save().then(() => {
                        console.log(`${channelC(` # ${msg.channel.name}`)}: ${botC(`@CuckBot`)} - ${warningC(trigger)} was created by ${userC(msg.author.username)}`);

                        bot.reply(msg, `New command '${trigger}' was successfully created! Commands must be reloaded before the new commands may be used!`);
                    });

                    break;
            }
        }
    },
    "deletecommand": {
        usage: "~deletecommand [commandname]",
        delete: true,
        type: "customCommand",
        process: (bot, msg, suffix) => {
            let adminsArray = Array.from(admins);

            if (!_.includes(adminsArray, msg.author.id.toString())) {
                bot.reply(`Sorry, you don't have permissions to execute this command!`);
                return;
            }

            CustomCommand.filter({ serverId: msg.server.id, commandText: suffix}).run({readMode: 'majority'}).then((result) => {
                console.log(`result: ${util.inspect(result)}`);
                if(result.length > 0) {
                    result[0].delete().then((result) => {
                        if (result.commandType == 'sound') {
                            removeFile(path.resolve('resources/', `${suffix.slice(1)}${msg.server.id}.mp3`));
                        }

                        bot.reply(msg, `Custom command '${suffix}' was successfully deleted`);
                    }).catch((err) => {
                        console.log(`error: ${err}`);
                    });
                } else {
                    bot.reply(msg, `Command '${suffix}' was not found!`);
                    return;
                }

            });
        }
    },
    "dbimagecommand": {
        usage: "~command",
        delete: true,
        type: "dbCommand",
        process: (bot, msg, dbCommand) => {
            bot.sendFile(msg.channel, dbCommand.imageUrl).catch(err => {
                console.log(`Error sending \'${dbCommand.commandText}\' from database: ${err}`);
            });
        }
    },
    "dbtextcommand": {
        usage: "~command",
        delete: true,
        type: "dbCommand",
        process: (bot, msg, dbCommand) => {
            bot.sendMessage(msg.channel, dbCommand.commandResponse).catch(err => {
                console.log(`Error sending \'${dbCommand.commandText}\' from database: ${err}`);
            })
        }
    },
    "dbsoundcommand": {
        usage: "~command",
        delete: true,
        type: "dbCommand",
        process: (bot, msg, dbCommand) => {
            let options = {
                volume: 0.5
            };

            let filename = `${dbCommand.commandText.substring(1)}${msg.server.id}.mp3`;

            let file = path.resolve('resources/', filename);

            joinAndPlay(bot, msg, file, options);
        }
    }
};

exports.customCommands = customCommands;
<|endoftext|>


<commit_before>/**
 * Module Name: Drama
 * Description: Experimental, you probably don't want it.
 */
var brain = require('brain');

var drama = function(dbot) {
    var dbot = dbot;
    var last = {};
    var options = {
        'backend': {
            'type': 'Redis',
            'options': {
                'hostname': 'localhost',
                'port': 6379,
                'name': 'dbotdrama'
            }
        },

        'thresholds': {
            'drama': 3,
            'beinganasshole': 3,
            'sd': 3, // self depracating
            'normal': 1
        },

        'def': 'normal'
    };
    var bayes = new brain.BayesianClassifier(options);

    var commands = {
        '~train': function(event) {
            if(dbot.admin.include(event.user)) {
                bayes.train(last[event.params[1]][event.params[2]], event.params[3]);
                event.reply('Last thing ' + event.params[2] + ' said in ' +
                        event.params[1] + ' (' +  last[event.params[1]][event.params[2]] + ') classified as \'' + event.params[3] + '\'');
            }
        },

        '~rtrain': function(event) {
            if(dbot.admin.include(event.user)) {
                var category = event.params[1];
                event.params.splice(0, 2);
                var msg = event.params.join(' ');
                bayes.train(msg, category);
                event.reply('\'' + msg + '\' classified as \'' + category + '\'');
            }
        },

        '~classify': function(event) {
            event.params.splice(0, 1);
            var msg = event.params.join(' ');
            bayes.classify(msg, function(category) {
                event.reply('Classified as: ' + category + '!');
            }.bind(this));
        }
    }

    return {
        'name': 'drama',
        'ignorable': false,
        'commands': commands,

        'listener': function(data) {
            var category = bayes.classify(data.message, function(category) {
                if(category !== 'normal') {
                    if(category === 'beinganasshole') {
                        if(dbot.db.drama.beinganasshole.hasOwnProperty(event.user)) {
                            dbot.db.drama.beinganasshole[event.user]++;
                        } else {
                            dbot.db.drama.beinganasshole[event.user] = 1;
                        }
                    } else if(category === 'sd') {
                        if(dbot.db.drama.sd.hasOwnProperty(event.user)) {
                            dbot.db.drama.sd[event.user]++;
                        } else {
                            dbot.db.drama.sd[event.user] = 1;
                        }
                    }
                }
            }.bind(this));

            if(last.hasOwnProperty(event.channel)) {
               last[event.channel][event.user] = data.message;
            } else {
                last[event.channel] = { };
                last[event.channel][event.user] = data.message;
            }
        },
        'on': 'PRIVMSG'
    };
}

exports.fetch = function(dbot) {
    return drama(dbot);
};
<commit_msg>removed drama because why bother<commit_after><|endoftext|>


<commit_before>import React from 'react'

// Define your routing setup in this class for correct hot-module-reloading.

class App extends React.Component {
  render () {
    return
ÐQuestions

  }
}

export default App
<commit_msg>Add a nice message and put content in grid column<commit_after>import React from 'react'
import { Col } from 'react-bootstrap'

// Define your routing setup in this class for correct hot-module-reloading.

class App extends React.Component {
  render () {
    return

      <Col sm={10} smOffset={1}>

ÐQuestions

Welcome to the Ðistributed version of 500 Questions!

      </Col>

  }
}

export default App
<|endoftext|>


<commit_before>import { Map as ImmutableMap, List, Set as ImmutableSet } from 'immutable';

import React from 'react';
import { connect } from 'react-redux';

import page from 'page';

import { max } from 'd3-array';

import {m52ToAggregated, hierarchicalAggregated, hierarchicalM52}  from '../../../../shared/js/finance/memoized';
import {default as visit, flattenTree} from '../../../../shared/js/finance/visitHierarchical.js';

import { DF, DI } from '../../../../shared/js/finance/constants';

import {fonctionLabels, natureLabels} from '../../../../../build/finances/m52-strings.json';

import StackChart from '../../../../shared/js/components/StackChart';
import {makeAmountString, default as MoneyAmount} from '../../../../shared/js/components/MoneyAmount';

import PageTitle from '../../../../shared/js/components/gironde.fr/PageTitle';
import SecundaryTitle from '../../../../shared/js/components/gironde.fr/SecundaryTitle';
import DownloadSection from '../../../../shared/js/components/gironde.fr/DownloadSection';

import {CHANGE_EXPLORATION_YEAR} from '../../constants/actions';

import colorClassById from '../../colorClassById';

import FinanceElementContext from '../FinanceElementContext';
import RollingNumber from '../RollingNumber';

/*
    In this component, there are several usages of dangerouslySetInnerHTML.

    In the context of the public dataviz project, the strings being used are HTML generated by
    a markdown parser+renderer. This part is considered trusted enough.

    The content being passed to the markdown parser is created and reviewed by the project team and likely
    by the communication team at the Département de la Gironde. So this content is very very unlikely to ever
    contain anything that could cause any harm.

    For these reasons, the usages of dangerouslySetInnerHTML are fine.
*/

/*

interface FinanceElementProps{
    contentId: string,
    amount, // amount of this element
    aboveTotal, // amount of the element in the above category
    topTotal // amount of total expenditures or revenue
    texts: FinanceElementTextsRecord,

    // the partition will be displayed in the order it's passed. Sort beforehand if necessary
    partition: Array<{
        contentId: string,
        partAmount: number,
        texts: FinanceElementTextsRecord,
        url: stringMarkdown
    }>
}

*/

export function FinanceElement({contentId, RDFI, amountByYear, contextElements, texts, partitionByYear, year, m52Rows, changeExplorationYear, screenWidth}) {
    const label = texts && texts.label || '';
    const atemporalText = texts && texts.atemporal;
    const temporalText = texts && texts.temporal;

    const amount = amountByYear.get(year);

    const years = partitionByYear.keySeq().toJS();

    // sort all partitions part according to the order of the last year partition
    let lastYearPartition = partitionByYear.get(max(years))
    lastYearPartition = lastYearPartition && lastYearPartition.sort((p1, p2) => p2.partAmount - p1.partAmount);
    const partitionIdsInOrder = lastYearPartition && lastYearPartition.map(p => p.contentId) || [];

    // reorder all partitions so they adhere to partitionIdsInOrder
    partitionByYear = partitionByYear.map(partition => {
        // indexOf inside a .map leads to O(n^2), but lists are 10 elements long max, so it's ok
        return partition && partition.sort((p1, p2) => partitionIdsInOrder.indexOf(p1.contentId) - partitionIdsInOrder.indexOf(p2.contentId))
    })

    let thisYearPartition = partitionByYear.get(year);

    let barchartPartitionByYear = partitionByYear;
    if(contentId === 'DF'){
        // For DF, for the split thing at the end, the whole partition is needed.
        // However, DF-1 === DF-2, so for the barchart, we only want one of them with the label "solidarité"
        barchartPartitionByYear = barchartPartitionByYear.map(partition => {
            partition = partition.remove(partition.findIndex(p => p.contentId === 'DF-1'))

            const df2 = partition.find(p => p.contentId === 'DF-2');

            return partition.set(partition.findIndex(p => p.contentId === 'DF-2'), {
                contentId: df2.contentId,
                partAmount: df2.partAmount,
                texts: df2.texts && df2.texts.set('label', [
                    'Actions sociales ',
                    React.createElement('a', {href: '#!/finance-details/DF-1'}, '(par prestation)'),
                    ' - ',
                    React.createElement('a', {href: '#!/finance-details/DF-2'}, '(par public)')
                ]),
                url: undefined
            });
        })

        // temporarily don't display DF-1
        thisYearPartition = thisYearPartition && thisYearPartition.remove(
            thisYearPartition.findIndex(p => p.contentId === 'DF-1')
        )
    }

    const legendItemIds = barchartPartitionByYear
    .map(partition => partition.map(part => part.contentId).toSet())
    .toSet().flatten().toArray()
    .sort( (lid1, lid2) => {
        return partitionIdsInOrder.indexOf(lid1) - partitionIdsInOrder.indexOf(lid2)
    } );

    const legendItems = legendItemIds.map(id => {
        let found;

        barchartPartitionByYear.find(partition => {
            found = partition.find(p => p.contentId === id )
            return found;
        })

        return {
            id: found.contentId,
            className: found.contentId,
            url: found.url,
            text: found.texts && found.texts.label,
            colorClassName: colorClassById.get(found.contentId)
        }
    });

    const RDFIText = RDFI === DF ?
        'Dépense de fonctionnement' :
        RDFI === DI ?
            `Dépense d'investissement`:
            '';

    const isLeaf = !(thisYearPartition && thisYearPartition.size >= 2);

    return React.createElement('article', {className: 'finance-element'},
        React.createElement(PageTitle, {text: RDFI ?
            `${RDFIText} - ${label} en ${year}` :
            `${label} en ${year}`}),
        React.createElement('section', {},
            React.createElement('div', {className: 'top-infos'},
                contextElements ? React.createElement(FinanceElementContext, { contextElements }) : undefined,
                React.createElement('div', {},
                    React.createElement('h2', {}, React.createElement(RollingNumber, {amount})),
                    atemporalText ? React.createElement('div', {className: 'atemporal', dangerouslySetInnerHTML: {__html: atemporalText}}) : undefined
                )
            )
        ),

        React.createElement('section', {},
            React.createElement(SecundaryTitle, {text: 'Évolution sur ces dernières années'}),
            temporalText ? React.createElement('div', {className: 'temporal', dangerouslySetInnerHTML: {__html: temporalText}}) : undefined,
            React.createElement(StackChart, {
                WIDTH: screenWidth >= 800 + 80 ?
                    800 :
                    (screenWidth - 85 >= 600 ? screenWidth - 85 : (
                        screenWidth <= 600 ? screenWidth - 10 : 600
                    )),
                portrait: screenWidth <= 600,
                xs: years,
                ysByX: barchartPartitionByYear.map(partition => partition.map(part => part.partAmount)),
                selectedX: year,
                onSelectedXAxisItem: changeExplorationYear,
                onBrickClicked: !isLeaf ? (year, id) => {
                    const url = barchartPartitionByYear.get(year).find(e => e.contentId === id).url;
                    page(url);
                } : undefined,
                legendItems: !isLeaf ? legendItems : undefined,
                uniqueColorClass: isLeaf ? colorClassById.get(contentId) : undefined,
                yValueDisplay: makeAmountString
            })
        ),

        isLeaf && m52Rows ? React.createElement('section', { className: 'raw-data'},
            React.createElement(SecundaryTitle, {text: `Consultez ces données en détail à la norme comptable M52 pour l'année ${year}`}),
            React.createElement('table', {},
                React.createElement('thead', {},
                    React.createElement('tr', {},
                        React.createElement('th', {}, 'Fonction'),
                        React.createElement('th', {}, 'Nature'),
                        React.createElement('th', {}, 'Montant')
                    )
                ),
                React.createElement('tbody', {},
                    m52Rows
                    .sort((r1, r2) => r2['MtReal'] - r1['MtReal'])
                    .map(row => {
                        return React.createElement('tr', {},
                            React.createElement('td', {}, fonctionLabels[row['Fonction']]),
                            React.createElement('td', {}, natureLabels[row['Nature']]),
                            React.createElement('td', {},
                                React.createElement(MoneyAmount, {amount: row['MtReal']})
                            )
                        )
                    })
                )
            ),
            React.createElement(
                DownloadSection,
                {
                    title: `Données brutes sur datalocale.fr`,
                    items: [
                        {
                            text: 'Comptes administratifs du Département de la Gironde',
                            url: 'https://www.datalocale.fr/dataset/comptes-administratifs-du-departement-de-la-gironde'
                        }
                    ]
                }
            )
        ) : undefined

    );
}

export function makePartition(element, totalById, textsById, possibleChildrenIds){
    if(!element){
        return new List();
    }

    let children = element.children;
    children = children && typeof children.toList === 'function' ? children.toList() : children;

    if(!possibleChildrenIds){
        possibleChildrenIds = children.map(c => c.id);
    }

    return children && children.size >= 1 ?
        possibleChildrenIds.map(id => {
            // .find over all possibleChildrenIds is O(n²), but n is small (<= 10)
            const child = children.find(c => c.id === id);

            return {
                contentId: id,
                partAmount: child ? totalById.get(child.id) : 0,
                texts: textsById.get(id),
                url: `#!/finance-details/${id}`
            };
        }) :
        List().push({
            contentId: element.id,
            partAmount: totalById.get(element.id),
            texts: textsById.get(element.id),
            url: `#!/finance-details/${element.id}`
        });
}

export function makeElementById(hierAgg, hierM52 = {}){
    let elementById = new ImmutableMap();

    flattenTree(hierAgg).forEach(aggHierNode => {
        elementById = elementById.set(aggHierNode.id, aggHierNode);
    });

    flattenTree(hierM52).forEach(m52HierNode => {
        elementById = elementById.set(m52HierNode.id, m52HierNode);
    });

    return elementById;
}

function fillChildToParent(tree, wm){
    visit(tree, e => {
        if(e.children){
            e.children.forEach(c => {
                wm.set(c, e);
            })
        }
    });
}

function makeContextList(element, childToParent){
    let contextList = [];
    let next = element;

    while(next){
        contextList.push(next);
        next = childToParent.get(next);
    }

    contextList = contextList
    // furtherest context first
    .reverse()
    // remove TOTAL
    .slice(1);

    if(contextList.length > 4){
        const [c1, c2, c3] = contextList;
        const [last] = contextList.slice(-1);

        contextList = [c1, c2, c3, last];
    }

    return contextList;
}

export default connect(
    state => {
        const { docBudgByYear, corrections, textsById, financeDetailId, explorationYear, screenWidth } = state;

        const isM52Element = financeDetailId.startsWith('M52-');

        let RDFI;
        if(isM52Element){
            RDFI = financeDetailId.slice(4, 4+2);
        }

        const m52Instruction = docBudgByYear.get(explorationYear);
        const hierM52 = m52Instruction && RDFI && hierarchicalM52(m52Instruction, RDFI);
        const aggregated = m52Instruction && corrections && m52ToAggregated(m52Instruction, corrections);
        const hierAgg = m52Instruction && hierarchicalAggregated(aggregated);

        const childToParent = new WeakMap();
        if(m52Instruction){
            if(hierM52)
                fillChildToParent(hierM52, childToParent);

            fillChildToParent(hierAgg, childToParent);
        }

        const displayedContentId = financeDetailId;

        const elementById = (m52Instruction && makeElementById(hierAgg, hierM52)) || new ImmutableMap();
        const element = elementById.get(displayedContentId);

        const contextList = makeContextList(element, childToParent);

        const elementByIdByYear = docBudgByYear.map(m52i => {
            return makeElementById(
                hierarchicalAggregated(m52ToAggregated(m52i, corrections)),
                RDFI ? hierarchicalM52(m52i, RDFI): undefined
            );
        });

        const displayedElementByYear = elementByIdByYear.map(elementById => {
            return elementById.get(displayedContentId);
        })

        // Depending on the year, all elements may not have the same children ids.
        // This is the set of all possible ids for the given years
        const displayedElementPossibleChildrenIds = displayedElementByYear.map(element => {
            if(!element)
                return new ImmutableSet();

            let children = element.children;
            children = children && typeof children.toList === 'function' ? children.toList() : children;

            if(!children)
                return new ImmutableSet();

            return new ImmutableSet(children).map(child => child.id);
        }).toSet().flatten().toList();

        const partitionByYear = elementByIdByYear.map((elementById) => {
            const yearElement = elementById.get(displayedContentId);

            return makePartition(yearElement, elementById.map(e => e.total), textsById, displayedElementPossibleChildrenIds)
        });

        const amountByYear = elementByIdByYear.map((elementById) => {
            const yearElement = elementById.get(displayedContentId);

            return yearElement && yearElement.total;
        });

        const m52Rows = element && (!element.children || element.children.size === 0) ?
            (isM52Element ?
                 element.elements :
                 element.elements.first()['M52Rows']
            ) :
            undefined;

        const texts = textsById.get(displayedContentId);

        return {
            contentId: displayedContentId,
            RDFI,
            amountByYear,
            contextElements: contextList.map((c, i) => ({
                id: c.id,
                url : c.id !== displayedContentId ? '#!/finance-details/'+c.id : undefined,
                proportion : c.total/contextList[0].total,
                colorClass : colorClassById.get(c.id),
                label: textsById.get(c.id).label +
                    (contextList.length >= 2 && i === contextList.length -1 ?
                        ` (${(c.total*100/contextList[0].total).toFixed(1)}%)` :
                        '')
            })),
            texts,
            partitionByYear,
            m52Rows,
            year: explorationYear,
            screenWidth
        }

    },
    dispatch => ({
        changeExplorationYear(year){
            dispatch({
                type: CHANGE_EXPLORATION_YEAR,
                year
            })
        }
    })
)(FinanceElement);
<commit_msg>mise à jour de la source de données M52<commit_after>import { Map as ImmutableMap, List, Set as ImmutableSet } from 'immutable';

import React from 'react';
import { connect } from 'react-redux';

import page from 'page';

import { max } from 'd3-array';

import {m52ToAggregated, hierarchicalAggregated, hierarchicalM52}  from '../../../../shared/js/finance/memoized';
import {default as visit, flattenTree} from '../../../../shared/js/finance/visitHierarchical.js';

import { DF, DI } from '../../../../shared/js/finance/constants';

import {fonctionLabels, natureLabels} from '../../../../../build/finances/m52-strings.json';

import StackChart from '../../../../shared/js/components/StackChart';
import {makeAmountString, default as MoneyAmount} from '../../../../shared/js/components/MoneyAmount';

import PageTitle from '../../../../shared/js/components/gironde.fr/PageTitle';
import SecundaryTitle from '../../../../shared/js/components/gironde.fr/SecundaryTitle';
import DownloadSection from '../../../../shared/js/components/gironde.fr/DownloadSection';

import {CHANGE_EXPLORATION_YEAR} from '../../constants/actions';

import colorClassById from '../../colorClassById';

import FinanceElementContext from '../FinanceElementContext';
import RollingNumber from '../RollingNumber';

/*
    In this component, there are several usages of dangerouslySetInnerHTML.

    In the context of the public dataviz project, the strings being used are HTML generated by
    a markdown parser+renderer. This part is considered trusted enough.

    The content being passed to the markdown parser is created and reviewed by the project team and likely
    by the communication team at the Département de la Gironde. So this content is very very unlikely to ever
    contain anything that could cause any harm.

    For these reasons, the usages of dangerouslySetInnerHTML are fine.
*/

/*

interface FinanceElementProps{
    contentId: string,
    amount, // amount of this element
    aboveTotal, // amount of the element in the above category
    topTotal // amount of total expenditures or revenue
    texts: FinanceElementTextsRecord,

    // the partition will be displayed in the order it's passed. Sort beforehand if necessary
    partition: Array<{
        contentId: string,
        partAmount: number,
        texts: FinanceElementTextsRecord,
        url: stringMarkdown
    }>
}

*/

export function FinanceElement({contentId, RDFI, amountByYear, contextElements, texts, partitionByYear, year, m52Rows, changeExplorationYear, screenWidth}) {
    const label = texts && texts.label || '';
    const atemporalText = texts && texts.atemporal;
    const temporalText = texts && texts.temporal;

    const amount = amountByYear.get(year);

    const years = partitionByYear.keySeq().toJS();

    // sort all partitions part according to the order of the last year partition
    let lastYearPartition = partitionByYear.get(max(years))
    lastYearPartition = lastYearPartition && lastYearPartition.sort((p1, p2) => p2.partAmount - p1.partAmount);
    const partitionIdsInOrder = lastYearPartition && lastYearPartition.map(p => p.contentId) || [];

    // reorder all partitions so they adhere to partitionIdsInOrder
    partitionByYear = partitionByYear.map(partition => {
        // indexOf inside a .map leads to O(n^2), but lists are 10 elements long max, so it's ok
        return partition && partition.sort((p1, p2) => partitionIdsInOrder.indexOf(p1.contentId) - partitionIdsInOrder.indexOf(p2.contentId))
    })

    let thisYearPartition = partitionByYear.get(year);

    let barchartPartitionByYear = partitionByYear;
    if(contentId === 'DF'){
        // For DF, for the split thing at the end, the whole partition is needed.
        // However, DF-1 === DF-2, so for the barchart, we only want one of them with the label "solidarité"
        barchartPartitionByYear = barchartPartitionByYear.map(partition => {
            partition = partition.remove(partition.findIndex(p => p.contentId === 'DF-1'))

            const df2 = partition.find(p => p.contentId === 'DF-2');

            return partition.set(partition.findIndex(p => p.contentId === 'DF-2'), {
                contentId: df2.contentId,
                partAmount: df2.partAmount,
                texts: df2.texts && df2.texts.set('label', [
                    'Actions sociales ',
                    React.createElement('a', {href: '#!/finance-details/DF-1'}, '(par prestation)'),
                    ' - ',
                    React.createElement('a', {href: '#!/finance-details/DF-2'}, '(par public)')
                ]),
                url: undefined
            });
        })

        // temporarily don't display DF-1
        thisYearPartition = thisYearPartition && thisYearPartition.remove(
            thisYearPartition.findIndex(p => p.contentId === 'DF-1')
        )
    }

    const legendItemIds = barchartPartitionByYear
    .map(partition => partition.map(part => part.contentId).toSet())
    .toSet().flatten().toArray()
    .sort( (lid1, lid2) => {
        return partitionIdsInOrder.indexOf(lid1) - partitionIdsInOrder.indexOf(lid2)
    } );

    const legendItems = legendItemIds.map(id => {
        let found;

        barchartPartitionByYear.find(partition => {
            found = partition.find(p => p.contentId === id )
            return found;
        })

        return {
            id: found.contentId,
            className: found.contentId,
            url: found.url,
            text: found.texts && found.texts.label,
            colorClassName: colorClassById.get(found.contentId)
        }
    });

    const RDFIText = RDFI === DF ?
        'Dépense de fonctionnement' :
        RDFI === DI ?
            `Dépense d'investissement`:
            '';

    const isLeaf = !(thisYearPartition && thisYearPartition.size >= 2);

    return React.createElement('article', {className: 'finance-element'},
        React.createElement(PageTitle, {text: RDFI ?
            `${RDFIText} - ${label} en ${year}` :
            `${label} en ${year}`}),
        React.createElement('section', {},
            React.createElement('div', {className: 'top-infos'},
                contextElements ? React.createElement(FinanceElementContext, { contextElements }) : undefined,
                React.createElement('div', {},
                    React.createElement('h2', {}, React.createElement(RollingNumber, {amount})),
                    atemporalText ? React.createElement('div', {className: 'atemporal', dangerouslySetInnerHTML: {__html: atemporalText}}) : undefined
                )
            )
        ),

        React.createElement('section', {},
            React.createElement(SecundaryTitle, {text: 'Évolution sur ces dernières années'}),
            temporalText ? React.createElement('div', {className: 'temporal', dangerouslySetInnerHTML: {__html: temporalText}}) : undefined,
            React.createElement(StackChart, {
                WIDTH: screenWidth >= 800 + 80 ?
                    800 :
                    (screenWidth - 85 >= 600 ? screenWidth - 85 : (
                        screenWidth <= 600 ? screenWidth - 10 : 600
                    )),
                portrait: screenWidth <= 600,
                xs: years,
                ysByX: barchartPartitionByYear.map(partition => partition.map(part => part.partAmount)),
                selectedX: year,
                onSelectedXAxisItem: changeExplorationYear,
                onBrickClicked: !isLeaf ? (year, id) => {
                    const url = barchartPartitionByYear.get(year).find(e => e.contentId === id).url;
                    page(url);
                } : undefined,
                legendItems: !isLeaf ? legendItems : undefined,
                uniqueColorClass: isLeaf ? colorClassById.get(contentId) : undefined,
                yValueDisplay: makeAmountString
            })
        ),

        isLeaf && m52Rows ? React.createElement('section', { className: 'raw-data'},
            React.createElement(SecundaryTitle, {text: `Consultez ces données en détail à la norme comptable M52 pour l'année ${year}`}),
            React.createElement('table', {},
                React.createElement('thead', {},
                    React.createElement('tr', {},
                        React.createElement('th', {}, 'Fonction'),
                        React.createElement('th', {}, 'Nature'),
                        React.createElement('th', {}, 'Montant')
                    )
                ),
                React.createElement('tbody', {},
                    m52Rows
                    .sort((r1, r2) => r2['MtReal'] - r1['MtReal'])
                    .map(row => {
                        return React.createElement('tr', {},
                            React.createElement('td', {}, fonctionLabels[row['Fonction']]),
                            React.createElement('td', {}, natureLabels[row['Nature']]),
                            React.createElement('td', {},
                                React.createElement(MoneyAmount, {amount: row['MtReal']})
                            )
                        )
                    })
                )
            ),
            React.createElement(
                DownloadSection,
                {
                    title: `Données brutes sur datalocale.fr`,
                    items: [
                        {
                            text: 'Comptes administratifs du Département de la Gironde au format TOTEM',
                            url: 'https://www.datalocale.fr/dataset/comptes-administratifs-budget-principal-donnees-budgetaires-du-departement-de-la-gironde'
                        }
                    ]
                }
            )
        ) : undefined

    );
}

export function makePartition(element, totalById, textsById, possibleChildrenIds){
    if(!element){
        return new List();
    }

    let children = element.children;
    children = children && typeof children.toList === 'function' ? children.toList() : children;

    if(!possibleChildrenIds){
        possibleChildrenIds = children.map(c => c.id);
    }

    return children && children.size >= 1 ?
        possibleChildrenIds.map(id => {
            // .find over all possibleChildrenIds is O(n²), but n is small (<= 10)
            const child = children.find(c => c.id === id);

            return {
                contentId: id,
                partAmount: child ? totalById.get(child.id) : 0,
                texts: textsById.get(id),
                url: `#!/finance-details/${id}`
            };
        }) :
        List().push({
            contentId: element.id,
            partAmount: totalById.get(element.id),
            texts: textsById.get(element.id),
            url: `#!/finance-details/${element.id}`
        });
}

export function makeElementById(hierAgg, hierM52 = {}){
    let elementById = new ImmutableMap();

    flattenTree(hierAgg).forEach(aggHierNode => {
        elementById = elementById.set(aggHierNode.id, aggHierNode);
    });

    flattenTree(hierM52).forEach(m52HierNode => {
        elementById = elementById.set(m52HierNode.id, m52HierNode);
    });

    return elementById;
}

function fillChildToParent(tree, wm){
    visit(tree, e => {
        if(e.children){
            e.children.forEach(c => {
                wm.set(c, e);
            })
        }
    });
}

function makeContextList(element, childToParent){
    let contextList = [];
    let next = element;

    while(next){
        contextList.push(next);
        next = childToParent.get(next);
    }

    contextList = contextList
    // furtherest context first
    .reverse()
    // remove TOTAL
    .slice(1);

    if(contextList.length > 4){
        const [c1, c2, c3] = contextList;
        const [last] = contextList.slice(-1);

        contextList = [c1, c2, c3, last];
    }

    return contextList;
}

export default connect(
    state => {
        const { docBudgByYear, corrections, textsById, financeDetailId, explorationYear, screenWidth } = state;

        const isM52Element = financeDetailId.startsWith('M52-');

        let RDFI;
        if(isM52Element){
            RDFI = financeDetailId.slice(4, 4+2);
        }

        const m52Instruction = docBudgByYear.get(explorationYear);
        const hierM52 = m52Instruction && RDFI && hierarchicalM52(m52Instruction, RDFI);
        const aggregated = m52Instruction && corrections && m52ToAggregated(m52Instruction, corrections);
        const hierAgg = m52Instruction && hierarchicalAggregated(aggregated);

        const childToParent = new WeakMap();
        if(m52Instruction){
            if(hierM52)
                fillChildToParent(hierM52, childToParent);

            fillChildToParent(hierAgg, childToParent);
        }

        const displayedContentId = financeDetailId;

        const elementById = (m52Instruction && makeElementById(hierAgg, hierM52)) || new ImmutableMap();
        const element = elementById.get(displayedContentId);

        const contextList = makeContextList(element, childToParent);

        const elementByIdByYear = docBudgByYear.map(m52i => {
            return makeElementById(
                hierarchicalAggregated(m52ToAggregated(m52i, corrections)),
                RDFI ? hierarchicalM52(m52i, RDFI): undefined
            );
        });

        const displayedElementByYear = elementByIdByYear.map(elementById => {
            return elementById.get(displayedContentId);
        })

        // Depending on the year, all elements may not have the same children ids.
        // This is the set of all possible ids for the given years
        const displayedElementPossibleChildrenIds = displayedElementByYear.map(element => {
            if(!element)
                return new ImmutableSet();

            let children = element.children;
            children = children && typeof children.toList === 'function' ? children.toList() : children;

            if(!children)
                return new ImmutableSet();

            return new ImmutableSet(children).map(child => child.id);
        }).toSet().flatten().toList();

        const partitionByYear = elementByIdByYear.map((elementById) => {
            const yearElement = elementById.get(displayedContentId);

            return makePartition(yearElement, elementById.map(e => e.total), textsById, displayedElementPossibleChildrenIds)
        });

        const amountByYear = elementByIdByYear.map((elementById) => {
            const yearElement = elementById.get(displayedContentId);

            return yearElement && yearElement.total;
        });

        const m52Rows = element && (!element.children || element.children.size === 0) ?
            (isM52Element ?
                 element.elements :
                 element.elements.first()['M52Rows']
            ) :
            undefined;

        const texts = textsById.get(displayedContentId);

        return {
            contentId: displayedContentId,
            RDFI,
            amountByYear,
            contextElements: contextList.map((c, i) => ({
                id: c.id,
                url : c.id !== displayedContentId ? '#!/finance-details/'+c.id : undefined,
                proportion : c.total/contextList[0].total,
                colorClass : colorClassById.get(c.id),
                label: textsById.get(c.id).label +
                    (contextList.length >= 2 && i === contextList.length -1 ?
                        ` (${(c.total*100/contextList[0].total).toFixed(1)}%)` :
                        '')
            })),
            texts,
            partitionByYear,
            m52Rows,
            year: explorationYear,
            screenWidth
        }

    },
    dispatch => ({
        changeExplorationYear(year){
            dispatch({
                type: CHANGE_EXPLORATION_YEAR,
                year
            })
        }
    })
)(FinanceElement);
<|endoftext|>


<commit_before>import React from 'react'
import {compose, withState, withHandlers} from 'recompose'
import Switch from 'rc-switch'
import './switch.css'
import styles from './horizontalmenu.css'

const SizeInput = compose(
  withState('auto', 'setAuto', (props) => props.value === 'auto'),
  withState('lastValue', 'setLastValue', (props) => props.value === 'auto' ? 0 : props.value),
  withHandlers({
    toggle: ({name, value, onChange, lastValue, setLastValue, auto, setAuto}) => (active) => {
      if (!active) {
        setAuto(true)
        setLastValue(value)
        onChange({[name]: 'auto'})
      } else {
        setAuto(false)
        onChange({[name]: lastValue})
      }
    },
    onChange: ({name, onChange, setLastValue, auto}) => (e) => {
      if (!auto) {
        setLastValue(e.target.value)
        onChange({[name]: e.target.value})
      }
    }
  })
)(({label, name, value, lastValue, toggle, onChange, auto}) =>

    <Switch checked={!auto} onChange={toggle} />

    {label}

    <input type='number' value={value === 'auto' ? lastValue : value} onChange={onChange} disabled={auto} />
    px

)

const Size = (
  withHandlers({
    onChange: ({size, onChange}) => (newSize) => onChange({size: Object.assign({}, size, newSize)})
  })
)(({size, onChange}) =>

  <SizeInput label='Width' name='width' value={size.width} onChange={onChange} />
  <SizeInput label='Height' name='height' value={size.height} onChange={onChange} />

)

Size.displayName = 'Size'

export default Size
<commit_msg>Properly set the display options visual even when the change is external<commit_after>import React from 'react'
import {compose, lifecycle, withState, withHandlers} from 'recompose'
import Switch from 'rc-switch'
import './switch.css'
import styles from './horizontalmenu.css'

const SizeInput = compose(
  withState('auto', 'setAuto', (props) => props.value === 'auto'),
  withState('lastValue', 'setLastValue', (props) => props.value === 'auto' ? 0 : props.value),
  lifecycle({
    componentWillReceiveProps (props) {
      if ((props.auto && props.value === 'auto') || (!props.auto && props.value !== 'auto')) {
        return
      }

      props.setAuto(props.value === 'auto')
      if (props.value !== 'auto') {
        props.setLastValue(props.value)
      }
    }
  }),
  withHandlers({
    toggle: ({name, value, onChange, lastValue, setLastValue, auto, setAuto}) => (active) => {
      if (!active) {
        setAuto(true)
        setLastValue(value)
        onChange({[name]: 'auto'})
      } else {
        setAuto(false)
        onChange({[name]: lastValue})
      }
    },
    onChange: ({name, onChange, setLastValue, auto}) => (e) => {
      if (!auto) {
        setLastValue(e.target.value)
        onChange({[name]: e.target.value})
      }
    }
  })
)(({label, name, value, lastValue, toggle, onChange, auto}) =>

    <Switch checked={!auto} onChange={toggle} />

    {label}

    <input type='number' value={value === 'auto' ? lastValue : value} onChange={onChange} disabled={auto} />
    px

)

const Size = (
  withHandlers({
    onChange: ({size, onChange}) => (newSize) => onChange({size: Object.assign({}, size, newSize)})
  })
)(({size, onChange}) =>

  <SizeInput label='Width' name='width' value={size.width} onChange={onChange} />
  <SizeInput label='Height' name='height' value={size.height} onChange={onChange} />

)

Size.displayName = 'Size'

export default Size
<|endoftext|>


<commit_before>import { Component } from 'substance'
import { addModelObserver, removeModelObserver } from '../../kit'
import CardComponent from '../shared/CardComponent'

export default class CollectionEditor extends Component {
  getActionHandlers () {
    return {
      'remove-item': this._removeCollectionItem
    }
  }

  // TODO: we need a simpler way to register an observer for a given model
  didMount () {
    addModelObserver(this.props.model, this.rerender, this, { stage: 'render' })
  }

  dispose () {
    removeModelObserver(this)
  }

  render ($$) {
    const model = this.props.model
    let items = model.getItems()
    let el = $$('div').addClass('sc-collection-editor')
    items.forEach(item => {
      let ItemEditor = this._getItemComponentClass(item)
      el.append(
        $$(CardComponent).append(
          $$(ItemEditor, {
            model: item,
            // LEGACY
            // TODO: try to get rid of this
            node: item._node
          }).ref(item.id)
        )
      )
    })
    return el
  }

  _getItemComponentClass (item) {
    let ItemComponent = this.getComponent(item.type, true)
    if (!ItemComponent) {
      // try to find a component registered for a parent type
      if (item._node) {
        ItemComponent = this._getParentTypeComponent(item._node)
      }
    }
    return ItemComponent || this.getComponent('entity')
  }

  _getParentTypeComponent (node) {
    let superTypes = node.getSchema().getSuperTypes()
    for (let type of superTypes) {
      let NodeComponent = this.getComponent(type, true)
      if (NodeComponent) return NodeComponent
    }
  }

  _removeCollectionItem (item) {
    const model = this.props.model
    model.removeItem(item)
    // TODO: this is only necessary for fake collection models
    // i.e. models that are only virtual, which I'd like to avoid
    this.rerender()
  }
}
<commit_msg>Tidy up.<commit_after>import { Component } from 'substance'
import { addModelObserver, removeModelObserver } from '../../kit'
import CardComponent from '../shared/CardComponent'

export default class CollectionEditor extends Component {
  getActionHandlers () {
    return {
      'remove-item': this._removeCollectionItem
    }
  }

  didMount () {
    addModelObserver(this.props.model, this.rerender, this, { stage: 'render' })
  }

  dispose () {
    removeModelObserver(this)
  }

  render ($$) {
    const model = this.props.model
    let items = model.getItems()
    let el = $$('div').addClass('sc-collection-editor')
    items.forEach(item => {
      let ItemEditor = this._getItemComponentClass(item)
      el.append(
        $$(CardComponent).append(
          $$(ItemEditor, {
            model: item,
            // LEGACY
            // TODO: try to get rid of this
            node: item._node
          }).ref(item.id)
        )
      )
    })
    return el
  }

  _getItemComponentClass (item) {
    let ItemComponent = this.getComponent(item.type, true)
    if (!ItemComponent) {
      // try to find a component registered for a parent type
      if (item._node) {
        ItemComponent = this._getParentTypeComponent(item._node)
      }
    }
    return ItemComponent || this.getComponent('entity')
  }

  _getParentTypeComponent (node) {
    let superTypes = node.getSchema().getSuperTypes()
    for (let type of superTypes) {
      let NodeComponent = this.getComponent(type, true)
      if (NodeComponent) return NodeComponent
    }
  }

  _removeCollectionItem (item) {
    const model = this.props.model
    model.removeItem(item)
    // TODO: this is only necessary for fake collection models
    // i.e. models that are only virtual, which I'd like to avoid
    this.rerender()
  }
}
<|endoftext|>