Repository: ambari Updated Branches: refs/heads/trunk 6d0eb71da -> ff4598373
AMBARI-10338. Create Widget wizard: Show threshold and unit field for a widget type.(xiwang) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/ff459837 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/ff459837 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/ff459837 Branch: refs/heads/trunk Commit: ff45983735c5346635af876233c9a02fcd8d247f Parents: 6d0eb71 Author: Xi Wang <[email protected]> Authored: Thu Apr 2 15:30:20 2015 -0700 Committer: Xi Wang <[email protected]> Committed: Thu Apr 2 17:35:19 2015 -0700 ---------------------------------------------------------------------- .../service/widgets/create/step2_controller.js | 112 ++++++++- ambari-web/app/models.js | 1 + ambari-web/app/models/widget.js | 10 + ambari-web/app/models/widget_property.js | 249 +++++++++++++++++++ .../app/styles/enhanced_service_dashboard.less | 26 ++ .../main/service/widgets/create/step2.hbs | 16 ++ .../create/widget_property_threshold.hbs | 32 +++ .../main/service/widgets/create/step2_view.js | 18 ++ 8 files changed, 461 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/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 2438a80..62d935a 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 @@ -21,14 +21,120 @@ var App = require('app'); App.WidgetWizardStep2Controller = Em.Controller.extend({ name: "widgetWizardStep2Controller", + widgetProperties: [], + widgetMetrics: {}, + widgetValues: {}, //TODO: Following computed property needs to be implemented. Next button should be enabled when there is no validation error and all required fields are filled - isSubmitDisabled: function() { - return false; - }.property(''), + isSubmitDisabled: function () { + return this.get('widgetProperties').someProperty('isValid', false); + }.property('[email protected]'), + /* + * Generate the thresholds, unit, time range.etc object based on the widget type selected in previous step. + */ + renderProperties: function () { + var widgetType = this.get('content.widgetType'); + this.set("widgetProperties", {}); + var widgetProperties = App.WidgetType.find().findProperty('name', widgetType).get('properties'); + var properties = []; + switch (widgetType) { + case 'GAUGE': + properties = this.renderGaugeProperties(widgetProperties); + break; + case 'NUMBER': + properties = this.renderNumberProperties(widgetProperties); + break; + case 'GRAPH': + properties = this.renderGraphProperties(widgetProperties); + break; + case 'TEMPLATE': + properties = this.renderTemplateProperties(widgetProperties); + break; + default: + console.error('Incorrect Widget Type: ', widgetType); + } + this.set('widgetProperties', properties); + }, + + /** + * Render properties for gauge-type widget + * @method renderGaugeProperties + * @returns {App.WidgetProperties[]} + */ + renderGaugeProperties: function (widgetProperties) { + var result = []; + result = result.concat([ + App.WidgetProperties.Thresholds.PercentageThreshold.create({ + smallValue: '0.7', + bigValue: '0.9', + isRequired: true + }) + ]); + return result; + }, + + /** + * Render properties for number-type widget + * @method renderNumberProperties + * @returns {App.WidgetProperties[]} + */ + renderNumberProperties: function (widgetProperties) { + var result = []; + + result = result.concat([ + App.WidgetProperties.Threshold.create({ + smallValue: '10', + bigValue: '20', + isRequired: false + }), + App.WidgetProperties.Unit.create({ + value: 'MB', + isRequired: false + }) + ]); + return result; + }, + /** + * Render properties for template-type widget + * @method renderTemplateProperties + * @returns {App.WidgetProperties[]} + */ + renderTemplateProperties: function (widgetProperties) { + var result = []; + result = result.concat([ + App.WidgetProperties.Unit.create({ + value: 'MB', + isRequired: false + }) + ]); + return result; + }, + /** + * Render properties for graph-type widget + * @method renderGraphProperties + * @returns {App.WidgetProperties[]} + */ + renderGraphProperties: function (widgetProperties) { + var result = []; + result = result.concat([ + App.WidgetProperties.GraphType.create({ + value: 'LINE', + isRequired: true + }), + App.WidgetProperties.TimeRange.create({ + value: 'Last 1 hour', + isRequired: true + }), + App.WidgetProperties.Unit.create({ + value: 'MB', + isRequired: false + }) + ]); + return result; + }, next: function () { if (!this.get('isSubmitDisabled')) { http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/ambari-web/app/models.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models.js b/ambari-web/app/models.js index e9de30f..f4e881e 100644 --- a/ambari-web/app/models.js +++ b/ambari-web/app/models.js @@ -69,4 +69,5 @@ require('models/configs/tab'); require('models/configs/section'); require('models/configs/sub_section'); require('models/widget'); +require('models/widget_property'); require('models/widget_layout'); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/ambari-web/app/models/widget.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/widget.js b/ambari-web/app/models/widget.js index 88a80e3..6147bf5 100644 --- a/ambari-web/app/models/widget.js +++ b/ambari-web/app/models/widget.js @@ -132,6 +132,11 @@ App.WidgetType.FIXTURES = [ { property_name : 'time_range', isRequired: true + }, + { + property_name : 'display_unit', + display_name: 'unit', + isRequired: false } ] }, @@ -141,6 +146,11 @@ App.WidgetType.FIXTURES = [ display_name: 'Template', description: Em.I18n.t('widget.type.template.description'), properties: [ + { + property_name : 'display_unit', + display_name: 'unit', + isRequired: false + } ] } ]; http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/ambari-web/app/models/widget_property.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/widget_property.js b/ambari-web/app/models/widget_property.js new file mode 100644 index 0000000..5b2db2d --- /dev/null +++ b/ambari-web/app/models/widget_property.js @@ -0,0 +1,249 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var App = require('app'); +var validator = require('utils/validator'); +var numericUtils = require('utils/number_utils'); + +App.WidgetProperty = Ember.Object.extend({ + + /** + * label to be shown for property + * @type {String} + */ + label: '', + + /** + * PORT|METRIC|AGGREGATE + * @type {String} + */ + type: '', + + /** + * config property value + * @type {*} + */ + value: null, + + + /** + * input displayType + * one of 'textFields', 'textArea', 'select' or 'threshold' + * @type {String} + */ + displayType: '', + + + /** + * space separated list of css class names to use + * @type {String} + */ + classNames: '', + + /** + * view class according to <code>displayType</code> + * @type {Em.View} + */ + viewClass: function () { + var displayType = this.get('displayType'); + switch (displayType) { + case 'textField': + return App.WidgetPropertyTextFieldView; + case 'threshold': + return App.WidgetPropertyThresholdView; + case 'select': + return App.WidgetPropertySelectView; + default: + console.error('Parsing Widget Property: Unable to find viewClass for displayType ', displayType); + } + }.property('displayType'), + + /** + * Define whether property is valid + * Computed property + * Should be defined in child class + * @type {Boolean} + */ + isValid: function () { + return true; + }.property(), + + /** + * Define whether property is required by user + * @type {Boolean} + */ + isRequired: true +}); + +App.WidgetProperties = { + + WidgetName: App.WidgetProperty.extend({ + name: 'widget_name', + label: 'Widget Name', + displayType: 'textField', + classNames: 'widget-property-text-input' + }), + + Description: App.WidgetProperty.extend({ + name: 'description', + label: 'Description', + displayType: 'textArea', + classNames: 'widget-property-text-area' + }), + + Unit: App.WidgetProperty.extend({ + name: 'display-unit', + label: 'Unit', + displayType: 'textField', + classNames: 'widget-property-unit', + isValid: function () { + return this.get('isRequired') ? this.get('value') : true; + }.property('value') + }), + + GraphType: App.WidgetProperty.extend({ + name: 'graph_type', + label: 'Graph Type', + displayType: 'select', + options: ["LINE", "STACK"] + }), + + TimeRange: App.WidgetProperty.extend({ + name: 'time_range', + label: 'Time Range', + displayType: 'select', + options: ["Last 1 hour", "Last 2 hours", "Last 4 hours", "Last 12 hours", "Last 24 hours", + "Last 1 week", "Last 1 month", "Last 1 year"] + }), + + + Threshold: App.WidgetProperty.extend({ + + name: 'threshold', + label: 'Thresholds', + + /** + * threshold-value + * @type {string} + */ + smallValue: '', + bigValue: '', + badgeOK: 'OK', + badgeWarning: 'WARNING', + badgeCritical: 'CRITICAL', + + displayType: 'threshold', + + classNames: 'widget-property-threshold', + + apiProperty: [], + + init: function () { + this._super(); + }, + + /** + * Check if <code>smallValue</code> is valid float number + * @return {boolean} + */ + isSmallValueValid: function () { + var value = this.get('smallValue'); + if (!this.get('isRequired') && !this.get('smallValue') && !this.get('bigValue')) { + return true; + } else if (!this.get('smallValue')) { + return false; + } + value = ('' + value).trim(); + return validator.isValidFloat(value) && value > 0; + }.property('smallValue', 'bigValue'), + + /** + * Check if <code>bigValue</code> is valid float number + * @return {boolean} + */ + isBigValueValid: function () { + var value = this.get('bigValue'); + if (!this.get('isRequired') && !this.get('smallValue') && !this.get('bigValue')) { + return true; + } else if (!this.get('bigValue')) { + return false; + } + value = ('' + value).trim(); + return validator.isValidFloat(value) && value > 0; + }.property('bigValue', 'smallValue'), + + thresholdError: function () { + if (this.get('isSmallValueValid') && this.get('isBigValueValid')) { + return Number(this.get('smallValue')) > Number(this.get('bigValue')); + } else { + return false; + } + }.property('smallValue', 'bigValue', 'isSmallValueValid', 'isBigValueValid'), + + isValid: function () { + return this.get('isSmallValueValid') && this.get('isBigValueValid') && (!this.get('thresholdError')); + }.property( 'isSmallValueValid', 'isBigValueValid', 'thresholdError'), + + /** + * Define whether warning threshold < critical threshold + * @type {Boolean} + */ + errorMsg: function () { + return this.get('thresholdError') ? "Threshold 1 should be smaller than threshold 2" : null; + }.property('thresholdError') + + }) +}; + +App.WidgetProperties.Thresholds = { + + PercentageThreshold: App.WidgetProperties.Threshold.extend({ + + /** + * Check if <code>smallValue</code> is valid float number + * @return {boolean} + */ + isSmallValueValid: function () { + var value = this.get('smallValue'); + if (!this.get('isRequired') && !this.get('smallValue') && !this.get('bigValue')) { + return true; + } else if (!this.get('smallValue')) { + return false; + } + value = ('' + value).trim(); + return validator.isValidFloat(value) && value > 0 && value <=1; + }.property('smallValue', 'bigValue'), + + /** + * Check if <code>bigValue</code> is valid float number + * @return {boolean} + */ + isBigValueValid: function () { + var value = this.get('bigValue'); + if (!this.get('isRequired') && !this.get('smallValue') && !this.get('bigValue')) { + return true; + } else if (!this.get('bigValue')) { + return false; + } + value = ('' + value).trim(); + return validator.isValidFloat(value) && value > 0 && value <= 1; + }.property('bigValue', 'smallValue') + + }) +} + http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/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 24e52a3..e2acbe2 100644 --- a/ambari-web/app/styles/enhanced_service_dashboard.less +++ b/ambari-web/app/styles/enhanced_service_dashboard.less @@ -108,3 +108,29 @@ } } } + +#add-widget-step2 { + .badge-container { + height: 26px; + .OK, .WARNING, .CRITICAL { + line-height: 26px; + height: 26px; + display: inline-block; + width: 70px; + text-align: center; + } + .OK { + background-color: @health-status-green; + } + .WARNING { + background-color: @health-status-orange; + } + .CRITICAL { + background-color: @health-status-red; + } + } + .icon-asterisk { + color: red; + font-size: 8px; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/ambari-web/app/templates/main/service/widgets/create/step2.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/service/widgets/create/step2.hbs b/ambari-web/app/templates/main/service/widgets/create/step2.hbs index 50f7b86..8ab4c03 100644 --- a/ambari-web/app/templates/main/service/widgets/create/step2.hbs +++ b/ambari-web/app/templates/main/service/widgets/create/step2.hbs @@ -25,6 +25,22 @@ </div> </div> + <div> + <form class="form-horizontal"> + {{#each property in controller.widgetProperties}} + <div {{bindAttr class=":control-group property.name property.isValid::error"}}> + <label class="control-label">{{property.label}} + {{#if property.isRequired }} + <i class="icon-asterisk"></i> + {{/if}} + </label> + <div class="controls"> + {{view property.viewClass propertyBinding="property"}} + </div> + </div> + {{/each}} + </form> + </div> <div class="btn-area"> http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/ambari-web/app/templates/main/service/widgets/create/widget_property_threshold.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/service/widgets/create/widget_property_threshold.hbs b/ambari-web/app/templates/main/service/widgets/create/widget_property_threshold.hbs new file mode 100644 index 0000000..ac8b194 --- /dev/null +++ b/ambari-web/app/templates/main/service/widgets/create/widget_property_threshold.hbs @@ -0,0 +1,32 @@ +{{! +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +}} + +<div> + <div class="span2 badge-container"><span {{bindAttr class=":label view.property.badgeOK"}}>{{view.property.badgeOK}}</span> </div> + <div {{bindAttr class=":span2 property.isSmallValueValid::error"}}> + {{view Em.TextField valueBinding="view.property.smallValue" class ="span10"}} + </div> + <div class="span2 badge-container"><span {{bindAttr class=":label view.property.badgeWarning"}}>{{view.property.badgeWarning}}</span> </div> + <div {{bindAttr class=":span2 property.isBigValueValid::error"}}> + {{view Em.TextField valueBinding="view.property.bigValue" class ="span10"}} + </div> + <div class="span3 badge-container"><span {{bindAttr class=":label view.property.badgeCritical"}}>{{view.property.badgeCritical}}</span> </div> + <div {{bindAttr class="property.threshold:error"}}> + {{view.property.errorMsg}} + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/ff459837/ambari-web/app/views/main/service/widgets/create/step2_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/service/widgets/create/step2_view.js b/ambari-web/app/views/main/service/widgets/create/step2_view.js index 610e98c..4bbcd44 100644 --- a/ambari-web/app/views/main/service/widgets/create/step2_view.js +++ b/ambari-web/app/views/main/service/widgets/create/step2_view.js @@ -22,8 +22,26 @@ App.WidgetWizardStep2View = Em.View.extend({ didInsertElement: function () { var controller = this.get('controller'); + controller.renderProperties(); } +}); + + +App.WidgetPropertyTextFieldView = Em.TextField.extend({ + valueBinding: 'property.value', + classNameBindings: ['property.classNames', 'parentView.basicClass'] +}); +App.WidgetPropertyThresholdView = Em.View.extend({ + templateName: require('templates/main/service/widgets/create/widget_property_threshold'), + classNameBindings: ['property.classNames', 'parentView.basicClass'] }); +App.WidgetPropertySelectView = Em.Select.extend({ + selectionBinding: 'property.value', + contentBinding: 'property.options', + classNameBindings: ['property.classNames', 'parentView.basicClass'] +}); + +
