You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2017/02/17 21:30:02 UTC

[04/15] ambari git commit: AMBARI-20043. Don't rerender all widgets when one of them is changed (onechiporenko)

AMBARI-20043. Don't rerender all widgets when one of them is changed (onechiporenko)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/05c76ed6
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/05c76ed6
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/05c76ed6

Branch: refs/heads/branch-feature-AMBARI-20053
Commit: 05c76ed609dfcf75e0ff211aa8032834ef8e9f73
Parents: 984b35e
Author: Oleg Nechiporenko <on...@apache.org>
Authored: Thu Feb 16 15:25:48 2017 +0200
Committer: Oleg Nechiporenko <on...@apache.org>
Committed: Fri Feb 17 10:26:47 2017 +0200

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   1 +
 ambari-web/app/data/dashboard_widgets.js        | 196 +++++++++
 ambari-web/app/messages.js                      |   2 +-
 .../app/mixins/common/track_request_mixin.js    |   6 +-
 .../mixins/main/dashboard/widgets/editable.js   |  91 +---
 .../dashboard/widgets/editable_with_limit.js    | 106 +----
 .../widgets/single_numeric_threshold.js         | 127 +-----
 .../main/dashboard/edit_widget_popup.hbs        |  20 +-
 .../edit_widget_popup_single_threshold.hbs      |  12 +-
 ambari-web/app/views.js                         |   1 +
 .../modal_popups/edit_dashboard_widget_popup.js | 436 +++++++++++++++++++
 ambari-web/app/views/main/dashboard/widget.js   | 173 ++------
 ambari-web/app/views/main/dashboard/widgets.js  | 266 ++---------
 .../views/main/dashboard/widgets/text_widget.js |  23 +-
 .../edit_dashboard_widget_popup_test.js         | 214 +++++++++
 .../test/views/main/dashboard/widget_test.js    | 112 +----
 .../test/views/main/dashboard/widgets_test.js   |  10 +-
 17 files changed, 968 insertions(+), 828 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index d47d558..05c1657 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -253,6 +253,7 @@ var files = [
   'test/views/common/widget/template_widget_view_test',
   'test/views/common/widget/heatmap_widget_view_test',
   'test/views/common/modal_popups/cluster_check_popup_test',
+  'test/views/common/modal_popups/edit_dashboard_widget_popup_test',
   'test/views/common/modal_popups/hosts_table_list_popup_test',
   'test/views/common/modal_popups/dependent_configs_list_popup_test',
   'test/views/main/admin_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/data/dashboard_widgets.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/data/dashboard_widgets.js b/ambari-web/app/data/dashboard_widgets.js
new file mode 100644
index 0000000..d58b0e2
--- /dev/null
+++ b/ambari-web/app/data/dashboard_widgets.js
@@ -0,0 +1,196 @@
+/**
+ * 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.
+ */
+
+module.exports = [
+  {
+    id: 1,
+    viewName: 'NameNodeHeapPieChartView',
+    sourceName: 'HDFS',
+    title: Em.I18n.t('dashboard.widgets.NameNodeHeap'),
+    threshold: [80, 90]
+  },
+  {
+    id: 2,
+    viewName: 'NameNodeCapacityPieChartView',
+    sourceName: 'HDFS',
+    title: Em.I18n.t('dashboard.widgets.HDFSDiskUsage'),
+    threshold: [85, 95]
+  },
+  {
+    id: 3,
+    viewName: 'NameNodeCpuPieChartView',
+    sourceName: 'HDFS',
+    title: Em.I18n.t('dashboard.widgets.NameNodeCpu'),
+    threshold: [90, 95]
+  },
+  {
+    id: 4,
+    viewName: 'DataNodeUpView',
+    sourceName: 'HDFS',
+    title: Em.I18n.t('dashboard.widgets.DataNodeUp'),
+    threshold: [80, 90]
+  },
+  {
+    id: 5,
+    viewName: 'NameNodeRpcView',
+    sourceName: 'HDFS',
+    title: Em.I18n.t('dashboard.widgets.NameNodeRpc'),
+    threshold: [1000, 3000]
+  },
+  {
+    id: 6,
+    viewName: 'ChartClusterMetricsMemoryWidgetView',
+    sourceName: 'HOST_METRICS',
+    title: Em.I18n.t('dashboard.clusterMetrics.memory'),
+    threshold: []
+  },
+  {
+    id: 7,
+    viewName: 'ChartClusterMetricsNetworkWidgetView',
+    sourceName: 'HOST_METRICS',
+    title: Em.I18n.t('dashboard.clusterMetrics.network'),
+    threshold: []
+  },
+  {
+    id: 8,
+    viewName: 'ChartClusterMetricsCPUWidgetView',
+    sourceName: 'HOST_METRICS',
+    title: Em.I18n.t('dashboard.clusterMetrics.cpu'),
+    threshold: []
+  },
+  {
+    id: 9,
+    viewName: 'ChartClusterMetricsLoadWidgetView',
+    sourceName: 'HOST_METRICS',
+    title: Em.I18n.t('dashboard.clusterMetrics.load'),
+    threshold: []
+  },
+  {
+    id: 10,
+    viewName: 'NameNodeUptimeView',
+    sourceName: 'HDFS',
+    title: Em.I18n.t('dashboard.widgets.NameNodeUptime'),
+    threshold: []
+  },
+  {
+    id: 11,
+    viewName: 'HDFSLinksView',
+    sourceName: 'HDFS',
+    title: Em.I18n.t('dashboard.widgets.HDFSLinks'),
+    threshold: []
+  },
+  {
+    id: 12,
+    viewName: 'HBaseLinksView',
+    sourceName: 'HBASE',
+    title: Em.I18n.t('dashboard.widgets.HBaseLinks'),
+    threshold: []
+  },
+  {
+    id: 13,
+    viewName: 'HBaseMasterHeapPieChartView',
+    sourceName: 'HBASE',
+    title: Em.I18n.t('dashboard.widgets.HBaseMasterHeap'),
+    threshold: [70, 90]
+  },
+  {
+    id: 14,
+    viewName: 'HBaseAverageLoadView',
+    sourceName: 'HBASE',
+    title: Em.I18n.t('dashboard.widgets.HBaseAverageLoad'),
+    threshold: [150, 250]
+  },
+  {
+    id: 15,
+    viewName: 'HBaseRegionsInTransitionView',
+    sourceName: 'HBASE',
+    title: Em.I18n.t('dashboard.widgets.HBaseRegionsInTransition'),
+    threshold: [3, 10],
+    isHiddenByDefault: true
+  },
+  {
+    id: 16,
+    viewName: 'HBaseMasterUptimeView',
+    sourceName: 'HBASE',
+    title: Em.I18n.t('dashboard.widgets.HBaseMasterUptime'),
+    threshold: []
+  },
+  {
+    id: 17,
+    viewName: 'ResourceManagerHeapPieChartView',
+    sourceName: 'YARN',
+    title: Em.I18n.t('dashboard.widgets.ResourceManagerHeap'),
+    threshold: [70, 90]
+  },
+  {
+    id: 18,
+    viewName: 'ResourceManagerUptimeView',
+    sourceName: 'YARN',
+    title: Em.I18n.t('dashboard.widgets.ResourceManagerUptime'),
+    threshold: []
+  },
+  {
+    id: 19,
+    viewName: 'NodeManagersLiveView',
+    sourceName: 'YARN',
+    title: Em.I18n.t('dashboard.widgets.NodeManagersLive'),
+    threshold: [50, 75]
+  },
+  {
+    id: 20,
+    viewName: 'YARNMemoryPieChartView',
+    sourceName: 'YARN',
+    title: Em.I18n.t('dashboard.widgets.YARNMemory'),
+    threshold: [50, 75]
+  },
+  {
+    id: 21,
+    viewName: 'SuperVisorUpView',
+    sourceName: 'STORM',
+    title: Em.I18n.t('dashboard.widgets.SuperVisorUp'),
+    threshold: [85, 95]
+  },
+  {
+    id: 22,
+    viewName: 'FlumeAgentUpView',
+    sourceName: 'FLUME',
+    title: Em.I18n.t('dashboard.widgets.FlumeAgentUp'),
+    threshold: [85, 95]
+  },
+  {
+    id: 23,
+    viewName: 'YARNLinksView',
+    sourceName: 'YARN',
+    title: Em.I18n.t('dashboard.widgets.YARNLinks'),
+    threshold: []
+  },
+  {
+    id: 24,
+    viewName: 'HawqSegmentUpView',
+    sourceName: 'HAWQ',
+    title: Em.I18n.t('dashboard.widgets.HawqSegmentUp'),
+    threshold: [75, 90]
+  },
+  {
+    id: 25,
+    viewName: 'PxfUpView',
+    sourceName: 'PXF',
+    title: Em.I18n.t('dashboard.widgets.PxfUp'),
+    threshold: []
+  }
+];
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 81833f3..5d69b53 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -2860,7 +2860,7 @@ Em.I18n.translations = {
   'dashboard.widgets.NodeManagersLive': 'NodeManagers Live',
   'dashboard.widgets.YARNMemory': 'YARN Memory',
   'dashboard.widgets.YARNLinks': 'YARN Links',
-  'dashboard.widgets.error.invalid': 'Invalid! Enter a number between 0 - {0}',
+  'dashboard.widgets.error.invalid': 'Invalid! Enter a number between {0} - {1}',
   'dashboard.widgets.error.smaller': 'Threshold 1 should be smaller than threshold 2!',
   'dashboard.widgets.HawqSegmentUp': 'HAWQ Segments Live',
   'dashboard.widgets.PxfUp': 'PXF Agents Live',

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/mixins/common/track_request_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/track_request_mixin.js b/ambari-web/app/mixins/common/track_request_mixin.js
index c665253..dd97b97 100644
--- a/ambari-web/app/mixins/common/track_request_mixin.js
+++ b/ambari-web/app/mixins/common/track_request_mixin.js
@@ -38,14 +38,14 @@ App.TrackRequestMixin = Em.Mixin.create({
     this.get('requestsInProgress').pushObject({
       request: request,
       id: requestId,
-      status: request.state(),
-      completed: ['resolved', 'rejected'].contains(request.state())
+      status: Em.tryInvoke(request, 'state'),
+      completed: ['resolved', 'rejected'].contains(Em.tryInvoke(request, 'state'))
     });
     request.always(function() {
       var requestInProgress = self.get('requestsInProgress').findProperty('id', requestId) || {};
       Em.setProperties(requestInProgress, {
         completed: true,
-        status: request.state()
+        status: Em.tryInvoke(request, 'state')
       });
     });
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/mixins/main/dashboard/widgets/editable.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/main/dashboard/widgets/editable.js b/ambari-web/app/mixins/main/dashboard/widgets/editable.js
index fcb6bfb..482ff6a 100644
--- a/ambari-web/app/mixins/main/dashboard/widgets/editable.js
+++ b/ambari-web/app/mixins/main/dashboard/widgets/editable.js
@@ -20,93 +20,22 @@ var App = require('app');
 
 App.EditableWidgetMixin = Em.Mixin.create({
 
-  hintInfo: '',
-
   editWidget: function () {
-    var self = this;
-    var configObj = Ember.Object.create({
-      thresholdMin: self.get('thresholdMin') + '',
-      thresholdMax: self.get('thresholdMax') + '',
-      hintInfo: self.get('hintInfo'),
-      isThresh1Error: false,
-      isThresh2Error: false,
-      errorMessage1: "",
-      errorMessage2: "",
-      maxValue: 'infinity',
-      observeNewThresholdValue: function () {
-        var thresholdMin = this.get('thresholdMin');
-        var thresholdMax = this.get('thresholdMax');
-        if (thresholdMin.trim() !== "") {
-          if (isNaN(thresholdMin) || thresholdMin < 0) {
-            this.set('isThresh1Error', true);
-            this.set('errorMessage1', 'Invalid! Enter a number larger than 0');
-          } else if ( this.get('isThresh2Error') === false && parseFloat(thresholdMax)<= parseFloat(thresholdMin)){
-            this.set('isThresh1Error', true);
-            this.set('errorMessage1', 'Threshold 1 should be smaller than threshold 2 !');
-          } else {
-            this.set('isThresh1Error', false);
-            this.set('errorMessage1', '');
-          }
-        } else {
-          this.set('isThresh1Error', true);
-          this.set('errorMessage1', 'This is required');
-        }
-
-        if (thresholdMax.trim() !== "") {
-          if (isNaN(thresholdMax) || thresholdMax < 0) {
-            this.set('isThresh2Error', true);
-            this.set('errorMessage2', 'Invalid! Enter a number larger than 0');
-          } else {
-            this.set('isThresh2Error', false);
-            this.set('errorMessage2', '');
-          }
-        } else {
-          this.set('isThresh2Error', true);
-          this.set('errorMessage2', 'This is required');
-        }
+    return App.EditDashboardWidgetPopup.show({
 
-      }.observes('thresholdMin', 'thresholdMax')
+      widgetView: this,
 
-    });
-
-    App.ModalPopup.show({
-      header: Em.I18n.t('dashboard.widgets.popupHeader'),
-      classNames: [ 'modal-edit-widget'],
-      modalDialogClasses: ['modal-lg'],
-      bodyClass: Ember.View.extend({
-        templateName: require('templates/main/dashboard/edit_widget_popup'),
-        configPropertyObj: configObj
+      sliderHandlersManager: App.EditDashboardWidgetPopup.DoubleHandlers.create({
+        maxValue: 'infinity',
+        thresholdMin: this.get('thresholdMin'),
+        thresholdMax: this.get('thresholdMax')
       }),
-      primary: Em.I18n.t('common.apply'),
-      onPrimary: function () {
-        configObj.observeNewThresholdValue();
-        if (!configObj.isThresh1Error && !configObj.isThresh2Error) {
-
-          var parent = self.get('parentView');
-          var userPreferences = parent.get('userPreferences');
-          userPreferences.threshold[Number(self.get('id'))] = [configObj.get('thresholdMin'), configObj.get('thresholdMax')];
-          parent.saveWidgetsSettings(userPreferences);
-          parent.renderWidgets();
-          this.hide();
-        }
-      },
 
-      didInsertElement: function () {
-        this._super();
-        var colors = [App.healthStatusGreen, App.healthStatusOrange, App.healthStatusRed]; //color green, orange ,red
-        var handlers = [33, 66]; //fixed value
+      sliderDisabled: true,
+      sliderHandlers: [33, 66],
+      sliderMaxValue: 100,
+      sliderColors: [App.healthStatusGreen, App.healthStatusOrange, App.healthStatusRed]
 
-        $("#slider-range").slider({
-          range: true,
-          disabled: true, //handlers cannot move
-          min: 0,
-          max: 100,
-          values: handlers,
-          create: function (event, ui) {
-            self.updateColors(handlers, colors);
-          }
-        });
-      }
     });
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/mixins/main/dashboard/widgets/editable_with_limit.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/main/dashboard/widgets/editable_with_limit.js b/ambari-web/app/mixins/main/dashboard/widgets/editable_with_limit.js
index ddf2a26..1cb96df 100644
--- a/ambari-web/app/mixins/main/dashboard/widgets/editable_with_limit.js
+++ b/ambari-web/app/mixins/main/dashboard/widgets/editable_with_limit.js
@@ -23,110 +23,18 @@ var App = require('app');
  */
 App.EditableWithLimitWidgetMixin = Em.Mixin.create({
 
-  hintInfo: '',
-
   editWidget: function () {
-    var parent = this;
-    var maxTmp = parseFloat(parent.get('maxValue'));
-    var configObj = Ember.Object.create({
-      thresholdMin: parent.get('thresholdMin') + '',
-      thresholdMax: parent.get('thresholdMax') + '',
-      hintInfo: parent.get('hintInfo'),
-      thresholdMinError: false,
-      thresholdMaxError: false,
-      thresholdMinErrorMessage: '',
-      thresholdMaxErrorMessage: '',
-      maxValue: maxTmp,
-      observeNewThresholdValue: function () {
-        var thresholdMin = this.get('thresholdMin');
-        var thresholdMax = this.get('thresholdMax');
-        if (thresholdMin.trim() !== '') {
-          if (isNaN(thresholdMin) || thresholdMin > maxTmp || thresholdMin < 0){
-            this.set('thresholdMinError', true);
-            this.set('thresholdMinErrorMessage', 'Invalid! Enter a number between 0 - ' + maxTmp);
-          } else if ( this.get('thresholdMaxError') === false && parseFloat(thresholdMax)<= parseFloat(thresholdMin)) {
-            this.set('thresholdMinError', true);
-            this.set('thresholdMinErrorMessage', 'Threshold 1 should be smaller than threshold 2 !');
-          } else {
-            this.set('thresholdMinError', false);
-            this.set('thresholdMinErrorMessage', '');
-          }
-        } else {
-          this.set('thresholdMinError', true);
-          this.set('thresholdMinErrorMessage', 'This is required');
-        }
-
-        if (thresholdMax.trim() !== '') {
-          if (isNaN(thresholdMax) || thresholdMax > maxTmp || thresholdMax < 0) {
-            this.set('thresholdMaxError', true);
-            this.set('thresholdMaxErrorMessage', 'Invalid! Enter a number between 0 - ' + maxTmp);
-          } else {
-            this.set('thresholdMaxError', false);
-            this.set('thresholdMaxErrorMessage', '');
-          }
-        } else {
-          this.set('thresholdMaxError', true);
-          this.set('thresholdMaxErrorMessage', 'This is required');
-        }
-
-        // update the slider handles and color
-        if (!this.get('thresholdMinError') && !this.get('thresholdMaxError')) {
-          $("#slider-range").slider('values', 0 , parseFloat(thresholdMin));
-          $("#slider-range").slider('values', 1 , parseFloat(thresholdMax));
-        }
-      }.observes('thresholdMin', 'thresholdMax')
-
-    });
 
-    App.ModalPopup.show({
-      header: Em.I18n.t('dashboard.widgets.popupHeader'),
-      classNames: ['modal-edit-widget'],
-      modalDialogClasses: ['modal-lg'],
-      bodyClass: Ember.View.extend({
-        templateName: require('templates/main/dashboard/edit_widget_popup'),
-        configPropertyObj: configObj
-      }),
-      primary: Em.I18n.t('common.apply'),
-      onPrimary: function () {
-        configObj.observeNewThresholdValue();
-        if (!configObj.thresholdMinError && !configObj.thresholdMaxError) {
-          parent.set('thresholdMin', parseFloat(configObj.get('thresholdMin')) );
-          parent.set('thresholdMax', parseFloat(configObj.get('thresholdMax')) );
-          if (!App.get('testMode')) {
-            var bigParent = parent.get('parentView');
-            bigParent.getUserPref(bigParent.get('persistKey'));
-            var oldValue = bigParent.get('currentPrefObject');
-            oldValue.threshold[parseInt(parent.id, 10)] = [configObj.get('thresholdMin'), configObj.get('thresholdMax')];
-            bigParent.postUserPref(bigParent.get('persistKey'),oldValue);
-          }
-          this.hide();
-        }
-      },
+    return App.EditDashboardWidgetPopup.show({
 
-      didInsertElement: function () {
-        this._super();
-        var handlers = [configObj.get('thresholdMin'), configObj.get('thresholdMax')];
-        var colors = [App.healthStatusRed, App.healthStatusOrange, App.healthStatusGreen]; //color red, orange, green
+      widgetView: this,
 
-        $("#slider-range").slider({
-          range: true,
-          min: 0,
-          max: maxTmp,
-          values: handlers,
-          create: function () {
-            parent.updateColors(handlers, colors);
-          },
-          slide: function (event, ui) {
-            parent.updateColors(ui.values, colors);
-            configObj.set('thresholdMin', ui.values[0] + '');
-            configObj.set('thresholdMax', ui.values[1] + '');
-          },
-          change: function (event, ui) {
-            parent.updateColors(ui.values, colors);
-          }
-        });
+      sliderHandlersManager: App.EditDashboardWidgetPopup.DoubleHandlers.create({
+        maxValue: parseFloat(this.get('maxValue')),
+        thresholdMin: this.get('thresholdMin'),
+        thresholdMax: this.get('thresholdMax')
+      })
 
-      }
     });
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/mixins/main/dashboard/widgets/single_numeric_threshold.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/main/dashboard/widgets/single_numeric_threshold.js b/ambari-web/app/mixins/main/dashboard/widgets/single_numeric_threshold.js
index 06bcffe..36172b7 100644
--- a/ambari-web/app/mixins/main/dashboard/widgets/single_numeric_threshold.js
+++ b/ambari-web/app/mixins/main/dashboard/widgets/single_numeric_threshold.js
@@ -23,126 +23,25 @@ var App = require('app');
  */
 App.SingleNumericThresholdMixin = Em.Mixin.create({
 
-  /**
-   * @type {Em.Object}
-   * @class
-   */
-  widgetConfig: Ember.Object.extend({
-    thresholdMin: '',
-    hintInfo: '',
-    isThresh1Error: false,
-    errorMessage1: "",
-
-    maxValue: 0,
-    observeThresh1Value: function () {
-      var thresholdMin = this.get('thresholdMin');
-      var maxValue = this.get('maxValue');
-
-      if (thresholdMin.trim() !== "") {
-        if (isNaN(thresholdMin) || thresholdMin > maxValue || thresholdMin < 0) {
-          this.set('isThresh1Error', true);
-          this.set('errorMessage1', Em.I18n.t('dashboard.widgets.error.invalid').format(maxValue));
-        } else {
-          this.set('isThresh1Error', false);
-          this.set('errorMessage1', '');
-        }
-      } else {
-        this.set('isThresh1Error', true);
-        this.set('errorMessage1', Em.I18n.t('admin.users.editError.requiredField'));
-      }
-        this.updateSlider();
-    }.observes('thresholdMin', 'maxValue'),
-
-    updateSlider: function () {
-      var thresholdMin = this.get('thresholdMin');
-      // update the slider handles and color
-      if (this.get('isThresh1Error') === false) {
-        $("#slider-range")
-          .slider('values', 0, parseFloat(thresholdMin))
-      }
-    }
-  }),
-
-  /**
-   * edit widget
-   * @param {object} event
-   */
   editWidget: function () {
-    var parent = this;
-    var maxTmp = parseFloat(this.get('maxValue'));
-    var configObj = this.get('widgetConfig').create({
-      thresholdMin: this.get('thresholdMin') + '',
-      hintInfo: this.get('hintInfo') + '',
-      maxValue: parseFloat(this.get('maxValue'))
-    });
+    return App.EditDashboardWidgetPopup.show({
+
+      widgetView: this,
 
-    App.ModalPopup.show({
-        header: Em.I18n.t('dashboard.widgets.popupHeader'),
-        classNames: ['modal-edit-widget'],
-        modalDialogClasses: ['modal-lg'],
-        bodyClass: Ember.View.extend({
-          templateName: require('templates/main/dashboard/edit_widget_popup_single_threshold'),
-          configPropertyObj: configObj
-        }),
-        primary: Em.I18n.t('common.apply'),
-        onPrimary: function () {
-          configObj.observeThresh1Value();
-          if (!configObj.isThresh1Error) {
-            var bigParent = parent.get('parentView');
-            parent.set('thresholdMin', parseFloat(configObj.get('thresholdMin')));
-            if (!App.get('testMode')) {
-              // save to persist
-              var userPreferences = bigParent.get('userPreferences');
-              userPreferences.threshold[parseInt(parent.get('id'), 10)] = [configObj.get('thresholdMin')];
-              bigParent.saveWidgetsSettings(userPreferences);
-              bigParent.renderWidgets();
-            }
-            this.hide();
-          }
-        },
-        didInsertElement: function () {
-          this._super();
-          var handlers = [configObj.get('thresholdMin')];
-          var _this = this;
+      sliderHandlersManager: App.EditDashboardWidgetPopup.SingleHandler.create({
+        thresholdMin: this.get('thresholdMin'),
+        maxValue: parseFloat(this.get('maxValue'))
+      }),
 
-          $("#slider-range").slider({
-            range: false,
-            min: 0,
-            max: maxTmp,
-            values: handlers,
-            create: function () {
-              _this.updateColors(handlers);
-            },
-            slide: function (event, ui) {
-              _this.updateColors(ui.values);
-              configObj.set('thresholdMin', ui.values[0] + '');
-            },
-            change: function (event, ui) {
-              _this.updateColors(ui.values);
-            }
-          });
-        },
+      bodyClass: App.EditDashboardWidgetPopup.EditDashboardWidgetPopupBody.extend({
+        templateName: require('templates/main/dashboard/edit_widget_popup_single_threshold')
+      }),
 
-      updateColors: function (handlers) {
-        var colors = [App.healthStatusGreen, App.healthStatusRed]; //color green,red
-        var colorstops = colors[0] + ", "; // start with the first color
-        for (var i = 0; i < handlers.length; i++) {
-          colorstops += colors[i] + " " + handlers[i] * 100 / maxTmp + "%,";
-          colorstops += colors[i + 1] + " " + handlers[i] * 100 / maxTmp + "%,";
-        }
-        colorstops += colors[colors.length - 1];
-        var sliderElement = $('#slider-range');
-        var css1 = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
-        sliderElement.css('background-image', css1);
-        var css2 = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
-        sliderElement.css('background-image', css2);
-        var css3 = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
-        sliderElement.css('background-image', css3);
+      sliderIsRange: false,
 
-        sliderElement.find('.ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
-      }
+      sliderColors: [App.healthStatusGreen, App.healthStatusRed]
 
-      });
+    });
 
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/templates/main/dashboard/edit_widget_popup.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/dashboard/edit_widget_popup.hbs b/ambari-web/app/templates/main/dashboard/edit_widget_popup.hbs
index 7caf085..60c9741 100644
--- a/ambari-web/app/templates/main/dashboard/edit_widget_popup.hbs
+++ b/ambari-web/app/templates/main/dashboard/edit_widget_popup.hbs
@@ -18,7 +18,7 @@
 <form class="form-horizontal" autocomplete="off">
     <div class="each-row">
         <div class="alert alert-info">
-          {{{view.configPropertyObj.hintInfo}}}
+          {{{view.parentView.widgetView.hintInfo}}}
         </div>
     </div>
 
@@ -32,20 +32,20 @@
         <div id="slider-value1" class="value-on-slider col-md-2">
           <input type="text" value="0" disabled="disabled" class="form-control" />
         </div>
-        <div id="slider-value2" {{bindAttr class="view.configPropertyObj.thresholdMinError:slider-error :value-on-slider :col-md-4 view.configPropertyObj.thresholdMinError:has-error"}}>
-          {{view Ember.TextField class="form-control" valueBinding="view.configPropertyObj.thresholdMin"}}
-          {{#if view.configPropertyObj.thresholdMinErrorMessage}}
-            <span class="help-block validation-block">{{view.configPropertyObj.thresholdMinErrorMessage}}</span>
+        <div id="slider-value2" {{bindAttr class="view.parentView.sliderHandlersManager.thresholdMinError:slider-error :value-on-slider :col-md-4 view.parentView.sliderHandlersManager.thresholdMinError:has-error"}}>
+          {{view Ember.TextField class="form-control" valueBinding="view.parentView.sliderHandlersManager.thresholdMin"}}
+          {{#if view.parentView.sliderHandlersManager.thresholdMinError}}
+            <span class="help-block validation-block">{{view.parentView.sliderHandlersManager.thresholdMinErrorMessage}}</span>
           {{/if}}
         </div>
-        <div id="slider-value3" {{bindAttr class="view.configPropertyObj.thresholdMaxError:slider-error :value-on-slider :col-md-4 view.configPropertyObj.thresholdMaxError:has-error"}}>
-          {{view Ember.TextField class="form-control" valueBinding="view.configPropertyObj.thresholdMax"}}
-          {{#if view.configPropertyObj.thresholdMaxErrorMessage}}
-            <span class="help-block validation-block">{{view.configPropertyObj.thresholdMaxErrorMessage}}</span>
+        <div id="slider-value3" {{bindAttr class="view.parentView.sliderHandlersManager.thresholdMaxError:slider-error :value-on-slider :col-md-4 view.parentView.sliderHandlersManager.thresholdMaxError:has-error"}}>
+          {{view Ember.TextField class="form-control" valueBinding="view.parentView.sliderHandlersManager.thresholdMax"}}
+          {{#if view.parentView.sliderHandlersManager.thresholdMaxError}}
+            <span class="help-block validation-block">{{view.parentView.sliderHandlersManager.thresholdMaxErrorMessage}}</span>
           {{/if}}
         </div>
         <div id="slider-value4" class="value-on-slider col-md-2">
-          {{view Em.TextField valueBinding="view.configPropertyObj.maxValue" classNames="form-control" disabled="disabled"}}
+          {{view Em.TextField valueBinding="view.parentView.sliderHandlersManager.maxValue" classNames="form-control" disabled="disabled"}}
         </div>
     </div>
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/templates/main/dashboard/edit_widget_popup_single_threshold.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/dashboard/edit_widget_popup_single_threshold.hbs b/ambari-web/app/templates/main/dashboard/edit_widget_popup_single_threshold.hbs
index 86ffb47..416852e 100644
--- a/ambari-web/app/templates/main/dashboard/edit_widget_popup_single_threshold.hbs
+++ b/ambari-web/app/templates/main/dashboard/edit_widget_popup_single_threshold.hbs
@@ -19,7 +19,7 @@
 <form class="form-horizontal" autocomplete="off">
   <div class="each-row">
     <div class="alert alert-info">
-      {{{view.configPropertyObj.hintInfo}}}
+      {{{view.parentView.widgetView.hintInfo}}}
     </div>
   </div>
 
@@ -33,14 +33,14 @@
     <div id="slider-value1" class="value-on-slider col-md-2">
       <input type="text" value="0" disabled="disabled" class="form-control" />
     </div>
-      <div id="slider-value2" {{bindAttr class="view.configPropertyObj.isThresh1Error:slider-error :value-on-slider :col-md-4 :col-md-offset-2 :col-sm-offset-2 view.configPropertyObj.isThresh1Error:has-error"}}>
-        {{view Ember.TextField valueBinding="view.configPropertyObj.thresholdMin" class="form-control"}}
-        {{#if view.configPropertyObj.errorMessage1}}
-          <span class="help-block validation-block">{{view.configPropertyObj.errorMessage1}}</span>
+      <div id="slider-value2" {{bindAttr class="view.parentView.sliderHandlersManager.thresholdMinError:slider-error :value-on-slider :col-md-4 :col-md-offset-2 :col-sm-offset-2 view.parentView.sliderHandlersManager.thresholdMinError:has-error"}}>
+        {{view Ember.TextField valueBinding="view.parentView.sliderHandlersManager.thresholdMin" class="form-control"}}
+        {{#if view.parentView.sliderHandlersManager.thresholdMinErrorMessage}}
+          <span class="help-block validation-block">{{view.parentView.sliderHandlersManager.thresholdMinErrorMessage}}</span>
         {{/if}}
       </div>
     <div id="slider-value3" class="value-on-slider col-md-2 col-md-offset-2 col-sm-offset-2">
-      {{view Em.TextField valueBinding="view.configPropertyObj.maxValue" classNames="form-control" disabled="disabled"}}
+      {{view Em.TextField valueBinding="view.parentView.sliderHandlersManager.maxValue" classNames="form-control" disabled="disabled"}}
     </div>
   </div>
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index 77b5d5a..6972d2a 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -33,6 +33,7 @@ require('views/common/chart/linear');
 require('views/common/chart/linear_time');
 require('views/common/modal_popup');
 require('views/common/modal_popups/alert_popup');
+require('views/common/modal_popups/edit_dashboard_widget_popup');
 require('views/common/modal_popups/manage_kdc_credentials_popup');
 require('views/common/modal_popups/confirmation_feedback_popup');
 require('views/common/modal_popups/confirmation_popup');

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/views/common/modal_popups/edit_dashboard_widget_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/modal_popups/edit_dashboard_widget_popup.js b/ambari-web/app/views/common/modal_popups/edit_dashboard_widget_popup.js
new file mode 100644
index 0000000..7ea1b32
--- /dev/null
+++ b/ambari-web/app/views/common/modal_popups/edit_dashboard_widget_popup.js
@@ -0,0 +1,436 @@
+/**
+ * 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');
+const {isValidFloat} = require('utils/validator');
+
+/**
+ * Thresholds manager for sliders with single value
+ * Usage:
+ * <pre>
+ * SingleHandler.create({
+ *    thresholdMin: 12,
+ *    minValue: 10,
+ *    maxValue: 100
+ * });
+ * </pre>
+ *
+ * @class SingleHandler
+ */
+const SingleHandler = Em.Object.extend({
+
+  /**
+   * @type {number}
+   */
+  thresholdMin: null,
+
+  /**
+   * @type {number}
+   * @default 0
+   */
+  minValue: 0,
+
+  /**
+   * @type {number}
+   */
+  maxValue: null,
+
+  /**
+   * Is <code>thresholdMin</code> invalid
+   * true - invalid
+   * false - valid
+   *
+   * @type {boolean}
+   */
+  thresholdMinError: Em.computed.bool('thresholdMinErrorMessage'),
+
+  /**
+   * Alias for <code>thresholdMinError</code>
+   *
+   * @type {boolean}
+   */
+  hasErrors: Em.computed.alias('thresholdMinError'),
+
+  /**
+   * Error message for <code>thresholdMin</code>
+   * <ul>
+   *  <li>Value is not a number</li>
+   *  <li>Value is out of range <code>(minValue - maxValue)</code></li>
+   * </ul>
+   * Empty message means that <code>thresholdMin</code> has valid value
+   *
+   * @type {string}
+   */
+  thresholdMinErrorMessage: function () {
+    var thresholdMin = this.get('thresholdMin');
+    var maxValue = this.get('maxValue');
+    var minValue = this.get('minValue');
+    if (!isValidFloat(thresholdMin) || thresholdMin > maxValue || thresholdMin < minValue) {
+      return Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
+    }
+    return '';
+  }.property('thresholdMin', 'maxValue'),
+
+  /**
+   * Formatted threshold value
+   *
+   * @type {number[]}
+   */
+  preparedThresholds: function () {
+    return [parseFloat(this.get('thresholdMin'))];
+  }.property('thresholdMin'),
+
+  /**
+   * Force set new values for threshold
+   *
+   * @param {number[]} newValues
+   */
+  updateThresholds(newValues) {
+    this.set('thresholdMin', newValues[0]);
+  }
+
+});
+
+/**
+ * Thresholds manager for sliders with double values
+ * Usage:
+ * <pre>
+ * SingleHandler.create({
+ *    thresholdMin: 12,
+ *    thresholdMax: 40,
+ *    minValue: 10,
+ *    maxValue: 100
+ * });
+ * </pre>
+ *
+ * @class DoubleHandlers
+ */
+const DoubleHandlers = SingleHandler.extend({
+
+  /**
+   * @type {number}
+   */
+  thresholdMax: null,
+
+  /**
+   * Is <code>thresholdMax</code> invalid
+   * true - invalid
+   * false - valid
+   *
+   * @type {boolean}
+   */
+  thresholdMaxError: Em.computed.bool('thresholdMaxErrorMessage'),
+
+  /**
+   * Is some threshold invalid
+   * true - some one is invalid
+   * false - thresholds are valid
+   *
+   * @type {boolean}
+   */
+  hasErrors: Em.computed.or('thresholdMinError', 'thresholdMaxError'),
+
+  /**
+   * Error message for <code>thresholdMin</code>
+   * <ul>
+   *  <li>Value is not a number</li>
+   *  <li>Value is out of range <code>(minValue - maxValue)</code></li>
+   *  <li><code>thresholdMin</code>-value greater than <code>thresholdMax</code>-value</li>
+   * </ul>
+   * Empty message means that <code>thresholdMin</code> has valid value
+   *
+   * @type {string}
+   */
+  thresholdMinErrorMessage: function () {
+    var thresholdMin = this.get('thresholdMin');
+    var thresholdMax = this.get('thresholdMax');
+    var maxValue = this.get('maxValue');
+    var minValue = this.get('minValue');
+    if (!isValidFloat(thresholdMin) || thresholdMin > maxValue || thresholdMin < minValue) {
+      return Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
+    }
+    if (this.get('thresholdMaxError') === false && thresholdMax <= thresholdMin) {
+      return Em.I18n.t('dashboard.widgets.error.smaller');
+    }
+    return '';
+  }.property('thresholdMin', 'thresholdMax'),
+
+  /**
+   * Error message for <code>thresholdMax</code>
+   * <ul>
+   *  <li>Value is not a number</li>
+   *  <li>Value is out of range <code>(minValue - maxValue)</code></li>
+   * </ul>
+   * Empty message means that <code>thresholdMax</code> has valid value
+   *
+   * @type {string}
+   */
+  thresholdMaxErrorMessage: function () {
+    var thresholdMax = this.get('thresholdMax');
+    var maxValue = this.get('maxValue');
+    var minValue = this.get('minValue');
+    if (!isValidFloat(thresholdMax) || thresholdMax > maxValue || thresholdMax < minValue) {
+      return Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
+    }
+    return '';
+  }.property('thresholdMax'),
+
+  /**
+   * Threshold values ready to save
+   *
+   * @type {number[]}
+   */
+  preparedThresholds: function () {
+    return [parseFloat(this.get('thresholdMin')), parseFloat(this.get('thresholdMax'))];
+  }.property('thresholdMin', 'thresholdMax'),
+
+  /**
+   * Force set new values for threshold
+   *
+   * @param {number[]} newValues
+   */
+  updateThresholds(newValues) {
+    this.set('thresholdMin', newValues[0]);
+    this.set('thresholdMax', newValues[1]);
+  }
+
+});
+
+/**
+ * Common body-view for popup with sliders
+ *
+ * @class EditDashboardWidgetPopupBody
+ */
+const EditDashboardWidgetPopupBody = Em.View.extend({
+  templateName: require('templates/main/dashboard/edit_widget_popup')
+});
+
+/**
+ * Popup with slider to edit dashboard widget
+ * Usage:
+ * <pre>
+ *   App.EditDashboardWidgetPopup.show({
+ *    widgetView: this,
+ *    sliderHandlersManager: App.EditDashboardWidgetPopup.DoubleHandlers.create({
+ *      maxValue: 100,
+ *      thresholdMin: this.get('thresholdMin'),
+ *      thresholdMax: this.get('thresholdMax')
+ *    })
+ *  });
+ * </pre>
+ *
+ * <code>widgetView</code> should be set to view with widget.
+ * Usually you will use <code>App.EditDashboardWidgetPopup</code> inside of some <code>App.DashboardWidgetView</code> instance,
+ * so <code>widgetView</code> may be set to <code>this</code>
+ * <code>sliderHandlersManager</code> should be set to some of the <code>App.EditDashboardWidgetPopup.SingleHandler</code>
+ * or <code>App.EditDashboardWidgetPopup.DoubleHandler</code>
+ *
+ * You can't use <code>App.EditDashboardWidgetPopup</code> without setting this two properties!
+ *
+ * @class App.EditDashboardWidgetPopup
+ */
+App.EditDashboardWidgetPopup = App.ModalPopup.extend({
+
+  header: Em.I18n.t('dashboard.widgets.popupHeader'),
+  classNames: ['modal-edit-widget'],
+  modalDialogClasses: ['modal-lg'],
+  primary: Em.I18n.t('common.apply'),
+  disablePrimary: Em.computed.alias('sliderHandlersManager.hasErrors'),
+
+  /**
+   * Can't be null or undefined
+   *
+   * @type {SingleHandler|DoubleHandlers}
+   */
+  sliderHandlersManager: null,
+
+  /**
+   * Widget view
+   * Can't be not a view. Used to save Thresholds. Normally it's an instance of <code>App.DashboardWidgetView</code>
+   *
+   * @type {Em.View}
+   */
+  widgetView: null,
+
+  /**
+   * Determines if slider is enabled for slide
+   * true - don't enabled
+   * false - enabled
+   *
+   * Determines if slider handlers should be updated with Threshold values
+   * true - don't update
+   * false - update
+   *
+   * Used as option <code>disabled</code> for $.ui.slider
+   *
+   * @type {boolean}
+   */
+  sliderDisabled: false,
+
+  /**
+   * Slider "ticks"
+   * Used as option <code>values</code> for $.ui.slider
+   *
+   * @type {number[]}
+   */
+  sliderHandlers: function () {
+    return this.get('sliderHandlersManager.preparedThresholds');
+  }.property('sliderHandlersManager.preparedThresholds.[]'),
+
+  /**
+   * Colors used for slider ranges
+   * @type {string[]}
+   */
+  sliderColors: [App.healthStatusRed, App.healthStatusOrange, App.healthStatusGreen],
+
+  /**
+   * Maximum value for slider
+   * Used as option <code>max</code> for $.ui.slider
+   *
+   * @type {number}
+   */
+  sliderMaxValue: Em.computed.alias('sliderHandlersManager.maxValue'),
+
+  /**
+   * Minimum value for slider
+   * Used as option <code>min</code> for $.ui.slider
+   *
+   * @type {number}
+   */
+  sliderMinValue: 0,
+
+  /**
+   * Check how many handlers has slider
+   * true - 2 handlers
+   * false - 1 handler
+   *
+   * Used as option <code>range</code> for $.ui.slider
+   *
+   * @type {boolean}
+   */
+  sliderIsRange: true,
+
+  bodyClass: EditDashboardWidgetPopupBody,
+
+  init() {
+    Em.assert('`widgetView` should be valid view', this.get('widgetView.isView'));
+    Em.assert('`sliderHandlersManager` should be set', !!this.get('sliderHandlersManager'));
+    return this._super(...arguments);
+  },
+
+  /**
+   * Save new threshold value on popup-close (means Primary click)
+   * Use <code>widgetView</code> to get <code>widgetsView</code> and save new values
+   * Current widget is updated too (without redrawing)
+   */
+  saveThreshold () {
+    let preparedThresholds = this.get('sliderHandlersManager.preparedThresholds');
+    this.get('widgetView').saveWidgetThresholds(preparedThresholds);
+  },
+
+  /**
+   * Update slider values when new threshold values are provided
+   * Don't do anything if some value is invalid or slider is disabled
+   *
+   * @private
+   */
+  _updateSliderValues: function() {
+    var sliderHandlersManager = this.get('sliderHandlersManager');
+    if (!sliderHandlersManager.get('hasErrors') && !this.get('sliderDisabled')) {
+      $('#slider-range').slider('values', sliderHandlersManager.get('preparedThresholds'));
+    }
+  }.observes('sliderDisabled', 'sliderHandlersManager.preparedThresholds.[]', 'sliderHandlersManager.hasErrors'),
+
+  onPrimary () {
+    let sliderHandlersManager = this.get('sliderHandlersManager');
+    if (!sliderHandlersManager.get('hasErrors')) {
+      this.saveThreshold();
+      this.hide();
+    }
+  },
+
+  /**
+   * Create slider in the popup when it's opened
+   */
+  createSlider() {
+    var self = this;
+    let sliderHandlersManager = this.get('sliderHandlersManager');
+    var handlers = this.get('sliderHandlers');
+
+    $('#slider-range').slider({
+      range: this.get('sliderIsRange'),
+      min: this.get('sliderMinValue'),
+      max: this.get('sliderMaxValue'),
+      disabled: this.get('sliderDisabled'),
+      values: handlers,
+      create: function () {
+        self.updateSliderColors(handlers);
+      },
+      slide: function (event, ui) {
+        self.updateSliderColors(ui.values);
+        sliderHandlersManager.updateThresholds(ui.values);
+      },
+      change: function (event, ui) {
+        self.updateSliderColors(ui.values);
+      }
+    });
+  },
+
+  didInsertElement: function () {
+    this._super();
+    this.createSlider();
+  },
+
+  /**
+   * Update colors on slider using <code>sliderColors</code> theme when user interacts with it
+   *
+   * @param {number[]} handlers
+   */
+  updateSliderColors(handlers) {
+    let gradient = this._getGradientStr(handlers);
+    $('#slider-range')
+      .css('background-image', '-webkit-' + gradient)
+      .css('background-image', '-ms-' + gradient)
+      .css('background-image', '-moz-' + gradient)
+      .find('.ui-widget-header').css({
+        'background-color': App.healthStatusOrange,
+        'background-image': 'none'
+      });
+  },
+
+  /**
+   * @param {number[]} handlers
+   * @returns {string}
+   * @private
+   */
+  _getGradientStr(handlers) {
+    let maxValue = this.get('sliderMaxValue');
+    let colors = this.get('sliderColors');
+    let gradient = colors[0] + ', ' + handlers.map((handler, i) => {
+        return `${colors[i]}  ${handlers[i] * 100 / maxValue}%, ${colors[i + 1]} ${handlers[i] * 100 / maxValue}%,`;
+      }).join('') + colors[colors.length - 1];
+    return `linear-gradient(left,${gradient})`;
+  }
+
+});
+
+App.EditDashboardWidgetPopup.reopenClass({
+  SingleHandler,
+  DoubleHandlers,
+  EditDashboardWidgetPopupBody
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/views/main/dashboard/widget.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/dashboard/widget.js b/ambari-web/app/views/main/dashboard/widget.js
index e7a626c..9fba906 100644
--- a/ambari-web/app/views/main/dashboard/widget.js
+++ b/ambari-web/app/views/main/dashboard/widget.js
@@ -30,9 +30,19 @@ App.DashboardWidgetView = Em.View.extend({
 
   sourceName: Em.computed.alias('widget.sourceName'),
 
+  /**
+   * Bound from template
+   *
+   * @type {object}
+   */
   widget: null,
 
   /**
+   * @type {Em.View}
+   */
+  widgetsView: Em.computed.alias('parentView'),
+
+  /**
    * @type {object} - record from model that serve as data source
    */
   model: function () {
@@ -79,6 +89,11 @@ App.DashboardWidgetView = Em.View.extend({
   hiddenInfoClass: "hidden-info-two-line",
 
   /**
+   * @type {string}
+   */
+  hintInfo: Em.computed.i18nFormat('dashboard.widgets.hintInfo.common', 'maxValue'),
+
+  /**
    * @type {number}
    * @default 0
    */
@@ -100,61 +115,6 @@ App.DashboardWidgetView = Em.View.extend({
    */
   isDataLoadedBinding: 'App.router.clusterController.isServiceContentFullyLoaded',
 
-  /**
-   * @type {Em.Object}
-   * @class
-   */
-  widgetConfig: Ember.Object.extend({
-    thresholdMin: '',
-    thresholdMax: '',
-    hintInfo: Em.computed.i18nFormat('dashboard.widgets.hintInfo.common', 'maxValue'),
-    thresholdMinError: false,
-    thresholdMaxError: false,
-    thresholdMinErrorMessage: "",
-    thresholdMaxErrorMessage: "",
-    maxValue: 0,
-    validateThreshold: function(thresholdName) {
-      var thresholdMin = this.get('thresholdMin'),
-       thresholdMax = this.get('thresholdMax'),
-       maxValue = this.get('maxValue'),
-       currentThreshold = this.get(thresholdName),
-       isError = false,
-       errorMessage = '';
-
-      if (currentThreshold.trim() !== "") {
-        if (isNaN(currentThreshold) || currentThreshold > maxValue || currentThreshold < 0) {
-          isError = true;
-          errorMessage = Em.I18n.t('dashboard.widgets.error.invalid').format(maxValue);
-        } else if (parseFloat(thresholdMax) <= parseFloat(thresholdMin)) {
-          isError = true;
-          errorMessage = Em.I18n.t('dashboard.widgets.error.smaller');
-        } else {
-          isError = false;
-          errorMessage = '';
-        }
-      } else {
-        isError = true;
-        errorMessage = Em.I18n.t('admin.users.editError.requiredField');
-      }
-      this.set(thresholdName + 'ErrorMessage', errorMessage);
-      this.set(thresholdName + 'Error', isError);
-      this.updateSlider();
-    },
-    observeThreshMinValue: function () {
-      this.validateThreshold('thresholdMin');
-    }.observes('thresholdMin', 'maxValue'),
-    observeThreshMaxValue: function () {
-      this.validateThreshold('thresholdMax');
-    }.observes('thresholdMax', 'maxValue'),
-    updateSlider: function () {
-      if (this.get('thresholdMinError') === false && this.get('thresholdMaxError') === false) {
-        $("#slider-range")
-          .slider('values', 0, parseFloat(this.get('thresholdMin')))
-          .slider('values', 1, parseFloat(this.get('thresholdMax')));
-      }
-    }
-  }),
-
   didInsertElement: function () {
     App.tooltip(this.$("[rel='ZoomInTooltip']"), {
       placement: 'left',
@@ -190,97 +150,36 @@ App.DashboardWidgetView = Em.View.extend({
   },
 
   /**
-   * edit widget
+   * Update thresholds for widget and save this them to persist
+   *
+   * @param {number[]} preparedThresholds
    */
-  editWidget: function () {
-    var configObj = this.get('widgetConfig').create({
-      thresholdMin: this.get('thresholdMin') + '',
-      thresholdMax: this.get('thresholdMax') + '',
-      maxValue: parseFloat(this.get('maxValue'))
-    });
-    this.showEditDialog(configObj);
+  saveWidgetThresholds(preparedThresholds) {
+    const widgetsView = this.get('widgetsView');
+    const userPreferences = widgetsView.get('userPreferences');
+    const widgetId = Number(this.get('id'));
+    userPreferences.threshold[widgetId] = preparedThresholds;
+    this.set('widget.threshold', userPreferences.threshold[widgetId]);
+    widgetsView.saveWidgetsSettings(userPreferences);
   },
 
   /**
-   *  show edit dialog
-   * @param {Em.Object} configObj
-   * @returns {App.ModalPopup}
+   * edit widget
    */
-  showEditDialog: function (configObj) {
-    var self = this;
-    var maxValue = this.get('maxValue');
-
-    return App.ModalPopup.show({
-      header: Em.I18n.t('dashboard.widgets.popupHeader'),
-      classNames: ['modal-edit-widget'],
-      modalDialogClasses: ['modal-lg'],
-      bodyClass: Ember.View.extend({
-        templateName: require('templates/main/dashboard/edit_widget_popup'),
-        configPropertyObj: configObj
-      }),
-      configObj: configObj,
-      disablePrimary: Em.computed.or('configObj.thresholdMinError', 'configObj.thresholdMaxError'),
-      primary: Em.I18n.t('common.apply'),
-      onPrimary: function () {
-        configObj.observeThreshMinValue();
-        configObj.observeThreshMaxValue();
-        if (!configObj.thresholdMinError && !configObj.thresholdMaxError) {
-          self.set('thresholdMin', parseFloat(configObj.get('thresholdMin')));
-          self.set('thresholdMax', parseFloat(configObj.get('thresholdMax')));
-
-          var parent = self.get('parentView');
-          var userPreferences = parent.get('userPreferences');
-          userPreferences.threshold[Number(self.get('id'))] = [configObj.get('thresholdMin'), configObj.get('thresholdMax')];
-          parent.saveWidgetsSettings(userPreferences);
-          parent.renderWidgets();
-
-          this.hide();
-        }
-      },
+  editWidget: function () {
+    return App.EditDashboardWidgetPopup.show({
 
-      didInsertElement: function () {
-        this._super();
-        var _this = this;
-        var handlers = [configObj.get('thresholdMin'), configObj.get('thresholdMax')];
+      widgetView: this,
 
-        $("#slider-range").slider({
-          range: true,
-          min: 0,
-          max: maxValue,
-          values: handlers,
-          create: function () {
-            _this.updateColors(handlers);
-          },
-          slide: function (event, ui) {
-            _this.updateColors(ui.values);
-            configObj.set('thresholdMin', ui.values[0] + '');
-            configObj.set('thresholdMax', ui.values[1] + '');
-          },
-          change: function (event, ui) {
-            _this.updateColors(ui.values);
-          }
-        });
-      },
-      updateColors: function (handlers) {
-        var colors = [App.healthStatusGreen, App.healthStatusOrange, App.healthStatusRed];
-        var colorStops = colors[0] + ", ";
+      sliderHandlersManager: App.EditDashboardWidgetPopup.DoubleHandlers.create({
+        thresholdMin: this.get('thresholdMin'),
+        thresholdMax: this.get('thresholdMax'),
+        maxValue: parseFloat(this.get('maxValue'))
+      }),
 
-        for (var i = 0; i < handlers.length; i++) {
-          colorStops += colors[i] + " " + handlers[i] * 100 / maxValue + "%,";
-          colorStops += colors[i + 1] + " " + handlers[i] * 100 / maxValue + "%,";
-        }
-        colorStops += colors[colors.length - 1];
-        var sliderElement = $('#slider-range');
-        var gradient = 'linear-gradient(left,' + colorStops + ')';
+      sliderColors: [App.healthStatusGreen, App.healthStatusOrange, App.healthStatusRed]
 
-        sliderElement.css('background-image', '-webkit-' + gradient);
-        sliderElement.css('background-image', '-ms-' + gradient);
-        sliderElement.css('background-image', '-moz-' + gradient);
-        sliderElement.find('.ui-widget-header').css({
-          'background-color': '#FF8E00',
-          'background-image': 'none'
-        });
-      }
     });
   }
+
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/views/main/dashboard/widgets.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/dashboard/widgets.js b/ambari-web/app/views/main/dashboard/widgets.js
index 572625d..16840a5 100644
--- a/ambari-web/app/views/main/dashboard/widgets.js
+++ b/ambari-web/app/views/main/dashboard/widgets.js
@@ -32,184 +32,7 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
   name: 'mainDashboardWidgetsView',
   templateName: require('templates/main/dashboard/widgets'),
 
-  widgetsDefinition: [
-    {
-      id: 1,
-      viewName: 'NameNodeHeapPieChartView',
-      sourceName: 'HDFS',
-      title: Em.I18n.t('dashboard.widgets.NameNodeHeap'),
-      threshold: [80, 90]
-    },
-    {
-      id: 2,
-      viewName: 'NameNodeCapacityPieChartView',
-      sourceName: 'HDFS',
-      title: Em.I18n.t('dashboard.widgets.HDFSDiskUsage'),
-      threshold: [85, 95]
-    },
-    {
-      id: 3,
-      viewName: 'NameNodeCpuPieChartView',
-      sourceName: 'HDFS',
-      title: Em.I18n.t('dashboard.widgets.NameNodeCpu'),
-      threshold: [90, 95]
-    },
-    {
-      id: 4,
-      viewName: 'DataNodeUpView',
-      sourceName: 'HDFS',
-      title: Em.I18n.t('dashboard.widgets.DataNodeUp'),
-      threshold: [80, 90]
-    },
-    {
-      id: 5,
-      viewName: 'NameNodeRpcView',
-      sourceName: 'HDFS',
-      title: Em.I18n.t('dashboard.widgets.NameNodeRpc'),
-      threshold: [1000, 3000]
-    },
-    {
-      id: 6,
-      viewName: 'ChartClusterMetricsMemoryWidgetView',
-      sourceName: 'HOST_METRICS',
-      title: Em.I18n.t('dashboard.clusterMetrics.memory'),
-      threshold: []
-    },
-    {
-      id: 7,
-      viewName: 'ChartClusterMetricsNetworkWidgetView',
-      sourceName: 'HOST_METRICS',
-      title: Em.I18n.t('dashboard.clusterMetrics.network'),
-      threshold: []
-    },
-    {
-      id: 8,
-      viewName: 'ChartClusterMetricsCPUWidgetView',
-      sourceName: 'HOST_METRICS',
-      title: Em.I18n.t('dashboard.clusterMetrics.cpu'),
-      threshold: []
-    },
-    {
-      id: 9,
-      viewName: 'ChartClusterMetricsLoadWidgetView',
-      sourceName: 'HOST_METRICS',
-      title: Em.I18n.t('dashboard.clusterMetrics.load'),
-      threshold: []
-    },
-    {
-      id: 10,
-      viewName: 'NameNodeUptimeView',
-      sourceName: 'HDFS',
-      title: Em.I18n.t('dashboard.widgets.NameNodeUptime'),
-      threshold: []
-    },
-    {
-      id: 11,
-      viewName: 'HDFSLinksView',
-      sourceName: 'HDFS',
-      title: Em.I18n.t('dashboard.widgets.HDFSLinks'),
-      threshold: []
-    },
-    {
-      id: 12,
-      viewName: 'HBaseLinksView',
-      sourceName: 'HBASE',
-      title: Em.I18n.t('dashboard.widgets.HBaseLinks'),
-      threshold: []
-    },
-    {
-      id: 13,
-      viewName: 'HBaseMasterHeapPieChartView',
-      sourceName: 'HBASE',
-      title: Em.I18n.t('dashboard.widgets.HBaseMasterHeap'),
-      threshold: [70, 90]
-    },
-    {
-      id: 14,
-      viewName: 'HBaseAverageLoadView',
-      sourceName: 'HBASE',
-      title: Em.I18n.t('dashboard.widgets.HBaseAverageLoad'),
-      threshold: [150, 250]
-    },
-    {
-      id: 15,
-      viewName: 'HBaseRegionsInTransitionView',
-      sourceName: 'HBASE',
-      title: Em.I18n.t('dashboard.widgets.HBaseRegionsInTransition'),
-      threshold: [3, 10],
-      isHiddenByDefault: true
-    },
-    {
-      id: 16,
-      viewName: 'HBaseMasterUptimeView',
-      sourceName: 'HBASE',
-      title: Em.I18n.t('dashboard.widgets.HBaseMasterUptime'),
-      threshold: []
-    },
-    {
-      id: 17,
-      viewName: 'ResourceManagerHeapPieChartView',
-      sourceName: 'YARN',
-      title: Em.I18n.t('dashboard.widgets.ResourceManagerHeap'),
-      threshold: [70, 90]
-    },
-    {
-      id: 18,
-      viewName: 'ResourceManagerUptimeView',
-      sourceName: 'YARN',
-      title: Em.I18n.t('dashboard.widgets.ResourceManagerUptime'),
-      threshold: []
-    },
-    {
-      id: 19,
-      viewName: 'NodeManagersLiveView',
-      sourceName: 'YARN',
-      title: Em.I18n.t('dashboard.widgets.NodeManagersLive'),
-      threshold: [50, 75]
-    },
-    {
-      id: 20,
-      viewName: 'YARNMemoryPieChartView',
-      sourceName: 'YARN',
-      title: Em.I18n.t('dashboard.widgets.YARNMemory'),
-      threshold: [50, 75]
-    },
-    {
-      id: 21,
-      viewName: 'SuperVisorUpView',
-      sourceName: 'STORM',
-      title: Em.I18n.t('dashboard.widgets.SuperVisorUp'),
-      threshold: [85, 95]
-    },
-    {
-      id: 22,
-      viewName: 'FlumeAgentUpView',
-      sourceName: 'FLUME',
-      title: Em.I18n.t('dashboard.widgets.FlumeAgentUp'),
-      threshold: [85, 95]
-    },
-    {
-      id: 23,
-      viewName: 'YARNLinksView',
-      sourceName: 'YARN',
-      title: Em.I18n.t('dashboard.widgets.YARNLinks'),
-      threshold: []
-    },
-    {
-      id: 24,
-      viewName: 'HawqSegmentUpView',
-      sourceName: 'HAWQ',
-      title: Em.I18n.t('dashboard.widgets.HawqSegmentUp'),
-      threshold: [75, 90]
-    },
-    {
-      id: 25,
-      viewName: 'PxfUpView',
-      sourceName: 'PXF',
-      title: Em.I18n.t('dashboard.widgets.PxfUp'),
-      threshold: []
-    }
-  ],
+  widgetsDefinition: require('data/dashboard_widgets'),
 
   widgetsDefinitionMap: function () {
     return this.get('widgetsDefinition').toMapByProperty('id');
@@ -275,15 +98,13 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
   userPreferences: null,
 
   didInsertElement: function () {
-    var self = this;
-
     this._super();
-    this.loadWidgetsSettings().complete(function() {
-      self.checkServicesChange();
-      self.renderWidgets();
-      self.set('isDataLoaded', true);
+    this.loadWidgetsSettings().complete(() => {
+      this.checkServicesChange();
+      this.renderWidgets();
+      this.set('isDataLoaded', true);
       App.loadTimer.finish('Dashboard Metrics Page');
-      Em.run.next(self, 'makeSortable');
+      Em.run.next(this, 'makeSortable');
     });
   },
 
@@ -337,11 +158,10 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
 
   resolveConfigDependencies: function(widgetsDefinition) {
     var clusterEnv = App.router.get('clusterController.clusterEnv').properties;
-    var yarnMemoryWidget = widgetsDefinition.findProperty('id', 20);
-
     if (clusterEnv.hide_yarn_memory_widget === 'true') {
-      yarnMemoryWidget.isHiddenByDefault = true;
+      widgetsDefinition.findProperty('id', 20).isHiddenByDefault = true;
     }
+    return widgetsDefinition;
   },
 
   generateDefaultUserPreferences: function() {
@@ -353,14 +173,10 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
     };
 
     this.resolveConfigDependencies(widgetsDefinition);
-
     widgetsDefinition.forEach(function(widget) {
       if (App.Service.find(widget.sourceName).get('isLoaded') || widget.sourceName === 'HOST_METRICS') {
-        if (widget.isHiddenByDefault) {
-          preferences.hidden.push(widget.id);
-        } else {
-          preferences.visible.push(widget.id);
-        }
+        let state = widget.isHiddenByDefault ? 'hidden' : 'visible';
+        preferences[state].push(widget.id);
       }
       preferences.threshold[widget.id] = widget.threshold;
     });
@@ -421,18 +237,13 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
     };
     var isChanged = false;
 
-    defaultPreferences.visible.forEach(function(id) {
-      if (!userPreferences.visible.contains(id) && !userPreferences.hidden.contains(id)) {
-        isChanged = true;
-        newValue.visible.push(id);
-      }
-    });
-
-    defaultPreferences.hidden.forEach(function(id) {
-      if (!userPreferences.visible.contains(id) && !userPreferences.hidden.contains(id)) {
-        isChanged = true;
-        newValue.hidden.push(id);
-      }
+    ['visible', 'hidden'].forEach(state => {
+      defaultPreferences[state].forEach(id => {
+        if (!userPreferences.visible.contains(id) && !userPreferences.hidden.contains(id)) {
+          isChanged = true;
+          newValue[state].push(id);
+        }
+      });
     });
     if (isChanged) {
       this.saveWidgetsSettings(newValue);
@@ -443,15 +254,14 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
    * Reset widgets visibility-status
    */
   resetAllWidgets: function () {
-    var self = this;
-    App.showConfirmationPopup(function () {
-      self.saveWidgetsSettings(self.generateDefaultUserPreferences());
-      self.setProperties({
+    App.showConfirmationPopup(() => {
+      this.saveWidgetsSettings(this.generateDefaultUserPreferences());
+      this.setProperties({
         currentTimeRangeIndex: 0,
         customStartTime: null,
         customEndTime: null
       });
-      self.renderWidgets();
+      this.renderWidgets();
     });
   },
 
@@ -469,23 +279,21 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
         var widgetsArray = $('div[viewid]');
 
         var userPreferences = self.get('userPreferences') || self.getDBProperty(self.get('persistKey'));
-        var newValue = Em.Object.create({
+        var newValue = {
           visible: [],
           hidden: userPreferences.hidden,
           threshold: userPreferences.threshold
+        };
+        newValue.visible = userPreferences.visible.map((item, index) => {
+          var viewID = widgetsArray.get(index).getAttribute('viewid');
+          return Number(viewID.split('-')[1]);
         });
-        var size = userPreferences.visible.length;
-        for (var j = 0; j <= size - 1; j++) {
-          var viewID = widgetsArray.get(j).getAttribute('viewid');
-          var id = Number(viewID.split("-").get(1));
-          newValue.visible.push(id);
-        }
         self.saveWidgetsSettings(newValue);
       },
-      activate: function (event, ui) {
+      activate: function () {
         self.set('isMoving', true);
       },
-      deactivate: function (event, ui) {
+      deactivate: function () {
         self.set('isMoving', false);
       }
     }).disableSelection();
@@ -511,21 +319,9 @@ App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, Ap
     }),
     applyFilter: function () {
       var parent = this.get('parentView'),
-        hiddenWidgets = this.get('hiddenWidgets'),
-        userPreferences = parent.get('userPreferences'),
-        newValue = {
-          visible: userPreferences.visible.slice(0),
-          hidden: userPreferences.hidden.slice(0),
-          threshold: userPreferences.threshold
-        };
-
-      hiddenWidgets.filterProperty('checked').forEach(function (item) {
-        newValue.visible.push(item.id);
-        newValue.hidden = newValue.hidden.without(item.id);
-        hiddenWidgets.removeObject(item);
-      }, this);
-      parent.saveWidgetsSettings(newValue);
-      parent.renderWidgets();
+        hiddenWidgets = this.get('hiddenWidgets');
+      hiddenWidgets.filterProperty('checked').setEach('isVisible', true);
+      parent.saveWidgetsSettings();
     }
   }),
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/app/views/main/dashboard/widgets/text_widget.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/dashboard/widgets/text_widget.js b/ambari-web/app/views/main/dashboard/widgets/text_widget.js
index daa354c..79f0573 100644
--- a/ambari-web/app/views/main/dashboard/widgets/text_widget.js
+++ b/ambari-web/app/views/main/dashboard/widgets/text_widget.js
@@ -32,26 +32,5 @@ App.TextDashboardWidgetView = App.DashboardWidgetView.extend({
     return this.get('data') === null;
   }.property('data'),
 
-  hiddenInfo: [],
-
-  maxValue: null,
-
-  updateColors: function (handlers, colors) {
-    var colorstops = colors[0] + ", "; // start with the first color
-    for (var i = 0; i < handlers.length; i++) {
-      colorstops += colors[i] + " " + handlers[i] + "%,";
-      colorstops += colors[i + 1] + " " + handlers[i] + "%,";
-    }
-    colorstops += colors[colors.length - 1];
-    var cssForChromeAndSafari = '-webkit-linear-gradient(left,' + colorstops + ')'; // chrome & safari
-    var slider = $('#slider-range');
-    slider.css('background-image', cssForChromeAndSafari);
-    var cssForIE = '-ms-linear-gradient(left,' + colorstops + ')'; // IE 10+
-    slider.css('background-image', cssForIE);
-    //$('#slider-range').css('filter', 'progid:DXImageTransform.Microsoft.gradient( startColorStr= ' + colors[0] + ', endColorStr= ' + colors[2] +',  GradientType=1 )' ); // IE 10-
-    var cssForFireFox = '-moz-linear-gradient(left,' + colorstops + ')'; // Firefox
-    slider.css('background-image', cssForFireFox);
-
-    slider.find('.ui-widget-header').css({'background-color': '#FF8E00', 'background-image': 'none'}); // change the  original ranger color
-  }
+  hiddenInfo: []
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/test/views/common/modal_popups/edit_dashboard_widget_popup_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/modal_popups/edit_dashboard_widget_popup_test.js b/ambari-web/test/views/common/modal_popups/edit_dashboard_widget_popup_test.js
new file mode 100644
index 0000000..3335cf3
--- /dev/null
+++ b/ambari-web/test/views/common/modal_popups/edit_dashboard_widget_popup_test.js
@@ -0,0 +1,214 @@
+/**
+ * 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');
+
+function getView() {
+  return App.EditDashboardWidgetPopup.create({
+    widgetView: Em.View.create(),
+    sliderHandlersManager: {}
+  });
+}
+
+describe('App.EditDashboardWidgetPopup', function () {
+
+  App.TestAliases.testAsComputedAlias(getView(), 'disablePrimary', 'sliderHandlersManager.hasErrors');
+
+  App.TestAliases.testAsComputedAlias(getView(), 'sliderMaxValue', 'sliderHandlersManager.maxValue');
+
+  describe('#init', function () {
+
+    it('should throw an Error if no `widgetView` provided', function () {
+      expect(function () {
+        App.EditDashboardWidgetPopup.create({
+          sliderHandlersManager: {}
+        });
+      }).to.throw(/`widgetView` should be valid view/);
+    });
+
+    it('should throw an Error if no `sliderHandlersManager` provided', function () {
+      expect(function () {
+        App.EditDashboardWidgetPopup.create({
+          widgetView: Em.View.create(),
+        });
+      }).to.throw(/`sliderHandlersManager` should be set/);
+    });
+
+  });
+
+});
+
+describe('App.EditDashboardWidgetPopup.SingleHandler', function () {
+
+  var handler;
+
+  function getSingleHandler() {
+    return App.EditDashboardWidgetPopup.SingleHandler.create();
+  }
+
+  beforeEach(function () {
+    handler = getSingleHandler();
+  });
+
+  App.TestAliases.testAsComputedAlias(getSingleHandler(), 'hasErrors', 'thresholdMinError');
+
+  describe('#updateThresholds', function () {
+
+    it('should update `thresholdMin`', function () {
+      handler.set('thresholdMin', -1);
+      handler.updateThresholds([100500]);
+      expect(handler.get('thresholdMin')).to.be.equal(100500);
+    });
+
+  });
+
+  describe('#thresholdMinErrorMessage', function() {
+
+    var minValue = 0;
+    var maxValue = 100;
+    var msg = Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
+
+    beforeEach(function() {
+      handler.setProperties({
+        minValue: minValue,
+        maxValue: maxValue
+      });
+    });
+
+    [
+      {thresholdMin: -1, e: msg},
+      {thresholdMin: 101, e: msg},
+      {thresholdMin: 'abc', e: msg},
+      {thresholdMin: 60, e: ''}
+    ].forEach(function(test) {
+      it('thresholdMin: ' + JSON.stringify(test.thresholdMin), function () {
+        handler.set('thresholdMin', test.thresholdMin);
+        expect(handler.get('thresholdMinErrorMessage')).to.be.equal(test.e);
+      });
+    });
+
+  });
+
+  describe('#preparedThresholds', function () {
+
+    it('mapped to array threshold values', function () {
+      handler.setProperties({
+        thresholdMin: 1
+      });
+      expect(handler.get('preparedThresholds')).to.be.eql([1]);
+    });
+
+  });
+
+});
+
+describe('App.EditDashboardWidgetPopup.DoubleHandlers', function () {
+
+  var handler;
+
+  function getDoubleHandlers() {
+    return App.EditDashboardWidgetPopup.DoubleHandlers.create();
+  }
+
+  beforeEach(function () {
+    handler = getDoubleHandlers();
+  });
+
+  App.TestAliases.testAsComputedOr(getDoubleHandlers(), 'hasErrors', ['thresholdMinError', 'thresholdMaxError']);
+
+  describe('#updateThresholds', function () {
+
+    it('should update `thresholdMin` and `thresholdMax`', function () {
+      handler.set('thresholdMin', -1);
+      handler.set('thresholdMax', 1);
+      handler.updateThresholds([1234, 4321]);
+      expect(handler.get('thresholdMin')).to.be.equal(1234);
+      expect(handler.get('thresholdMax')).to.be.equal(4321);
+    });
+
+  });
+
+  describe('#thresholdMinErrorMessage', function() {
+
+    var minValue = 0;
+    var maxValue = 100;
+    var msg = Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
+    var msg2 = Em.I18n.t('dashboard.widgets.error.smaller');
+
+    beforeEach(function() {
+      handler.setProperties({
+        minValue: minValue,
+        maxValue: maxValue
+      });
+    });
+
+    [
+      {thresholdMin: -1, e: msg},
+      {thresholdMin: 101, e: msg},
+      {thresholdMin: 'abc', e: msg},
+      {thresholdMin: 60, e: ''},
+      {thresholdMin: 99, e: msg2}
+    ].forEach(function(test) {
+      it('thresholdMin: ' + JSON.stringify(test.thresholdMin), function () {
+        handler.set('thresholdMin', test.thresholdMin);
+        handler.set('thresholdMax', 98);
+        expect(handler.get('thresholdMinErrorMessage')).to.be.equal(test.e);
+      });
+    });
+
+  });
+
+  describe('#thresholdMaxErrorMessage', function () {
+    var minValue = 0;
+    var maxValue = 100;
+    var msg = Em.I18n.t('dashboard.widgets.error.invalid').format(minValue, maxValue);
+
+    beforeEach(function() {
+      handler.setProperties({
+        minValue: minValue,
+        maxValue: maxValue
+      });
+    });
+
+    [
+      {thresholdMax: -1, e: msg},
+      {thresholdMax: 101, e: msg},
+      {thresholdMax: 'abc', e: msg},
+      {thresholdMax: 60, e: ''}
+    ].forEach(function(test) {
+      it('thresholdMax: ' + JSON.stringify(test.thresholdMax), function () {
+        handler.set('thresholdMax', test.thresholdMax);
+        expect(handler.get('thresholdMaxErrorMessage')).to.be.equal(test.e);
+      });
+    });
+  });
+
+  describe('#preparedThresholds', function () {
+
+    it('mapped to array threshold values', function () {
+      handler.setProperties({
+        thresholdMin: 1,
+        thresholdMax: 2
+      });
+      expect(handler.get('preparedThresholds')).to.be.eql([1, 2]);
+    });
+
+  });
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/test/views/main/dashboard/widget_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/dashboard/widget_test.js b/ambari-web/test/views/main/dashboard/widget_test.js
index a504c6d..8e10cef 100644
--- a/ambari-web/test/views/main/dashboard/widget_test.js
+++ b/ambari-web/test/views/main/dashboard/widget_test.js
@@ -89,97 +89,6 @@ describe('App.DashboardWidgetView', function () {
     });
   });
 
-  describe('#widgetConfig', function () {
-    var widgetConfig;
-
-    beforeEach(function() {
-      widgetConfig = view.get('widgetConfig').create();
-    });
-
-    describe('#validateThreshold()', function () {
-
-      beforeEach(function () {
-        sinon.stub(widgetConfig, 'updateSlider');
-      });
-
-      afterEach(function () {
-        widgetConfig.updateSlider.restore();
-      });
-
-      it('updateSlider should be called', function () {
-        widgetConfig.validateThreshold('thresholdMin');
-        expect(widgetConfig.updateSlider).to.be.called;
-      });
-
-      it('thresholdMin is empty', function () {
-        widgetConfig.set('thresholdMin', '');
-        widgetConfig.validateThreshold('thresholdMin');
-        expect(widgetConfig.get('thresholdMinError')).to.be.true;
-        expect(widgetConfig.get('thresholdMinErrorMessage')).to.be.equal(Em.I18n.t('admin.users.editError.requiredField'));
-      });
-
-      it('thresholdMin is NaN', function () {
-        widgetConfig.set('thresholdMin', 'a');
-        widgetConfig.validateThreshold('thresholdMin');
-        expect(widgetConfig.get('thresholdMinError')).to.be.true;
-        expect(widgetConfig.get('thresholdMinErrorMessage')).to.be.equal(Em.I18n.t('dashboard.widgets.error.invalid').format(0));
-      });
-
-      it('thresholdMin bigger than maxValue', function () {
-        widgetConfig.set('thresholdMin', '1');
-        widgetConfig.validateThreshold('thresholdMin');
-        expect(widgetConfig.get('thresholdMinError')).to.be.true;
-        expect(widgetConfig.get('thresholdMinErrorMessage')).to.be.equal(Em.I18n.t('dashboard.widgets.error.invalid').format(0));
-      });
-
-      it('thresholdMin less than 0', function () {
-        widgetConfig.set('thresholdMin', '-1');
-        widgetConfig.validateThreshold('thresholdMin');
-        expect(widgetConfig.get('thresholdMinError')).to.be.true;
-        expect(widgetConfig.get('thresholdMinErrorMessage')).to.be.equal(Em.I18n.t('dashboard.widgets.error.invalid').format(0));
-      });
-
-      it('thresholdMin bigger than thresholdMax', function () {
-        widgetConfig.set('thresholdMin', '2');
-        widgetConfig.set('thresholdMax', '1');
-        widgetConfig.set('maxValue', 100);
-        widgetConfig.validateThreshold('thresholdMin');
-        expect(widgetConfig.get('thresholdMinError')).to.be.true;
-        expect(widgetConfig.get('thresholdMinErrorMessage')).to.be.equal(Em.I18n.t('dashboard.widgets.error.smaller'));
-      });
-    });
-
-    describe('#observeThreshMinValue()', function() {
-
-      beforeEach(function() {
-        sinon.stub(widgetConfig, 'validateThreshold');
-      });
-      afterEach(function() {
-        widgetConfig.validateThreshold.restore();
-      });
-
-      it('validateThreshold should be called', function() {
-        widgetConfig.observeThreshMinValue();
-        expect(widgetConfig.validateThreshold.calledWith('thresholdMin')).to.be.true;
-      });
-    });
-
-    describe('#observeThreshMaxValue()', function() {
-
-      beforeEach(function() {
-        sinon.stub(widgetConfig, 'validateThreshold');
-      });
-      afterEach(function() {
-        widgetConfig.validateThreshold.restore();
-      });
-
-      it('validateThreshold should be called', function() {
-        widgetConfig.observeThreshMaxValue();
-        expect(widgetConfig.validateThreshold.calledWith('thresholdMax')).to.be.true;
-      });
-    });
-  });
-
   describe('#didInsertElement()', function() {
 
     beforeEach(function() {
@@ -250,25 +159,6 @@ describe('App.DashboardWidgetView', function () {
   describe('#editWidget()', function() {
 
     beforeEach(function() {
-      sinon.stub(view, 'showEditDialog');
-    });
-
-    afterEach(function() {
-      view.showEditDialog.restore();
-    });
-
-    it('showEditDialog should be called', function() {
-      view.reopen({
-        widgetConfig: Em.Object.extend()
-      });
-      view.editWidget();
-      expect(view.showEditDialog).to.be.calledOnce;
-    });
-  });
-
-  describe('#showEditDialog()', function() {
-
-    beforeEach(function() {
       sinon.stub(App.ModalPopup, 'show');
     });
 
@@ -277,7 +167,7 @@ describe('App.DashboardWidgetView', function () {
     });
 
     it('App.ModalPopup.show should be called', function() {
-      view.showEditDialog();
+      view.editWidget();
       expect(App.ModalPopup.show).to.be.calledOnce;
     });
   });

http://git-wip-us.apache.org/repos/asf/ambari/blob/05c76ed6/ambari-web/test/views/main/dashboard/widgets_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/dashboard/widgets_test.js b/ambari-web/test/views/main/dashboard/widgets_test.js
index 5d1ddb2..4700ac8 100644
--- a/ambari-web/test/views/main/dashboard/widgets_test.js
+++ b/ambari-web/test/views/main/dashboard/widgets_test.js
@@ -426,15 +426,7 @@ describe('App.MainDashboardWidgetsView', function () {
       });
 
       it('saveWidgetsSettings should be called', function() {
-        expect(plusButtonFilterView.get('parentView').saveWidgetsSettings.getCall(0).args[0]).to.be.eql({
-          visible: [2, 1],
-          hidden: [3],
-          threshold: {}
-        });
-      });
-
-      it('renderWidgets should be called', function() {
-        expect(plusButtonFilterView.get('parentView').renderWidgets).to.be.calledOnce;
+        expect(plusButtonFilterView.get('parentView').saveWidgetsSettings.calledOnce).to.be.true;
       });
     });
   });