Преглед на файлове

AMBARI-10951 Misc ui tweaks for template widget type. (atkach)

Andrii Tkach преди 10 години
родител
ревизия
dff2e0e026

+ 4 - 0
ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json

@@ -6,6 +6,10 @@
     "service_name" : "HBASE"
   },
   "metrics" : {
+    "network": {
+      "bytes_in": 7.698,
+      "bytes_out": 2.23342
+    },
     "jvm": {
       "HeapMemoryUsed": 61,
       "HeapMemoryMax": 100

+ 1 - 0
ambari-web/app/assets/test/tests.js

@@ -88,6 +88,7 @@ var files = ['test/init_model_test',
   'test/controllers/main/service/reassign/step6_controller_test',
   'test/controllers/main/service/reassign/step7_controller_test',
   'test/controllers/main/service/widgets/create/step1_controller_test',
+  'test/controllers/main/service/widgets/create/step2_controller_test',
   'test/controllers/main/dashboard_test',
   'test/controllers/main/host_test',
   'test/controllers/main/service/item_test',

+ 7 - 5
ambari-web/app/controllers/main/service/widgets/create/step2_controller.js

@@ -126,7 +126,7 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
    * @returns {boolean}
    */
   isExpressionComplete: function (expression) {
-    return expression && !expression.get('isInvalid') && !expression.get('isEmpty');
+    return Boolean(expression && !expression.get('isInvalid') && !expression.get('isEmpty'));
   },
 
   /**
@@ -155,10 +155,12 @@ App.WidgetWizardStep2Controller = Em.Controller.extend({
   isTemplateDataComplete: function (expressions, templateValue) {
     var isComplete = Boolean(expressions.length > 0 && templateValue.trim() !== '');
 
-    for (var i = 0; i < expressions.length; i++) {
-      if (!this.isExpressionComplete(expressions[i])) {
-        isComplete = false;
-        break;
+    if (isComplete) {
+      for (var i = 0; i < expressions.length; i++) {
+        if (!this.isExpressionComplete(expressions[i])) {
+          isComplete = false;
+          break;
+        }
       }
     }
     return isComplete;

+ 16 - 6
ambari-web/app/mixins/common/widgets/widget_mixin.js

@@ -71,6 +71,14 @@ App.WidgetMixin = Ember.Mixin.create({
    */
   content: null,
 
+  /**
+   * color of content calculated by thresholds
+   * @type {string}
+   */
+  contentColor: function () {
+    return this.get('value') ? 'green' : 'grey';
+  }.property('value'),
+
   beforeRender: function () {
     this.get('metrics').clear();
     this.loadMetrics();
@@ -350,7 +358,7 @@ App.WidgetMixin = Ember.Mixin.create({
   drawWidget: function () {
     if (this.get('isLoaded')) {
       this.calculateValues();
-      this.set('value', this.get('content.values')[0] && this.get('content.values')[0].computedValue);
+      this.set('value', (this.get('content.values')[0]) ? this.get('content.values')[0].computedValue : '');
     }
   },
 
@@ -358,13 +366,15 @@ App.WidgetMixin = Ember.Mixin.create({
    * calculate series datasets for graph widgets
    */
   calculateValues: function () {
-    var metrics = this.get('metrics');
-    var displayUnit = this.get('content.properties.display_unit');
-
     this.get('content.values').forEach(function (value) {
-      var computeExpression = this.computeExpression(this.extractExpressions(value), metrics);
+      var computeExpression = this.computeExpression(this.extractExpressions(value), this.get('metrics'));
       value.computedValue = value.value.replace(this.get('EXPRESSION_REGEX'), function (match) {
-        return (!Em.isNone(computeExpression[match])) ? computeExpression[match] + (displayUnit || "") : Em.I18n.t('common.na');
+        var float = parseFloat(computeExpression[match]);
+        if (isNaN(float)) {
+          return computeExpression[match] || "";
+        } else {
+          return String((float % 1 !== 0) ? float.toFixed(2) : float);
+        }
       });
     }, this);
   },

+ 1 - 8
ambari-web/app/models/widget.js

@@ -151,13 +151,6 @@ App.WidgetType.FIXTURES = [
     icon_path: '/img/widget-template.png',
     display_name: 'Template',
     description: Em.I18n.t('widget.type.template.description'),
-    properties: [
-      {
-        name : 'display_unit',
-        isRequired: false,
-        value: '',
-        placeholder: 'Optional: MB, ms, etc.'
-      }
-    ]
+    properties: []
   }
 ];

+ 1 - 1
ambari-web/app/templates/common/widget/template_widget.hbs

@@ -30,7 +30,7 @@
         <i class="icon-edit"></i>
       </a>
     {{/isAccessible}}
-    <div class="content"> {{view.value}}</div>
+    <div {{bindAttr class="view.contentColor :content"}}>{{view.displayValue}}</div>
     {{#if view.content.description}}
       <div class="hidden-description">
         {{view.content.description}}

+ 5 - 4
ambari-web/app/views/common/widget/number_widget_view.js

@@ -26,13 +26,14 @@ App.NumberWidgetView = Em.View.extend(App.WidgetMixin, {
    */
   value: '',
 
+  /**
+   * @type {string}
+   */
   displayValue: function () {
     var value = parseFloat(this.get('value'));
     if (isNaN(value)) return Em.I18n.t('common.na');
-    value = value % 1 != 0? value.toFixed(2): value;
-    var unit = this.get('content.properties.display_unit')? this.get('content.properties.display_unit'): '';
-    return value + unit;
-  }.property('value'),
+    return value + (this.get('content.properties.display_unit') || '');
+  }.property('value', 'content.properties.display_unit'),
 
   /**
    * common metrics container

+ 7 - 0
ambari-web/app/views/common/widget/template_widget_view.js

@@ -26,6 +26,13 @@ App.TemplateWidgetView = Em.View.extend(App.WidgetMixin, {
    */
   value: '',
 
+  /**
+   * @type {string}
+   */
+  displayValue: function () {
+    return (this.get('value')) ? this.get('value') : Em.I18n.t('common.na');
+  }.property('value'),
+
   /**
    * common metrics container
    * @type {Array}

+ 278 - 0
ambari-web/test/controllers/main/service/widgets/create/step2_controller_test.js

@@ -0,0 +1,278 @@
+/**
+ * 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.
+ */
+
+App = require('app');
+
+require('controllers/main/service/widgets/create/step2_controller');
+
+
+describe('App.WidgetWizardStep2Controller', function () {
+  var controller = App.WidgetWizardStep2Controller.create({
+    content: Em.Object.create()
+  });
+
+  describe("#isEditWidget", function() {
+    it("empty name", function() {
+      controller.set('content.controllerName', '');
+      controller.propertyDidChange('isEditWidget');
+      expect(controller.get('isEditWidget')).to.be.false;
+    });
+    it("correct name", function() {
+      controller.set('content.controllerName', 'widgetEditController');
+      controller.propertyDidChange('isEditWidget');
+      expect(controller.get('isEditWidget')).to.be.true;
+    });
+  });
+
+  describe("#filteredMetrics", function() {
+    var testCases = [
+      {
+        metric: {
+          point_in_time: false
+        },
+        type: null,
+        result: []
+      },
+      {
+        metric: {
+          point_in_time: true
+        },
+        type: null,
+        result: [
+          {
+            point_in_time: true
+          }
+        ]
+      },
+      {
+        metric: {
+          temporal: false
+        },
+        type: 'GRAPH',
+        result: []
+      },
+      {
+        metric: {
+          temporal: true
+        },
+        type: 'GRAPH',
+        result: [
+          {
+            temporal: true
+          }
+        ]
+      }
+    ];
+
+    testCases.forEach(function (test) {
+      it("type=" + test.type + "; temporal=" + test.metric.temporal + "; point_in_time=" + test.metric.point_in_time, function () {
+        controller.get('content').setProperties({
+          widgetType: test.type,
+          allMetrics: [test.metric]
+        });
+        controller.propertyDidChange('filteredMetrics');
+        expect(controller.get('filteredMetrics')).to.eql(test.result);
+      });
+    });
+  });
+
+  describe("#isSubmitDisabled", function () {
+    beforeEach(function () {
+      this.expressionFunc = sinon.stub(controller, 'isExpressionComplete');
+      this.graphFunc = sinon.stub(controller, 'isGraphDataComplete');
+      this.templateFunc = sinon.stub(controller, 'isTemplateDataComplete');
+      controller.set('expressions', ['']);
+    });
+    afterEach(function () {
+      this.expressionFunc.restore();
+      this.graphFunc.restore();
+      this.templateFunc.restore();
+      controller.get('expressions').clear();
+    });
+    it("invalid property", function () {
+      controller.set('widgetPropertiesViews', [Em.Object.create({isValid: false})]);
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.true;
+    });
+    it("valid number widget", function () {
+      controller.set('widgetPropertiesViews', []);
+      controller.set('content.widgetType', 'NUMBER');
+      this.expressionFunc.returns(true);
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.false;
+    });
+    it("invalid number widget", function () {
+      controller.set('widgetPropertiesViews', []);
+      controller.set('content.widgetType', 'NUMBER');
+      this.expressionFunc.returns(false);
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.true;
+    });
+    it("valid graph widget", function () {
+      controller.set('widgetPropertiesViews', []);
+      controller.set('content.widgetType', 'GRAPH');
+      this.graphFunc.returns(true);
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.false;
+    });
+    it("invalid graph widget", function () {
+      controller.set('widgetPropertiesViews', []);
+      controller.set('content.widgetType', 'GRAPH');
+      this.graphFunc.returns(false);
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.true;
+    });
+    it("valid template widget", function () {
+      controller.set('widgetPropertiesViews', []);
+      controller.set('content.widgetType', 'TEMPLATE');
+      this.templateFunc.returns(true);
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.false;
+    });
+    it("invalid template widget", function () {
+      controller.set('widgetPropertiesViews', []);
+      controller.set('content.widgetType', 'TEMPLATE');
+      this.templateFunc.returns(false);
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.true;
+    });
+    it("unknown widget type", function () {
+      controller.set('widgetPropertiesViews', []);
+      controller.set('content.widgetType', '');
+      controller.propertyDidChange('isSubmitDisabled');
+      expect(controller.get('isSubmitDisabled')).to.be.false;
+    });
+  });
+
+  describe("#isExpressionComplete()", function() {
+    var testCases = [
+      {
+        expression: null,
+        result: false
+      },
+      {
+        expression: Em.Object.create({isInvalid: true}),
+        result: false
+      },
+      {
+        expression: Em.Object.create({isInvalid: false, isEmpty: false}),
+        result: true
+      },
+      {
+        expression: Em.Object.create({isInvalid: false, isEmpty: true}),
+        result: false
+      }
+    ];
+    testCases.forEach(function (test) {
+      it("expression = " + test.expression, function () {
+        expect(controller.isExpressionComplete(test.expression)).to.equal(test.result);
+      });
+    });
+  });
+
+  describe("#isGraphDataComplete()", function() {
+    beforeEach(function () {
+      this.mock = sinon.stub(controller, 'isExpressionComplete');
+    });
+    afterEach(function () {
+      this.mock.restore();
+    });
+    it("dataSets is empty", function() {
+      expect(controller.isGraphDataComplete([])).to.be.false;
+    });
+    it("label is empty", function() {
+      expect(controller.isGraphDataComplete([Em.Object.create({label: ''})])).to.be.false;
+    });
+    it("expression is not complete", function() {
+      this.mock.returns(false);
+      expect(controller.isGraphDataComplete([Em.Object.create({label: 'abc'})])).to.be.false;
+    });
+    it("expression is complete", function() {
+      this.mock.returns(true);
+      expect(controller.isGraphDataComplete([Em.Object.create({label: 'abc'})])).to.be.true;
+    });
+  });
+
+  describe("#isTemplateDataComplete()", function() {
+    beforeEach(function () {
+      this.mock = sinon.stub(controller, 'isExpressionComplete');
+    });
+    afterEach(function () {
+      this.mock.restore();
+    });
+    it("expressions is empty", function() {
+      expect(controller.isTemplateDataComplete([])).to.be.false;
+    });
+    it("templateValue is empty", function() {
+      expect(controller.isTemplateDataComplete([{}], '')).to.be.false;
+    });
+    it("expression is not complete", function() {
+      this.mock.returns(false);
+      expect(controller.isTemplateDataComplete([{}], 'abc')).to.be.false;
+    });
+    it("expression is complete", function() {
+      this.mock.returns(true);
+      expect(controller.isTemplateDataComplete([{}], 'abc')).to.be.true;
+    });
+  });
+
+  describe("#addDataSet()", function() {
+    it("", function() {
+      controller.get('dataSets').clear();
+      controller.addDataSet(null, true);
+      expect(controller.get('dataSets').objectAt(0).get('id')).to.equal(1);
+      expect(controller.get('dataSets').objectAt(0).get('isRemovable')).to.equal(false);
+      controller.addDataSet(null);
+      expect(controller.get('dataSets').objectAt(1).get('id')).to.equal(2);
+      expect(controller.get('dataSets').objectAt(1).get('isRemovable')).to.equal(true);
+      controller.get('dataSets').clear();
+    });
+  });
+
+  describe("#removeDataSet()", function () {
+    it("", function () {
+      var dataSet = Em.Object.create();
+      controller.get('dataSets').pushObject(dataSet);
+      controller.removeDataSet({context: dataSet});
+      expect(controller.get('dataSets')).to.be.empty;
+    });
+  });
+
+  describe("#addExpression()", function() {
+    it("", function() {
+      controller.get('expressions').clear();
+      controller.addExpression(null, true);
+      expect(controller.get('expressions').objectAt(0).get('id')).to.equal(1);
+      expect(controller.get('expressions').objectAt(0).get('isRemovable')).to.equal(false);
+      controller.addExpression(null);
+      expect(controller.get('expressions').objectAt(1).get('id')).to.equal(2);
+      expect(controller.get('expressions').objectAt(1).get('isRemovable')).to.equal(true);
+      controller.get('expressions').clear();
+    });
+  });
+
+  describe("#removeExpression()", function () {
+    it("", function () {
+      var expression = Em.Object.create();
+      controller.get('expressions').pushObject(expression);
+      controller.removeExpression({context: expression});
+      expect(controller.get('expressions')).to.be.empty;
+    });
+  });
+
+});

+ 9 - 1
ambari-web/test/mixins/common/widget_mixin_test.js

@@ -275,7 +275,15 @@ describe('App.WidgetMixin', function() {
         value: '${a}'
       }]);
       mixinObject.calculateValues();
-      expect(mixinObject.get('content.values')[0].computedValue).to.equal(Em.I18n.t('common.na'));
+      expect(mixinObject.get('content.values')[0].computedValue).to.be.empty;
+    });
+    it("value is null", function() {
+      this.mock.returns({'${a}': null});
+      mixinObject.set('content.values', [{
+        value: '${a}'
+      }]);
+      mixinObject.calculateValues();
+      expect(mixinObject.get('content.values')[0].computedValue).to.be.empty;
     });
   });