<commit_before>/*
This file in the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkId=518007
*/

var gulp = require('gulp');

/**
 * PostCSS - Tool for transforming styles with JS plugins
 */
var postcss = require("gulp-postcss");

/**
 * PostCSS Reporter - Log PostCSS messages in the console
 */
var reporter = require('postcss-reporter');

/**
 * Stylelint - Modern CSS linter
 */
var stylelint = require('stylelint');

gulp.task('default', function () {

    console.log('You are running Node Version: ' + process.version);

});

gulp.task('stylelint', function () {
    // Stylelint config rules
    var stylelintConfig = {
        "rules": {
            //Color Rules via http://stylelint.io/user-guide/rules/
            //"color-hex-case": "lower", //TBD
            "color-hex-length": "short",
            "color-named": "never",
            "color-no-invalid-hex": true,

            //Font Family
            "font-family-name-quotes": "always-where-recommended",
            "font-family-no-duplicate-names": true,

            //Font weight
            "font-weight-notation": "named-where-possible", //TODO: Look into this more

            //Function
            "function-calc-no-unspaced-operator": true,
            "function-linear-gradient-no-nonstandard-direction": true,
            "function-max-empty-lines": 0,
            "function-name-case": "lower",
            "function-parentheses-newline-inside": "always-multi-line",
            "function-parentheses-space-inside": "never",
            "function-url-data-uris": "never",
            "function-url-no-scheme-relative": true,
            "function-url-quotes": ["always", {
                "except": ["empty"]
            }],
            "function-whitespace-after": "always",

            //Number
            "number-leading-zero":"always",
            "number-no-trailing-zeros": true,

            //String
            "string-no-newline": true,
            "string-quotes": "double",

            //Length
            "length-zero-no-unit": true,

            //Time
            "time-min-milliseconds": 100, //According to the notes to the original issue (https://github.com/stylelint/stylelint/issues/597#issuecomment-180872504) the human eye can't see transition times under 100ms. //TODO: More resources maybe recommended to confirm.

            //Unit
            "unit-no-unknown": true,
            "unit-case": "lower",

            //Value
            "value-no-vendor-prefix": true,
            "value-keyword-case": "lower",

            //Value list
            "value-list-comma-newline-after": "always-multi-line",
            "value-list-comma-newline-before": "always-multi-line",
            "value-list-comma-space-after": "always",
            "value-list-comma-space-before": "never",
            "value-list-max-empty-lines": 0,

            //Custom Properties
            //"custom-property-no-outside-root": true, //TODO: Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place

            //Shorthand property
            "shorthand-property-no-redundant-values": true,

            //Property
            "property-case": "lower",
            "property-no-unknown":true,
            //"property-no-vendor-prefix": true, //Currently the impact isn't huge but should be a todo in the future

            //Keyframe declaration
            "keyframe-declaration-no-important": true,

            //Declaration
            "declaration-bang-space-after": "never",
            "declaration-bang-space-before": "always",
            //"declaration-colon-space-after": "always-single-line", //Currently the impact isn't huge but should be a todo in the future
            "declaration-colon-space-before": "never",
            "declaration-empty-line-before": "never",
            //"declaration-no-important": true, //Only use when checking if !important is being used - note that !important is acceptable in utilities

            //Declaration Block
            "declaration-block-no-duplicate-properties": [true, {
                "ignore": ["consecutive-duplicates-with-different-values"]
            }],
            "declaration-block-no-redundant-longhand-properties": true,
            "declaration-block-no-shorthand-property-overrides": true,
            "declaration-block-semicolon-newline-after": "always-multi-line",
            "declaration-block-semicolon-newline-before": "never-multi-line",
            "declaration-block-semicolon-space-after": "always-single-line",
            "declaration-block-semicolon-space-before": "never",
            //"declaration-block-single-line-max-declarations": 1, //TODO: Make this change in the future - this would allow for reduced chance of merge conflicts and would be more 'truthful' when viewing a diff - see cssguidlin.es
            "declaration-block-trailing-semicolon": "always",
            //Block
            "block-closing-brace-empty-line-before": "never", //False Positives in nested rules - look into following feature request https://github.com/stylelint/stylelint/issues/2090
            "block-closing-brace-newline-after": "always",
            "block-closing-brace-newline-before": "always-multi-line",
            //"block-closing-brace-space-before": "always", //Currently the impact to get rid of this is pretty large, but I can see where it's useful. TODO: need to look into more
            "block-opening-brace-newline-after": "always-multi-line",
            //"block-opening-brace-space-after": "always", //Currently the impact to get rid of this is pretty large, but I can see where it's useful. TODO: need to look into more
            "block-opening-brace-space-before": "always",
            //TODO: Selector
            "selector-attribute-brackets-space-inside": "never",
            "selector-attribute-operator-space-after": "never",
            "selector-attribute-operator-space-before": "never",
            "selector-combinator-space-after": "always",
            "selector-combinator-space-before": "always",
            "selector-descendant-combinator-no-non-space": true,
            "selector-max-compound-selectors": 3,
            //"selector-max-specificity": "0,2,1", // Set to what works for you - https://stylelint.io/user-guide/rules/selector-max-specificity/
            "selector-no-id": true,
            "selector-no-universal": true,
            "selector-no-vendor-prefix": true,
            "selector-pseudo-class-case": "lower",
            "selector-pseudo-class-no-unknown": true,
            "selector-pseudo-class-parentheses-space-inside": "never",
            "selector-pseudo-element-case": "lower",
            "selector-pseudo-element-no-unknown": true,
            "selector-type-case": "lower",
            "selector-type-no-unknown": true,
            "selector-max-empty-lines":0,
            //Selector List
            "selector-list-comma-newline-after": "always-multi-line",
            "selector-list-comma-newline-before": "never-multi-line",

            //"root-no-standard-properties": true, //Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place

            "rule-empty-line-before": ["always-multi-line", {
                "ignore": ["after-comment","inside-block"]
            }],

            //TODO: Root rule
            //TODO: Rule
            //TODO: Media Feature
            "media-feature-name-case": "lower",
            "media-feature-name-no-unknown": true,
            //TODO: Custom Media
            //Media query list
            "media-query-list-comma-newline-after": "always-multi-line",
            "media-query-list-comma-newline-before": "never-multi-line",
            "media-query-list-comma-space-after": "always-single-line",
            "media-query-list-comma-space-before": "never-single-line",

            //At-rule
            //"at-rule-empty-line-before": "always",
            "at-rule-name-case": "lower",
            //"at-rule-name-newline-after": "always", // Undefined Rule as of as of stylelint: "6.3.3"
            "at-rule-name-space-after": "always",
            "at-rule-no-unknown": true,
            "at-rule-no-vendor-prefix": true,
            "at-rule-semicolon-newline-after": "always",

            //stylelint-disable comment
            //"stylelint-disable-reason": "always-before", //Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place

            //Comments
            "comment-no-empty": true,

            //TODO: General / Sheet
            "max-empty-lines": 2,
            //"max-line-length": [110, {
            //    "ignore": ["comments"]
            // }], //This may or may not be an issue...
            //"no-browser-hacks": true, //Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place - See: stylelint-no-browser-hacks: https://stylelint.io/user-guide/plugins/
            //"no-descending-specificity": true, //Need to look into
            "no-duplicate-selectors": true,
            "no-eol-whitespace": true,
            "no-extra-semicolons": true,
            //"no-indistinguishable-colors": true, // Either Settings needs to be excluded or need to look into when this issue is taken care of: https://github.com/SlexAxton/css-colorguard/issues/49 - or attempt to disable around rules due to this issue - Deprecated
            "no-invalid-double-slash-comments": true,
            "no-missing-end-of-source-newline": true,
            "no-unknown-animations": true,
            //TODO: Setup supported browser list for not only this rule but theme building - https://stylelint.io/user-guide/rules/no-unsupported-browser-features/
            //"no-unsupported-browser-features": true
        }
    }

    var processors = [
      stylelint(stylelintConfig),
      // Pretty reporting config
      reporter({
          clearAllMessages: true,
          throwError: false
      })
    ];

    return gulp.src(
    // Stylesheet source:
    ['./Content/css/src/**/*.css',
    // Ignore linting vendor assets:
    '!./Content/css/src/vendor/**/*.css', '!./Content/css/src/utilities/debug.css', '!./Content/css/src/utilities/deprecated.css', '!./Content/css/src/base/00-Normalize.css']//, '!./Content/css/src/_settings/**/*.css']
  )
  .pipe(postcss(processors));
});
<commit_msg>Add todo for stylelint<commit_after>/*
This file in the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkId=518007
*/

var gulp = require('gulp');

/**
 * PostCSS - Tool for transforming styles with JS plugins
 */
var postcss = require("gulp-postcss");

/**
 * PostCSS Reporter - Log PostCSS messages in the console
 */
var reporter = require('postcss-reporter');

/**
 * Stylelint - Modern CSS linter
 */
var stylelint = require('stylelint');

gulp.task('default', function () {

    console.log('You are running Node Version: ' + process.version);

});

gulp.task('stylelint', function ()
{
    // TODO: Look also into stylelint-suitcss pluginpack - https://github.com/suitcss/stylelint-suitcss based off the recommendation of this rule and see if useful https://stylelint.io/user-guide/rules/selector-root-no-composition/
    // Stylelint config rules
    var stylelintConfig = {
        "rules": {
            //Color Rules via http://stylelint.io/user-guide/rules/
            //"color-hex-case": "lower", //TBD
            "color-hex-length": "short",
            "color-named": "never",
            "color-no-invalid-hex": true,

            //Font Family
            "font-family-name-quotes": "always-where-recommended",
            "font-family-no-duplicate-names": true,

            //Font weight
            "font-weight-notation": "named-where-possible", //TODO: Look into this more

            //Function
            "function-calc-no-unspaced-operator": true,
            "function-linear-gradient-no-nonstandard-direction": true,
            "function-max-empty-lines": 0,
            "function-name-case": "lower",
            "function-parentheses-newline-inside": "always-multi-line",
            "function-parentheses-space-inside": "never",
            "function-url-data-uris": "never",
            "function-url-no-scheme-relative": true,
            "function-url-quotes": ["always", {
                "except": ["empty"]
            }],
            "function-whitespace-after": "always",

            //Number
            "number-leading-zero":"always",
            "number-no-trailing-zeros": true,

            //String
            "string-no-newline": true,
            "string-quotes": "double",

            //Length
            "length-zero-no-unit": true,

            //Time
            "time-min-milliseconds": 100, //According to the notes to the original issue (https://github.com/stylelint/stylelint/issues/597#issuecomment-180872504) the human eye can't see transition times under 100ms. //TODO: More resources maybe recommended to confirm.

            //Unit
            "unit-no-unknown": true,
            "unit-case": "lower",

            //Value
            "value-no-vendor-prefix": true,
            "value-keyword-case": "lower",

            //Value list
            "value-list-comma-newline-after": "always-multi-line",
            "value-list-comma-newline-before": "always-multi-line",
            "value-list-comma-space-after": "always",
            "value-list-comma-space-before": "never",
            "value-list-max-empty-lines": 0,

            //Custom Properties
            //"custom-property-no-outside-root": true, //TODO: Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place

            //Shorthand property
            "shorthand-property-no-redundant-values": true,

            //Property
            "property-case": "lower",
            "property-no-unknown":true,
            //"property-no-vendor-prefix": true, //Currently the impact isn't huge but should be a todo in the future

            //Keyframe declaration
            "keyframe-declaration-no-important": true,

            //Declaration
            "declaration-bang-space-after": "never",
            "declaration-bang-space-before": "always",
            //"declaration-colon-space-after": "always-single-line", //Currently the impact isn't huge but should be a todo in the future
            "declaration-colon-space-before": "never",
            "declaration-empty-line-before": "never",
            //"declaration-no-important": true, //Only use when checking if !important is being used - note that !important is acceptable in utilities

            //Declaration Block
            "declaration-block-no-duplicate-properties": [true, {
                "ignore": ["consecutive-duplicates-with-different-values"]
            }],
            "declaration-block-no-redundant-longhand-properties": true,
            "declaration-block-no-shorthand-property-overrides": true,
            "declaration-block-semicolon-newline-after": "always-multi-line",
            "declaration-block-semicolon-newline-before": "never-multi-line",
            "declaration-block-semicolon-space-after": "always-single-line",
            "declaration-block-semicolon-space-before": "never",
            //"declaration-block-single-line-max-declarations": 1, //TODO: Make this change in the future - this would allow for reduced chance of merge conflicts and would be more 'truthful' when viewing a diff - see cssguidlin.es
            "declaration-block-trailing-semicolon": "always",
            //Block
            "block-closing-brace-empty-line-before": "never", //False Positives in nested rules - look into following feature request https://github.com/stylelint/stylelint/issues/2090
            "block-closing-brace-newline-after": "always",
            "block-closing-brace-newline-before": "always-multi-line",
            //"block-closing-brace-space-before": "always", //Currently the impact to get rid of this is pretty large, but I can see where it's useful. TODO: need to look into more
            "block-opening-brace-newline-after": "always-multi-line",
            //"block-opening-brace-space-after": "always", //Currently the impact to get rid of this is pretty large, but I can see where it's useful. TODO: need to look into more
            "block-opening-brace-space-before": "always",
            //TODO: Selector
            "selector-attribute-brackets-space-inside": "never",
            "selector-attribute-operator-space-after": "never",
            "selector-attribute-operator-space-before": "never",
            "selector-combinator-space-after": "always",
            "selector-combinator-space-before": "always",
            "selector-descendant-combinator-no-non-space": true,
            "selector-max-compound-selectors": 3,
            //"selector-max-specificity": "0,2,1", // Set to what works for you - https://stylelint.io/user-guide/rules/selector-max-specificity/
            "selector-no-id": true,
            "selector-no-universal": true,
            "selector-no-vendor-prefix": true,
            "selector-pseudo-class-case": "lower",
            "selector-pseudo-class-no-unknown": true,
            "selector-pseudo-class-parentheses-space-inside": "never",
            "selector-pseudo-element-case": "lower",
            "selector-pseudo-element-no-unknown": true,
            "selector-type-case": "lower",
            "selector-type-no-unknown": true,
            "selector-max-empty-lines":0,
            //Selector List
            "selector-list-comma-newline-after": "always-multi-line",
            "selector-list-comma-newline-before": "never-multi-line",

            //"root-no-standard-properties": true, //Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place

            "rule-empty-line-before": ["always-multi-line", {
                "ignore": ["after-comment","inside-block"]
            }],

            //TODO: Root rule
            //TODO: Rule
            //TODO: Media Feature
            "media-feature-name-case": "lower",
            "media-feature-name-no-unknown": true,
            //TODO: Custom Media
            //Media query list
            "media-query-list-comma-newline-after": "always-multi-line",
            "media-query-list-comma-newline-before": "never-multi-line",
            "media-query-list-comma-space-after": "always-single-line",
            "media-query-list-comma-space-before": "never-single-line",

            //At-rule
            //"at-rule-empty-line-before": "always",
            "at-rule-name-case": "lower",
            //"at-rule-name-newline-after": "always", // Undefined Rule as of as of stylelint: "6.3.3"
            "at-rule-name-space-after": "always",
            "at-rule-no-unknown": true,
            "at-rule-no-vendor-prefix": true,
            "at-rule-semicolon-newline-after": "always",

            //stylelint-disable comment
            //"stylelint-disable-reason": "always-before", //Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place

            //Comments
            "comment-no-empty": true,

            //TODO: General / Sheet
            "max-empty-lines": 2,
            //"max-line-length": [110, {
            //    "ignore": ["comments"]
            // }], //This may or may not be an issue...
            //"no-browser-hacks": true, //Deprecated as of 7.8.0 - a stylelint plugin may exist in it's place - See: stylelint-no-browser-hacks: https://stylelint.io/user-guide/plugins/
            //"no-descending-specificity": true, //Need to look into
            "no-duplicate-selectors": true,
            "no-eol-whitespace": true,
            "no-extra-semicolons": true,
            //"no-indistinguishable-colors": true, // Either Settings needs to be excluded or need to look into when this issue is taken care of: https://github.com/SlexAxton/css-colorguard/issues/49 - or attempt to disable around rules due to this issue - Deprecated
            "no-invalid-double-slash-comments": true,
            "no-missing-end-of-source-newline": true,
            "no-unknown-animations": true,
            //TODO: Setup supported browser list for not only this rule but theme building - https://stylelint.io/user-guide/rules/no-unsupported-browser-features/
            //"no-unsupported-browser-features": true
        }
    }

    var processors = [
      stylelint(stylelintConfig),
      // Pretty reporting config
      reporter({
          clearAllMessages: true,
          throwError: false
      })
    ];

    return gulp.src(
    // Stylesheet source:
    ['./Content/css/src/**/*.css',
    // Ignore linting vendor assets:
    '!./Content/css/src/vendor/**/*.css', '!./Content/css/src/utilities/debug.css', '!./Content/css/src/utilities/deprecated.css', '!./Content/css/src/base/00-Normalize.css']//, '!./Content/css/src/_settings/**/*.css']
  )
  .pipe(postcss(processors));
});
<|endoftext|>


<commit_before>/**
 * @class Oskari.mapframework.bundle.backendstatus.BackendStatusBundleInstance
 *
 * Utility bundle to manage updating backend status information.
 * Updates information only when LayerSelector2 is being opened.
 *
 */
Oskari.clazz.define("Oskari.mapframework.bundle.backendstatus.BackendStatusBundleInstance",

/**
 * @method create called automatically on construction
 * @static
 */
function() {
	this._localization = null;
	this._sandbox = null;
	this._started = false;
	this._pendingAjaxQuery = {
		busy : false,
		jqhr : null,
		timestamp : null
	};

	this.timeInterval = this.ajaxSettings.defaultTimeThreshold;
	this.backendStatus = {};
	this.backendExtendedStatus = {};

	/* IE debug ... */
	this.gotStartupEvent = false;
	this.gotStartupTimeoutEvent = false;
	this.gotStartupProcessCall = false;

	/* maplayerservice */
	this._mapLayerService = null;

}, {
	ajaxSettings : {
		defaultTimeThreshold : 15000
	},
	/**
	 * @method getLocalization
	 * Returns JSON presentation of bundles localization data for
	 * current language.
	 * If key-parameter is not given, returns the whole localization
	 * data.
	 *
	 * @param {String} key (optional) if given, returns the value for
	 *         key
	 * @return {String/Object} returns single localization string or
	 * 		JSON object for complete data depending on localization
	 * 		structure and if parameter key is given
	 */
	getLocalization : function(key) {
		if(!this._localization) {
			this._localization = Oskari.getLocalization(this.getName());
		}
		if(key) {
			return this._localization[key];
		}
		return this._localization;
	},
	/**
	 * @static
	 * @property __name
	 */
	__name : 'BackendStatus',
	/**
	 * @method getName
	 * @return {String} the name for the component
	 */
	"getName" : function() {
		return this.__name;
	},
	/**
	 * @method setSandbox
	 * @param {Oskari.mapframework.sandbox.Sandbox} sandbox
	 * Sets the sandbox reference to this component
	 */
	setSandbox : function(sandbox) {
		this._sandbox = sandbox;
	},
	/**
	 * @method getSandbox
	 * @return {Oskari.mapframework.sandbox.Sandbox}
	 */
	getSandbox : function() {
		return this._sandbox;
	},
	getAjaxUrl : function(key, allKnown) {
		var ajaxUrl = this.getSandbox().getAjaxUrl();
		var url = null;

		if(allKnown) {
			url = ajaxUrl + 'action_route=GetBackendStatus&Subset=AllKnown';
		} else {
			url = ajaxUrl + 'action_route=GetBackendStatus&Subset=Alert';
		}

		return url;
		/*return 'GetBackendStatus.json';*/
	},
	/**
	 * @method start
	 * implements BundleInstance protocol start method
	 */
	"start" : function() {
		var me = this;

		if(me._started) {
			return;
		}

		me._started = true;

		var sandbox = Oskari.$("sandbox");
		me._sandbox = sandbox;

		me._mapLayerService = sandbox.getService("Oskari.mapframework.service.MapLayerService");

		sandbox.register(me);
		for(p in me.eventHandlers) {
			sandbox.registerForEventByName(me, p);
		}

		/* we may have missed the maplayerevent */
		if(me._mapLayerService.isAllLayersLoaded() && !me.gotStartupProcessCall) {
			me.updateBackendStatus(true);
		}
	},
	/**
	 * @method init
	 * implements Module protocol init method - does nothing atm
	 */
	"init" : function() {
		return null;
	},
	/**
	 * @method update
	 * implements BundleInstance protocol update method - does nothing atm
	 */
	"update" : function() {

	},
	/**
	 * @method onEvent
	 * @param {Oskari.mapframework.event.Event} event a Oskari event object
	 * Event is handled forwarded to correct #eventHandlers if found or discarded if not.
	 */
	onEvent : function(event) {

		var handler = this.eventHandlers[event.getName()];
		if(!handler)
			return;

		return handler.apply(this, [event]);

	},
	/**
	 * @property extensionsByName
	 * @static
	 * extensions that trigger update
	 *
	 */
	extensionsByName : {
		'LayerSelector' : true
	},

	/**
	 * @property extensionStatesByName
	 * @static
	 * extension states that trigger update
	 *
	 */
	extensionStatesByName : {
		'attach' : true,
		'detach' : true
	},

	/**
	 * @property {Object} eventHandlers
	 * @static
	 */
	eventHandlers : {

		/**
		 * @method ExtensionUpdatedEvent
		 */
		'userinterface.ExtensionUpdatedEvent' : function(event) {

			var extension = event.getExtension();
			if(!extension) {
				return;
			}
			var extensionName = extension.getName();
			if(!extensionName) {
				return;
			}
			if(!this.extensionsByName[extensionName]) {
				return;
			}
			var extensionViewState = event.getViewState();

			if(!this.extensionStatesByName[extensionViewState]) {
				return;
			}

			this.updateBackendStatus();
		},
		/**
		 * @method AfterShowMapLayerInfoEvent
		 */

		'AfterShowMapLayerInfoEvent' : function(event) {

			var mapLayer = event.getMapLayer();
			var mapLayerId = mapLayer.getId();
			var mapLayerBackendStatus = mapLayer.getBackendStatus();
			/*console.log("ABOUT to show information for "+mapLayerId,mapLayer,mapLayerBackendStatus);*/

			/*if(!mapLayerBackendStatus) {
			 this.showFeedbackDialog('missing_backendstatus_status');
			 return;
			 }*/

			var backendExtendedStatusForLayer = this.backendExtendedStatus[mapLayerId];

			/*console.log("MIGHT show information for "+mapLayerId,mapLayer,backendExtendedStatusForLayer);*/

			if(!backendExtendedStatusForLayer) {
				this.showFeedbackDialog('missing_backendstatus_information');
				return;
			}

			var infoUrl = backendExtendedStatusForLayer.infourl;
			if(!infoUrl) {
				this.showFeedbackDialog('missing_backendstatus_infourl');
				return;
			}

			/*console.log("WOULD show information for "+mapLayerId,mapLayer,infoUrl);*/

			this.openURLinWindow(infoUrl);
		},
		'MapLayerEvent' : function(event) {
			if(!(event.getLayerId() == null && event.getOperation() == 'add')) {
				return;
			}

			/* this is where we get after layers.json has been read from server to maplayer service */
			/* we do not know the order of bundles and modules notifications though */
			var me = this;
			me.gotStartupEvent = true;
			window.setTimeout(function() {
				me.gotStartupTimeoutEvent = true;
				me.updateBackendStatus(true);
			}, 15);
		}
	},

	/**
	 *
	 */
	showFeedbackDialog : function(context) {
		var feedBackTextx = this.getLocalization('feedback')[context];

		var dialog = Oskari.clazz.create('Oskari.userinterface.component.Popup');
		dialog.show(feedBackTextx['title'], feedBackTextx['message']);
		dialog.fadeout();

	},
	openURLinWindow : function(infoUrl) {
		var wopParm = "location=1," + "status=1," + "scrollbars=1," + "width=850," + "height=1200";
		var link = infoUrl;
		window.open(link, "BackendStatus", wopParm);
	},
	/**
	 * @method stop
	 * implements BundleInstance protocol stop method
	 */
	"stop" : function() {

		var me = this;
		var sandbox = me._sandbox;

		me._cancelAjaxRequest();

		for(p in me.eventHandlers) {
			sandbox.unregisterFromEventByName(this, p);
		}

		sandbox.unregister(this);
		this._started = false;
		this._sandbox = null;
	},
	/**
	 * @method getTitle
	 * @return {String} localized text for the title of the component
	 */
	getTitle : function() {
		return 'Backend Status';
	},
	/**
	 * @method getDescription
	 * @return {String} localized text for the description of the component
	 */
	getDescription : function() {
		return 'Backend Status';
	},
	_cancelAjaxRequest : function() {
		var me = this;
		if(!me._pendingAjaxQuery.busy) {
			return;
		}
		var jqhr = me._pendingAjaxQuery.jqhr;
		me._pendingAjaxQuery.jqhr = null;
		if(!jqhr) {
			return;
		}
		this._sandbox.printDebug("[BackendStatus] Abort jqhr ajax request");
		jqhr.abort();
		jqhr = null;
		me._pendingAjaxQuery.busy = false;
	},
	_startAjaxRequest : function(dteMs) {
		var me = this;
		me._pendingAjaxQuery.busy = true;
		me._pendingAjaxQuery.timestamp = dteMs;

	},
	_finishAjaxRequest : function() {
		var me = this;
		me._pendingAjaxQuery.busy = false;
		me._pendingAjaxQuery.jqhr = null;
		this._sandbox.printDebug("[BackendStatus] finished jqhr ajax request");
	},
	_notifyAjaxFailure : function() {
		var me = this;
		me._sandbox.printDebug("[BackendStatus] BackendStatus AJAX failed");
		me._processResponse({
			backendstatus : []
		});
	},
	_isAjaxRequestBusy : function() {
		var me = this;
		return me._pendingAjaxQuery.busy;
	},
	updateBackendStatus : function(allKnown) {
		var me = this;
		me.gotStartupProcessCall = me.gotStartupProcessCall || allKnown;
		var sandbox = me._sandbox;
		if(!allKnown && me._pendingAjaxQuery.busy) {
			sandbox.printDebug("[BackendStatus] updateBackendStatus NOT SENT previous query is busy");
			return;
		}
		var dte = new Date();
		var dteMs = dte.getTime();

		if(!allKnown && me._pendingAjaxQuery.timestamp && dteMs - me._pendingAjaxQuery.timestamp < me.timeInterval) {
			sandbox.printDebug("[BackendStatus] updateBackendStatus NOT SENT (time difference < " + me.timeInterval + "ms)");
			return;
		}

		me._cancelAjaxRequest();
		me._startAjaxRequest(dteMs);

		var ajaxUrl = me.getAjaxUrl(null, allKnown);

		jQuery.ajax({
			beforeSend : function(x) {
				me._pendingAjaxQuery.jqhr = x;
				if(x && x.overrideMimeType) {
					x.overrideMimeType("application/j-son;charset=UTF-8");
				}
			},
			success : function(resp) {
				me._finishAjaxRequest();
				me._processResponse(resp, allKnown);
			},
			error : function() {
				me._finishAjaxRequest();
				me._notifyAjaxFailure();
			},
			always : function() {
				me._finishAjaxRequest();
			},
			complete : function() {
				me._finishAjaxRequest();
			},
			data : {
			},
			type : 'POST',
			dataType : 'json',
			url : ajaxUrl
			/*url : 'GetBackendStatus.json'*/
		});
	},
	_processResponse : function(resp, allKnown) {
		var me = this;
		var sandbox = this._sandbox;
		var evtBuilder = sandbox.getEventBuilder('MapLayerEvent');

		var backendStatusArr = resp.backendstatus;
		if(!backendStatusArr || backendStatusArr.length == undefined) {
			sandbox.printDebug("[BackendStatus] backendStatus NO data");
			return;
		}

		var changeNotifications = {};

		/* let's update AllKnown */
		var extendedStatuses = allKnown ? {} : this.backendExtendedStatus;

		for(var n = 0; n < backendStatusArr.length; n++) {
			var data = backendStatusArr[n];
			var layerId = data.maplayer_id;
			if(!this.backendStatus[layerId]) {
				changeNotifications[layerId] = {
					status : data.status,
					changed : true
				};
				extendedStatuses[layerId] = data;
				/*sandbox.printDebug("[BackendStatus] "+layerId+" new alert");*/
			} else if(this.backendStatus[layerId].status != data.status) {
				changeNotifications[layerId] = {
					status : data.status,
					changed : true
				};
				extendedStatuses[layerId] = data;
				/*sandbox.printDebug("[BackendStatus] "+layerId+" changed alert");*/
			} else {
				changeNotifications[layerId] = {
					status : data.status,
					changed : false
				};
				extendedStatuses[layerId] = data;
			}
		}

		for(p in this.backendStatus ) {
			if(!changeNotifications[p] && this.backendStatus[p].status != null) {
				changeNotifications[p] = {
					status : null,
					changed : true
				};
				/*sandbox.printDebug("[BackendStatus] "+p+" alert closed");*/
			}
		}

		this.backendExtendedStatus = extendedStatuses;

		var maplayers = {};

		for(p in changeNotifications ) {
			this.backendStatus[p] = changeNotifications[p];

			var maplayer = sandbox.findMapLayerFromAllAvailable(p);
			if(!maplayer) {
				continue;
			}
			maplayer.setBackendStatus(this.backendStatus[p].status);

			/* forcing DOWN to be notified - we do not know if layerselector2 has shown the msg or not...*/
			if(changeNotifications[p].changed || "DOWN" == maplayer.getBackendStatus()) {
				maplayers[p] = maplayer;
			}
		}

		if(allKnown) {
			me._pendingAjaxQuery.timestamp = null;
			me.backendStatus = {};
		} else {
			for(p in maplayers ) {
				var evt = evtBuilder(p, 'update');
				sandbox.notifyAll(evt);
			}
		}
	}
}, {
	/**
	 * @property {String[]} protocol
	 * @static
	 */
	"protocol" : ["Oskari.bundle.BundleInstance", 'Oskari.mapframework.module.Module']
});
<commit_msg>[PORTTISK-1316] handle empty response from server<commit_after>/**
 * @class Oskari.mapframework.bundle.backendstatus.BackendStatusBundleInstance
 *
 * Utility bundle to manage updating backend status information.
 * Updates information only when LayerSelector2 is being opened.
 *
 */
Oskari.clazz.define("Oskari.mapframework.bundle.backendstatus.BackendStatusBundleInstance",

/**
 * @method create called automatically on construction
 * @static
 */
function() {
	this._localization = null;
	this._sandbox = null;
	this._started = false;
	this._pendingAjaxQuery = {
		busy : false,
		jqhr : null,
		timestamp : null
	};

	this.timeInterval = this.ajaxSettings.defaultTimeThreshold;
	this.backendStatus = {};
	this.backendExtendedStatus = {};

	/* IE debug ... */
	this.gotStartupEvent = false;
	this.gotStartupTimeoutEvent = false;
	this.gotStartupProcessCall = false;

	/* maplayerservice */
	this._mapLayerService = null;

}, {
	ajaxSettings : {
		defaultTimeThreshold : 15000
	},
	/**
	 * @method getLocalization
	 * Returns JSON presentation of bundles localization data for
	 * current language.
	 * If key-parameter is not given, returns the whole localization
	 * data.
	 *
	 * @param {String} key (optional) if given, returns the value for
	 *         key
	 * @return {String/Object} returns single localization string or
	 * 		JSON object for complete data depending on localization
	 * 		structure and if parameter key is given
	 */
	getLocalization : function(key) {
		if(!this._localization) {
			this._localization = Oskari.getLocalization(this.getName());
		}
		if(key) {
			return this._localization[key];
		}
		return this._localization;
	},
	/**
	 * @static
	 * @property __name
	 */
	__name : 'BackendStatus',
	/**
	 * @method getName
	 * @return {String} the name for the component
	 */
	"getName" : function() {
		return this.__name;
	},
	/**
	 * @method setSandbox
	 * @param {Oskari.mapframework.sandbox.Sandbox} sandbox
	 * Sets the sandbox reference to this component
	 */
	setSandbox : function(sandbox) {
		this._sandbox = sandbox;
	},
	/**
	 * @method getSandbox
	 * @return {Oskari.mapframework.sandbox.Sandbox}
	 */
	getSandbox : function() {
		return this._sandbox;
	},
	getAjaxUrl : function(key, allKnown) {
		var ajaxUrl = this.getSandbox().getAjaxUrl();
		var url = null;

		if(allKnown) {
			url = ajaxUrl + 'action_route=GetBackendStatus&Subset=AllKnown';
		} else {
			url = ajaxUrl + 'action_route=GetBackendStatus&Subset=Alert';
		}

		return url;
		/*return 'GetBackendStatus.json';*/
	},
	/**
	 * @method start
	 * implements BundleInstance protocol start method
	 */
	"start" : function() {
		var me = this;

		if(me._started) {
			return;
		}

		me._started = true;

		var sandbox = Oskari.$("sandbox");
		me._sandbox = sandbox;

		me._mapLayerService = sandbox.getService("Oskari.mapframework.service.MapLayerService");

		sandbox.register(me);
		for(p in me.eventHandlers) {
			sandbox.registerForEventByName(me, p);
		}

		/* we may have missed the maplayerevent */
		if(me._mapLayerService.isAllLayersLoaded() && !me.gotStartupProcessCall) {
			me.updateBackendStatus(true);
		}
	},
	/**
	 * @method init
	 * implements Module protocol init method - does nothing atm
	 */
	"init" : function() {
		return null;
	},
	/**
	 * @method update
	 * implements BundleInstance protocol update method - does nothing atm
	 */
	"update" : function() {

	},
	/**
	 * @method onEvent
	 * @param {Oskari.mapframework.event.Event} event a Oskari event object
	 * Event is handled forwarded to correct #eventHandlers if found or discarded if not.
	 */
	onEvent : function(event) {

		var handler = this.eventHandlers[event.getName()];
		if(!handler)
			return;

		return handler.apply(this, [event]);

	},
	/**
	 * @property extensionsByName
	 * @static
	 * extensions that trigger update
	 *
	 */
	extensionsByName : {
		'LayerSelector' : true
	},

	/**
	 * @property extensionStatesByName
	 * @static
	 * extension states that trigger update
	 *
	 */
	extensionStatesByName : {
		'attach' : true,
		'detach' : true
	},

	/**
	 * @property {Object} eventHandlers
	 * @static
	 */
	eventHandlers : {

		/**
		 * @method ExtensionUpdatedEvent
		 */
		'userinterface.ExtensionUpdatedEvent' : function(event) {

			var extension = event.getExtension();
			if(!extension) {
				return;
			}
			var extensionName = extension.getName();
			if(!extensionName) {
				return;
			}
			if(!this.extensionsByName[extensionName]) {
				return;
			}
			var extensionViewState = event.getViewState();

			if(!this.extensionStatesByName[extensionViewState]) {
				return;
			}

			this.updateBackendStatus();
		},
		/**
		 * @method AfterShowMapLayerInfoEvent
		 */

		'AfterShowMapLayerInfoEvent' : function(event) {

			var mapLayer = event.getMapLayer();
			var mapLayerId = mapLayer.getId();
			var mapLayerBackendStatus = mapLayer.getBackendStatus();
			/*console.log("ABOUT to show information for "+mapLayerId,mapLayer,mapLayerBackendStatus);*/

			/*if(!mapLayerBackendStatus) {
			 this.showFeedbackDialog('missing_backendstatus_status');
			 return;
			 }*/

			var backendExtendedStatusForLayer = this.backendExtendedStatus[mapLayerId];

			/*console.log("MIGHT show information for "+mapLayerId,mapLayer,backendExtendedStatusForLayer);*/

			if(!backendExtendedStatusForLayer) {
				this.showFeedbackDialog('missing_backendstatus_information');
				return;
			}

			var infoUrl = backendExtendedStatusForLayer.infourl;
			if(!infoUrl) {
				this.showFeedbackDialog('missing_backendstatus_infourl');
				return;
			}

			/*console.log("WOULD show information for "+mapLayerId,mapLayer,infoUrl);*/

			this.openURLinWindow(infoUrl);
		},
		'MapLayerEvent' : function(event) {
			if(!(event.getLayerId() == null && event.getOperation() == 'add')) {
				return;
			}

			/* this is where we get after layers.json has been read from server to maplayer service */
			/* we do not know the order of bundles and modules notifications though */
			var me = this;
			me.gotStartupEvent = true;
			window.setTimeout(function() {
				me.gotStartupTimeoutEvent = true;
				me.updateBackendStatus(true);
			}, 15);
		}
	},

	/**
	 *
	 */
	showFeedbackDialog : function(context) {
		var feedBackTextx = this.getLocalization('feedback')[context];

		var dialog = Oskari.clazz.create('Oskari.userinterface.component.Popup');
		dialog.show(feedBackTextx['title'], feedBackTextx['message']);
		dialog.fadeout();

	},
	openURLinWindow : function(infoUrl) {
		var wopParm = "location=1," + "status=1," + "scrollbars=1," + "width=850," + "height=1200";
		var link = infoUrl;
		window.open(link, "BackendStatus", wopParm);
	},
	/**
	 * @method stop
	 * implements BundleInstance protocol stop method
	 */
	"stop" : function() {

		var me = this;
		var sandbox = me._sandbox;

		me._cancelAjaxRequest();

		for(p in me.eventHandlers) {
			sandbox.unregisterFromEventByName(this, p);
		}

		sandbox.unregister(this);
		this._started = false;
		this._sandbox = null;
	},
	/**
	 * @method getTitle
	 * @return {String} localized text for the title of the component
	 */
	getTitle : function() {
		return 'Backend Status';
	},
	/**
	 * @method getDescription
	 * @return {String} localized text for the description of the component
	 */
	getDescription : function() {
		return 'Backend Status';
	},
	_cancelAjaxRequest : function() {
		var me = this;
		if(!me._pendingAjaxQuery.busy) {
			return;
		}
		var jqhr = me._pendingAjaxQuery.jqhr;
		me._pendingAjaxQuery.jqhr = null;
		if(!jqhr) {
			return;
		}
		this._sandbox.printDebug("[BackendStatus] Abort jqhr ajax request");
		jqhr.abort();
		jqhr = null;
		me._pendingAjaxQuery.busy = false;
	},
	_startAjaxRequest : function(dteMs) {
		var me = this;
		me._pendingAjaxQuery.busy = true;
		me._pendingAjaxQuery.timestamp = dteMs;

	},
	_finishAjaxRequest : function() {
		var me = this;
		me._pendingAjaxQuery.busy = false;
		me._pendingAjaxQuery.jqhr = null;
		this._sandbox.printDebug("[BackendStatus] finished jqhr ajax request");
	},
	_notifyAjaxFailure : function() {
		var me = this;
		me._sandbox.printDebug("[BackendStatus] BackendStatus AJAX failed");
		me._processResponse({
			backendstatus : []
		});
	},
	_isAjaxRequestBusy : function() {
		var me = this;
		return me._pendingAjaxQuery.busy;
	},
	updateBackendStatus : function(allKnown) {
		var me = this;
		me.gotStartupProcessCall = me.gotStartupProcessCall || allKnown;
		var sandbox = me._sandbox;
		if(!allKnown && me._pendingAjaxQuery.busy) {
			sandbox.printDebug("[BackendStatus] updateBackendStatus NOT SENT previous query is busy");
			return;
		}
		var dte = new Date();
		var dteMs = dte.getTime();

		if(!allKnown && me._pendingAjaxQuery.timestamp && dteMs - me._pendingAjaxQuery.timestamp < me.timeInterval) {
			sandbox.printDebug("[BackendStatus] updateBackendStatus NOT SENT (time difference < " + me.timeInterval + "ms)");
			return;
		}

		me._cancelAjaxRequest();
		me._startAjaxRequest(dteMs);

		var ajaxUrl = me.getAjaxUrl(null, allKnown);

		jQuery.ajax({
			beforeSend : function(x) {
				me._pendingAjaxQuery.jqhr = x;
				if(x && x.overrideMimeType) {
					x.overrideMimeType("application/j-son;charset=UTF-8");
				}
			},
			success : function(resp) {
				me._finishAjaxRequest();
				me._processResponse(resp, allKnown);
			},
			error : function() {
				me._finishAjaxRequest();
				me._notifyAjaxFailure();
			},
			always : function() {
				me._finishAjaxRequest();
			},
			complete : function() {
				me._finishAjaxRequest();
			},
			data : {
			},
			type : 'POST',
			dataType : 'json',
			url : ajaxUrl
			/*url : 'GetBackendStatus.json'*/
		});
	},
	_processResponse : function(resp, allKnown) {
		var me = this;
		var sandbox = this._sandbox;
		if(!resp) {
            sandbox.printDebug("[BackendStatus] empty data from server");
            return;
		}

		var backendStatusArr = resp.backendstatus;
		if(!backendStatusArr || backendStatusArr.length == undefined) {
			sandbox.printDebug("[BackendStatus] backendStatus NO data");
			return;
		}

        var evtBuilder = sandbox.getEventBuilder('MapLayerEvent');
		var changeNotifications = {};

		/* let's update AllKnown */
		var extendedStatuses = allKnown ? {} : this.backendExtendedStatus;

		for(var n = 0; n < backendStatusArr.length; n++) {
			var data = backendStatusArr[n];
			var layerId = data.maplayer_id;
			if(!this.backendStatus[layerId]) {
				changeNotifications[layerId] = {
					status : data.status,
					changed : true
				};
				extendedStatuses[layerId] = data;
				/*sandbox.printDebug("[BackendStatus] "+layerId+" new alert");*/
			} else if(this.backendStatus[layerId].status != data.status) {
				changeNotifications[layerId] = {
					status : data.status,
					changed : true
				};
				extendedStatuses[layerId] = data;
				/*sandbox.printDebug("[BackendStatus] "+layerId+" changed alert");*/
			} else {
				changeNotifications[layerId] = {
					status : data.status,
					changed : false
				};
				extendedStatuses[layerId] = data;
			}
		}

		for(p in this.backendStatus ) {
			if(!changeNotifications[p] && this.backendStatus[p].status != null) {
				changeNotifications[p] = {
					status : null,
					changed : true
				};
				/*sandbox.printDebug("[BackendStatus] "+p+" alert closed");*/
			}
		}

		this.backendExtendedStatus = extendedStatuses;

		var maplayers = {};

		for(p in changeNotifications ) {
			this.backendStatus[p] = changeNotifications[p];

			var maplayer = sandbox.findMapLayerFromAllAvailable(p);
			if(!maplayer) {
				continue;
			}
			maplayer.setBackendStatus(this.backendStatus[p].status);

			/* forcing DOWN to be notified - we do not know if layerselector2 has shown the msg or not...*/
			if(changeNotifications[p].changed || "DOWN" == maplayer.getBackendStatus()) {
				maplayers[p] = maplayer;
			}
		}

		if(allKnown) {
			me._pendingAjaxQuery.timestamp = null;
			me.backendStatus = {};
		} else {
			for(p in maplayers ) {
				var evt = evtBuilder(p, 'update');
				sandbox.notifyAll(evt);
			}
		}
	}
}, {
	/**
	 * @property {String[]} protocol
	 * @static
	 */
	"protocol" : ["Oskari.bundle.BundleInstance", 'Oskari.mapframework.module.Module']
});
<|endoftext|>


<commit_before>import React from 'react';
import MarketingFeatureItem from './MarketingFeatureItem';

const localMessages = {

  globalCollectionsTitle: { id: 'marketing.globalCollections.title', defaultMessage: 'Search with Global Coverage' },
  globalCollectionsDescription: { id: 'marketing.globalCollections.description', defaultMessage: '
Media Cloud supports searching individual media sources, or across media sources grouped into collections. We have collections covering top media sources in over 100 countries, and add more all the time. Media Cloud also lets you search by language, including various levels of support for English, Spanish, Arabic, Japanese, and other languages. Our Source Manager tool gives you an overview of what content we have in our database.
' },

  attentionTitle: { id: 'marketing.attention.title', defaultMessage: 'Track Attention Over Time' },
  attentionDescription: { id: 'marketing.attention.description', defaultMessage: '
Media Cloud shows you attention to an issue over time to help you understand how much it is covered. Our data can reveal key events that cause spikes in coverage and conversation. Plateaus can reveal stable, "normal", levels of attention to compare against. You can download all our charts and the underlying aggregated data.
' },

  influenceTitle: { id: 'marketing.influence.title', defaultMessage: 'Find Influential Sources and Stories' },
  influenceDescription: { id: 'marketing.influence.description', defaultMessage: '
Ranking media by social shares and cross-linking patterns can reveal influential sources and stories that drive narratives about an issue. Media Cloud can gather bit.ly clicks, Facebook shares, media inlinks and outlinks, and Twitter shares. You can can also simply download lists of stories with URLs to drive your own custom analysis. Due to copyright restrictions Media Cloud can\'t share the full content of stories in our database.
' },

  framingTitle: { id: 'marketing.framing.title', defaultMessage: 'Identify How an Issue is Talked About' },
  framingDescription: { id: 'marketing.framing.description', defaultMessage: '
Examine the words used to talk about an issue in order to pinpoint differing media narratives. Media Cloud can show word clouds, word counts, bri-grams, word trees, word embeddings, and more to help you narrow in on the language used.
' },

  mapTitle: { id: 'marketing.map.title', defaultMessage: 'Map Geographic Coverage' },
  mapDescription: { id: 'marketing.map.description', defaultMessage: '
We geocode all our stories to identify the countries and states they are about. Media Cloud\'s maps can help you narrow in on the places that talk about your issue the most, or identify issue "deserts" where it isn\'t talked about at all.
' },

};

const MarketingFeatureList = () => (

    <MarketingFeatureItem
      titleMsg={localMessages.globalCollectionsTitle}
      contentMsg={localMessages.globalCollectionsDescription}
      imageName={'global-collections-2x.png'}
    />
    <MarketingFeatureItem
      titleMsg={localMessages.attentionTitle}
      contentMsg={localMessages.attentionDescription}
      imageName={'attention-2x.png'}
      imageOnLeft
    />
    <MarketingFeatureItem
      titleMsg={localMessages.influenceTitle}
      contentMsg={localMessages.influenceDescription}
      imageName={'influence-2x.png'}
    />
    <MarketingFeatureItem
      titleMsg={localMessages.framingTitle}
      contentMsg={localMessages.framingDescription}
      imageName={'framing-2x.png'}
      imageOnLeft
    />
    <MarketingFeatureItem
      titleMsg={localMessages.mapTitle}
      contentMsg={localMessages.mapDescription}
      imageName={'mapping-2x.png'}
    />

);

export default
  MarketingFeatureList;
<commit_msg>Remove influence marketing copy #992<commit_after>import React from 'react';
import MarketingFeatureItem from './MarketingFeatureItem';

const localMessages = {

  globalCollectionsTitle: { id: 'marketing.globalCollections.title', defaultMessage: 'Search with Global Coverage' },
  globalCollectionsDescription: { id: 'marketing.globalCollections.description', defaultMessage: '
Media Cloud supports searching individual media sources, or across media sources grouped into collections. We have collections covering top media sources in over 100 countries, and add more all the time. Media Cloud also lets you search by language, including various levels of support for English, Spanish, Arabic, Japanese, and other languages. Our Source Manager tool gives you an overview of what content we have in our database.
' },

  attentionTitle: { id: 'marketing.attention.title', defaultMessage: 'Track Attention Over Time' },
  attentionDescription: { id: 'marketing.attention.description', defaultMessage: '
Media Cloud shows you attention to an issue over time to help you understand how much it is covered. Our data can reveal key events that cause spikes in coverage and conversation. Plateaus can reveal stable, "normal", levels of attention to compare against. You can download all our charts and the underlying aggregated data.
' },
/*
  influenceTitle: { id: 'marketing.influence.title', defaultMessage: 'Find Influential Sources and Stories' },
  influenceDescription: { id: 'marketing.influence.description', defaultMessage: '
Ranking media by social shares and cross-linking patterns can reveal influential sources and stories that drive narratives about an issue. Media Cloud can gather bit.ly clicks, Facebook shares, media inlinks and outlinks, and Twitter shares. You can can also simply download lists of stories with URLs to drive your own custom analysis. Due to copyright restrictions Media Cloud can\'t share the full content of stories in our database.
' },
*/
  framingTitle: { id: 'marketing.framing.title', defaultMessage: 'Identify How an Issue is Talked About' },
  framingDescription: { id: 'marketing.framing.description', defaultMessage: '
Examine the words used to talk about an issue in order to pinpoint differing media narratives. Media Cloud can show word clouds, word counts, bri-grams, word trees, word embeddings, and more to help you narrow in on the language used.
' },

  mapTitle: { id: 'marketing.map.title', defaultMessage: 'Map Geographic Coverage' },
  mapDescription: { id: 'marketing.map.description', defaultMessage: '
We geocode all our stories to identify the countries and states they are about. Media Cloud\'s maps can help you narrow in on the places that talk about your issue the most, or identify issue "deserts" where it isn\'t talked about at all.
' },

};

const MarketingFeatureList = () => (

    <MarketingFeatureItem
      titleMsg={localMessages.globalCollectionsTitle}
      contentMsg={localMessages.globalCollectionsDescription}
      imageName={'global-collections-2x.png'}
    />
    <MarketingFeatureItem
      titleMsg={localMessages.attentionTitle}
      contentMsg={localMessages.attentionDescription}
      imageName={'attention-2x.png'}
      imageOnLeft
    />
    {/*
        <MarketingFeatureItem
          titleMsg={localMessages.influenceTitle}
          contentMsg={localMessages.influenceDescription}
          imageName={'influence-2x.png'}
        />
    */}
    <MarketingFeatureItem
      titleMsg={localMessages.framingTitle}
      contentMsg={localMessages.framingDescription}
      imageName={'framing-2x.png'}
      imageOnLeft
    />
    <MarketingFeatureItem
      titleMsg={localMessages.mapTitle}
      contentMsg={localMessages.mapDescription}
      imageName={'mapping-2x.png'}
    />

);

export default
  MarketingFeatureList;
<|endoftext|>


<commit_before>import React from 'react';
import { storiesOf } from '@storybook/react';

import {
  withKnobs,
  withKnobsOptions,
  text,
  number,
  boolean,
  color,
  select,
  array,
  date,
  button,
  object,
  files,
} from '@storybook/addon-knobs';

class AsyncItemLoader extends React.Component {
  constructor() {
    super();
    this.state = { items: [] };
  }

  handleLoadItems() {
    this.setState({
      isLoading: true,
      items: [],
    });
    setTimeout(() => this.setState({ items: ['pencil', 'pen', 'eraser'], isLoading: false }), 1500);
  }

  render() {
    // this existing example seems like a react anti-pattern?
    // perhaps for testing purposes only?
    button('Load the items', () => this.handleLoadItems());

    const { isLoading, items } = this.state;

    if (isLoading) {
      return
Loading data
;
    } else if (!items.length) {
      return
No items loaded
;
    }
    return
{items.map(i =>
{i}
)}
;
  }
}

storiesOf('Addons|Knobs.withKnobs', module)
  .addDecorator(withKnobs)
  .add('tweaks static values', () => {
    const name = text('Name', 'Storyteller');
    const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 });
    const fruits = {
      Apple: 'apple',
      Banana: 'banana',
      Cherry: 'cherry',
    };
    const fruit = select('Fruit', fruits, 'apple');
    const dollars = number('Dollars', 12.5, { min: 0, max: 100, step: 0.01 });
    const years = number('Years in NY', 9);

    const backgroundColor = color('background', '#ffff00');
    const items = array('Items', ['Laptop', 'Book', 'Whiskey']);
    const otherStyles = object('Styles', {
      border: '3px solid #ff00ff',
      padding: '10px',
    });
    const nice = boolean('Nice', true);
    const images = files('Happy Picture', 'image/*', [
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfiARwMCyEWcOFPAAAAP0lEQVQoz8WQMQoAIAwDL/7/z3GwghSp4KDZyiUpBMCYUgd8rehtH16/l3XewgU2KAzapjXBbNFaPS6lDMlKB6OiDv3iAH1OAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTAxLTI4VDEyOjExOjMzLTA3OjAwlAHQBgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wMS0yOFQxMjoxMTozMy0wNzowMOVcaLoAAAAASUVORK5CYII=',
    ]);

    // NOTE: the default value must not change - e.g., do not do date('Label', new Date()) or date('Label')
    const defaultBirthday = new Date('Jan 20 2017 GMT+0');
    const birthday = date('Birthday', defaultBirthday);

    const intro = `My name is ${name}, I'm ${age} years old, and my favorite fruit is ${fruit}.`;
    const style = { backgroundColor, ...otherStyles };
    const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!';
    const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' };

    return (

{intro}

My birthday is: {new Date(birthday).toLocaleDateString('en-US', dateOptions)}

I live in NY for {years} years.

My wallet contains: ${dollars.toFixed(2)}

In my backpack, I have:

{items.map(item =>
{item}
)}

{salutation}

          When I am happy I look like this:

PS. My shirt pocket contains:

    );
  })
  .add('tweaks static values organized in groups', () => {
    const GROUP_IDS = {
      DISPLAY: 'DISPLAY',
      GENERAL: 'GENERAL',
      FAVORITES: 'FAVORITES',
    };

    const fruits = {
      Apple: 'apple',
      Banana: 'banana',
      Cherry: 'cherry',
    };

    // NOTE: the default value must not change - e.g., do not do date('Label', new Date()) or date('Label')
    const defaultBirthday = new Date('Jan 20 2017 GMT+0');

    // General
    const name = text('Name', 'Storyteller', GROUP_IDS.GENERAL);
    const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 }, GROUP_IDS.GENERAL);
    const birthday = date('Birthday', defaultBirthday, GROUP_IDS.GENERAL);
    const dollars = number(
      'Account Balance',
      12.5,
      { min: 0, max: 100, step: 0.01 },
      GROUP_IDS.GENERAL
    );
    const years = number('Years in NY', 9, {}, GROUP_IDS.GENERAL);

    // Favorites
    const nice = boolean('Nice', true, GROUP_IDS.FAVORITES);
    const fruit = select('Fruit', fruits, 'apple', GROUP_IDS.FAVORITES);
    const items = array('Items', ['Laptop', 'Book', 'Whiskey'], ',', GROUP_IDS.FAVORITES);

    // Display
    const backgroundColor = color('Color', '#ffff00', GROUP_IDS.DISPLAY);
    const otherStyles = object(
      'Styles',
      { border: '3px solid #ff00ff', padding: '10px' },
      GROUP_IDS.DISPLAY
    );

    const style = { backgroundColor, ...otherStyles };

    const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!';
    const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' };

    return (

General Information

Name: {name}

Age: {age}

Birthday: {new Date(birthday).toLocaleDateString('en-US', dateOptions)}

Account Balance: {dollars}

Years in NY: {years}

Favorites

Catchphrase: {salutation}

Fruit: {fruit}

Items:

{items.map(item => <li key={`${item}`}>{item}
)}

    );
  })
  .add('dynamic knobs', () => {
    const showOptional = select('Show optional', ['yes', 'no'], 'yes');
    return (

{text('compulsary', 'I must be here')}

        {showOptional === 'yes' ?
{text('optional', 'I can disapear')}
 : null}

    );
  })
  .add('triggers actions via button', () => (

Hit the knob load button and it should trigger an async load after a short delay

      <AsyncItemLoader />

  ))
  .add('XSS safety', () => (

{text('Rendered string', '')}

  ));

storiesOf('Addons|Knobs.withKnobsOptions', module)
  .addDecorator(
    withKnobsOptions({
      escapeHTML: false,
    })
  )
  .add('displays HTML code', () =>
{text('Rendered string', '
Hello
')}
);
<commit_msg>FIX double-escaping<commit_after>import React from 'react';
import { storiesOf } from '@storybook/react';

import {
  withKnobs,
  withKnobsOptions,
  text,
  number,
  boolean,
  color,
  select,
  array,
  date,
  button,
  object,
  files,
} from '@storybook/addon-knobs';

class AsyncItemLoader extends React.Component {
  constructor() {
    super();
    this.state = { items: [] };
  }

  handleLoadItems() {
    this.setState({
      isLoading: true,
      items: [],
    });
    setTimeout(() => this.setState({ items: ['pencil', 'pen', 'eraser'], isLoading: false }), 1500);
  }

  render() {
    // this existing example seems like a react anti-pattern?
    // perhaps for testing purposes only?
    button('Load the items', () => this.handleLoadItems());

    const { isLoading, items } = this.state;

    if (isLoading) {
      return
Loading data
;
    } else if (!items.length) {
      return
No items loaded
;
    }
    return
{items.map(i =>
{i}
)}
;
  }
}

storiesOf('Addons|Knobs.withKnobs', module)
  .addDecorator(withKnobs)
  .add('tweaks static values', () => {
    const name = text('Name', 'Storyteller');
    const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 });
    const fruits = {
      Apple: 'apple',
      Banana: 'banana',
      Cherry: 'cherry',
    };
    const fruit = select('Fruit', fruits, 'apple');
    const dollars = number('Dollars', 12.5, { min: 0, max: 100, step: 0.01 });
    const years = number('Years in NY', 9);

    const backgroundColor = color('background', '#ffff00');
    const items = array('Items', ['Laptop', 'Book', 'Whiskey']);
    const otherStyles = object('Styles', {
      border: '3px solid #ff00ff',
      padding: '10px',
    });
    const nice = boolean('Nice', true);
    const images = files('Happy Picture', 'image/*', [
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfiARwMCyEWcOFPAAAAP0lEQVQoz8WQMQoAIAwDL/7/z3GwghSp4KDZyiUpBMCYUgd8rehtH16/l3XewgU2KAzapjXBbNFaPS6lDMlKB6OiDv3iAH1OAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTAxLTI4VDEyOjExOjMzLTA3OjAwlAHQBgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wMS0yOFQxMjoxMTozMy0wNzowMOVcaLoAAAAASUVORK5CYII=',
    ]);

    // NOTE: the default value must not change - e.g., do not do date('Label', new Date()) or date('Label')
    const defaultBirthday = new Date('Jan 20 2017 GMT+0');
    const birthday = date('Birthday', defaultBirthday);

    const intro = `My name is ${name}, I'm ${age} years old, and my favorite fruit is ${fruit}.`;
    const style = { backgroundColor, ...otherStyles };
    const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!';
    const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' };

    return (

{intro}

My birthday is: {new Date(birthday).toLocaleDateString('en-US', dateOptions)}

I live in NY for {years} years.

My wallet contains: ${dollars.toFixed(2)}

In my backpack, I have:

{items.map(item =>
{item}
)}

{salutation}

          When I am happy I look like this:

PS. My shirt pocket contains:

    );
  })
  .add('tweaks static values organized in groups', () => {
    const GROUP_IDS = {
      DISPLAY: 'DISPLAY',
      GENERAL: 'GENERAL',
      FAVORITES: 'FAVORITES',
    };

    const fruits = {
      Apple: 'apple',
      Banana: 'banana',
      Cherry: 'cherry',
    };

    // NOTE: the default value must not change - e.g., do not do date('Label', new Date()) or date('Label')
    const defaultBirthday = new Date('Jan 20 2017 GMT+0');

    // General
    const name = text('Name', 'Storyteller', GROUP_IDS.GENERAL);
    const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 }, GROUP_IDS.GENERAL);
    const birthday = date('Birthday', defaultBirthday, GROUP_IDS.GENERAL);
    const dollars = number(
      'Account Balance',
      12.5,
      { min: 0, max: 100, step: 0.01 },
      GROUP_IDS.GENERAL
    );
    const years = number('Years in NY', 9, {}, GROUP_IDS.GENERAL);

    // Favorites
    const nice = boolean('Nice', true, GROUP_IDS.FAVORITES);
    const fruit = select('Fruit', fruits, 'apple', GROUP_IDS.FAVORITES);
    const items = array('Items', ['Laptop', 'Book', 'Whiskey'], ',', GROUP_IDS.FAVORITES);

    // Display
    const backgroundColor = color('Color', '#ffff00', GROUP_IDS.DISPLAY);
    const otherStyles = object(
      'Styles',
      { border: '3px solid #ff00ff', padding: '10px' },
      GROUP_IDS.DISPLAY
    );

    const style = { backgroundColor, ...otherStyles };

    const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!';
    const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' };

    return (

General Information

Name: {name}

Age: {age}

Birthday: {new Date(birthday).toLocaleDateString('en-US', dateOptions)}

Account Balance: {dollars}

Years in NY: {years}

Favorites

Catchphrase: {salutation}

Fruit: {fruit}

Items:

{items.map(item => <li key={`${item}`}>{item}
)}

    );
  })
  .add('dynamic knobs', () => {
    const showOptional = select('Show optional', ['yes', 'no'], 'yes');
    return (

{text('compulsary', 'I must be here')}

        {showOptional === 'yes' ?
{text('optional', 'I can disapear')}
 : null}

    );
  })
  .add('triggers actions via button', () => (

Hit the knob load button and it should trigger an async load after a short delay

      <AsyncItemLoader />

  ))
  .add('XSS safety', () => (
    <div
      dangerouslySetInnerHTML={{
        __html: text('Rendered string', ''),
      }}
    />
  ));

storiesOf('Addons|Knobs.withKnobsOptions', module)
  .addDecorator(
    withKnobsOptions({
      escapeHTML: false,
    })
  )
  .add('displays HTML code', () =>
{text('Rendered string', '
Hello
')}
);
<|endoftext|>


<commit_before>
let i18nMixin = require('../../i18n').mixin;
let validate = require('focus-core').definition.validator.validate;
let {isNull, isUndefined} = require('lodash/lang');
let validationMixin ={
    mixins: [i18nMixin],
    /** @inheritdoc */
    getDefaultProps: function getDefaultProps(){
        return {
            isRequired: false,
            validator: undefined
        };
    },
    /**
    * Compute the validation status and merge all errors into one.
    * @param  {object} validationStatus - The result from the validation.
    * @return {true | string} - true if the validation is ok and a message if it is not the case.
    */
    _computeValidationStatus(validationStatus){
        if(validationStatus.isValid){
            return true;
        }
        return validationStatus.errors.join(', ');
    },
    /**
    * Validate the input.
    * @return {object}
    */
    validateInput: function validateInputText() {
        let value = this.getValue();
        let {isRequired, validator, label} = this.props;
        if (isRequired && (undefined === value || null === value)) {
            return this.i18n('field.required', {name: this.i18n(label)});
        }
        //console.log('validation', label, 'value', value, 'validator', validator);
        //The validation is performed only when the field has a value, otherwise, only the required validation is performed.
        if (validator && !isUndefined(value) && !isNull(value)) {
            let validStat = this._computeValidationStatus(
                validate(
                    {value: value, name: this.i18n(label)},
                    validator
                )
            );
            if(true !== validStat){
                validStat = this.i18n(validStat);
            }
            return validStat;
        }
        return true;
    },
    /**
    * Validate the field.
    * @return {object} - undefined if valid, {name: "errors"} if not valid.
    */
    validate: function validateField() {
        let validationStatus = this.validateInput();
        if(true !== validationStatus){
            this.setError(validationStatus);
            return validationStatus;
        }
    },
    /**
    * Set the error on the field.
    * @param error Error to set.
    */
    setError: function setErrorOnField(error) {
        this.setState({ error: error });
    }
};
module.exports = validationMixin;
<commit_msg>Update validation-behaviour.js<commit_after>
let i18nMixin = require('../../i18n').mixin;
let validate = require('focus-core').definition.validator.validate;
let {isNull, isUndefined, isFunction} = require('lodash/lang');
let validationMixin ={
    mixins: [i18nMixin],
    /** @inheritdoc */
    getDefaultProps: function getDefaultProps(){
        return {
            isRequired: false,
            validator: undefined
        };
    },
    /**
    * Compute the validation status and merge all errors into one.
    * @param  {object} validationStatus - The result from the validation.
    * @return {true | string} - true if the validation is ok and a message if it is not the case.
    */
    _computeValidationStatus(validationStatus){
        if(validationStatus.isValid){
            return true;
        }
        return validationStatus.errors.join(', ');
    },
    /**
    * Validate the input.
    * @return {object}
    */
    validateInput: function validateInputText() {
        let value = this.getValue();
        let {isRequired, validator, label} = this.props;
        if (isRequired && (undefined === value || null === value)) {
            return this.i18n('field.required', {name: this.i18n(label)});
        }
        //console.log('validation', label, 'value', value, 'validator', validator);
        //The validation is performed only when the field has a value, otherwise, only the required validation is performed.
        if (validator && !isUndefined(value) && !isNull(value)) {
            let validStat = this._computeValidationStatus(
                validate(
                    {value: value, name: this.i18n(label)},
                    validator
                )
            );
            if(true !== validStat){
                validStat = this.i18n(validStat);
            }
            return validStat;
        }
        return true;
    },
    /**
    * Validate the field.
    * @return {object} - undefined if valid, {name: "errors"} if not valid.
    */
    validate: function validateField() {
        const isComponentValidation = this.refs && this.refs.input && isFunction(this.refs.input.validate);
        let validationStatus = isComponentValidation ? this.refs.input.validate() : this.validateInput();
        if(true !== validationStatus){
            this.setError(validationStatus);
            return validationStatus;
        }
    },
    /**
    * Set the error on the field.
    * @param error Error to set.
    */
    setError: function setErrorOnField(error) {
        this.setState({ error: error });
    }
};
module.exports = validationMixin;
<|endoftext|>


<commit_before>/*
 * Flow Status Controller
 */

define([], function () {

	var Controller = Ember.Controller.extend({

		elements: Em.Object.create(),

		load: function () {

			var model = this.get('model');
			var self = this;

			var flowlets = model.flowlets;
			var objects = [];
			for (var i = 0; i < flowlets.length; i ++) {

				flowlets[i].flow = model.get('name');
				flowlets[i].app = model.get('app');

				objects.push(C.Flowlet.create(flowlets[i]));
				objects[i].trackMetric('/process/events/{app}/flows/{flow}/flowlets/{id}', 'aggregates', 'events');

			}
			this.set('elements.Flowlet', Em.ArrayProxy.create({content: objects}));

			var streams = model.flowStreams;
			objects = [];
			for (var i = 0; i < streams.length; i ++) {

				objects.push(C.Stream.create(streams[i]));
				objects[i].trackMetric('/collect/events/streams/{id}', 'aggregates', 'events');

			}
			this.set('elements.Stream', Em.ArrayProxy.create({content: objects}));

			this.interval = setInterval(function () {
				self.updateStats();
			}, C.POLLING_INTERVAL);

			/*
			 * Give the chart Embeddables 100ms to configure
			 * themselves before updating.
			 */
			setTimeout(function () {
				self.updateStats();
			}, C.EMBEDDABLE_DELAY);

		},

		unload: function () {

			this.set('elements.Flowlet', Em.Object.create());
			this.set('elements.Stream', Em.Object.create());

			clearInterval(this.interval);

		},

		statusButtonAction: function () {
			return 'No Action';
		}.property(),
		statusButtonClass: function () {
			return 'btn btn-warning';
		}.property(),

		get_flowlet: function (id) {
			id = id + "";
			var content = this.elements.Flowlet.content;
			for (var k = 0; k < content.length; k++) {
				if (content[k].name === id) {
					return content[k];
				}
			}
			content = this.elements.Stream.content;
			for (k = 0; k < content.length; k++) {
				if (content[k].name === id) {
					return content[k];
				}
			}
		},

		updateStats: function () {

			this.get('model').updateState(this.HTTP);
			C.Util.updateTimeSeries([this.get('model')], this.HTTP);

			var models = this.get('elements.Flowlet.content').concat(
				this.get('elements.Stream.content'));

			C.Util.updateAggregates(models, this.HTTP);

		},

		/**
		 * Lifecycle
		 */
		start: function (app, id, version, config) {

			var self = this;
			var model = this.get('model');

			model.set('currentState', 'STARTING');

			this.HTTP.rpc('runnable', 'start', [app, id, version, 'FLOW', config],
				function (response) {

					if (response.error) {
						C.Modal.show(response.error.name, response.error.message);
					} else {
						model.set('lastStarted', new Date().getTime() / 1000);
					}

			});

		},
		stop: function (app, id, version) {

			var self = this;
			var model = this.get('model');

			model.set('currentState', 'STOPPING');

			this.HTTP.rpc('runnable', 'stop', [app, id, version, 'FLOW'],
				function (response) {

					if (response.error) {
						C.Modal.show(response.error.name, response.error.message);
					}

			});

		},

		/**
		 * Action handlers from the View
		 */
		config: function () {

			var self = this;
			var model = this.get('model');

			this.transitionToRoute('FlowStatus.Config');

		},

		exec: function (action) {

			var control = $(event.target);
			if (event.target.tagName === "SPAN") {
				control = control.parent();
			}

			var id = control.attr('flow-id');
			var app = control.attr('flow-app');
			var action = control.attr('flow-action');

			if (action && action.toLowerCase() in this) {
				this[action.toLowerCase()](app, id, -1);
			}
		},
		"delete": function () {

			var self = this;

			if (this.get('model').get('currentState') !== 'STOPPED' &&
				this.get('model').get('currentState') !== 'DEPLOYED') {
				C.Modal.show('Cannot Delete', 'Please stop the Flow before deleting.');
			} else {
				C.Modal.show(
					"Delete Flow",
					"You are about to remove a Flow, which is irreversible. You can upload this Flow again if you'd like. Do you want to proceed?",
					$.proxy(function (event) {

						var flow = this.get('model');

						self.HTTP.rpc('runnable', 'remove', [flow.app, flow.name, flow.version],
							function (response) {

							C.Modal.hide(function () {

								if (response.error) {
									C.Modal.show('Delete Error', response.error.message || 'No reason given. Please check the logs.');
								} else {
									window.history.go(-1);
								}

							});

						});
					}, this));
			}
		},

		setFlowletLabel: function (label) {

			this.set('__currentFlowletLabel', label);

			this.__resetFlowletLabels();

		},
		flowletLabelName: function () {

			return {
				'arrival.count': 'Enqueue Rate',
				'queue.depth': 'Queue Size',
				'queue.maxdepth': 'Max Queue Depth',
				'processed.count': 'Total Processed'
			}[this.__currentFlowletLabel];

		}.property('__currentFlowletLabel')

	});

	Controller.reopenClass({
		type: 'FlowStatus',
		kind: 'Controller'
	});

	return Controller;

});<commit_msg>Fix flowlet events path<commit_after>/*
 * Flow Status Controller
 */

define([], function () {

	var Controller = Ember.Controller.extend({

		elements: Em.Object.create(),

		load: function () {

			var model = this.get('model');
			var self = this;

			var flowlets = model.flowlets;
			var objects = [];
			for (var i = 0; i < flowlets.length; i ++) {

				flowlets[i].flow = model.get('name');
				flowlets[i].app = model.get('app');

				objects.push(C.Flowlet.create(flowlets[i]));
				objects[i].trackMetric('/process/events/{app}/flows/{flow}/{id}', 'aggregates', 'events');

			}
			this.set('elements.Flowlet', Em.ArrayProxy.create({content: objects}));

			var streams = model.flowStreams;
			objects = [];
			for (var i = 0; i < streams.length; i ++) {

				objects.push(C.Stream.create(streams[i]));
				objects[i].trackMetric('/collect/events/streams/{id}', 'aggregates', 'events');

			}
			this.set('elements.Stream', Em.ArrayProxy.create({content: objects}));

			this.interval = setInterval(function () {
				self.updateStats();
			}, C.POLLING_INTERVAL);

			/*
			 * Give the chart Embeddables 100ms to configure
			 * themselves before updating.
			 */
			setTimeout(function () {
				self.updateStats();
			}, C.EMBEDDABLE_DELAY);

		},

		unload: function () {

			this.set('elements.Flowlet', Em.Object.create());
			this.set('elements.Stream', Em.Object.create());

			clearInterval(this.interval);

		},

		statusButtonAction: function () {
			return 'No Action';
		}.property(),
		statusButtonClass: function () {
			return 'btn btn-warning';
		}.property(),

		get_flowlet: function (id) {
			id = id + "";
			var content = this.elements.Flowlet.content;
			for (var k = 0; k < content.length; k++) {
				if (content[k].name === id) {
					return content[k];
				}
			}
			content = this.elements.Stream.content;
			for (k = 0; k < content.length; k++) {
				if (content[k].name === id) {
					return content[k];
				}
			}
		},

		updateStats: function () {

			this.get('model').updateState(this.HTTP);
			C.Util.updateTimeSeries([this.get('model')], this.HTTP);

			var models = this.get('elements.Flowlet.content').concat(
				this.get('elements.Stream.content'));

			C.Util.updateAggregates(models, this.HTTP);

		},

		/**
		 * Lifecycle
		 */
		start: function (app, id, version, config) {

			var self = this;
			var model = this.get('model');

			model.set('currentState', 'STARTING');

			this.HTTP.rpc('runnable', 'start', [app, id, version, 'FLOW', config],
				function (response) {

					if (response.error) {
						C.Modal.show(response.error.name, response.error.message);
					} else {
						model.set('lastStarted', new Date().getTime() / 1000);
					}

			});

		},
		stop: function (app, id, version) {

			var self = this;
			var model = this.get('model');

			model.set('currentState', 'STOPPING');

			this.HTTP.rpc('runnable', 'stop', [app, id, version, 'FLOW'],
				function (response) {

					if (response.error) {
						C.Modal.show(response.error.name, response.error.message);
					}

			});

		},

		/**
		 * Action handlers from the View
		 */
		config: function () {

			var self = this;
			var model = this.get('model');

			this.transitionToRoute('FlowStatus.Config');

		},

		exec: function (action) {

			var control = $(event.target);
			if (event.target.tagName === "SPAN") {
				control = control.parent();
			}

			var id = control.attr('flow-id');
			var app = control.attr('flow-app');
			var action = control.attr('flow-action');

			if (action && action.toLowerCase() in this) {
				this[action.toLowerCase()](app, id, -1);
			}
		},
		"delete": function () {

			var self = this;

			if (this.get('model').get('currentState') !== 'STOPPED' &&
				this.get('model').get('currentState') !== 'DEPLOYED') {
				C.Modal.show('Cannot Delete', 'Please stop the Flow before deleting.');
			} else {
				C.Modal.show(
					"Delete Flow",
					"You are about to remove a Flow, which is irreversible. You can upload this Flow again if you'd like. Do you want to proceed?",
					$.proxy(function (event) {

						var flow = this.get('model');

						self.HTTP.rpc('runnable', 'remove', [flow.app, flow.name, flow.version],
							function (response) {

							C.Modal.hide(function () {

								if (response.error) {
									C.Modal.show('Delete Error', response.error.message || 'No reason given. Please check the logs.');
								} else {
									window.history.go(-1);
								}

							});

						});
					}, this));
			}
		},

		setFlowletLabel: function (label) {

			this.set('__currentFlowletLabel', label);

			this.__resetFlowletLabels();

		},
		flowletLabelName: function () {

			return {
				'arrival.count': 'Enqueue Rate',
				'queue.depth': 'Queue Size',
				'queue.maxdepth': 'Max Queue Depth',
				'processed.count': 'Total Processed'
			}[this.__currentFlowletLabel];

		}.property('__currentFlowletLabel')

	});

	Controller.reopenClass({
		type: 'FlowStatus',
		kind: 'Controller'
	});

	return Controller;

});
<|endoftext|>


<commit_before><commit_msg>Removed space, removed unused that variable<commit_after><|endoftext|>


<commit_before>// @flow
import * as React from 'react'
import styled from 'styled-components'
import {List, AutoSizer} from 'react-virtualized'
import {DraggableCourse} from '../course'
import {
	toPrettyTerm,
	expandYear,
	semesterName,
} from '@gob/school-st-olaf-college'

const GROUP_BY_TO_TITLE = {
	'Day of Week': days => days,
	Department: depts => depts,
	GenEd: gereqs => gereqs,
	Semester: sem => semesterName(sem),
	Term: term => toPrettyTerm(term),
	'Time of Day': times => times,
	Year: year => expandYear(year),
	None: () => '',
}

type Props = {
	groupBy: string,
	results: Array<[string, Array<any>]>,
	sortBy?: string,
	studentId?: string,
}

type State = {
	itemCount: number,
}

export default class CourseResultsList extends React.Component<Props, State> {
	state = {itemCount: 0}

	static getDerivedStateFromProps = (props: Props) => {
		let itemCount = props.results.reduce((acc, [_title, items]) => {
			// add 1 for the section heading
			let childCount = items.length + 1
			return acc + childCount
		}, 0)
		return {itemCount}
	}

	// method borrowed from https://github.com/lucasferreira/react-virtualized-sectionlist/blob/1ed9437c76dbc9b3fcf01dd9c5b50c96d9dc211c/source/index.js#L100-L123
	extractItemAtIndex = (
		index: number,
	): {
		title: ?string,
		items: Array<any>,
		itemIndex: ?number,
		sectionIndex: ?number,
		isHeader: boolean,
	} => {
		let itemIndex = index

		for (let i = 0; i < this.props.results.length; i++) {
			let [title, items] = this.props.results[i]

			// The section adds an item for the header
			itemIndex -= 1

			if (itemIndex >= items.length) {
				itemIndex -= items.length
			} else if (itemIndex === -1) {
				return {
					title,
					items,
					itemIndex: null,
					sectionIndex: i,
					isHeader: true,
				}
			} else {
				return {
					title,
					items,
					itemIndex,
					sectionIndex: i,
					isHeader: false,
				}
			}
		}

		return {
			title: null,
			items: [],
			itemIndex: null,
			sectionIndex: null,
			isHeader: false,
		}
	}

	getRowHeight = ({index}: {|index: number|}) => {
		let {isHeader, itemIndex, items} = this.extractItemAtIndex(index)

		if (isHeader) {
			return 35
		}

		if (itemIndex == null || !items[itemIndex]) {
			return 0
		}

		let course = items[itemIndex]
		let courseRows = 2

		let hasTimes = course.times && course.times.length
		let hasSubtitle =
			course.name &&
			course.title &&
			(course.type === 'Seminar' || course.type === 'Topic')

		if (hasTimes) {
			courseRows += 1
		}
		if (hasSubtitle) {
			courseRows += 1
		}

		if (courseRows === 2) {
			return 38
		} else if (courseRows === 3) {
			return 56
		} else if (courseRows === 4) {
			return 71
		}

		return 0
	}

	renderHeader = (args: {title: ?string, key: string, style: any}) => {
		let {title: groupTitle, key, style} = args
		if (!groupTitle) {
			return null
		}

		const title = GROUP_BY_TO_TITLE[this.props.groupBy](groupTitle)
		return (

				{title}

		)
	}

	renderRow = (args: {index: number, style: Object, key: string}) => {
		let {index, style, key} = args
		let {isHeader, itemIndex, items, title} = this.extractItemAtIndex(index)

		if (isHeader) {
			return this.renderHeader({title, key, style})
		}

		if (itemIndex == null || !items[itemIndex]) {
			return null
		}

		let course = items[itemIndex]

		return (
			<DraggableCourse
				key={key}
				style={style}
				course={course}
				studentId={this.props.studentId}
			/>
		)
	}

	render() {
		return (
			<AutoSizer>
				{({height, width}) => (
					<List
						className="term-list"
						height={height}
						rowCount={this.state.itemCount}
						rowHeight={this.getRowHeight}
						rowRenderer={this.renderRow}
						width={width}
					/>
				)}
			</AutoSizer>
		)
	}

	renderAllAtOnce = () => {
		const {results, groupBy: groupByValue, studentId} = this.props

		return (

				{results.map(([groupTitle, courses]) => {
					const title = GROUP_BY_TO_TITLE[groupByValue](groupTitle)
					return (

							{title && (

{title}

							)}

								{courses.map((course, index) => (

										<DraggableCourse
											course={course}
											studentId={studentId}
										/>

								))}

					)
				})}

		)
	}
}
<commit_msg>remove unused import<commit_after>// @flow
import * as React from 'react'
import {List, AutoSizer} from 'react-virtualized'
import {DraggableCourse} from '../course'
import {
	toPrettyTerm,
	expandYear,
	semesterName,
} from '@gob/school-st-olaf-college'

const GROUP_BY_TO_TITLE = {
	'Day of Week': days => days,
	Department: depts => depts,
	GenEd: gereqs => gereqs,
	Semester: sem => semesterName(sem),
	Term: term => toPrettyTerm(term),
	'Time of Day': times => times,
	Year: year => expandYear(year),
	None: () => '',
}

type Props = {
	groupBy: string,
	results: Array<[string, Array<any>]>,
	sortBy?: string,
	studentId?: string,
}

type State = {
	itemCount: number,
}

export default class CourseResultsList extends React.Component<Props, State> {
	state = {itemCount: 0}

	static getDerivedStateFromProps = (props: Props) => {
		let itemCount = props.results.reduce((acc, [_title, items]) => {
			// add 1 for the section heading
			let childCount = items.length + 1
			return acc + childCount
		}, 0)
		return {itemCount}
	}

	// method borrowed from https://github.com/lucasferreira/react-virtualized-sectionlist/blob/1ed9437c76dbc9b3fcf01dd9c5b50c96d9dc211c/source/index.js#L100-L123
	extractItemAtIndex = (
		index: number,
	): {
		title: ?string,
		items: Array<any>,
		itemIndex: ?number,
		sectionIndex: ?number,
		isHeader: boolean,
	} => {
		let itemIndex = index

		for (let i = 0; i < this.props.results.length; i++) {
			let [title, items] = this.props.results[i]

			// The section adds an item for the header
			itemIndex -= 1

			if (itemIndex >= items.length) {
				itemIndex -= items.length
			} else if (itemIndex === -1) {
				return {
					title,
					items,
					itemIndex: null,
					sectionIndex: i,
					isHeader: true,
				}
			} else {
				return {
					title,
					items,
					itemIndex,
					sectionIndex: i,
					isHeader: false,
				}
			}
		}

		return {
			title: null,
			items: [],
			itemIndex: null,
			sectionIndex: null,
			isHeader: false,
		}
	}

	getRowHeight = ({index}: {|index: number|}) => {
		let {isHeader, itemIndex, items} = this.extractItemAtIndex(index)

		if (isHeader) {
			return 35
		}

		if (itemIndex == null || !items[itemIndex]) {
			return 0
		}

		let course = items[itemIndex]
		let courseRows = 2

		let hasTimes = course.times && course.times.length
		let hasSubtitle =
			course.name &&
			course.title &&
			(course.type === 'Seminar' || course.type === 'Topic')

		if (hasTimes) {
			courseRows += 1
		}
		if (hasSubtitle) {
			courseRows += 1
		}

		if (courseRows === 2) {
			return 38
		} else if (courseRows === 3) {
			return 56
		} else if (courseRows === 4) {
			return 71
		}

		return 0
	}

	renderHeader = (args: {title: ?string, key: string, style: any}) => {
		let {title: groupTitle, key, style} = args
		if (!groupTitle) {
			return null
		}

		const title = GROUP_BY_TO_TITLE[this.props.groupBy](groupTitle)
		return (

				{title}

		)
	}

	renderRow = (args: {index: number, style: Object, key: string}) => {
		let {index, style, key} = args
		let {isHeader, itemIndex, items, title} = this.extractItemAtIndex(index)

		if (isHeader) {
			return this.renderHeader({title, key, style})
		}

		if (itemIndex == null || !items[itemIndex]) {
			return null
		}

		let course = items[itemIndex]

		return (
			<DraggableCourse
				key={key}
				style={style}
				course={course}
				studentId={this.props.studentId}
			/>
		)
	}

	render() {
		return (
			<AutoSizer>
				{({height, width}) => (
					<List
						className="term-list"
						height={height}
						rowCount={this.state.itemCount}
						rowHeight={this.getRowHeight}
						rowRenderer={this.renderRow}
						width={width}
					/>
				)}
			</AutoSizer>
		)
	}

	renderAllAtOnce = () => {
		const {results, groupBy: groupByValue, studentId} = this.props

		return (

				{results.map(([groupTitle, courses]) => {
					const title = GROUP_BY_TO_TITLE[groupByValue](groupTitle)
					return (

							{title && (

{title}

							)}

								{courses.map((course, index) => (

										<DraggableCourse
											course={course}
											studentId={studentId}
										/>

								))}

					)
				})}

		)
	}
}
<|endoftext|>