Repository: ambari Updated Branches: refs/heads/trunk 52d2cc421 -> 0fe39d5e8
AMBARI-11166 Graph widgets: convert % metrics to show 0 - 100% rather than 0 - 1. (atkach) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/0fe39d5e Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/0fe39d5e Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/0fe39d5e Branch: refs/heads/trunk Commit: 0fe39d5e856f43ccfca00a70b9354e8bf899e768 Parents: 52d2cc4 Author: Andrii Tkach <[email protected]> Authored: Fri May 15 15:03:02 2015 +0300 Committer: Andrii Tkach <[email protected]> Committed: Fri May 15 17:54:47 2015 +0300 ---------------------------------------------------------------------- .../service/widgets/create/step2_controller.js | 53 +++++--- ambari-web/app/messages.js | 1 + .../app/mixins/common/widgets/widget_mixin.js | 11 +- ambari-web/app/routes/add_widget.js | 2 - .../app/styles/enhanced_service_dashboard.less | 16 ++- .../main/service/widgets/create/expression.hbs | 5 + ambari-web/app/utils/number_utils.js | 9 ++ .../views/common/widget/graph_widget_view.js | 28 ++-- .../service/widgets/create/expression_view.js | 134 +++++++++++++++---- 9 files changed, 189 insertions(+), 70 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/controllers/main/service/widgets/create/step2_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/widgets/create/step2_controller.js b/ambari-web/app/controllers/main/service/widgets/create/step2_controller.js index b6a3782..9c48872 100644 --- a/ambari-web/app/controllers/main/service/widgets/create/step2_controller.js +++ b/ambari-web/app/controllers/main/service/widgets/create/step2_controller.js @@ -343,7 +343,14 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({ value = '${'; expression.data.forEach(function (element) { if (element.isMetric) { - metrics.push(element); + metrics.push({ + "name": element.name, + "service_name": element.serviceName, + "component_name": element.componentName, + "metric_path": element.metricPath, + "host_component_criteria": element.hostComponentCriteria, + "category": element.category + }); } value += element.name; }, this); @@ -463,21 +470,11 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({ var str = ''; var data = []; var id = 0; - var metric; for (var i = 0, l = expression.length; i < l; i++) { if (this.get('OPERATORS').contains(expression[i])) { if (str.trim().length > 0) { - metric = metrics.findProperty('name', str.trim()); - data.pushObject(Em.Object.create({ - id: ++id, - name: str.trim(), - isMetric: true, - componentName: metric.component_name, - serviceName: metric.service_name, - metricPath: metric.metric_path, - hostComponentCriteria: metric.host_component_criteria - })); + data.pushObject(this.getExpressionVariable(str.trim(), id, metrics)); str = ''; } data.pushObject(Em.Object.create({ @@ -490,18 +487,40 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({ } } if (str.trim().length > 0) { - metric = metrics.findProperty('name', str.trim()); - data.pushObject(Em.Object.create({ + data.pushObject(this.getExpressionVariable(str.trim(), id, metrics)); + } + return data; + }, + + /** + * get variable of expression + * could be name of metric "m1" or constant number "1" + * @param {string} name + * @param {Array} metrics + * @param {number} id + * @returns {Em.Object} + */ + getExpressionVariable: function (name, id, metrics) { + var metric; + + if (isNaN(Number(name))) { + metric = metrics.findProperty('name', name); + return Em.Object.create({ id: ++id, - name: str.trim(), + name: metric.name, isMetric: true, componentName: metric.component_name, serviceName: metric.service_name, metricPath: metric.metric_path, hostComponentCriteria: metric.host_component_criteria - })); + }); + } else { + return Em.Object.create({ + id: ++id, + name: name, + isNumber: true + }); } - return data; }, next: function () { http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index d54cdd7..67ad9fd 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -2579,6 +2579,7 @@ Em.I18n.translations = { 'dashboard.widgets.wizard.step2.addMetrics': 'Add Metrics and operators here...', 'dashboard.widgets.wizard.step2.newMetric': '+ Add Metric', 'dashboard.widgets.wizard.step2.newOperator': '+ Add Operator', + 'dashboard.widgets.wizard.step2.newNumber': '+ Add Number', 'dashboard.widgets.wizard.step2.Component': 'Component', 'dashboard.widgets.wizard.step2.Metric': 'Metric', 'dashboard.widgets.wizard.step2.selectComponent': 'Select a Component', http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/mixins/common/widgets/widget_mixin.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/widgets/widget_mixin.js b/ambari-web/app/mixins/common/widgets/widget_mixin.js index a7a748a..7f9834d 100644 --- a/ambari-web/app/mixins/common/widgets/widget_mixin.js +++ b/ambari-web/app/mixins/common/widgets/widget_mixin.js @@ -617,16 +617,7 @@ App.WidgetPreviewMixin = Ember.Mixin.create({ 'values': this.get('controller.widgetValues'), 'properties': this.get('controller.widgetProperties'), 'widgetName': this.get('controller.widgetName'), - 'metrics': this.get('controller.widgetMetrics').map(function (metric) { - return { - "name": metric.name, - "service_name": metric.serviceName, - "component_name": metric.componentName, - "metric_path": metric.metricPath, - "host_component_criteria": metric.hostComponentCriteria, - "category": metric.category - } - }) + 'metrics': this.get('controller.widgetMetrics') }); this._super(); }.observes('controller.widgetProperties', 'controller.widgetValues', 'controller.widgetMetrics', 'controller.widgetName'), http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/routes/add_widget.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/routes/add_widget.js b/ambari-web/app/routes/add_widget.js index 8c427f4..2631b16 100644 --- a/ambari-web/app/routes/add_widget.js +++ b/ambari-web/app/routes/add_widget.js @@ -111,8 +111,6 @@ module.exports = App.WizardRoute.extend({ widgetWizardController.save('widgetProperties', widgetStep2controller.get('widgetProperties')); widgetWizardController.save('widgetMetrics', widgetStep2controller.get('widgetMetrics')); widgetWizardController.save('widgetValues', widgetStep2controller.get('widgetValues')); - widgetWizardController.save('expressions', widgetStep2controller.get('expressions')); - widgetWizardController.save('dataSets', widgetStep2controller.get('dataSets')); widgetWizardController.save('templateValue', widgetStep2controller.get('templateValue')); widgetWizardController.save('widgetName', ""); widgetWizardController.save('widgetDescription', ""); http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/styles/enhanced_service_dashboard.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/enhanced_service_dashboard.less b/ambari-web/app/styles/enhanced_service_dashboard.less index 4ca4b54..1cc27b8 100644 --- a/ambari-web/app/styles/enhanced_service_dashboard.less +++ b/ambari-web/app/styles/enhanced_service_dashboard.less @@ -19,6 +19,7 @@ @import 'common.less'; @border-color: #ddd; +@invalid-color: red; .service-metrics-block { @@ -319,7 +320,7 @@ .is-invalid.controls, .metric-container.is-invalid { .metric-field { - border-color: red; + border-color: @invalid-color; } } .metric-container { @@ -337,6 +338,15 @@ .add-operator { margin-left: 25px; } + .add-number { + margin-left: 40px; + .add-on { + height: inherit; + } + } + .is-invalid { + border-color: @invalid-color + } } .metric-field { height: 85%; @@ -375,7 +385,7 @@ border-top: none; } .controls.is-invalid { - border-color: red; + border-color: @invalid-color; } .add-item-input { display: inline-block!important; @@ -400,7 +410,7 @@ transition: none; } .ember-text-field.is-invalid { - border: 1px solid red; + border: 1px solid @invalid-color; } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/templates/main/service/widgets/create/expression.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/service/widgets/create/expression.hbs b/ambari-web/app/templates/main/service/widgets/create/expression.hbs index 3398725..969c56e 100644 --- a/ambari-web/app/templates/main/service/widgets/create/expression.hbs +++ b/ambari-web/app/templates/main/service/widgets/create/expression.hbs @@ -29,6 +29,11 @@ {{/each}} </ul> </div> + <div class="input-append span2 add-number"> + {{view App.AddNumberExpressionView valueBinding="view.numberValue" class="input-small"}} + <button class="btn add-on" {{action addNumber target="view"}} {{bindAttr disabled="view.isNumberValueInvalid"}}>{{t dashboard.widgets.wizard.step2.newNumber}}</button> + </div> + </div> <div class="metric-field"> {{#if view.expression.isRemovable}} http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/utils/number_utils.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/number_utils.js b/ambari-web/app/utils/number_utils.js index b39496e..4331f85 100644 --- a/ambari-web/app/utils/number_utils.js +++ b/ambari-web/app/utils/number_utils.js @@ -114,5 +114,14 @@ module.exports = { decimals = decimals[1] ? decimals[1].length : 0; return decimals; + }, + + /** + * @param n + * @return {boolean} + */ + isPositiveNumber: function(n) { + var number = Number(n); + return !isNaN(number) && isFinite(number) && (number > 0); } }; http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/views/common/widget/graph_widget_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/widget/graph_widget_view.js b/ambari-web/app/views/common/widget/graph_widget_view.js index 6c9b9b5..6483608 100644 --- a/ambari-web/app/views/common/widget/graph_widget_view.js +++ b/ambari-web/app/views/common/widget/graph_widget_view.js @@ -111,15 +111,17 @@ App.GraphWidgetView = Em.View.extend(App.WidgetMixin, { //replace values with metrics data expression.match(this.get('VALUE_NAME_REGEX')).forEach(function (match) { - if (metrics.someProperty('name', match)) { - dataLinks[match] = metrics.findProperty('name', match).data; - if (!isDataCorrupted) { - isDataCorrupted = (dataLength !== -1 && dataLength !== dataLinks[match].length); + if (isNaN(match)) { + if (metrics.someProperty('name', match)) { + dataLinks[match] = metrics.findProperty('name', match).data; + if (!isDataCorrupted) { + isDataCorrupted = (dataLength !== -1 && dataLength !== dataLinks[match].length); + } + dataLength = (dataLinks[match].length > dataLength) ? dataLinks[match].length : dataLength; + } else { + validExpression = false; + console.error('Metrics with name "' + match + '" not found to compute expression'); } - dataLength = (dataLinks[match].length > dataLength) ? dataLinks[match].length : dataLength; - } else { - validExpression = false; - console.error('Metrics with name "' + match + '" not found to compute expression'); } }); @@ -130,9 +132,13 @@ App.GraphWidgetView = Em.View.extend(App.WidgetMixin, { for (var i = 0, timestamp; i < dataLength; i++) { isPointNull = false; beforeCompute = expression.replace(this.get('VALUE_NAME_REGEX'), function (match) { - timestamp = dataLinks[match][i][1]; - isPointNull = (isPointNull) ? true : (Em.isNone(dataLinks[match][i][0])); - return dataLinks[match][i][0]; + if (isNaN(match)) { + timestamp = dataLinks[match][i][1]; + isPointNull = (isPointNull) ? true : (Em.isNone(dataLinks[match][i][0])); + return dataLinks[match][i][0]; + } else { + return match; + } }); var dataLinkPointValue = isPointNull ? null : Number(window.eval(beforeCompute)); // expression resulting into `0/0` will produce NaN Object which is not a valid series data value for RickShaw graphs http://git-wip-us.apache.org/repos/asf/ambari/blob/0fe39d5e/ambari-web/app/views/main/service/widgets/create/expression_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/service/widgets/create/expression_view.js b/ambari-web/app/views/main/service/widgets/create/expression_view.js index 5c12ca9..a232d89 100644 --- a/ambari-web/app/views/main/service/widgets/create/expression_view.js +++ b/ambari-web/app/views/main/service/widgets/create/expression_view.js @@ -17,6 +17,7 @@ */ var misc = require('utils/misc'); +var number_utils = require("utils/number_utils"); App.WidgetWizardExpressionView = Em.View.extend({ templateName: require('templates/main/service/widgets/create/expression'), @@ -39,6 +40,18 @@ App.WidgetWizardExpressionView = Em.View.extend({ OPERATORS: ["+", "-", "*", "/", "(", ")"], /** + * @type {Array} + * @const + */ + AGGREGATE_FUNCTIONS: ['avg', 'sum', 'min', 'max'], + + /** + * @type {RegExp} + * @const + */ + VALID_EXPRESSION_REGEX: /^((\(\s)*[\d]+)[\(\)\+\-\*\/\.\d\s]*[\d\)]*$/, + + /** * contains expression data before editing in order to restore previous state */ dataBefore: [], @@ -54,6 +67,19 @@ App.WidgetWizardExpressionView = Em.View.extend({ isInvalid: false, /** + * contains value of number added to expression + * @type {string} + */ + numberValue: "", + + /** + * @type {boolean} + */ + isNumberValueInvalid: function () { + return this.get('numberValue').trim() === "" || !number_utils.isPositiveNumber(this.get('numberValue').trim()); + }.property('numberValue'), + + /** * add operator to expression data * @param event */ @@ -69,6 +95,22 @@ App.WidgetWizardExpressionView = Em.View.extend({ }, /** + * add operator to expression data + * @param event + */ + addNumber: function (event) { + var data = this.get('expression.data'); + var lastId = (data.length > 0) ? Math.max.apply(this, data.mapProperty('id')) : 0; + + data.pushObject(Em.Object.create({ + id: ++lastId, + name: this.get('numberValue'), + isNumber: true + })); + this.set('numberValue', ""); + }, + + /** * redraw expression * NOTE: needed in order to avoid collision between scrollable lib and metric action event */ @@ -117,7 +159,7 @@ App.WidgetWizardExpressionView = Em.View.extend({ }, this).join(" "); if (expression.length > 0) { - if (/^((\(\s)*[\d]+)[\(\)\+\-\*\/\d\s]*[\d\)]*$/.test(expression)) { + if (this.get('VALID_EXPRESSION_REGEX').test(expression)) { try { isInvalid = !isFinite(window.eval(expression)); } catch (e) { @@ -134,13 +176,23 @@ App.WidgetWizardExpressionView = Em.View.extend({ if (!isInvalid) { this.get('controller').updateExpressions(); } - }.observes('expression.data.length'), + }.observes('expression.data.length') +}); + +/** + * input used to add number to expression + * @type {Em.TextField} + * @class + */ +App.AddNumberExpressionView = Em.TextField.extend({ + classNameBindings: ['isInvalid'], /** - * @type {Array} - * @const + * @type {boolean} */ - AGGREGATE_FUNCTIONS: ['avg', 'sum', 'min', 'max'] + isInvalid: function () { + return this.get('value').trim().length > 0 && !number_utils.isPositiveNumber(this.get('value').trim()); + }.property('value') }); @@ -212,10 +264,10 @@ App.AddMetricExpressionView = Em.View.extend({ */ addMetric: function (event) { var selectedMetric = event.context.get('selectedMetric'), - aggregateFunction = event.context.get('selectedAggregation'); - var isAddEnabled = event.context.get('isAddEnabled'); - var result = jQuery.extend(true, {}, selectedMetric); - if (isAddEnabled) { + aggregateFunction = event.context.get('selectedAggregation'), + result = Em.Object.create(selectedMetric); + + if (event.context.get('isAddEnabled')) { var data = this.get('parentView').get('expression.data'), id = (data.length > 0) ? Math.max.apply(this.get('parentView'), data.mapProperty('id')) + 1 : 1; result.set('id', id); @@ -347,28 +399,36 @@ App.InputCursorTextfieldView = Ember.TextField.extend({ Em.run.next( function() { $('.add-item-input .ember-text-field').focus(); }); }.observes('parentView.expression.data.length'), + focusOut: function(evt) { + this.saveNumber(); + }, + validateInput: function () { var value = this.get('value'); var parentView = this.get('parentView'); - this.set('isInvalid', false); - if (value && parentView.get('OPERATORS').contains(value)) { - // add operator - var data = parentView.get('expression.data'); - var lastId = (data.length > 0) ? Math.max.apply(parentView, data.mapProperty('id')) : 0; - data.pushObject(Em.Object.create({ - id: ++lastId, - name: value, - isOperator: true - })); - this.set('value', ''); - } else if (value && value == 'm') { - // open add metric menu - $('#add-metric-menu > div > a').click(); - this.set('value', ''); - } else if (value) { - // invalid operator - this.set('isInvalid', true); + var isInvalid = false; + + if (!number_utils.isPositiveNumber(value)) { + if (value && parentView.get('OPERATORS').contains(value)) { + // add operator + var data = parentView.get('expression.data'); + var lastId = (data.length > 0) ? Math.max.apply(parentView, data.mapProperty('id')) : 0; + data.pushObject(Em.Object.create({ + id: ++lastId, + name: value, + isOperator: true + })); + this.set('value', ''); + } else if (value && value == 'm') { + // open add metric menu + $('#add-metric-menu > div > a').click(); + this.set('value', ''); + } else if (value) { + // invalid operator + isInvalid = true; + } } + this.set('isInvalid', isInvalid); }.observes('value'), keyDown: function (event) { @@ -377,6 +437,26 @@ App.InputCursorTextfieldView = Ember.TextField.extend({ if (data.length >= 1) { data.removeObject(data[data.length - 1]); } + } else if (event.keyCode == 13) { //Enter + this.saveNumber(); + } + }, + + saveNumber: function() { + var number_utils = require("utils/number_utils"); + var value = this.get('value'); + if (number_utils.isPositiveNumber(value)) { + var parentView = this.get('parentView'); + var data = parentView.get('expression.data'); + var lastId = (data.length > 0) ? Math.max.apply(this, data.mapProperty('id')) : 0; + data.pushObject(Em.Object.create({ + id: ++lastId, + name: this.get('value'), + isNumber: true + })); + this.set('numberValue', ""); + this.set('isInvalid', false); + this.set('value', ''); } } });
