<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

import _ from 'lodash';
import { Bar } from 'react-chartjs-2';
import moment from 'moment';
import { connect } from 'react-redux';
import GraphColors from '../utils/GraphColors';

/**
 * @param {State} state
 */
function mapStateToProps(state) {
	const timeInterval = state.graph.timeInterval;
	const barDuration = state.graph.barDuration;
	const data = { datasets: [] };
	const graphColors = new GraphColors();

	const labelsSet = new Set();
	for (const meterID of state.graph.selectedMeters) {
		const readingsData = state.readings.bar.byMeterID[meterID][timeInterval][barDuration];
		if (readingsData !== undefined && !readingsData.isFetching) {
			const color = graphColors.getColor();
			data.datasets.push({
				label: state.meters.byMeterID[meterID].name,
				data: readingsData.readings.map(arr => arr[1]),
				backgroundColor: color,
				hoverBackgroundColor: color
			});
			// Add only the unique time intervals to the label set
			for (const element of _.flatten(readingsData.readings.map(arr => arr[0]))) {
				labelsSet.add(`${moment(element).format('MMM DD, YYYY, hh:mm a')} - ${moment(element).add(barDuration).format('MMM DD, YYYY, hh:mm a')}`);
			}
		}
	}
    // Converts the label set into an array for Chart.js and sorts the labels based on the first date of the time interval
	data.labels = Array.from(labelsSet).sort((x, y) => moment(x.split(' - ')[0], 'MMM DD, YYYY, hh:mm a').format('x') - moment(y.split(' - ')[0], 'MMM DD, YYYY, hh:mm a').format('x'));

	const options = {
		animation: {
			duration: 0
		},
		elements: {
			point: {
				radius: 0
			}
		},
		scales: {
			xAxes: [{
				stacked: state.graph.barStacking,
				gridLines: {
					display: true
				}
			}],
			yAxes: [{
				stacked: state.graph.barStacking,
				scaleLabel: {
					display: true,
					labelString: 'kWh'
				},
				ticks: {
					beginAtZero: true
				}
			}]
		},
		tooltips: {
			mode: 'nearest',
			intersect: false,
			backgroundColor: 'rgba(0,0,0,0.6)',
			displayColors: false,
			callbacks: {
				label: tooltipItems => `${data.datasets[tooltipItems.datasetIndex].label}: ${tooltipItems.yLabel} kWh`
			}
		}
	};

	return {
		data,
		options,
		redraw: true
	};
}

export default connect(mapStateToProps)(Bar);
<commit_msg>Reduce the length of the date labels on BarChartContainer.<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

import _ from 'lodash';
import { Bar } from 'react-chartjs-2';
import moment from 'moment';
import { connect } from 'react-redux';
import GraphColors from '../utils/GraphColors';

/**
 * @param {State} state
 */
function mapStateToProps(state) {
	const timeInterval = state.graph.timeInterval;
	const barDuration = state.graph.barDuration;
	const data = { datasets: [] };
	const graphColors = new GraphColors();

	const labelsSet = new Set();
	for (const meterID of state.graph.selectedMeters) {
		const readingsData = state.readings.bar.byMeterID[meterID][timeInterval][barDuration];
		if (readingsData !== undefined && !readingsData.isFetching) {
			const color = graphColors.getColor();
			data.datasets.push({
				label: state.meters.byMeterID[meterID].name,
				data: readingsData.readings.map(arr => arr[1]),
				backgroundColor: color,
				hoverBackgroundColor: color
			});
			// Add only the unique time intervals to the label set
			for (const element of _.flatten(readingsData.readings.map(arr => arr[0]))) {
				labelsSet.add(`${moment(element).format('MMM DD')} - ${moment(element).add(barDuration).format('MMM DD YYYY')}`);
			}
		}
	}
    // Converts the label set into an array for Chart.js and sorts the labels based on the first date of the time interval
	data.labels = Array.from(labelsSet).sort((x, y) => moment(x.split(' - ')[0], 'MMM DD, YYYY, hh:mm a').format('x') - moment(y.split(' - ')[0], 'MMM DD, YYYY, hh:mm a').format('x'));

	const options = {
		animation: {
			duration: 0
		},
		elements: {
			point: {
				radius: 0
			}
		},
		scales: {
			xAxes: [{
				stacked: state.graph.barStacking,
				gridLines: {
					display: true
				}
			}],
			yAxes: [{
				stacked: state.graph.barStacking,
				scaleLabel: {
					display: true,
					labelString: 'kWh'
				},
				ticks: {
					beginAtZero: true
				}
			}]
		},
		tooltips: {
			mode: 'nearest',
			intersect: false,
			backgroundColor: 'rgba(0,0,0,0.6)',
			displayColors: false,
			callbacks: {
				label: tooltipItems => `${data.datasets[tooltipItems.datasetIndex].label}: ${tooltipItems.yLabel} kWh`
			}
		}
	};

	return {
		data,
		options,
		redraw: true
	};
}

export default connect(mapStateToProps)(Bar);
<|endoftext|>


<commit_before>import React from 'react';
import { Redirect } from 'react-router-dom';
import * as firebase from 'firebase';

class Logout extends React.Component {
    componentDidMount() {
        ;
    }

    render() {
        const signout = firebase.auth().signOut();
        return signout.then(() => {
            return (
                <Redirect to="/login" />
            )
        });
    }
}

export  { Logout };
<commit_msg>Update signout component<commit_after>import React from 'react';
import { Redirect } from 'react-router-dom';
import * as firebase from 'firebase';

class PageSignout extends React.Component {
    state = {
        redirect: false
    }

    componentDidMount() {
        const signoutPromise = firebase.auth().signOut();
        signoutPromise.then(() => {
            this.setState({redirect: true});
        }).catch(e => {
            console.log(e);
        });
    }

    render() {
        return (
            this.state.redirect
            ?<Redirect to="/signin" />
            :

        )
    }
}

export  { PageSignout };
<|endoftext|>


<commit_before>/**
 * @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.html or http://ckeditor.com/license
 */
(function() {
    CKEDITOR.plugins.addExternal('readmore','extras/readmore/', 'plugin.js');
    CKEDITOR.plugins.addExternal('images','extras/images/', 'plugin.js');
    CKEDITOR.plugins.addExternal('autosave','extras/autosave/', 'plugin.js');
    CKEDITOR.plugins.addExternal('files','extras/files/', 'plugin.js');
    CKEDITOR.plugins.addExternal('links','extras/links/', 'plugin.js');
})();

CKEDITOR.editorConfig = function( config ) {

	// Define changes to default configuration here. For example:
	// config.language = 'fr';
	// config.uiColor = '#AADC6E';

    config.extraPlugins = 'codemirror,readmore,autosave,images,onchange,files,links';
    // wee need to remove the default image and link plugins for the custom plugin's to work properly
    config.removePlugins ='link';

    config.protectedSource.push( /<\?[\s\S]*?\?>/g );
    config.autosave_delay = '10';
    config.codemirror = {
        theme: 'default',
        lineNumbers: true,
        lineWrapping: true,
        matchBrackets: true,
        autoCloseTags: true,
        autoCloseBrackets: true,
        enableSearchTools: true, // Whether or not to enable search tools, CTRL+F (Find), CTRL+SHIFT+F (Replace), CTRL+SHIFT+R (Replace All), CTRL+G (Find Next), CTRL+SHIFT+G (Find Previous)
        enableCodeFolding: true,
        enableCodeFormatting: true,
        autoFormatOnStart: true,
        autoFormatOnUncomment: true,
        highlightActiveLine: true,
        highlightMatches: true,
        showFormatButton: true,
        showCommentButton: true,
        showUncommentButton: true
    };

    config.allowedContent = true;
    config.toolbar_full =
        [
            { name: 'document', items : [ 'Source','-','Save','NewPage','DocProps','Preview','Print','-','Templates' ] },
            { name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
            { name: 'editing', items : [ 'Find','Replace','-','SelectAll','-','SpellChecker', 'Scayt' ] },
            { name: 'forms', items : [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton',
                'HiddenField' ] },
            '/',
            { name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
            { name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv',
                '-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','-','BidiLtr','BidiRtl' ] },
            { name: 'links', items : [ 'Link','Unlink','Anchor' ] },
            { name: 'insert', items : [ 'images','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak','Iframe' ] },
            '/',
            { name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] },
            { name: 'colors', items : [ 'TextColor','BGColor' ] },
            { name: 'tools', items : [ 'Maximize', 'ShowBlocks','-','readmore' ] }
        ];

    config.toolbar_basic =
        [
            ['Bold', 'Italic', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink','-','HorizontalRule','readmore','images']
        ];

    config.toolbar_standard =
        [
            { name: 'styles', items: [ 'Styles' ] },
            { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic' ] },
            { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent' ] },
            { name: 'links', items: [ 'readmore' ] },
            { name: 'insert', items: [ 'images', 'files','links' ,'Table' ] },
            { name: 'clipboard', groups: [ 'clipboard', 'undo' ], items: [ 'PasteText', '-', 'Undo', 'Redo' ] },
            { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ], items: [ 'Scayt' ] },
            { name: 'document', groups: [ 'mode', 'document', 'doctools' ], items: [ 'Source' ] }
        ];
    config.toolbar_title =
        [
            ['Undo','Redo']
        ];
};
<commit_msg>Removed the link to page plugin and put the default link plugin back re #386<commit_after>/**
 * @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.html or http://ckeditor.com/license
 */
(function() {
    CKEDITOR.plugins.addExternal('readmore','extras/readmore/', 'plugin.js');
    CKEDITOR.plugins.addExternal('images','extras/images/', 'plugin.js');
    CKEDITOR.plugins.addExternal('autosave','extras/autosave/', 'plugin.js');
    CKEDITOR.plugins.addExternal('files','extras/files/', 'plugin.js');
    //CKEDITOR.plugins.addExternal('links','extras/links/', 'plugin.js');
})();

CKEDITOR.editorConfig = function( config ) {

	// Define changes to default configuration here. For example:
	// config.language = 'fr';
	// config.uiColor = '#AADC6E';

    config.extraPlugins = 'codemirror,readmore,autosave,images,onchange,files';
    // wee need to remove the default image and link plugins for the custom plugin's to work properly

    config.protectedSource.push( /<\?[\s\S]*?\?>/g );
    config.autosave_delay = '10';
    config.codemirror = {
        theme: 'default',
        lineNumbers: true,
        lineWrapping: true,
        matchBrackets: true,
        autoCloseTags: true,
        autoCloseBrackets: true,
        enableSearchTools: true, // Whether or not to enable search tools, CTRL+F (Find), CTRL+SHIFT+F (Replace), CTRL+SHIFT+R (Replace All), CTRL+G (Find Next), CTRL+SHIFT+G (Find Previous)
        enableCodeFolding: true,
        enableCodeFormatting: true,
        autoFormatOnStart: true,
        autoFormatOnUncomment: true,
        highlightActiveLine: true,
        highlightMatches: true,
        showFormatButton: true,
        showCommentButton: true,
        showUncommentButton: true
    };

    config.allowedContent = true;
    config.toolbar_full =
        [
            { name: 'document', items : [ 'Source','-','Save','NewPage','DocProps','Preview','Print','-','Templates' ] },
            { name: 'clipboard', items : [ 'Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo' ] },
            { name: 'editing', items : [ 'Find','Replace','-','SelectAll','-','SpellChecker', 'Scayt' ] },
            { name: 'forms', items : [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton',
                'HiddenField' ] },
            '/',
            { name: 'basicstyles', items : [ 'Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat' ] },
            { name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','CreateDiv',
                '-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','-','BidiLtr','BidiRtl' ] },
            { name: 'links', items : [ 'Link','Unlink','Anchor' ] },
            { name: 'insert', items : [ 'images','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak','Iframe' ] },
            '/',
            { name: 'styles', items : [ 'Styles','Format','Font','FontSize' ] },
            { name: 'colors', items : [ 'TextColor','BGColor' ] },
            { name: 'tools', items : [ 'Maximize', 'ShowBlocks','-','readmore' ] }
        ];

    config.toolbar_basic =
        [
            ['Bold', 'Italic', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink','-','HorizontalRule','readmore','images']
        ];

    config.toolbar_standard =
        [
            { name: 'styles', items: [ 'Styles' ] },
            { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic' ] },
            { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent' ] },
            { name: 'links', items: [ 'readmore' ] },
            { name: 'insert', items: [ 'images', 'files','Link','Unlink' ,'Table' ] },
            { name: 'clipboard', groups: [ 'clipboard', 'undo' ], items: [ 'PasteText', '-', 'Undo', 'Redo' ] },
            { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ], items: [ 'Scayt' ] },
            { name: 'document', groups: [ 'mode', 'document', 'doctools' ], items: [ 'Source' ] }
        ];
    config.toolbar_title =
        [
            ['Undo','Redo']
        ];
};
<|endoftext|>


<commit_before>//Helps in creating a structure will help later.
function makeStruct(names) {
	var names = names.split(' ');
	var count = names.length;
	function constructor() {
		for (var i = 0; i < count; i++) {
			this[names[i]] = arguments[i];
		}
	}
	return constructor;
}

var rootcondition = '<fieldset class="condition-set">';
	rootcondition += '
';
		rootcondition += '<button class="remove" alt="Remove">×</button> ';
		rootcondition += 'Match ';
		rootcondition += '<select>';
			rootcondition += '<option value="AND">all</option>';
			rootcondition += '<option value="OR">any</option>';
		rootcondition += '</select> of the following:';
	rootcondition += '
';
	rootcondition += '
';
		rootcondition += '
';
			rootcondition += '';
				rootcondition += '<button class="add">+</button> <button class="addroot">...</button>';
			rootcondition += '';
		rootcondition += '
';
	rootcondition += '
';
rootcondition += '</fieldset>';

var statement = '
';
	statement += '<button class="remove" alt="Remove">×</button> ';

statement += '<select class="fld">';
	statement += '<option value="open">Open/Closed</option>';
	statement += '<option value="status">Status</option>';
	statement += '<option value="login">Submitter</option>';
	statement += '<option value="owner">Owner</option>';
	statement += '<option value="group">Group</option>';
	statement += '<option value="id">ID</option>';
	statement += '<option value="report">Report</option>';
	statement += '<option value="resolved">Resolution</option>';
	statement += '<option value="severity">Severity</option>';
	statement += '<option value="tag">Tag</option>';
	statement += '<option value="type" selected="selected">Type</option>';
	statement += '<option value="created">Created</option>';
	statement += '<option value="category">Category</option>';
statement += '</select>';

statement += '<select class="op">';
	statement += '<option value="=">is</option>';
	statement += '<option value="!=">is not</option>';
statement += '</select>'

statement += '<select class="val">';
	statement += '<option value="0">user submitted</option>';
	statement += '<option value="1">automatic</option>';
	statement += '<option value="3">tool</option>';
statement += '</select>';

//statement += '<input type="text" class="val" />';
statement += '
';

if (!jq) {
	var jq = $;
}

var Conditions = {
	jQuery: jq,

	addqueryroot: function (sel, isroot) {
		var $ = this.jQuery;

		var q = $(sel).find('fieldset');
		var l = q.length;
		if (l < 1) {
			$(sel).append(rootcondition);
			q = $(sel).find('fieldset');
			l = q.length;
		//} else {
			//rootcondition = q;
		}
		var elem = q;

		if (l > 1) {
			if (isroot) {
				l = 1;
			}
			elem = $(q[l - 1]);
		}
		//console.log(elem);

		//If root element remove the close image
		if (isroot) {
			elem.find('>p.operator > .remove').detach();
		}
		elem.find('p.operator > .remove').on('click', function (e) {
			// (this=button)>p>fieldset
			e.preventDefault();
			$(this).parent().parent().detach();
		});

		// Add the default statement segment to the root condition
		if (elem.find('select.fld').length < 1) {
			elem.find('div > .querystmts').append(statement);
		}

		// Add the head class to the first statement
		/*elem.find('div > .querystmts p > .remove').addClass('head').click(function(e){
			e.preventDefault();
		});
		elem.find('div > .querystmts p:first-child > .remove').addClass('head').on('click', function(e){
			e.preventDefault();
		});
		//elem.find('div > .querystmts p > .remove').filter(':not(.head)').each(function(i, el){
		elem.find('div > .querystmts p > .remove').each(function(i, el){
			if (i == 0) {
				$(el).addClass('head').on('click', function(e){
					e.preventDefault();
				});
			} else {
				$(el).unbind('click');
				$(el).on('click', function () {
					$(this).parent().detach();
				});
			}
		});*/
		elem.find('div > .querystmts').each(function(j, elm){
			$(elm).find('p > .remove').each(function(i, el){
				if (i == 0) {
					$(el).addClass('head').on('click', function(e){
						e.preventDefault();
					});
				} else {
					$(el).unbind('click');
					$(el).on('click', function () {
						$(this).parent().detach();
					});
				}
			});
		});

		// Handle click for adding new statement segment
		// When a new statement is added add a condition to handle remove click.
		elem.find('span > .add').on('click', function (e) {
			e.preventDefault();
			$(this).parent().parent().append(statement);

			var sels = $(this).parent().parent().find('select.fld');
			sels.unbind('change');
			sels.on('change', function () {
				var options = $(this).siblings('select.op').find('option').remove().end();
				var val = null;
				$.each(Conditions.option[$(this).val()].operators, function() {
					if (this.sel) {
						val = this.val;
					}
				    options.append($("<option />").val(this.val).text(this.label));
				});
				options.val(val);

				Conditions.populate($(this).val(), options);
			});

			var stmts = $(this).parent().parent().find('p > .remove').filter(':not(.head)');
			stmts.unbind('click');
			stmts.on('click', function () {
				$(this).parent().detach();
			});
		});

		//Conditions.populate(elem.find('select.fld').val(), elem.find('select.op'));

		// Repopulate options based on field selected
		elem.find('select.fld').on('change', function () {
			var options = $(this).siblings('select.op').find('option').remove().end();
			var val = null;
			$.each(Conditions.option[$(this).val()].operators, function() {
				if (this.sel) {
					val = this.val;
				}
			    options.append($("<option />").val(this.val).text(this.label));
			});
			options.val(val);

			Conditions.populate($(this).val(), options);
		});

		// Handle click to add new root condition
		elem.find('span > .addroot').on('click', function (e) {
			e.preventDefault();
			Conditions.addqueryroot($(this).parent().parent().parent(), false);
		});
	},

	populate: function(val, options) {
		var $ = this.jQuery;

		var values = Conditions.option[val].values;
		var select = $('<input type="text" class="val" />');

		if (values instanceof Array) {
			select = $('<select class="val"></select>');
			$.each(values, function() {
				if (this.sel) {
					val = this.val;
				}
			    select.append('<option value="' + this.val + '">' + this.label + '</option>');
			});
			select.val(val);
		}
		options.siblings('.val').remove();
		options.after(select);
	},

	//Recursive method to parse the condition and generate the query. Takes the selector for the root condition
	getCondition: function (rootsel) {
		var $ = this.jQuery;

		//Get the columns from table (to find a clean way to do it later) //tbody>tr>td
		var elem = $(rootsel).children();
		//elem 0 is for operator, elem 1 is for expressions

		var q = {},
			expressions = [],
			nestedexpressions = [];

		//var operator = $(elem[0]).find(':selected').val();
		q.operator = $(elem[0]).find(':selected').val(); //operator;

		// Get all the expressions in a condition
		var expressionelem = $(elem[1]).find('> .querystmts p');
		for (var i = 0; i < expressionelem.length; i++) {
			expressions[i] = {};

			var col = $(expressionelem[i]).find('.fld :selected');
			var op  = $(expressionelem[i]).find('.op :selected');

			expressions[i].fldval  = col.val();
			expressions[i].flddisp = col.text();
			expressions[i].opval   = op.val();
			expressions[i].opdisp  = op.text();
			expressions[i].val     = $($(expressionelem[i]).find('.val')[0]).val();//$(expressionelem[i]).find(':text').val();
		}
		q.expressions = expressions;

		// Get all the nested expressions
		if ($(elem[1]).find('fieldset').length != 0) {
			var len = $(elem[1]).find('fieldset').length;

			for (var k = 0; k < len; k++) {
				nestedexpressions[k] = Conditions.getCondition($(elem[1]).find('fieldset')[k]);
			}
		}
		q.nestedexpressions = nestedexpressions;

		return q;
	},

	// Recursive method to iterate over the condition tree and generate the query
	getQuery: function (condition) {
		var $ = this.jQuery;

		var op = [' ', condition.operator, ' '].join('');

		var e = [];
		var elen = condition.expressions.length;
		for (var i = 0; i < elen; i++) {
			var expr = condition.expressions[i];
			if (expr.opval.indexOf('$1') != -1) {
				e.push("`" + expr.fldval + "` " + expr.opval.replace('$1', expr.val));
			} else {
				e.push("`" + expr.fldval + "` " + expr.opval + " '" + expr.val + "'");
			}
		}

		var n = [];
		var nlen = condition.nestedexpressions.length;
		for (var k = 0; k < nlen; k++) {
			var nestexpr = condition.nestedexpressions[k];
			var result = Conditions.getQuery(nestexpr);
			n.push(result);
		}

		var q = [];
		if (e.length > 0)
			q.push(e.join(op));
		if (n.length > 0)
			q.push(n.join(op));

		return ['(', q.join(op), ')'].join(' ');
	}
};<commit_msg>[COM_SUPPORT] Fixing line returns to be unix<commit_after>//Helps in creating a structure will help later.
function makeStruct(names) {
	var names = names.split(' ');
	var count = names.length;
	function constructor() {
		for (var i = 0; i < count; i++) {
			this[names[i]] = arguments[i];
		}
	}
	return constructor;
}

var rootcondition = '<fieldset class="condition-set">';
	rootcondition += '
';
		rootcondition += '<button class="remove" alt="Remove">×</button> ';
		rootcondition += 'Match ';
		rootcondition += '<select>';
			rootcondition += '<option value="AND">all</option>';
			rootcondition += '<option value="OR">any</option>';
		rootcondition += '</select> of the following:';
	rootcondition += '
';
	rootcondition += '
';
		rootcondition += '
';
			rootcondition += '';
				rootcondition += '<button class="add">+</button> <button class="addroot">...</button>';
			rootcondition += '';
		rootcondition += '
';
	rootcondition += '
';
rootcondition += '</fieldset>';

var statement = '
';
	statement += '<button class="remove" alt="Remove">×</button> ';

statement += '<select class="fld">';
	statement += '<option value="open">Open/Closed</option>';
	statement += '<option value="status">Status</option>';
	statement += '<option value="login">Submitter</option>';
	statement += '<option value="owner">Owner</option>';
	statement += '<option value="group">Group</option>';
	statement += '<option value="id">ID</option>';
	statement += '<option value="report">Report</option>';
	statement += '<option value="resolved">Resolution</option>';
	statement += '<option value="severity">Severity</option>';
	statement += '<option value="tag">Tag</option>';
	statement += '<option value="type" selected="selected">Type</option>';
	statement += '<option value="created">Created</option>';
	statement += '<option value="category">Category</option>';
statement += '</select>';

statement += '<select class="op">';
	statement += '<option value="=">is</option>';
	statement += '<option value="!=">is not</option>';
statement += '</select>'

statement += '<select class="val">';
	statement += '<option value="0">user submitted</option>';
	statement += '<option value="1">automatic</option>';
	statement += '<option value="3">tool</option>';
statement += '</select>';

//statement += '<input type="text" class="val" />';
statement += '
';

if (!jq) {
	var jq = $;
}

var Conditions = {
	jQuery: jq,

	addqueryroot: function (sel, isroot) {
		var $ = this.jQuery;

		var q = $(sel).find('fieldset');
		var l = q.length;
		if (l < 1) {
			$(sel).append(rootcondition);
			q = $(sel).find('fieldset');
			l = q.length;
		//} else {
			//rootcondition = q;
		}
		var elem = q;

		if (l > 1) {
			if (isroot) {
				l = 1;
			}
			elem = $(q[l - 1]);
		}
		//console.log(elem);

		//If root element remove the close image
		if (isroot) {
			elem.find('>p.operator > .remove').detach();
		}
		elem.find('p.operator > .remove').on('click', function (e) {
			// (this=button)>p>fieldset
			e.preventDefault();
			$(this).parent().parent().detach();
		});

		// Add the default statement segment to the root condition
		if (elem.find('select.fld').length < 1) {
			elem.find('div > .querystmts').append(statement);
		}

		// Add the head class to the first statement
		/*elem.find('div > .querystmts p > .remove').addClass('head').click(function(e){
			e.preventDefault();
		});
		elem.find('div > .querystmts p:first-child > .remove').addClass('head').on('click', function(e){
			e.preventDefault();
		});
		//elem.find('div > .querystmts p > .remove').filter(':not(.head)').each(function(i, el){
		elem.find('div > .querystmts p > .remove').each(function(i, el){
			if (i == 0) {
				$(el).addClass('head').on('click', function(e){
					e.preventDefault();
				});
			} else {
				$(el).unbind('click');
				$(el).on('click', function () {
					$(this).parent().detach();
				});
			}
		});*/
		elem.find('div > .querystmts').each(function(j, elm){
			$(elm).find('p > .remove').each(function(i, el){
				if (i == 0) {
					$(el).addClass('head').on('click', function(e){
						e.preventDefault();
					});
				} else {
					$(el).unbind('click');
					$(el).on('click', function () {
						$(this).parent().detach();
					});
				}
			});
		});

		// Handle click for adding new statement segment
		// When a new statement is added add a condition to handle remove click.
		elem.find('span > .add').on('click', function (e) {
			e.preventDefault();
			$(this).parent().parent().append(statement);

			var sels = $(this).parent().parent().find('select.fld');
			sels.unbind('change');
			sels.on('change', function () {
				var options = $(this).siblings('select.op').find('option').remove().end();
				var val = null;
				$.each(Conditions.option[$(this).val()].operators, function() {
					if (this.sel) {
						val = this.val;
					}
				    options.append($("<option />").val(this.val).text(this.label));
				});
				options.val(val);

				Conditions.populate($(this).val(), options);
			});

			var stmts = $(this).parent().parent().find('p > .remove').filter(':not(.head)');
			stmts.unbind('click');
			stmts.on('click', function () {
				$(this).parent().detach();
			});
		});

		//Conditions.populate(elem.find('select.fld').val(), elem.find('select.op'));

		// Repopulate options based on field selected
		elem.find('select.fld').on('change', function () {
			var options = $(this).siblings('select.op').find('option').remove().end();
			var val = null;
			$.each(Conditions.option[$(this).val()].operators, function() {
				if (this.sel) {
					val = this.val;
				}
			    options.append($("<option />").val(this.val).text(this.label));
			});
			options.val(val);

			Conditions.populate($(this).val(), options);
		});

		// Handle click to add new root condition
		elem.find('span > .addroot').on('click', function (e) {
			e.preventDefault();
			Conditions.addqueryroot($(this).parent().parent().parent(), false);
		});
	},

	populate: function(val, options) {
		var $ = this.jQuery;

		var values = Conditions.option[val].values;
		var select = $('<input type="text" class="val" />');

		if (values instanceof Array) {
			select = $('<select class="val"></select>');
			$.each(values, function() {
				if (this.sel) {
					val = this.val;
				}
			    select.append('<option value="' + this.val + '">' + this.label + '</option>');
			});
			select.val(val);
		}
		options.siblings('.val').remove();
		options.after(select);
	},

	//Recursive method to parse the condition and generate the query. Takes the selector for the root condition
	getCondition: function (rootsel) {
		var $ = this.jQuery;

		//Get the columns from table (to find a clean way to do it later) //tbody>tr>td
		var elem = $(rootsel).children();
		//elem 0 is for operator, elem 1 is for expressions

		var q = {},
			expressions = [],
			nestedexpressions = [];

		//var operator = $(elem[0]).find(':selected').val();
		q.operator = $(elem[0]).find(':selected').val(); //operator;

		// Get all the expressions in a condition
		var expressionelem = $(elem[1]).find('> .querystmts p');
		for (var i = 0; i < expressionelem.length; i++) {
			expressions[i] = {};

			var col = $(expressionelem[i]).find('.fld :selected');
			var op  = $(expressionelem[i]).find('.op :selected');

			expressions[i].fldval  = col.val();
			expressions[i].flddisp = col.text();
			expressions[i].opval   = op.val();
			expressions[i].opdisp  = op.text();
			expressions[i].val     = $($(expressionelem[i]).find('.val')[0]).val();//$(expressionelem[i]).find(':text').val();
		}
		q.expressions = expressions;

		// Get all the nested expressions
		if ($(elem[1]).find('fieldset').length != 0) {
			var len = $(elem[1]).find('fieldset').length;

			for (var k = 0; k < len; k++) {
				nestedexpressions[k] = Conditions.getCondition($(elem[1]).find('fieldset')[k]);
			}
		}
		q.nestedexpressions = nestedexpressions;

		return q;
	},

	// Recursive method to iterate over the condition tree and generate the query
	getQuery: function (condition) {
		var $ = this.jQuery;

		var op = [' ', condition.operator, ' '].join('');

		var e = [];
		var elen = condition.expressions.length;
		for (var i = 0; i < elen; i++) {
			var expr = condition.expressions[i];
			if (expr.opval.indexOf('$1') != -1) {
				e.push("`" + expr.fldval + "` " + expr.opval.replace('$1', expr.val));
			} else {
				e.push("`" + expr.fldval + "` " + expr.opval + " '" + expr.val + "'");
			}
		}

		var n = [];
		var nlen = condition.nestedexpressions.length;
		for (var k = 0; k < nlen; k++) {
			var nestexpr = condition.nestedexpressions[k];
			var result = Conditions.getQuery(nestexpr);
			n.push(result);
		}

		var q = [];
		if (e.length > 0)
			q.push(e.join(op));
		if (n.length > 0)
			q.push(n.join(op));

		return ['(', q.join(op), ')'].join(' ');
	}
};<|endoftext|>


<commit_before>describe('The dashboard component', function () {
    var $compile,
        $rootScope,
        store,
        dashboardColumns,
        defaultState,
        mockedState;

    beforeEach(function () {
        angular.mock.module(
            'atlas',
            {
                store: {
                    subscribe: angular.noop,
                    getState: function () {
                        return mockedState;
                    }
                }
            },
            function ($provide) {
                [
                    'dpHeaderDirective',
                    'dpPageDirective',
                    'dpDetailDirective',
                    'dpSearchResultsDirective',
                    'dpLayerSelectionDirective',
                    'dpMapDirective',
                    'dpStraatbeeldDirective',
                    'dpDataSelectionDirective'
                ].forEach(d => $provide.factory(d, () => {
                    return {};
                }));
            }
        );

        angular.mock.inject(function (_$compile_, _$rootScope_, _store_, _dashboardColumns_, _DEFAULT_STATE_) {
            $compile = _$compile_;
            $rootScope = _$rootScope_;
            store = _store_;
            dashboardColumns = _dashboardColumns_;
            defaultState = angular.copy(_DEFAULT_STATE_);
        });

        mockedState = angular.copy(defaultState);

        // spyOn(store, 'getState').and.callThrough();
    });

    function getComponent () {
        var component,
            element,
            scope;

        element = document.createElement('dp-dashboard');
        scope = $rootScope.$new();

        component = $compile(element)(scope);
        scope.$apply();

        return component;
    }

    it('subscribes to the store to listen for changes', function () {
        spyOn(store, 'subscribe');

        getComponent();

        expect(store.subscribe).toHaveBeenCalledWith(jasmine.any(Function));
    });

    describe('error message', function () {
        var component,
            mockedVisibility = {
                httpStatus: {
                    hasErrors: false
                }
            };

        beforeEach(function () {
            spyOn(dashboardColumns, 'determineVisibility').and.returnValue(mockedVisibility);
        });

        it('when not shown, does not flags the dashboard body', function () {
            component = getComponent();

            expect(component.find('.c-dashboard__body').attr('class')).not.toContain('c-dashboard__body--error');
        });

        it('when shown, flags the dashboard body', function () {
            mockedVisibility.httpStatus.hasErrors = true;
            component = getComponent();

            expect(component.find('.c-dashboard__body').attr('class')).toContain('c-dashboard__body--error');
        });
    });

    describe('column sizes', function () {
        var component,
            mockedVisibility,
            mockedColumnSizes;

        beforeEach(function () {
            mockedVisibility = {
                httpStatus: {
                    hasErrors: false
                },
                map: true
            };
            mockedColumnSizes = {
                left: 1,
                middle: 2,
                right: 3
            };

            spyOn(dashboardColumns, 'determineVisibility').and.returnValue(mockedVisibility);
            spyOn(dashboardColumns, 'determineColumnSizes').and.returnValue(mockedColumnSizes);
        });

        it('displays the columns according to the column size', function () {
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(1);
            expect(component.find('.c-dashboard__map').length).toBe(1);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });

        it('does not display a column on zero size', function () {
            mockedColumnSizes.left = 0;
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(0);
            expect(component.find('.c-dashboard__map').length).toBe(1);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });

        it('does not display a column on missing size', function () {
            delete mockedColumnSizes.left;
            delete mockedColumnSizes.middle;
            delete mockedColumnSizes.right;
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(0);
            expect(component.find('.c-dashboard__map').length).toBe(0);
            expect(component.find('.c-dashboard__content').length).toBe(0);
        });

        it('adds the correct class according to the column size', function () {
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').attr('class')).toContain('u-col-sm--1');
            expect(component.find('.c-dashboard__map').attr('class')).toContain('u-col-sm--2');
            expect(component.find('.c-dashboard__content').attr('class')).toContain('u-col-sm--3');
        });
    });

    describe('straatbeeld', function () {
        var component,
            mockedVisibility,
            mockedColumnSizes;

        beforeEach(function () {
            mockedVisibility = {
                httpStatus: {
                    hasErrors: false
                },
                map: false,
                straatbeeld: true
            };
            mockedColumnSizes = {
                left: 1,
                middle: 2,
                right: 3
            };

            spyOn(dashboardColumns, 'determineVisibility').and.returnValue(mockedVisibility);
            spyOn(dashboardColumns, 'determineColumnSizes').and.returnValue(mockedColumnSizes);
        });

        it('displays a fullscreen straatbeeld on straatbeeld.isFullscreen = true', function () {
            spyOn(store, 'getState').and.returnValue({
                straatbeeld: {
                    isFullscreen: true
                },
                map: {
                }
            });
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(1);
            expect(component.find('.c-dashboard__map').length).toBe(0);
            expect(component.find('.c-dashboard__straatbeeld').length).toBe(1);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });

        it('displays a normal straatbeeld on straatbeeld.isFullscreen = false', function () {
            spyOn(store, 'getState').and.returnValue({
                straatbeeld: {
                    isFullscreen: false
                },
                map: {
                }
            });
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(1);
            expect(component.find('.c-dashboard__map').length).toBe(0);
            expect(component.find('.c-dashboard__straatbeeld').length).toBe(0);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });
    });
});
<commit_msg>Allow for fullscreen straatbeeld<commit_after>describe('The dashboard component', function () {
    var $compile,
        $rootScope,
        store,
        dashboardColumns,
        defaultState,
        mockedState;

    beforeEach(function () {
        angular.mock.module(
            'atlas',
            {
                store: {
                    subscribe: angular.noop,
                    getState: function () {
                        return mockedState;
                    }
                }
            },
            function ($provide) {
                [
                    'dpHeaderDirective',
                    'dpPageDirective',
                    'dpDetailDirective',
                    'dpSearchResultsDirective',
                    'dpLayerSelectionDirective',
                    'dpMapDirective',
                    'dpStraatbeeldDirective',
                    'dpDataSelectionDirective'
                ].forEach(d => $provide.factory(d, () => {
                    return {};
                }));
            }
        );

        angular.mock.inject(function (_$compile_, _$rootScope_, _store_, _dashboardColumns_, _DEFAULT_STATE_) {
            $compile = _$compile_;
            $rootScope = _$rootScope_;
            store = _store_;
            dashboardColumns = _dashboardColumns_;
            defaultState = angular.copy(_DEFAULT_STATE_);
        });

        mockedState = angular.copy(defaultState);
    });

    function getComponent () {
        var component,
            element,
            scope;

        element = document.createElement('dp-dashboard');
        scope = $rootScope.$new();

        component = $compile(element)(scope);
        scope.$apply();

        return component;
    }

    it('subscribes to the store to listen for changes', function () {
        spyOn(store, 'subscribe');

        getComponent();

        expect(store.subscribe).toHaveBeenCalledWith(jasmine.any(Function));
    });

    describe('error message', function () {
        var component,
            mockedVisibility = {
                httpStatus: {
                    hasErrors: false
                }
            };

        beforeEach(function () {
            spyOn(dashboardColumns, 'determineVisibility').and.returnValue(mockedVisibility);
        });

        it('when not shown, does not flags the dashboard body', function () {
            component = getComponent();

            expect(component.find('.c-dashboard__body').attr('class')).not.toContain('c-dashboard__body--error');
        });

        it('when shown, flags the dashboard body', function () {
            mockedVisibility.httpStatus.hasErrors = true;
            component = getComponent();

            expect(component.find('.c-dashboard__body').attr('class')).toContain('c-dashboard__body--error');
        });
    });

    describe('column sizes', function () {
        var component,
            mockedVisibility,
            mockedColumnSizes;

        beforeEach(function () {
            mockedVisibility = {
                httpStatus: {
                    hasErrors: false
                },
                map: true
            };
            mockedColumnSizes = {
                left: 1,
                middle: 2,
                right: 3
            };

            spyOn(dashboardColumns, 'determineVisibility').and.returnValue(mockedVisibility);
            spyOn(dashboardColumns, 'determineColumnSizes').and.returnValue(mockedColumnSizes);
        });

        it('displays the columns according to the column size', function () {
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(1);
            expect(component.find('.c-dashboard__map').length).toBe(1);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });

        it('does not display a column on zero size', function () {
            mockedColumnSizes.left = 0;
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(0);
            expect(component.find('.c-dashboard__map').length).toBe(1);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });

        it('does not display a column on missing size', function () {
            delete mockedColumnSizes.left;
            delete mockedColumnSizes.middle;
            delete mockedColumnSizes.right;
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(0);
            expect(component.find('.c-dashboard__map').length).toBe(0);
            expect(component.find('.c-dashboard__content').length).toBe(0);
        });

        it('adds the correct class according to the column size', function () {
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').attr('class')).toContain('u-col-sm--1');
            expect(component.find('.c-dashboard__map').attr('class')).toContain('u-col-sm--2');
            expect(component.find('.c-dashboard__content').attr('class')).toContain('u-col-sm--3');
        });
    });

    describe('straatbeeld', function () {
        var component,
            mockedVisibility,
            mockedColumnSizes;

        beforeEach(function () {
            mockedVisibility = {
                httpStatus: {
                    hasErrors: false
                },
                map: false,
                straatbeeld: true
            };
            mockedColumnSizes = {
                left: 1,
                middle: 2,
                right: 3
            };

            spyOn(dashboardColumns, 'determineVisibility').and.returnValue(mockedVisibility);
            spyOn(dashboardColumns, 'determineColumnSizes').and.returnValue(mockedColumnSizes);
        });

        it('displays a fullscreen straatbeeld on straatbeeld.isFullscreen = true', function () {
            spyOn(store, 'getState').and.returnValue({
                straatbeeld: {
                    isFullscreen: true
                },
                map: {
                }
            });
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(1);
            expect(component.find('.c-dashboard__map').length).toBe(0);
            expect(component.find('.c-dashboard__straatbeeld').length).toBe(1);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });

        it('displays a normal straatbeeld on straatbeeld.isFullscreen = false', function () {
            spyOn(store, 'getState').and.returnValue({
                straatbeeld: {
                    isFullscreen: false
                },
                map: {
                }
            });
            component = getComponent();

            expect(component.find('.c-dashboard__layer-selection').length).toBe(1);
            expect(component.find('.c-dashboard__map').length).toBe(0);
            expect(component.find('.c-dashboard__straatbeeld').length).toBe(0);
            expect(component.find('.c-dashboard__content').length).toBe(1);
        });
    });
});
<|endoftext|>


<commit_before>import { createSelector, createStructuredSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import uniqBy from 'lodash/uniqBy';
import sumBy from 'lodash/sumBy';
import sortBy from 'lodash/sortBy';
import { formatNumber } from 'utils/format';
import { format } from 'd3-format';

// get list data
const getLoss = (state) => state.data && state.data.lossFires;
const getExtent = (state) => state.data && state.data.extent;
const getSettings = (state) => state.settings;
const getIndicator = (state) => state.indicator;
const getLocationsMeta = (state) => state.childData;
const getLocationName = (state) => state.locationLabel;
const getLocation = (state) => state.location;
const getColors = (state) => state.colors;
const getSentences = (state) => state.sentences;
const getTitle = (state) => state.title;

export const mapData = createSelector(
  [getLoss, getExtent, getSettings, getLocationsMeta],
  (data, extent, settings, meta) => {
    if (isEmpty(data) || isEmpty(meta)) return null;
    const { startYear, endYear } = settings;

    const mappedData = data.map((d) => {
      const region = meta[d.id];

      const loss =
        sumBy(
          d.loss.filter((l) => l.year >= startYear && l.year <= endYear),
          'areaLossFires'
        ) || 0;
      const lossInTotal =
        sumBy(
          d.loss.filter((l) => l.year >= startYear && l.year <= endYear),
          'areaLoss'
        ) || 0;
      const numberOfYears = d.loss.filter(
        (l) => l.year >= startYear && l.year <= endYear
      ).length;
      // const locationExtentById = extent.filter((l) => l.id === d.id);
      // const locationExtent =
      //   locationExtentById &&
      //   !!locationExtentById.length &&
      //   locationExtentById[0].extent;
      // const percentage =
      //   loss && locationExtent ? (loss / locationExtent) * 100 : 0;
      const percentage = loss && lossInTotal ? (loss * 100) / lossInTotal : 0;
      const normalPercentage = percentage > 100 ? 100 : percentage;

      return {
        label: (region && region.label) || '',
        loss,
        path: (region && region.path) || '',
        percentage: normalPercentage,
        value: settings.unit === 'ha' ? loss / numberOfYears : normalPercentage,
        numberOfYears,
      };
    });

    return sortBy(mappedData, 'loss');
  }
);

export const parseData = createSelector(
  [mapData, getColors],
  (data, colors) => {
    if (!data) return null;
    const sortedData = sortBy(uniqBy(data, 'label'), 'value').reverse();

    return sortedData.map((o) => ({
      ...o,
      color: colors.main,
    }));
  }
);

export const parseSentence = createSelector(
  [parseData, getSettings, getIndicator, getLocation, getSentences],
  (data, settings, indicator, location, sentences) => {
    if (!data || !data.length || !location) return null;
    const { startYear, endYear } = settings;
    const {
      initial,
      initialPercent,
      withIndicator,
      globalInitial,
      globalWithIndicator,
      withIndicatorPercent,
      noLoss,
    } = sentences;
    const locationData = location && data.find((l) => l.id === location.value);

    const loss = locationData && locationData.loss;
    const globalLoss = sumBy(data, 'loss') || 0;
    const globalExtent = sumBy(data, 'extent') || 0;
    const lossArea = location.label === 'global' ? globalLoss : loss;
    const areaPercent =
      location.label === 'global'
        ? (100 * globalLoss) / globalExtent
        : (locationData && format('.1f')(locationData.percentage)) || 0;
    const lossPercent = loss && locationData ? (100 * loss) / globalLoss : 0;

    const indicatorName = !indicator ? 'region-wide' : `${indicator.label}`;
    // let sentence = settings.unit !== '%' ? initial : initialPercent;
    let sentence = !indicator ? initialPercent : withIndicatorPercent;
    if (settings.unit !== '%') {
      sentence = !indicator ? initial : withIndicator;
    }
    if (location.label === 'global') {
      sentence = !indicator ? globalInitial : globalWithIndicator;
    }
    if (loss === 0) sentence = noLoss;

    const topRegionData = data[0];

    const params = {
      indicator: indicatorName,
      topLocationLabel: topRegionData && topRegionData.label,
      topLocationPerc:
        topRegionData &&
        formatNumber({ num: topRegionData.percentage, unit: '%' }),
      topLocationLoss:
        topRegionData && formatNumber({ num: topRegionData.loss, unit: 'ha' }),
      topLocationLossAverage:
        topRegionData &&
        formatNumber({
          num: topRegionData.loss / topRegionData.numberOfYears,
          unit: 'ha',
        }),
      location:
        location.label === 'global' ? 'globally' : location && location.label,
      // indicator_alt: indicatorName,
      startYear,
      endYear,
      loss: formatNumber({ num: lossArea, unit: 'ha' }),
      localPercent: formatNumber({ num: areaPercent, unit: '%' }),
      globalPercent: formatNumber({ num: lossPercent, unit: '%' }),
      extentYear: settings.extentYear,
    };

    return {
      sentence,
      params,
    };
  }
);

export const parseTitle = createSelector(
  [getTitle, getLocationName],
  (title, name) => {
    let selectedTitle = title.default;
    if (name === 'global') {
      selectedTitle = title.global;
    }
    return selectedTitle;
  }
);

export default createStructuredSelector({
  data: parseData,
  sentence: parseSentence,
  title: parseTitle,
});
<commit_msg>Regions TCLF fix sentence + Cleanup<commit_after>import { createSelector, createStructuredSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import uniqBy from 'lodash/uniqBy';
import sumBy from 'lodash/sumBy';
import sortBy from 'lodash/sortBy';
import { formatNumber } from 'utils/format';
import { format } from 'd3-format';

// get list data
const getLoss = (state) => state.data && state.data.lossFires;
const getSettings = (state) => state.settings;
const getIndicator = (state) => state.indicator;
const getLocationsMeta = (state) => state.childData;
const getLocationName = (state) => state.locationLabel;
const getLocation = (state) => state.location;
const getColors = (state) => state.colors;
const getSentences = (state) => state.sentences;
const getTitle = (state) => state.title;

export const mapData = createSelector(
  [getLoss, getSettings, getLocationsMeta],
  (data, settings, meta) => {
    if (isEmpty(data) || isEmpty(meta)) return null;
    const { startYear, endYear } = settings;

    const mappedData = data.map((d) => {
      const region = meta[d.id];

      const loss =
        sumBy(
          d.loss.filter((l) => l.year >= startYear && l.year <= endYear),
          'areaLossFires'
        ) || 0;
      const lossInTotal =
        sumBy(
          d.loss.filter((l) => l.year >= startYear && l.year <= endYear),
          'areaLoss'
        ) || 0;
      const numberOfYears = d.loss.filter(
        (l) => l.year >= startYear && l.year <= endYear
      ).length;
      const percentage = loss && lossInTotal ? (loss * 100) / lossInTotal : 0;
      const normalPercentage = percentage > 100 ? 100 : percentage;

      return {
        label: (region && region.label) || '',
        loss,
        path: (region && region.path) || '',
        percentage: normalPercentage,
        value: settings.unit === 'ha' ? loss / numberOfYears : normalPercentage,
        numberOfYears,
      };
    });

    return sortBy(mappedData, 'loss');
  }
);

export const parseData = createSelector(
  [mapData, getColors],
  (data, colors) => {
    if (!data) return null;
    const sortedData = sortBy(uniqBy(data, 'label'), 'value').reverse();

    return sortedData.map((o) => ({
      ...o,
      color: colors.main,
    }));
  }
);

export const parseSentence = createSelector(
  [parseData, getSettings, getIndicator, getLocation, getSentences],
  (data, settings, indicator, location, sentences) => {
    if (!data || !data.length || !location) return null;
    const { startYear, endYear } = settings;
    const {
      initial,
      initialPercent,
      withIndicator,
      withIndicatorPercent,
    } = sentences;
    const locationData = location && data.find((l) => l.id === location.value);

    const loss = locationData && locationData.loss;
    const globalLoss = sumBy(data, 'loss') || 0;
    const globalExtent = sumBy(data, 'extent') || 0;
    const lossArea = location.label === 'global' ? globalLoss : loss;
    const areaPercent =
      location.label === 'global'
        ? (100 * globalLoss) / globalExtent
        : (locationData && format('.1f')(locationData.percentage)) || 0;
    const lossPercent = loss && locationData ? (100 * loss) / globalLoss : 0;

    const indicatorName = !indicator ? 'region-wide' : `${indicator.label}`;
    let sentence = !indicator ? initialPercent : withIndicatorPercent;
    if (settings.unit !== '%') {
      sentence = !indicator ? initial : withIndicator;
    }

    const topRegionData = data[0];

    const params = {
      indicator: indicatorName,
      topLocationLabel: topRegionData && topRegionData.label,
      topLocationPerc:
        topRegionData &&
        formatNumber({ num: topRegionData.percentage, unit: '%' }),
      topLocationLoss:
        topRegionData && formatNumber({ num: topRegionData.loss, unit: 'ha' }),
      topLocationLossAverage:
        topRegionData &&
        formatNumber({
          num: topRegionData.loss / topRegionData.numberOfYears,
          unit: 'ha',
        }),
      location:
        location.label === 'global' ? 'globally' : location && location.label,
      indicator_alt: indicatorName,
      startYear,
      endYear,
      loss: formatNumber({ num: lossArea, unit: 'ha' }),
      localPercent: formatNumber({ num: areaPercent, unit: '%' }),
      globalPercent: formatNumber({ num: lossPercent, unit: '%' }),
      extentYear: settings.extentYear,
    };

    return {
      sentence,
      params,
    };
  }
);

export const parseTitle = createSelector(
  [getTitle, getLocationName],
  (title, name) => {
    let selectedTitle = title.default;
    if (name === 'global') {
      selectedTitle = title.global;
    }
    return selectedTitle;
  }
);

export default createStructuredSelector({
  data: parseData,
  sentence: parseSentence,
  title: parseTitle,
});
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

String.prototype.trim = function () {
  return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
};

String.prototype.endsWith = function(suffix) {
  return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

String.prototype.startsWith = function (prefix){
  return this.indexOf(prefix) == 0;
};

String.prototype.contains = function(substring) {
  return this.indexOf(substring) != -1;
};

String.prototype.capitalize = function () {
  return this.charAt(0).toUpperCase() + this.slice(1);
};

/**
 * Replace {i} with argument. where i is number of argument to replace with
 * @return {String}
 */
String.prototype.format = function () {
  var args = arguments;
  return this.replace(/{(\d+)}/g, function (match, number) {
    return typeof args[number] != 'undefined' ? args[number] : match;
  });
};

String.prototype.highlight = function (words, highlightTemplate) {
  var self = this;
  highlightTemplate = highlightTemplate ? highlightTemplate : "{0}";

  words.forEach(function (word) {
    var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
    self = self.replace(searchRegExp, function (found) {
      return highlightTemplate.format(found);
    });
  });

  return self;
};

Number.prototype.toDaysHoursMinutes = function () {
  var formatted = {},
    dateDiff = this,
    secK = 1000, //ms
    minK = 60 * secK, // sec
    hourK = 60 * minK, // sec
    dayK = 24 * hourK;

  dateDiff = parseInt(dateDiff);
  formatted.d = Math.floor(dateDiff / dayK);
  dateDiff -= formatted.d * dayK;
  formatted.h = Math.floor(dateDiff / hourK);
  dateDiff -= formatted.h * hourK;
  formatted.m = (dateDiff / minK).toFixed(2);

  return formatted;
};

/**
 * sort array of objects by property;
 * by default sorting has ascending order
 * if @desc - true, then descending order
 * @param property
 * @param desc
 */
Array.prototype.sortBy = function(property, desc) {
  this.sort(function (a, b) {
    if (a[property] > b[property])
      return (desc) ? -1 : 1;
    else
      return (desc) ? 1 : -1;
  });
  return this;
};

Em.CoreObject.reopen({
  t:function (key, attrs) {
    return Em.I18n.t(key, attrs)
  }
});

Em.Handlebars.registerHelper('log', function (variable) {
  console.log(variable);
});

Em.Handlebars.registerHelper('warn', function (variable) {
  console.warn(variable);
});

Em.Handlebars.registerHelper('highlight', function (property, words, fn) {
  var context = (fn.contexts && fn.contexts[0]) || this;
  property = Em.Handlebars.getPath(context, property, fn);

  words = words.split(";");

//  if (highlightTemplate == undefined) {
  var highlightTemplate = "{0}";
//  }

  words.forEach(function (word) {
    var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
    property = property.replace(searchRegExp, function (found) {
      return highlightTemplate.format(found);
    });
  });

  return new Em.Handlebars.SafeString(property);
});

App = require('app');

/**
 * Certain variables can have JSON in string
 * format, or in JSON format itself.
 */
App.parseJSON = function (value) {
  if (typeof value == "string") {
    return jQuery.parseJSON(value);
  }
  return value;
};

App.format = {

  /**
   * @type Object
   */
  components: {
    'APP_TIMELINE_SERVER': 'App Timeline Server',
    'DATANODE': 'DataNode',
    'DECOMMISSION_DATANODE': 'Update Exclude File',
    'DRPC_SERVER': 'DRPC Server',
    'FALCON': 'Falcon',
    'FALCON_CLIENT': 'Falcon Client',
    'FALCON_SERVER': 'Falcon Server',
    'FALCON_SERVICE_CHECK': 'Falcon Service Check',
    'FLUME_SERVER': 'Flume Agent',
    'GANGLIA_MONITOR': 'Ganglia Monitor',
    'GANGLIA_SERVER': 'Ganglia Server',
    'GLUSTERFS_CLIENT': 'GLUSTERFS Client',
    'GLUSTERFS_SERVICE_CHECK': 'GLUSTERFS Service Check',
    'GMETAD_SERVICE_CHECK': 'Gmetad Service Check',
    'GMOND_SERVICE_CHECK': 'Gmond Service Check',
    'HADOOP_CLIENT': 'Hadoop Client',
    'HBASE_CLIENT': 'HBase Client',
    'HBASE_MASTER': 'HBase Master',
    'HBASE_REGIONSERVER': 'HBase RegionServer',
    'HBASE_SERVICE_CHECK': 'HBase Service Check',
    'HCAT': 'HCat',
    'HCAT_SERVICE_CHECK': 'HCat Service Check',
    'HDFS_CLIENT': 'HDFS Client',
    'HDFS_SERVICE_CHECK': 'HDFS Service Check',
    'HISTORYSERVER': 'History Server',
    'HIVE_CLIENT': 'Hive Client',
    'HIVE_METASTORE': 'Hive Metastore',
    'HIVE_SERVER': 'HiveServer2',
    'HIVE_SERVICE_CHECK': 'Hive Service Check',
    'HUE_SERVER': 'Hue Server',
    'JAVA_JCE': 'Java JCE',
    'JOBTRACKER': 'JobTracker',
    'JOBTRACKER_SERVICE_CHECK': 'JobTracker Service Check',
    'JOURNALNODE': 'JournalNode',
    'KERBEROS_ADMIN_CLIENT': 'Kerberos Admin Client',
    'KERBEROS_CLIENT': 'Kerberos Client',
    'KERBEROS_SERVER': 'Kerberos Server',
    'LOGVIEWER_SERVER': 'Logviewer Server',
    'MAPREDUCE2_CLIENT': 'MapReduce2 Client',
    'MAPREDUCE2_SERVICE_CHECK': 'MapReduce2 Service Check',
    'MAPREDUCE_CLIENT': 'MapReduce Client',
    'MAPREDUCE_SERVICE_CHECK': 'MapReduce Service Check',
    'MYSQL_SERVER': 'MySQL Server',
    'NAGIOS_SERVER': 'Nagios Server',
    'NAMENODE': 'NameNode',
    'NAMENODE_SERVICE_CHECK': 'NameNode Service Check',
    'NIMBUS': 'Nimbus',
    'NODEMANAGER': 'NodeManager',
    'OOZIE_CLIENT': 'Oozie Client',
    'OOZIE_SERVER': 'Oozie Server',
    'OOZIE_SERVICE_CHECK': 'Oozie Service Check',
    'PIG': 'Pig',
    'PIG_SERVICE_CHECK': 'Pig Service Check',
    'RESOURCEMANAGER': 'ResourceManager',
    'SECONDARY_NAMENODE': 'SNameNode',
    'SQOOP': 'Sqoop',
    'SQOOP_SERVICE_CHECK': 'Sqoop Service Check',
    'STORM_REST_API': 'Storm REST API Server',
    'STORM_SERVICE_CHECK': 'Storm Service Check',
    'STORM_UI_SERVER': 'Storm UI Server',
    'SUPERVISOR': 'Supervisor',
    'TASKTRACKER': 'TaskTracker',
    'TEZ_CLIENT': 'Tez Client',
    'WEBHCAT_SERVER': 'WebHCat Server',
    'WEBHCAT_SERVICE_CHECK': 'WebHCat Service Check',
    'YARN_CLIENT': 'YARN Client',
    'YARN_SERVICE_CHECK': 'YARN Service Check',
    'ZKFC': 'ZKFailoverController',
    'ZOOKEEPER_CLIENT': 'ZooKeeper Client',
    'ZOOKEEPER_QUORUM_SERVICE_CHECK': 'ZK Quorum Service Check',
    'ZOOKEEPER_SERVER': 'ZooKeeper Server',
    'ZOOKEEPER_SERVICE_CHECK': 'ZooKeeper Service Check'
  },

  /**
   * @type Object
   */
  command: {
    'INSTALL': 'Install',
    'UNINSTALL': 'Uninstall',
    'START': 'Start',
    'STOP': 'Stop',
    'EXECUTE': 'Execute',
    'ABORT': 'Abort',
    'UPGRADE': 'Upgrade',
    'RESTART': 'Restart',
    'SERVICE_CHECK': 'Check',
    'DECOMMISSION,': 'Decommission,'
  },

  /**
   * convert role to readable string
   * @param role
   */
  role:function (role) {
    return this.components[role] ? this.components[role] : '';
  },

  /**
   * convert command_detail to readable string, show the string for all tasks name
   * @param command_detail
   */
  commandDetail: function (command_detail) {
    var detailArr = command_detail.split(' ');
    var self = this;
    var result = '';
    detailArr.forEach( function(item) {
      // if the item has the pattern SERVICE/COMPONENT, drop the SERVICE part
      if (item.contains('/')) {
        item = item.split('/')[1];
      }
      if (self.components[item]) {
        result = result + ' ' + self.components[item];
      } else if (self.command[item]) {
        result = result + ' ' + self.command[item];
      } else {
        result = result + ' ' + item;
      }
    });
    if (result === ' nagios_update_ignore ACTIONEXECUTE') {
       result = Em.I18n.t('common.maintenance.task');
    }
    return result;
  },

  /**
   * PENDING - Not queued yet for a host
   * QUEUED - Queued for a host
   * IN_PROGRESS - Host reported it is working
   * COMPLETED - Host reported success
   * FAILED - Failed
   * TIMEDOUT - Host did not respond in time
   * ABORTED - Operation was abandoned
   */
  taskStatus:function (_taskStatus) {
    return _taskStatus.toLowerCase();
  }
};

/**
 * wrapper to bootstrap popover
 * fix issue when popover stuck on view routing
 * @param self
 * @param options
 */
App.popover = function(self, options) {
  self.popover(options);
  self.on("remove", function () {
    $(this).trigger('mouseleave');
  });
};

/**
 * wrapper to bootstrap tooltip
 * fix issue when tooltip stuck on view routing
 * @param self - DOM element
 * @param options
 */
App.tooltip = function(self, options) {
  self.tooltip(options);
  self.on("remove", function () {
    $(this).trigger('mouseleave');
  });
};

/**
 * wrapper to Date().getTime()
 * fix issue when client clock and server clock not sync
 * @return timeStamp of current server clock
 */
App.dateTime = function() {
  return new Date().getTime() + App.clockDistance;
};

/*
 * Helper function for bound property helper registration
 * @params name {String} - name of helper
 * @params view {Em.View} - view
 */
App.registerBoundHelper = function(name, view) {
  Em.Handlebars.registerHelper(name, function(property, options) {
    options.hash.contentBinding = property;
    return Em.Handlebars.helpers.view.call(this, view, options);
  });
};

/*
 * Return singular or plural word based on Em.I18n property key.
 *
 *  Example: {{pluralize hostsCount singular="t:host" plural="t:hosts"}}
 */
App.registerBoundHelper('pluralize', Em.View.extend({
  tagName: 'span',
  template: Em.Handlebars.compile('{{view.wordOut}}'),

  wordOut: function() {
    var count, singular, plural;
    count = this.get('content');
    singular = this.get('singular');
    plural = this.get('plural');
    return this.getWord(count, singular, plural);
  }.property('content'),

  getWord: function(count, singular, plural) {
    singular = this.tDetect(singular);
    plural = this.tDetect(plural);
    if (singular && plural) {
      if (count > 1) {
        return plural;
      } else {
        return singular;
      }
    }
    return '';
  },

  /*
   * Detect for Em.I18n.t reference call
   * @params word {String}
   * return {String}
  */
  tDetect: function(word) {
    var splitted = word.split(':');
    if (splitted.length > 1 && splitted[0] == 't') {
      return Em.I18n.t(splitted[1]);
    } else {
      return splitted[0];
    }
  }
  })
);

/**
 * Ambari overrides the default date transformer.
 * This is done because of the non-standard data
 * sent. For example Nagios sends date as "12345678".
 * The problem is that it is a String and is represented
 * only in seconds whereas Javascript's Date needs
 * milliseconds representation.
 */
DS.attr.transforms.date = {
  from: function (serialized) {
    var type = typeof serialized;
    if (type === "string") {
      serialized = parseInt(serialized);
      type = typeof serialized;
    }
    if (type === "number") {
      // The number could be seconds or milliseconds.
      // If seconds, then multiplying with 1000 should still
      // keep it below the current time.
      if (serialized * 1000 < App.dateTime()) {
        serialized = serialized * 1000;
      }
      return new Date(serialized);
    } else if (serialized === null || serialized === undefined) {
      // if the value is not present in the data,
      // return undefined, not null.
      return serialized;
    } else {
      return null;
    }
  },
  to: function (deserialized) {
    if (deserialized instanceof Date) {
      return deserialized.getTime();
    } else if (deserialized === undefined) {
      return undefined;
    } else {
      return null;
    }
  }
};

DS.attr.transforms.object = {
  from: function(serialized) {
    return Ember.none(serialized) ? null : Object(serialized);
  },

  to: function(deserialized) {
    return Ember.none(deserialized) ? null : Object(deserialized);
  }
};

/**
 * Allows EmberData models to have array properties.
 *
 * Declare the property as
 *  operations: DS.attr('array'),
 *  and
 * during load provide a JSON array for value.
 *
 * This transform simply assigns the same array in both directions.
 */
DS.attr.transforms.array = {
  from : function(serialized) {
    return serialized;
  },
  to : function(deserialized) {
    return deserialized;
  }
};
<commit_msg>AMBARI-4810. Bulk decommissioning multiple slaves makes it look like only one slave is being decommissioned. (xiwang via yusaku)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

String.prototype.trim = function () {
  return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
};

String.prototype.endsWith = function(suffix) {
  return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

String.prototype.startsWith = function (prefix){
  return this.indexOf(prefix) == 0;
};

String.prototype.contains = function(substring) {
  return this.indexOf(substring) != -1;
};

String.prototype.capitalize = function () {
  return this.charAt(0).toUpperCase() + this.slice(1);
};

/**
 * Replace {i} with argument. where i is number of argument to replace with
 * @return {String}
 */
String.prototype.format = function () {
  var args = arguments;
  return this.replace(/{(\d+)}/g, function (match, number) {
    return typeof args[number] != 'undefined' ? args[number] : match;
  });
};

String.prototype.highlight = function (words, highlightTemplate) {
  var self = this;
  highlightTemplate = highlightTemplate ? highlightTemplate : "{0}";

  words.forEach(function (word) {
    var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
    self = self.replace(searchRegExp, function (found) {
      return highlightTemplate.format(found);
    });
  });

  return self;
};

Number.prototype.toDaysHoursMinutes = function () {
  var formatted = {},
    dateDiff = this,
    secK = 1000, //ms
    minK = 60 * secK, // sec
    hourK = 60 * minK, // sec
    dayK = 24 * hourK;

  dateDiff = parseInt(dateDiff);
  formatted.d = Math.floor(dateDiff / dayK);
  dateDiff -= formatted.d * dayK;
  formatted.h = Math.floor(dateDiff / hourK);
  dateDiff -= formatted.h * hourK;
  formatted.m = (dateDiff / minK).toFixed(2);

  return formatted;
};

/**
 * sort array of objects by property;
 * by default sorting has ascending order
 * if @desc - true, then descending order
 * @param property
 * @param desc
 */
Array.prototype.sortBy = function(property, desc) {
  this.sort(function (a, b) {
    if (a[property] > b[property])
      return (desc) ? -1 : 1;
    else
      return (desc) ? 1 : -1;
  });
  return this;
};

Em.CoreObject.reopen({
  t:function (key, attrs) {
    return Em.I18n.t(key, attrs)
  }
});

Em.Handlebars.registerHelper('log', function (variable) {
  console.log(variable);
});

Em.Handlebars.registerHelper('warn', function (variable) {
  console.warn(variable);
});

Em.Handlebars.registerHelper('highlight', function (property, words, fn) {
  var context = (fn.contexts && fn.contexts[0]) || this;
  property = Em.Handlebars.getPath(context, property, fn);

  words = words.split(";");

//  if (highlightTemplate == undefined) {
  var highlightTemplate = "{0}";
//  }

  words.forEach(function (word) {
    var searchRegExp = new RegExp("\\b" + word + "\\b", "gi");
    property = property.replace(searchRegExp, function (found) {
      return highlightTemplate.format(found);
    });
  });

  return new Em.Handlebars.SafeString(property);
});

App = require('app');

/**
 * Certain variables can have JSON in string
 * format, or in JSON format itself.
 */
App.parseJSON = function (value) {
  if (typeof value == "string") {
    return jQuery.parseJSON(value);
  }
  return value;
};

App.format = {

  /**
   * @type Object
   */
  components: {
    'APP_TIMELINE_SERVER': 'App Timeline Server',
    'DATANODE': 'DataNode',
    'DECOMMISSION_DATANODE': 'Update Exclude File',
    'DRPC_SERVER': 'DRPC Server',
    'FALCON': 'Falcon',
    'FALCON_CLIENT': 'Falcon Client',
    'FALCON_SERVER': 'Falcon Server',
    'FALCON_SERVICE_CHECK': 'Falcon Service Check',
    'FLUME_SERVER': 'Flume Agent',
    'GANGLIA_MONITOR': 'Ganglia Monitor',
    'GANGLIA_SERVER': 'Ganglia Server',
    'GLUSTERFS_CLIENT': 'GLUSTERFS Client',
    'GLUSTERFS_SERVICE_CHECK': 'GLUSTERFS Service Check',
    'GMETAD_SERVICE_CHECK': 'Gmetad Service Check',
    'GMOND_SERVICE_CHECK': 'Gmond Service Check',
    'HADOOP_CLIENT': 'Hadoop Client',
    'HBASE_CLIENT': 'HBase Client',
    'HBASE_MASTER': 'HBase Master',
    'HBASE_REGIONSERVER': 'HBase RegionServer',
    'HBASE_SERVICE_CHECK': 'HBase Service Check',
    'HCAT': 'HCat',
    'HCAT_SERVICE_CHECK': 'HCat Service Check',
    'HDFS_CLIENT': 'HDFS Client',
    'HDFS_SERVICE_CHECK': 'HDFS Service Check',
    'HISTORYSERVER': 'History Server',
    'HIVE_CLIENT': 'Hive Client',
    'HIVE_METASTORE': 'Hive Metastore',
    'HIVE_SERVER': 'HiveServer2',
    'HIVE_SERVICE_CHECK': 'Hive Service Check',
    'HUE_SERVER': 'Hue Server',
    'JAVA_JCE': 'Java JCE',
    'JOBTRACKER': 'JobTracker',
    'JOBTRACKER_SERVICE_CHECK': 'JobTracker Service Check',
    'JOURNALNODE': 'JournalNode',
    'KERBEROS_ADMIN_CLIENT': 'Kerberos Admin Client',
    'KERBEROS_CLIENT': 'Kerberos Client',
    'KERBEROS_SERVER': 'Kerberos Server',
    'LOGVIEWER_SERVER': 'Logviewer Server',
    'MAPREDUCE2_CLIENT': 'MapReduce2 Client',
    'MAPREDUCE2_SERVICE_CHECK': 'MapReduce2 Service Check',
    'MAPREDUCE_CLIENT': 'MapReduce Client',
    'MAPREDUCE_SERVICE_CHECK': 'MapReduce Service Check',
    'MYSQL_SERVER': 'MySQL Server',
    'NAGIOS_SERVER': 'Nagios Server',
    'NAMENODE': 'NameNode',
    'NAMENODE_SERVICE_CHECK': 'NameNode Service Check',
    'NIMBUS': 'Nimbus',
    'NODEMANAGER': 'NodeManager',
    'OOZIE_CLIENT': 'Oozie Client',
    'OOZIE_SERVER': 'Oozie Server',
    'OOZIE_SERVICE_CHECK': 'Oozie Service Check',
    'PIG': 'Pig',
    'PIG_SERVICE_CHECK': 'Pig Service Check',
    'RESOURCEMANAGER': 'ResourceManager',
    'SECONDARY_NAMENODE': 'SNameNode',
    'SQOOP': 'Sqoop',
    'SQOOP_SERVICE_CHECK': 'Sqoop Service Check',
    'STORM_REST_API': 'Storm REST API Server',
    'STORM_SERVICE_CHECK': 'Storm Service Check',
    'STORM_UI_SERVER': 'Storm UI Server',
    'SUPERVISOR': 'Supervisor',
    'TASKTRACKER': 'TaskTracker',
    'TEZ_CLIENT': 'Tez Client',
    'WEBHCAT_SERVER': 'WebHCat Server',
    'WEBHCAT_SERVICE_CHECK': 'WebHCat Service Check',
    'YARN_CLIENT': 'YARN Client',
    'YARN_SERVICE_CHECK': 'YARN Service Check',
    'ZKFC': 'ZKFailoverController',
    'ZOOKEEPER_CLIENT': 'ZooKeeper Client',
    'ZOOKEEPER_QUORUM_SERVICE_CHECK': 'ZK Quorum Service Check',
    'ZOOKEEPER_SERVER': 'ZooKeeper Server',
    'ZOOKEEPER_SERVICE_CHECK': 'ZooKeeper Service Check'
  },

  /**
   * @type Object
   */
  command: {
    'INSTALL': 'Install',
    'UNINSTALL': 'Uninstall',
    'START': 'Start',
    'STOP': 'Stop',
    'EXECUTE': 'Execute',
    'ABORT': 'Abort',
    'UPGRADE': 'Upgrade',
    'RESTART': 'Restart',
    'SERVICE_CHECK': 'Check',
    'Excluded:': 'Decommission:',
    'Included:': 'Recommission:'
  },

  /**
   * convert role to readable string
   * @param role
   */
  role:function (role) {
    return this.components[role] ? this.components[role] : '';
  },

  /**
   * convert command_detail to readable string, show the string for all tasks name
   * @param command_detail
   */
  commandDetail: function (command_detail) {
    var detailArr = command_detail.split(' ');
    var self = this;
    var result = '';
    detailArr.forEach( function(item) {
      // if the item has the pattern SERVICE/COMPONENT, drop the SERVICE part
      if (item.contains('/')) {
        item = item.split('/')[1];
      }
      // ignore 'DECOMMISSION', command came from 'excluded/included'
      if (item == 'DECOMMISSION,') {
        item = '';
      }
      if (self.components[item]) {
        result = result + ' ' + self.components[item];
      } else if (self.command[item]) {
        result = result + ' ' + self.command[item];
      } else {
        result = result + ' ' + item;
      }
    });
    if (result === ' nagios_update_ignore ACTIONEXECUTE') {
       result = Em.I18n.t('common.maintenance.task');
    }
    return result;
  },

  /**
   * PENDING - Not queued yet for a host
   * QUEUED - Queued for a host
   * IN_PROGRESS - Host reported it is working
   * COMPLETED - Host reported success
   * FAILED - Failed
   * TIMEDOUT - Host did not respond in time
   * ABORTED - Operation was abandoned
   */
  taskStatus:function (_taskStatus) {
    return _taskStatus.toLowerCase();
  }
};

/**
 * wrapper to bootstrap popover
 * fix issue when popover stuck on view routing
 * @param self
 * @param options
 */
App.popover = function(self, options) {
  self.popover(options);
  self.on("remove", function () {
    $(this).trigger('mouseleave');
  });
};

/**
 * wrapper to bootstrap tooltip
 * fix issue when tooltip stuck on view routing
 * @param self - DOM element
 * @param options
 */
App.tooltip = function(self, options) {
  self.tooltip(options);
  self.on("remove", function () {
    $(this).trigger('mouseleave');
  });
};

/**
 * wrapper to Date().getTime()
 * fix issue when client clock and server clock not sync
 * @return timeStamp of current server clock
 */
App.dateTime = function() {
  return new Date().getTime() + App.clockDistance;
};

/*
 * Helper function for bound property helper registration
 * @params name {String} - name of helper
 * @params view {Em.View} - view
 */
App.registerBoundHelper = function(name, view) {
  Em.Handlebars.registerHelper(name, function(property, options) {
    options.hash.contentBinding = property;
    return Em.Handlebars.helpers.view.call(this, view, options);
  });
};

/*
 * Return singular or plural word based on Em.I18n property key.
 *
 *  Example: {{pluralize hostsCount singular="t:host" plural="t:hosts"}}
 */
App.registerBoundHelper('pluralize', Em.View.extend({
  tagName: 'span',
  template: Em.Handlebars.compile('{{view.wordOut}}'),

  wordOut: function() {
    var count, singular, plural;
    count = this.get('content');
    singular = this.get('singular');
    plural = this.get('plural');
    return this.getWord(count, singular, plural);
  }.property('content'),

  getWord: function(count, singular, plural) {
    singular = this.tDetect(singular);
    plural = this.tDetect(plural);
    if (singular && plural) {
      if (count > 1) {
        return plural;
      } else {
        return singular;
      }
    }
    return '';
  },

  /*
   * Detect for Em.I18n.t reference call
   * @params word {String}
   * return {String}
  */
  tDetect: function(word) {
    var splitted = word.split(':');
    if (splitted.length > 1 && splitted[0] == 't') {
      return Em.I18n.t(splitted[1]);
    } else {
      return splitted[0];
    }
  }
  })
);

/**
 * Ambari overrides the default date transformer.
 * This is done because of the non-standard data
 * sent. For example Nagios sends date as "12345678".
 * The problem is that it is a String and is represented
 * only in seconds whereas Javascript's Date needs
 * milliseconds representation.
 */
DS.attr.transforms.date = {
  from: function (serialized) {
    var type = typeof serialized;
    if (type === "string") {
      serialized = parseInt(serialized);
      type = typeof serialized;
    }
    if (type === "number") {
      // The number could be seconds or milliseconds.
      // If seconds, then multiplying with 1000 should still
      // keep it below the current time.
      if (serialized * 1000 < App.dateTime()) {
        serialized = serialized * 1000;
      }
      return new Date(serialized);
    } else if (serialized === null || serialized === undefined) {
      // if the value is not present in the data,
      // return undefined, not null.
      return serialized;
    } else {
      return null;
    }
  },
  to: function (deserialized) {
    if (deserialized instanceof Date) {
      return deserialized.getTime();
    } else if (deserialized === undefined) {
      return undefined;
    } else {
      return null;
    }
  }
};

DS.attr.transforms.object = {
  from: function(serialized) {
    return Ember.none(serialized) ? null : Object(serialized);
  },

  to: function(deserialized) {
    return Ember.none(deserialized) ? null : Object(deserialized);
  }
};

/**
 * Allows EmberData models to have array properties.
 *
 * Declare the property as
 *  operations: DS.attr('array'),
 *  and
 * during load provide a JSON array for value.
 *
 * This transform simply assigns the same array in both directions.
 */
DS.attr.transforms.array = {
  from : function(serialized) {
    return serialized;
  },
  to : function(deserialized) {
    return deserialized;
  }
};
<|endoftext|>


<commit_before>import actionBuilder from 'action-builder'
import backend from 'backend'
import {gzip$, ungzip$} from 'gzip'
import JSZip from 'jszip'
import _ from 'lodash'
import {from, of, Subject} from 'rxjs'
import {map, switchMap} from 'rxjs/operators'
import {select, subscribe} from 'store'
import {addTab, closeTab} from 'widget/tabs'

export const recipePath = (recipeId, path) => {
    const recipeTabIndex = select('process.tabs')
        .findIndex((recipe) => recipe.id === recipeId)
    if (recipeTabIndex === -1)
        throw new Error(`Recipe not found: ${recipeId}`)
    if (path && Array.isArray(path))
        path = path.join('.')
    return ['process.tabs', recipeTabIndex, path]
        .filter(e => e !== undefined)
        .join('.')
}

export const RecipeState = (recipeId) => {
    if (!isRecipeOpen(recipeId))
        return null

    return (path) =>
        select(recipePath(recipeId, path))
}

export const saveRecipe$ = (recipe) => {
    if (!recipe.type)
        return
    const listItem = {
        id: recipe.id,
        name: recipe.title || recipe.placeholder,
        type: recipe.type
    }
    const recipes = [...select('process.recipes')]
    const index = recipes.findIndex(savedRecipe => savedRecipe.id === recipe.id)
    if (index > -1)
        recipes[index] = listItem
    else
        recipes.push(listItem)

    return of(
        actionBuilder('SET_RECIPES', {recipes})
            .set('process.recipes', recipes)
            .sideEffect(() => saveToBackend$.next(recipe))
            .build()
    )
}

export const saveRecipe = (recipe) =>
    saveRecipe$(recipe).subscribe(action => action.dispatch())

export const exportRecipe = (recipe) => {
    const name = `${recipe.title || recipe.placeholder}`
    const recipeString = JSON.stringify(_.omit(recipe, ['ui']), null, 2)
    from(new JSZip().file(name + '.json', recipeString).generateAsync({
        type: 'blob',
        compression: 'DEFLATE',
        compressionOptions: {
            level: 5
        }
    })).pipe(
        map(zippedRecipe => {
            const data = window.URL.createObjectURL(zippedRecipe)
            var downloadElement = document.createElement('a')
            downloadElement.setAttribute('href', data)
            downloadElement.setAttribute('download', name + '.zip')
            document.body.appendChild(downloadElement)
            downloadElement.click()
            downloadElement.remove()
            window.URL.revokeObjectURL(data)
        })
    ).subscribe()
}

export const loadRecipes$ = () =>
    backend.recipe.loadAll$().pipe(
        map((recipes) => actionBuilder('SET_RECIPES', {recipes})
            .set('process.recipes', recipes)
            .build())
    )

export const loadRecipe$ = (recipeId) => {
    const selectedTabId = select('process.selectedTabId')
    if (isRecipeOpen(recipeId)) {
        const recipe = select(recipePath(recipeId))
        return of([
            actionBuilder('SELECT_RECIPE')
                .set('process.selectedTabId', recipe.id)
                .build()
        ])
    } else {
        return backend.recipe.load$(recipeId).pipe(
            map(recipe =>
                actionBuilder('OPEN_RECIPE')
                    .set(recipePath(selectedTabId), recipe)
                    .set('process.selectedTabId', recipe.id)
                    .build())
        )
    }
}

export const addRecipe = (recipe) => {
    const tab = addTab('process')
    recipe.id = tab.id
    return actionBuilder('SELECT_RECIPE')
        .set(recipePath(recipe.id), recipe)
        .set('process.selectedTabId', recipe.id)
        .dispatch()
}

export const deleteRecipe$ = (recipeId) =>
    backend.recipe.delete$(recipeId).pipe(
        map(() => {
            const recipes = select('process.recipes')
                .filter(recipe => recipe.id !== recipeId)
            return actionBuilder('SET_RECIPES', {recipes})
                .set('process.recipes', recipes)
                .build()
        })
    )

export const deleteRecipe = (recipeId) =>
    deleteRecipe$(recipeId).subscribe(action => action.dispatch())

const isRecipeOpen = (recipeId) =>
    select('process.tabs').findIndex(recipe => recipe.id === recipeId) > -1

const saveToBackend$ = new Subject()

let prevTabs = []
const findPrevRecipe = (recipe) =>
    prevTabs.find(prevRecipe => prevRecipe.id === recipe.id) || {}
subscribe('process.tabs', (recipes) => {
        if (recipes && (prevTabs.length === 0 || prevTabs !== recipes)) {
            const recipesToSave = recipes
                .filter(recipe =>
                    (select('process.recipes') || []).find(saved =>
                        saved.id === recipe.id
                    )
                )
                .filter(recipe => {
                    const prevRecipe = findPrevRecipe(recipe)
                    return prevRecipe.model && !_.isEqual(prevRecipe.model, recipe.model)
                })
            if (recipesToSave.length > 0) {
                recipesToSave.forEach(recipe => saveToBackend$.next(recipe))
            }
            prevTabs = recipes
        }
    }
)

saveToBackend$.pipe(
    switchMap(recipe => {
        gzip$(_.omit(recipe, ['ui']), {to: 'string'}).subscribe(revision =>
            saveRevisionToLocalStorage(recipe.id, revision)
        )
        return backend.recipe.save$(recipe)
    })
).subscribe()

const saveRevisionToLocalStorage = (recipeId, revision) => {
    try {
        localStorage.setItem(`${recipeId}:${Date.now()}`, revision)
    } catch (exception) {
        if (expireRevisionFromLocalStorage(recipeId))
            saveRevisionToLocalStorage(recipeId, revision)
    }
}

const expireRevisionFromLocalStorage = (recipeId) => {
    const keyToExpire = _(localStorage)
        .keys()
        .map(key => ({key, timestamp: key.split(':')[1]}))
        .sortBy({key: 1})
        .first()
        .key
    localStorage.removeItem(keyToExpire)
    return true
}

export const getRevisions = (recipeId) =>
    _(localStorage)
        .keys()
        .map(key => (key.split(':')))
        .filter(([id, timestamp]) => recipeId === id)
        .map(([id, timestamp]) => timestamp)
        .sortBy()
        .reverse()
        .value()

export const revertToRevision$ = (recipeId, revision) => {
    const compressed = localStorage[`${recipeId}:${revision}`]
    return ungzip$(compressed, {to: 'string'}).pipe(
        map(recipe => {
            prevTabs = prevTabs.filter(tab => tab.id !== recipeId)
            closeTab(recipeId, 'process')
            const selectedTabId = select('process.selectedTabId')
            actionBuilder('REVERT_RECIPE')
                .set(recipePath(selectedTabId), recipe)
                .set('process.selectedTabId', recipeId)
                .dispatch()
            console.log('saving', recipe)
            backend.recipe.save$(recipe).subscribe()
            return recipe
        })
    )
}<commit_msg>Prefix recipe revisions in local storage, to allow for other data.<commit_after>import actionBuilder from 'action-builder'
import backend from 'backend'
import {gzip$, ungzip$} from 'gzip'
import JSZip from 'jszip'
import _ from 'lodash'
import {from, of, Subject} from 'rxjs'
import {map, switchMap} from 'rxjs/operators'
import {select, subscribe} from 'store'
import {addTab, closeTab} from 'widget/tabs'

export const recipePath = (recipeId, path) => {
    const recipeTabIndex = select('process.tabs')
        .findIndex((recipe) => recipe.id === recipeId)
    if (recipeTabIndex === -1)
        throw new Error(`Recipe not found: ${recipeId}`)
    if (path && Array.isArray(path))
        path = path.join('.')
    return ['process.tabs', recipeTabIndex, path]
        .filter(e => e !== undefined)
        .join('.')
}

export const RecipeState = (recipeId) => {
    if (!isRecipeOpen(recipeId))
        return null

    return (path) =>
        select(recipePath(recipeId, path))
}

export const saveRecipe$ = (recipe) => {
    if (!recipe.type)
        return
    const listItem = {
        id: recipe.id,
        name: recipe.title || recipe.placeholder,
        type: recipe.type
    }
    const recipes = [...select('process.recipes')]
    const index = recipes.findIndex(savedRecipe => savedRecipe.id === recipe.id)
    if (index > -1)
        recipes[index] = listItem
    else
        recipes.push(listItem)

    return of(
        actionBuilder('SET_RECIPES', {recipes})
            .set('process.recipes', recipes)
            .sideEffect(() => saveToBackend$.next(recipe))
            .build()
    )
}

export const saveRecipe = (recipe) =>
    saveRecipe$(recipe).subscribe(action => action.dispatch())

export const exportRecipe = (recipe) => {
    const name = `${recipe.title || recipe.placeholder}`
    const recipeString = JSON.stringify(_.omit(recipe, ['ui']), null, 2)
    from(new JSZip().file(name + '.json', recipeString).generateAsync({
        type: 'blob',
        compression: 'DEFLATE',
        compressionOptions: {
            level: 5
        }
    })).pipe(
        map(zippedRecipe => {
            const data = window.URL.createObjectURL(zippedRecipe)
            var downloadElement = document.createElement('a')
            downloadElement.setAttribute('href', data)
            downloadElement.setAttribute('download', name + '.zip')
            document.body.appendChild(downloadElement)
            downloadElement.click()
            downloadElement.remove()
            window.URL.revokeObjectURL(data)
        })
    ).subscribe()
}

export const loadRecipes$ = () =>
    backend.recipe.loadAll$().pipe(
        map((recipes) => actionBuilder('SET_RECIPES', {recipes})
            .set('process.recipes', recipes)
            .build())
    )

export const loadRecipe$ = (recipeId) => {
    const selectedTabId = select('process.selectedTabId')
    if (isRecipeOpen(recipeId)) {
        const recipe = select(recipePath(recipeId))
        return of([
            actionBuilder('SELECT_RECIPE')
                .set('process.selectedTabId', recipe.id)
                .build()
        ])
    } else {
        return backend.recipe.load$(recipeId).pipe(
            map(recipe =>
                actionBuilder('OPEN_RECIPE')
                    .set(recipePath(selectedTabId), recipe)
                    .set('process.selectedTabId', recipe.id)
                    .build())
        )
    }
}

export const addRecipe = (recipe) => {
    const tab = addTab('process')
    recipe.id = tab.id
    return actionBuilder('SELECT_RECIPE')
        .set(recipePath(recipe.id), recipe)
        .set('process.selectedTabId', recipe.id)
        .dispatch()
}

export const deleteRecipe$ = (recipeId) =>
    backend.recipe.delete$(recipeId).pipe(
        map(() => {
            const recipes = select('process.recipes')
                .filter(recipe => recipe.id !== recipeId)
            return actionBuilder('SET_RECIPES', {recipes})
                .set('process.recipes', recipes)
                .build()
        })
    )

export const deleteRecipe = (recipeId) =>
    deleteRecipe$(recipeId).subscribe(action => action.dispatch())

const isRecipeOpen = (recipeId) =>
    select('process.tabs').findIndex(recipe => recipe.id === recipeId) > -1

const saveToBackend$ = new Subject()

let prevTabs = []
const findPrevRecipe = (recipe) =>
    prevTabs.find(prevRecipe => prevRecipe.id === recipe.id) || {}
subscribe('process.tabs', (recipes) => {
        if (recipes && (prevTabs.length === 0 || prevTabs !== recipes)) {
            const recipesToSave = recipes
                .filter(recipe =>
                    (select('process.recipes') || []).find(saved =>
                        saved.id === recipe.id
                    )
                )
                .filter(recipe => {
                    const prevRecipe = findPrevRecipe(recipe)
                    return prevRecipe.model && !_.isEqual(prevRecipe.model, recipe.model)
                })
            if (recipesToSave.length > 0) {
                recipesToSave.forEach(recipe => saveToBackend$.next(recipe))
            }
            prevTabs = recipes
        }
    }
)

saveToBackend$.pipe(
    switchMap(recipe => {
        gzip$(_.omit(recipe, ['ui']), {to: 'string'}).subscribe(revision =>
            saveRevisionToLocalStorage(recipe.id, revision)
        )
        return backend.recipe.save$(recipe)
    })
).subscribe()

const saveRevisionToLocalStorage = (recipeId, revision) => {
    try {
        localStorage.setItem(`sepal:${recipeId}:${Date.now()}`, revision)
    } catch (exception) {
        if (expireRevisionFromLocalStorage(recipeId))
            saveRevisionToLocalStorage(recipeId, revision)
    }
}

const expireRevisionFromLocalStorage = (recipeId) => {
    const keyToExpire = _(localStorage)
        .keys()
        .filter(key => key.startsWith('sepal:'))
        .map(key => ({key, timestamp: key.split(':')[2]}))
        .filter(value => value)
        .sortBy({key: 1})
        .first()
        .key
    localStorage.removeItem(keyToExpire)
    return true
}

export const getRevisions = (recipeId) =>
    _(localStorage)
        .keys()
        .filter(key => key.startsWith('sepal:'))
        .map(key => (key.split(':')))
        .filter(([prefix, id, timestamp]) => recipeId === id)
        .map(([prefix, id, timestamp]) => timestamp)
        .sortBy()
        .reverse()
        .value()

export const revertToRevision$ = (recipeId, revision) => {
    const compressed = localStorage[`sepal:${recipeId}:${revision}`]
    return ungzip$(compressed, {to: 'string'}).pipe(
        map(recipe => {
            prevTabs = prevTabs.filter(tab => tab.id !== recipeId)
            closeTab(recipeId, 'process')
            const selectedTabId = select('process.selectedTabId')
            actionBuilder('REVERT_RECIPE')
                .set(recipePath(selectedTabId), recipe)
                .set('process.selectedTabId', recipeId)
                .dispatch()
            console.log('saving', recipe)
            backend.recipe.save$(recipe).subscribe()
            return recipe
        })
    )
}<|endoftext|>