Selaa lähdekoodia

AMBARI-10183. Implement toggle and combo widget for config (onechiporenko)

Oleg Nechiporenko 10 vuotta sitten
vanhempi
commit
aaf4233c15

+ 4 - 1
ambari-web/app/assets/licenses/NOTICE.txt

@@ -47,4 +47,7 @@ This product includes Moment.js (https://github.com/moment/moment/ - MIT license
 This product includes Ember QUnit (https://github.com/rwjblue/ember-qunit - MIT license)
 
 This product includes bootstrap-slider 4.5.6 (https://github.com/seiyria/bootstrap-slider - MIT License)
-Copyright (C) 2015 by Kyle Kemp, Rohit Kalkur, and contributors
+Copyright (C) 2015 by Kyle Kemp, Rohit Kalkur, and contributors
+
+This product includes bootstrap-slider 3.3.2 (https://github.com/nostalgiaz/bootstrap-switch - Apache License, Version 2.0)
+Copyright (C) 2015 by Mattia Larentis (mattia [at] larentis [*dot*] eu)

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

@@ -165,9 +165,11 @@ var files = ['test/init_model_test',
   'test/utils/ui_effects_test',
   'test/utils/updater_test',
   'test/views/common/chart/linear_time_test',
+  'test/views/common/configs/widgets/combo_config_widget_view_test',
   'test/views/common/configs/widgets/config_widget_view_test',
   'test/views/common/configs/widgets/list_config_widget_view_test',
   'test/views/common/configs/widgets/slider_config_widget_view_test',
+  'test/views/common/configs/widgets/toggle_config_widget_view_test',
   'test/views/common/ajax_default_error_popup_body_test',
   'test/views/common/filter_combo_cleanable_test',
   'test/views/common/filter_view_test',

+ 0 - 1
ambari-web/app/controllers/global/cluster_controller.js

@@ -280,7 +280,6 @@ App.ClusterController = Em.Controller.extend({
         });
 
         updater.updateServiceMetric(function () {
-          App.config.loadConfigsFromStack(App.Service.find().mapProperty('serviceName'));
           if (App.get('supports.enhancedConfigs')) {
             App.config.loadConfigsFromStack(App.Service.find().mapProperty('serviceName'));
           }

+ 40 - 4
ambari-web/app/styles/widgets.less

@@ -17,9 +17,12 @@
  */
 @import 'common.less';
 
+@undo-btn-margin: 20px;
+@combo-widget-width: 100px;
+
 .widget {
   .action-button {
-    margin-left: 20px;
+    margin-left: @undo-btn-margin;
   }
 }
 
@@ -72,7 +75,6 @@
     border-radius: 11px;
     box-shadow: none;
   }
-
 }
 
 .spinner-input-widget {
@@ -110,8 +112,42 @@
     }
   }
 
-  .restore-btn {
+  .undo-button {
     padding: 2px 10px;
     float: left;
   }
-}
+}
+
+.toggle-widget {
+  .undo-button {
+    margin-left: @undo-btn-margin;
+  }
+}
+
+.combo-widget {
+  .input-append {
+
+    > input {
+      border-radius: 3px 0 0 3px;
+      width: @combo-widget-width;
+
+      &[disabled] {
+        background: #fff;
+        cursor: default;
+      }
+    }
+
+    .btn-group {
+      display: inline-block;
+      margin-left: -1px;
+
+      .btn {
+        border-radius: 0 3px 3px 0;
+      }
+    }
+  }
+
+  .action-button {
+    margin-left: @undo-btn-margin;
+  }
+}

+ 42 - 0
ambari-web/app/templates/common/configs/widgets/combo_config_widget.hbs

@@ -0,0 +1,42 @@
+{{!
+* 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.
+}}
+
+<p>{{view.config.name}}</p>
+<div class="input-append pull-left">
+  {{view Em.TextField valueBinding="view.content.value" disabled="disabled"}}
+  <div class="dropdown btn-group">
+    <button class="btn dropdown-toggle" data-toggle="dropdown">
+      <span class="caret"></span>
+    </button>
+    <ul class="dropdown-menu">
+      {{#each item in view.content.valuesList}}
+        <li>
+          <a href="#" {{action setConfigValue item.configValue target="view"}}>{{item.widgetValue}}</a>
+        </li>
+      {{/each}}
+    </ul>
+  </div>
+</div>
+{{#if view.valueIsChanged}}
+  <div class="action-button pull-left">
+    <a class="btn btn-small" href="#" {{action "restoreValue" target="view"}}>
+      <i class="icon-undo"></i>
+    </a>
+  </div>
+{{/if}}
+<div class="clearfix"></div>

+ 1 - 1
ambari-web/app/templates/common/configs/widgets/time_interval_spinner.hbs

@@ -20,7 +20,7 @@
   {{view App.SpinnerInputView contentBinding="spinnerContent" disabledBinding="view.disabled"}}
 {{/each}}
 {{#if view.valueIsChanged}}
-  <div class="restore-btn">
+  <div class="action-button">
     <a class="btn btn-small" href="#" {{action "restoreValue" target="view"}}>
       <i class="icon-undo"></i>
     </a>

+ 30 - 0
ambari-web/app/templates/common/configs/widgets/toggle_config_widget.hbs

@@ -0,0 +1,30 @@
+{{!
+* 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.
+}}
+
+<p>{{view.config.name}}</p>
+<div class="pull-left">
+  {{view Ember.Checkbox checkedBinding="view.switcherValue"}}
+</div>
+{{#if view.valueIsChanged}}
+  <div class="pull-left action-button">
+    <a class="btn btn-small" href="#" {{action "restoreValue" target="view"}}>
+      <i class="icon-undo"></i>
+    </a>
+  </div>
+{{/if}}
+<div class="clearfix"></div>

+ 2 - 0
ambari-web/app/views.js

@@ -52,9 +52,11 @@ require('views/common/configs/compare_property_view');
 require('views/common/configs/config_history_flow');
 require('views/common/configs/custom_category_views/notification_configs_view');
 require('views/common/configs/widgets/config_widget_view');
+require('views/common/configs/widgets/combo_config_widget_view');
 require('views/common/configs/widgets/list_config_widget_view');
 require('views/common/configs/widgets/slider_config_widget_view');
 require('views/common/configs/widgets/time_interval_spinner_view');
+require('views/common/configs/widgets/toggle_config_widget_view');
 require('views/common/configs/widgets/overrides/slider_config_widget_override_view');
 require('views/common/configs/service_config_layout_tab_view');
 require('views/common/filter_combobox');

+ 113 - 0
ambari-web/app/views/common/configs/widgets/combo_config_widget_view.js

@@ -0,0 +1,113 @@
+/**
+ * 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');
+
+/**
+ * Combo box widget view for config property.
+ * @type {Em.View}
+ */
+App.ComboConfigWidgetView = App.ConfigWidgetView.extend({
+  templateName: require('templates/common/configs/widgets/combo_config_widget'),
+  classNames: ['widget', 'combo-widget'],
+
+  /**
+   * Object with following structure:
+   * {String} .value - value in widget format
+   * {Object[]} .valuesList - map of entries and entry_labels
+   *   {String} .configValue - value in config format
+   *   {String} .widgetValue - value in widget format
+   *
+   * @property content
+   * @type {Em.Object}
+   */
+  content: null,
+
+  didInsertElement: function() {
+    this.generateContent();
+  },
+
+  /**
+   * Generate content for view. Set values map and current value.
+   *
+   * @method generateContent
+   */
+  generateContent: function() {
+    this.set('content', Em.Object.create({}));
+    this.set('content.valuesList', this.convertToWidgetUnits(this.get('config.stackConfigProperty.valueAttributes')));
+    this.set('content.value', this.generateWidgetValue(this.get('config.value')));
+  },
+
+  /**
+   * Generate values map according to widget/value format.
+   *
+   * @method convertToWidgetUnits
+   * @params {Object} valueAttributes
+   * @returns {Object[]} - values list map @see content.valuesList
+   */
+  convertToWidgetUnits: function(valueAttributes) {
+    return Em.get(valueAttributes, 'entries').map(function(item, index) {
+      return Em.Object.create({
+        configValue: item,
+        widgetValue: Em.get(valueAttributes, 'entry_labels.' + index)
+      });
+    });
+  },
+
+  /**
+   * Get widget value by specified config value.
+   *
+   * @method generateWidgetValue
+   * @param {String} value - value in config property format
+   * @returns {String}
+   */
+  generateWidgetValue: function(value) {
+    return this.get('content.valuesList').findProperty('configValue', value).get('widgetValue');
+  },
+
+  /**
+   * Get config value by specified widget value.
+   *
+   * @method generateConfigValue
+   * @param {String} value - value in widget property format
+   * @returns {String}
+   */
+  generateConfigValue: function(value) {
+    return this.get('content.valuesList').findProperty('widgetValue', value).get('configValue');
+  },
+
+  /**
+   * Action to set config value.
+   *
+   * @method setConfigValue
+   * @param {Object} e
+   */
+  setConfigValue: function(e) {
+    this.set('config.value', e.context);
+    this.set('content.value', this.generateWidgetValue(e.context));
+  },
+
+  /**
+   * @override App.ConfigWidgetView.restoreValue
+   * @method restoreValue
+   */
+  restoreValue: function() {
+    this.setConfigValue({ context: this.get('config.defaultValue') });
+  }
+
+});

+ 1 - 0
ambari-web/app/views/common/configs/widgets/time_interval_spinner_view.js

@@ -131,6 +131,7 @@ App.TimeIntervalSpinnerView = App.ConfigWidgetView.extend({
     var propertyUnit = property.get('stackConfigProperty.valueAttributes').unit;
     return this.convertToWidgetUnits(value, propertyUnit, widgetUnits);
   },
+
   /**
    * Convert property value to widget format units.
    *

+ 108 - 0
ambari-web/app/views/common/configs/widgets/toggle_config_widget_view.js

@@ -0,0 +1,108 @@
+/**
+ * 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');
+
+/**
+ * Toggle wiget view for config property.
+ * @type {Em.View}
+ */
+App.ToggleConfigWidgetView = App.ConfigWidgetView.extend({
+  templateName: require('templates/common/configs/widgets/toggle_config_widget'),
+  classNames: ['widget', 'toggle-widget'],
+
+  /**
+   * Saved switcher for current config.
+   *
+   * @property switcher
+   */
+  switcher: null,
+
+  /**
+   * Value used in the checkbox.
+   * <code>config.value</code> can't be used because it's string.
+   *
+   * @property switcherValue
+   * @type {boolean}
+   */
+  switcherValue: false,
+
+  /**
+   * Update config value using <code>switcherValue</code>.
+   * switcherValue is boolean, but config value should be a string 'true'|'false'.
+   *
+   * @method updateConfigValue
+   */
+  updateConfigValue: function () {
+    this.set('config.value', '' + this.get('switcherValue'));
+  },
+
+  /**
+   * Get value for <code>switcherValue</code> (boolean) using <code>config.value</code> (string).
+   *
+   * @param configValue
+   * @returns {boolean} true for 'true', false for 'false'
+   * @method getNewSwitcherValue
+   */
+  getNewSwitcherValue: function (configValue) {
+    return 'true' === configValue;
+  },
+
+  didInsertElement: function () {
+    this.set('switcherValue', this.getNewSwitcherValue(this.get('config.value')));
+    // plugin should be initiated after applying binding for switcherValue
+    Em.run.later('sync', function() {
+      this.initSwitcher();
+    }.bind(this), 10);
+    this.addObserver('switcherValue', this.updateConfigValue);
+  },
+
+  /**
+   * Init switcher plugin.
+   *
+   * @method initSwitcher
+   */
+  initSwitcher: function () {
+    var labels = this.get('config.stackConfigProperty.valueAttributes.entry_labels'),
+      self = this;
+    var switcher = this.$("input").bootstrapSwitch({
+      onText: labels[0],
+      offText: labels[1],
+      offColor: 'danger',
+      handleWidth: 85,
+      onSwitchChange: function (event, state) {
+        self.set('switcherValue', state);
+      }
+    });
+    this.set('switcher', switcher);
+  },
+
+  /**
+   * Restore default config value and toggle switcher.
+   *
+   * @override App.ConfigWidgetView.restoreValue
+   * @method restoreValue
+   */
+  restoreValue: function () {
+    this._super();
+    var value = this.getNewSwitcherValue(this.get('config.value'));
+    this.get('switcher').bootstrapSwitch('toggleState', value);
+    this.set('switcherValue', value);
+  }
+
+});

+ 141 - 0
ambari-web/test/views/common/configs/widgets/combo_config_widget_view_test.js

@@ -0,0 +1,141 @@
+/**
+ * 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');
+
+describe('App.ComboConfigWidgetView', function() {
+
+  beforeEach(function() {
+    this.view = App.ComboConfigWidgetView.create({});
+  });
+
+  afterEach(function() {
+    this.view.destroy();
+    this.view = null;
+  });
+
+  describe('#convertToWidgetUnits', function() {
+    var tests = [
+      {
+        valueAttributes: {
+          entry_labels: ["Item A", "Item B"],
+          entries: ["a", "b"]
+        },
+        e: [
+          {
+            configValue: "a",
+            widgetValue: "Item A"
+          },
+          {
+            configValue: "b",
+            widgetValue: "Item B"
+          }
+        ]
+      }
+    ];
+
+    tests.forEach(function(test) {
+      it('should convert {0} to {1}'.format(JSON.stringify(test.valueAttributes), JSON.stringify(test.e)), function() {
+        var result = this.view.convertToWidgetUnits(test.valueAttributes);
+        expect(JSON.parse(JSON.stringify(result))).to.eql(test.e);
+      });
+    });
+  });
+
+  describe('#generateWidgetValue', function() {
+    var tests = [
+      {
+        valuesList: [
+          Em.Object.create({
+            configValue: 'a',
+            widgetValue: 'Item A'
+          }),
+          Em.Object.create({
+            configValue: 'b',
+            widgetValue: 'Item B'
+          })
+        ],
+        value: 'a',
+        e: 'Item A'
+      },
+      {
+        valuesList: [
+          Em.Object.create({
+            configValue: 'a',
+            widgetValue: 'Item A'
+          }),
+          Em.Object.create({
+            configValue: 'b',
+            widgetValue: 'Item B'
+          })
+        ],
+        value: 'b',
+        e: 'Item B'
+      }
+    ];
+
+    tests.forEach(function(test) {
+      it('should convert config value: `{0}` to widget value: `{1}`'.format(test.value, test.e), function() {
+        this.view.set('content', {});
+        this.view.set('content.valuesList', Em.A(test.valuesList));
+        expect(this.view.generateWidgetValue(test.value)).to.be.equal(test.e);
+      });
+    });
+  });
+
+  describe('#generateConfigValue', function() {
+    var tests = [
+      {
+        valuesList: [
+          Em.Object.create({
+            configValue: 'a',
+            widgetValue: 'Item A'
+          }),
+          Em.Object.create({
+            configValue: 'b',
+            widgetValue: 'Item B'
+          })
+        ],
+        value: 'Item A',
+        e: 'a'
+      },
+      {
+        valuesList: [
+          Em.Object.create({
+            configValue: 'a',
+            widgetValue: 'Item A'
+          }),
+          Em.Object.create({
+            configValue: 'b',
+            widgetValue: 'Item B'
+          })
+        ],
+        value: 'Item B',
+        e: 'b'
+      }
+    ];
+
+    tests.forEach(function(test) {
+      it('should convert widget value: `{0}` to config value: `{1}`'.format(test.value, test.e), function() {
+        this.view.set('content', {});
+        this.view.set('content.valuesList', Em.A(test.valuesList));
+        expect(this.view.generateConfigValue(test.value)).to.be.equal(test.e);
+      });
+    });
+  });
+});

+ 70 - 0
ambari-web/test/views/common/configs/widgets/toggle_config_widget_view_test.js

@@ -0,0 +1,70 @@
+/**
+ * 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');
+
+describe('App.ToggleConfigWidgetView', function () {
+
+  beforeEach(function () {
+
+    this.view = App.ToggleConfigWidgetView.create({
+      initSwitcher: Em.K,
+      config: Em.Object.create({
+        name: 'a.b.c',
+        value: 'true',
+        defaultValue: 'true',
+        stackConfigProperty: Em.Object.create({
+          valueAttributes: {
+            "type": "value-list",
+            "entries": ["true", "false"],
+            "entry_labels": ["Active", "Inactive"],
+            "entries_editable": "false",
+            "selection_cardinality": 1
+          }
+        })
+      })
+    });
+    this.view.didInsertElement();
+  });
+
+  afterEach(function() {
+    this.view.destroy();
+    this.view = null;
+  });
+
+  describe('#getNewSwitcherValue', function () {
+
+    it('should represent string value to boolean', function () {
+      expect(this.view.getNewSwitcherValue('false')).to.be.false;
+      expect(this.view.getNewSwitcherValue('true')).to.be.true;
+    });
+
+  });
+
+  describe('#updateConfigValue', function () {
+
+    it('should represent boolean value to string', function () {
+      this.view.set('switcherValue', false);
+      expect(this.view.get('config.value')).to.equal('false');
+      this.view.set('switcherValue', true);
+      expect(this.view.get('config.value')).to.equal('true');
+    });
+
+  });
+
+});

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 21 - 0
ambari-web/vendor/scripts/bootstrap-switch.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 21 - 0
ambari-web/vendor/styles/bootstrap-switch.min.css


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä