You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by at...@apache.org on 2014/11/14 16:40:03 UTC

ambari git commit: AMBARI-8334 Refactoring table mixin responsible for interacting with server. (atkach)

Repository: ambari
Updated Branches:
  refs/heads/trunk 99a24d8c3 -> 87e03ea3c


AMBARI-8334 Refactoring table mixin responsible for interacting with server. (atkach)


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

Branch: refs/heads/trunk
Commit: 87e03ea3c39122d3cac3fc84bec252857b099ee0
Parents: 99a24d8
Author: Andrii Tkach <at...@hortonworks.com>
Authored: Fri Nov 14 17:39:56 2014 +0200
Committer: Andrii Tkach <at...@hortonworks.com>
Committed: Fri Nov 14 17:39:56 2014 +0200

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   1 +
 .../app/controllers/global/update_controller.js |   2 +-
 ambari-web/app/controllers/main/host.js         | 126 +++---------
 ambari-web/app/mappers/users_mapper.js          |   4 +-
 ambari-web/app/mixins.js                        |   2 +-
 .../app/mixins/common/tableServerProvider.js    | 100 ----------
 .../mixins/common/table_server_view_mixin.js    | 114 +++++++++++
 ambari-web/app/router.js                        |   2 +-
 ambari-web/app/templates/main/host.hbs          |   2 +-
 ambari-web/app/views/main/alerts.js             |   2 +-
 .../views/main/dashboard/config_history_view.js |  70 +------
 ambari-web/app/views/main/host.js               | 113 +++++------
 .../common/table_server_view_mixin_test.js      | 197 +++++++++++++++++++
 .../main/dashboard/config_history_view_test.js  | 167 ----------------
 14 files changed, 396 insertions(+), 506 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/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 b8b8387..ec2bf00 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -114,6 +114,7 @@ var files = ['test/init_model_test',
   'test/mappers/stack_mapper_test',
   'test/mixins/common/chart/storm_linear_time_test',
   'test/mixins/common/localStorage_test',
+  'test/mixins/common/table_server_view_mixin_test',
   'test/mixins/main/host/details/host_components/decommissionable_test',
   'test/utils/ajax/ajax_test',
   'test/utils/ajax/ajax_queue_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/controllers/global/update_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/global/update_controller.js b/ambari-web/app/controllers/global/update_controller.js
index 00fc486..a220e01 100644
--- a/ambari-web/app/controllers/global/update_controller.js
+++ b/ambari-web/app/controllers/global/update_controller.js
@@ -177,7 +177,7 @@ App.UpdateController = Em.Controller.extend({
       }
     }
     var mainHostController = App.router.get('mainHostController'),
-      sortProperties = mainHostController.getSortProperties();
+      sortProperties = mainHostController.getSortProps();
     if (hostDetailsFilter) {
       //if host details page opened then request info only of one displayed host
       this.get('queryParams').set('Hosts', [

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/controllers/main/host.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/host.js b/ambari-web/app/controllers/main/host.js
index dd40d3a..a3d3711 100644
--- a/ambari-web/app/controllers/main/host.js
+++ b/ambari-web/app/controllers/main/host.js
@@ -20,13 +20,19 @@ var App = require('app');
 var validator = require('utils/validator');
 var batchUtils = require('utils/batch_scheduled_requests');
 
-App.MainHostController = Em.ArrayController.extend({
+App.MainHostController = Em.ArrayController.extend(App.TableServerMixin, {
   name: 'mainHostController',
 
   dataSource: App.Host.find(),
   clearFilters: null,
 
   filteredCount: 0,
+  /**
+   * total number of installed hosts
+   */
+  totalCount: function () {
+    return this.get('hostsCountMap')['TOTAL'] || 0;
+  }.property('hostsCountMap'),
   resetStartIndex: false,
   /**
    * flag responsible for updating status counters of hosts
@@ -118,72 +124,31 @@ App.MainHostController = Em.ArrayController.extend({
     }
   ],
 
-  viewProperties: [
-    Em.Object.create({
-      key: 'displayLength',
-      getValue: function (controller) {
-        var name = controller.get('name');
-        var dbValue = App.db.getDisplayLength(name);
-        if (Em.isNone(this.get('viewValue'))) {
-          if (dbValue) {
-            this.set('viewValue', dbValue);
-          } else {
-            this.set('viewValue', '25'); //25 is default displayLength value for hosts page
-            App.db.setDisplayLength(name, '25');
-          }
-        }
-        return this.get('viewValue');
-      },
-      viewValue: null,
-      alias: 'page_size'
-    }),
-    Em.Object.create({
-      key: 'startIndex',
-      getValue: function (controller) {
-        var name = controller.get('name');
-        var startIndex = App.db.getStartIndex(name);
-        var value = this.get('viewValue');
-
-        if (Em.isNone(value)) {
-          if (Em.isNone(startIndex)) {
-            value = 0;
-          } else {
-            value = startIndex;
-            App.db.setStartIndex(name, startIndex);
-          }
-        }
-        return (value > 0) ? value - 1 : value;
-      },
-      viewValue: null,
-      alias: 'from'
-    })
-  ],
-
   sortProps: [
     {
-      key: 'publicHostName',
-      alias: 'Hosts/public_host_name'
+      name: 'publicHostName',
+      key: 'Hosts/public_host_name'
     },
     {
-      key: 'ip',
-      alias: 'Hosts/ip'
+      name: 'ip',
+      key: 'Hosts/ip'
     },
     {
-      key: 'cpu',
-      alias: 'Hosts/cpu_count'
+      name: 'cpu',
+      key: 'Hosts/cpu_count'
     },
     {
-      key: 'memoryFormatted',
-      alias: 'Hosts/total_mem'
+      name: 'memoryFormatted',
+      key: 'Hosts/total_mem'
     },
     {
-      key: 'diskUsage',
+      name: 'diskUsage',
       //TODO disk_usage is relative property and need support from API, metrics/disk/disk_free used temporarily
-      alias: 'metrics/disk/disk_free'
+      key: 'metrics/disk/disk_free'
     },
     {
-      key: 'loadAvg',
-      alias: 'metrics/load/load_one'
+      name: 'loadAvg',
+      key: 'metrics/load/load_one'
     }
   ],
 
@@ -201,50 +166,15 @@ App.MainHostController = Em.ArrayController.extend({
     return value;
   },
 
-  /**
-   * Transform <code>viewProperties</code> to queryParameters
-   * @returns {Object[]}
-   * @method getViewProperties
-   */
-  getViewProperties: function() {
-    return this.get('viewProperties').map(function (property) {
-      return {
-        key: property.get('alias'),
-        value: property.getValue(this),
-        type: 'EQUAL'
-      };
-    }, this);
-  },
-
-  /**
-   * Transform <code>sortProps</code> to queryParameters
-   * @returns {Object[]}
-   * @method getSortProperties
-   */
-  getSortProperties: function() {
-    var savedSortConditions = App.db.getSortingStatuses(this.get('name')) || [],
-      sortProperties = this.get('sortProps'),
-      queryParams = [];
-    savedSortConditions.forEach(function (sort) {
-      var property = sortProperties.findProperty('key', sort.name);
-
-      if (property && (sort.status === 'sorting_asc' || sort.status === 'sorting_desc')) {
-        queryParams.push({
-          key: property.alias,
-          value: sort.status.replace('sorting_', ''),
-          type: 'SORT'
-        });
-      }
-    });
+  getSortProps: function () {
     // sort by public_host_name by default
-    if (queryParams.length === 0) {
-      queryParams.push({
-        key: 'Hosts/public_host_name',
-        value: 'asc',
-        type: 'SORT'
-      });
+    if (App.db.getSortingStatuses(this.get('name')) && App.db.getSortingStatuses(this.get('name')).length === 0) {
+      App.db.setSortingStatuses(this.get('name'), {
+        name: 'publicHostName',
+        status: 'sorting_asc'
+      })
     }
-    return queryParams;
+    return this._super();
   },
 
   /**
@@ -264,7 +194,7 @@ App.MainHostController = Em.ArrayController.extend({
 
     this.set('resetStartIndex', false);
 
-    queryParams.pushObjects(this.getViewProperties());
+    queryParams.pushObjects(this.getPaginationProps());
 
     savedFilterConditions.forEach(function (filter) {
       var property = filterProperties.findProperty('key', colPropAssoc[filter.iColumn]);
@@ -320,7 +250,7 @@ App.MainHostController = Em.ArrayController.extend({
     }
 
     if (!skipNonFilterProperties) {
-      queryParams.pushObjects(this.getSortProperties());
+      queryParams.pushObjects(this.getSortProps());
     }
 
     return queryParams;

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/mappers/users_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/users_mapper.js b/ambari-web/app/mappers/users_mapper.js
index b90c20f..b9528f1 100644
--- a/ambari-web/app/mappers/users_mapper.js
+++ b/ambari-web/app/mappers/users_mapper.js
@@ -34,8 +34,8 @@ App.usersMapper = App.QuickDataMapper.create({
       var result= [];
       if(!App.User.find().someProperty("userName", item.Users.user_name)) {
         item.permissions = [];
-        var privileges = item.privileges;
-        if (!!Em.get(privileges, 'length')) {
+        var privileges = item.privileges || [];
+        if (privileges.length) {
           item.permissions = privileges.mapProperty('PrivilegeInfo.permission_name');
         }
         item.Users.admin = self.isAdmin(item.permissions);

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/mixins.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js
index e68fcc4..27c9c03 100644
--- a/ambari-web/app/mixins.js
+++ b/ambari-web/app/mixins.js
@@ -23,7 +23,7 @@ require('mixins/common/blueprint');
 require('mixins/common/localStorage');
 require('mixins/common/userPref');
 require('mixins/common/serverValidator');
-require('mixins/common/tableServerProvider');
+require('mixins/common/table_server_view_mixin');
 require('mixins/common/table_server_mixin');
 require('mixins/main/host/details/host_components/decommissionable');
 require('mixins/wizard/selectHost');

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/mixins/common/tableServerProvider.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/tableServerProvider.js b/ambari-web/app/mixins/common/tableServerProvider.js
deleted file mode 100644
index 4ba45d1..0000000
--- a/ambari-web/app/mixins/common/tableServerProvider.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * 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');
-
-
-App.TableServerProvider = Em.Mixin.create({
-  tableName: '',
-  updaterBinding: 'App.router.updateController',
-  filteringComplete: true,
-  filterConditions: [],
-  filterWaitingTime: 500,
-  timeOut: null,
-  /**
-   * total number of entities in table
-   */
-  totalCount: 0,
-  /**
-   * Request error data
-   *
-   */
-  requestError: null,
-
-  filteredContent: function () {
-    return this.get('content');
-  }.property('content'),
-
-  pageContent: function () {
-    return this.get('filteredContent');
-  }.property('filteredContent'),
-
-  /**
-   * request latest data filtered by new parameters
-   * called when trigger property(<code>refreshTriggers</code>) is changed
-   */
-  refresh: function () {
-    var self = this;
-    this.set('filteringComplete', false);
-    var updaterMethodName = this.get('updater.tableUpdaterMap')[this.get('tableName')];
-    this.get('updater')[updaterMethodName](function () {
-      self.set('filteringComplete', true);
-      self.propertyDidChange('pageContent');
-    }, function() {
-      self.set('requestError', arguments);
-    });
-    return true;
-  },
-  /**
-   * reset filters value by column to which filter belongs
-   * @param columns {Array}
-   */
-  resetFilterByColumns: function (columns) {
-    var filterConditions = this.get('filterConditions');
-    columns.forEach(function (iColumn) {
-      var filterCondition = filterConditions.findProperty('iColumn', iColumn);
-
-      if (filterCondition) {
-        filterCondition.value = '';
-        this.saveFilterConditions(filterCondition.iColumn, filterCondition.value, filterCondition.type, filterCondition.skipFilter);
-      }
-    }, this);
-  },
-  /**
-   * Apply each filter to each row
-   * @param iColumn {Number}
-   * @param value {String}
-   * @param type {String}
-   */
-  updateFilter: function (iColumn, value, type) {
-    var self = this;
-    this.saveFilterConditions(iColumn, value, type, false);
-    // if initial load finished
-    if (this.get('tableFilteringComplete')) {
-      if (!this.get('filteringComplete')) {
-        clearTimeout(this.get('timeOut'));
-        this.set('timeOut', setTimeout(function () {
-          self.updateFilter(iColumn, value, type);
-        }, this.get('filterWaitingTime')));
-      } else {
-        clearTimeout(this.get('timeOut'));
-        this.refresh();
-      }
-    }
-  }
-});

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/mixins/common/table_server_view_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/table_server_view_mixin.js b/ambari-web/app/mixins/common/table_server_view_mixin.js
new file mode 100644
index 0000000..47b16b5
--- /dev/null
+++ b/ambari-web/app/mixins/common/table_server_view_mixin.js
@@ -0,0 +1,114 @@
+/**
+ * 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');
+
+/**
+ * Mixin should be used for Em.View of table that uses server to filter, sort, paginate content
+ */
+App.TableServerViewMixin = Em.Mixin.create({
+  filteringComplete: true,
+  timeOut: null,
+  /**
+   * filter delay time, used to combine filters change into one content update
+   */
+  filterWaitingTime: 500,
+
+  /**
+   * count of filtered items
+   */
+  filteredCount: function () {
+    return this.get('controller.filteredCount');
+  }.property('controller.filteredCount'),
+  /**
+   * total count of items
+   */
+  totalCount: function () {
+    return this.get('controller.totalCount');
+  }.property('controller.totalCount'),
+  /**
+   * data requested from server
+   */
+  content: function () {
+    return this.get('controller.content');
+  }.property('controller.content'),
+  /**
+   * content already filtered on server-side
+   */
+  filteredContent: function () {
+    return this.get('content');
+  }.property('content'),
+  /**
+   * sort and slice recieved content by pagination parameters
+   */
+  pageContent: function () {
+    var content = this.get('filteredContent');
+    if (content.length > ((this.get('endIndex') - this.get('startIndex')) + 1)) {
+      content = content.slice(0, (this.get('endIndex') - this.get('startIndex')) + 1);
+    }
+    return content.sort(function (a, b) {
+      return a.get('index') - b.get('index');
+    });
+  }.property('filteredContent'),
+  /**
+   * compute applied filters and run content update from server
+   * @param iColumn
+   * @param value
+   * @param type
+   */
+  updateFilter: function (iColumn, value, type) {
+    var self = this;
+    this.set('controller.resetStartIndex', false);
+    this.saveFilterConditions(iColumn, value, type, false);
+    if (!this.get('filteringComplete')) {
+      clearTimeout(this.get('timeOut'));
+      this.set('timeOut', setTimeout(function () {
+        self.updateFilter(iColumn, value, type);
+      }, this.get('filterWaitingTime')));
+    } else {
+      clearTimeout(this.get('timeOut'));
+      this.set('controller.resetStartIndex', true);
+      this.refresh();
+    }
+  },
+  /**
+   * synchronize properties of view with controller to generate query parameters
+   */
+  updatePagination: function (key) {
+    if (!Em.isNone(this.get('displayLength'))) {
+      App.db.setDisplayLength(this.get('controller.name'), this.get('displayLength'));
+      this.get('controller.paginationProps').findProperty('name', 'displayLength').value = this.get('displayLength');
+    }
+    if (!Em.isNone(this.get('startIndex'))) {
+      App.db.setStartIndex(this.get('controller.name'), this.get('startIndex'));
+      this.get('controller.paginationProps').findProperty('name', 'startIndex').value = this.get('startIndex');
+    }
+
+    if (key !== 'SKIP_REFRESH') {
+      this.refresh();
+    }
+  },
+  /**
+   * when new filter applied page index should be reset to first page
+   */
+  resetStartIndex: function () {
+    if (this.get('controller.resetStartIndex') && this.get('filteredCount') > 0) {
+      this.set('startIndex', 1);
+    }
+  }.observes('controller.resetStartIndex')
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/router.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/router.js b/ambari-web/app/router.js
index 6781451..50297f2 100644
--- a/ambari-web/app/router.js
+++ b/ambari-web/app/router.js
@@ -255,7 +255,7 @@ App.Router = Em.Router.extend({
     //TODO: Replace hard coded value with query. Same in templates/application.hbs
     var loginController = this.get('loginController');
     var loginData = params.loginData;
-    var privileges = loginData.privileges;
+    var privileges = loginData.privileges || [];
     var router = this;
     var permissionList = privileges.mapProperty('PrivilegeInfo.permission_name');
       var isAdmin = permissionList.contains('AMBARI.ADMIN');

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/templates/main/host.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host.hbs b/ambari-web/app/templates/main/host.hbs
index a9be8d8..383dfdd 100644
--- a/ambari-web/app/templates/main/host.hbs
+++ b/ambari-web/app/templates/main/host.hbs
@@ -149,7 +149,7 @@
     {{else}}
       <tr>
         <td class="first"> </td>
-        <td colspan="12">
+        <td {{bindAttr colspan="view.colspan"}}>
           {{t hosts.table.noHosts}}
         </td>
       </tr>

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/views/main/alerts.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/alerts.js b/ambari-web/app/views/main/alerts.js
index 395820e..d84062e 100644
--- a/ambari-web/app/views/main/alerts.js
+++ b/ambari-web/app/views/main/alerts.js
@@ -21,7 +21,7 @@ var filters = require('views/common/filter_view');
 var sort = require('views/common/sort_view');
 var date = require('utils/date');
 
-App.MainAlertsView = App.TableView.extend(App.TableServerProvider, {
+App.MainAlertsView = App.TableView.extend(App.TableServerViewMixin, {
   templateName:require('templates/main/alerts'),
 
   tableName: 'Alerts'

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/views/main/dashboard/config_history_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/dashboard/config_history_view.js b/ambari-web/app/views/main/dashboard/config_history_view.js
index 85fe853..a7250ab 100644
--- a/ambari-web/app/views/main/dashboard/config_history_view.js
+++ b/ambari-web/app/views/main/dashboard/config_history_view.js
@@ -20,34 +20,12 @@ var App = require('app');
 var filters = require('views/common/filter_view');
 var sort = require('views/common/sort_view');
 
-App.MainConfigHistoryView = App.TableView.extend({
+App.MainConfigHistoryView = App.TableView.extend(App.TableServerViewMixin, {
   templateName: require('templates/main/dashboard/config_history'),
 
   controllerBinding: 'App.router.mainConfigHistoryController',
   filteringComplete: false,
-  timeOut: null,
 
-  content: function () {
-    return this.get('controller.content');
-  }.property('controller.content'),
-
-  pageContent: function () {
-    var content = this.get('filteredContent');
-    if (content.length > ((this.get('endIndex') - this.get('startIndex')) + 1)) {
-      content = content.slice(0, (this.get('endIndex') - this.get('startIndex')) + 1);
-    }
-    return content.sort(function (a, b) {
-      return a.get('index') - b.get('index');
-    });
-  }.property('filteredContent'),
-
-  filteredCount: function () {
-    return this.get('controller.filteredCount');
-  }.property('controller.filteredCount'),
-
-  totalCount: function () {
-    return this.get('controller.totalCount');
-  }.property('controller.totalCount'),
   /**
    * return filtered number of all content number information displayed on the page footer bar
    * @returns {String}
@@ -56,24 +34,6 @@ App.MainConfigHistoryView = App.TableView.extend({
     return this.t('tableView.filters.filteredConfigVersionInfo').format(this.get('filteredCount'), this.get('totalCount'));
   }.property('filteredCount', 'totalCount'),
 
-  /**
-   * synchronize properties of view with controller to generate query parameters
-   */
-  updatePagination: function (key) {
-    if (!Em.isNone(this.get('displayLength'))) {
-      App.db.setDisplayLength(this.get('controller.name'), this.get('displayLength'));
-      this.get('controller.paginationProps').findProperty('name', 'displayLength').value = this.get('displayLength');
-    }
-    if (!Em.isNone(this.get('startIndex'))) {
-      App.db.setStartIndex(this.get('controller.name'), this.get('startIndex'));
-      this.get('controller.paginationProps').findProperty('name', 'startIndex').value = this.get('startIndex');
-    }
-
-    if (key !== 'SKIP_REFRESH') {
-      this.refresh();
-    }
-  },
-
   didInsertElement: function () {
     this.addObserver('startIndex', this, 'updatePagination');
     this.addObserver('displayLength', this, 'updatePagination');
@@ -225,22 +185,6 @@ App.MainConfigHistoryView = App.TableView.extend({
     }
   }),
 
-  updateFilter: function (iColumn, value, type) {
-    var self = this;
-    this.set('controller.resetStartIndex', false);
-    this.saveFilterConditions(iColumn, value, type, false);
-    if (!this.get('filteringComplete')) {
-      clearTimeout(this.get('timeOut'));
-      this.set('timeOut', setTimeout(function () {
-        self.updateFilter(iColumn, value, type);
-      }, this.get('filterWaitingTime')));
-    } else {
-      clearTimeout(this.get('timeOut'));
-      this.set('controller.resetStartIndex', true);
-      this.refresh();
-    }
-  },
-
   ConfigVersionView: Em.View.extend({
     tagName: 'tr',
     showLessNotes: true,
@@ -253,7 +197,7 @@ App.MainConfigHistoryView = App.TableView.extend({
   }),
 
   /**
-   * sort content
+   * refresh table content
    */
   refresh: function () {
     var self = this;
@@ -271,13 +215,5 @@ App.MainConfigHistoryView = App.TableView.extend({
    */
   colPropAssoc: function () {
     return this.get('controller.colPropAssoc');
-  }.property('controller.colPropAssoc'),
-
-  resetStartIndex: function () {
-    if (this.get('controller.resetStartIndex') && this.get('filteredCount') > 0) {
-      this.set('startIndex', 1);
-      this.updatePagination('SKIP_REFRESH');
-    }
-  }.observes('controller.resetStartIndex')
-
+  }.property('controller.colPropAssoc')
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/app/views/main/host.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host.js b/ambari-web/app/views/main/host.js
index d3f114f..d331764 100644
--- a/ambari-web/app/views/main/host.js
+++ b/ambari-web/app/views/main/host.js
@@ -21,10 +21,12 @@ var filters = require('views/common/filter_view');
 var sort = require('views/common/sort_view');
 var date = require('utils/date');
 
-App.MainHostView = App.TableView.extend(App.TableServerProvider, {
+App.MainHostView = App.TableView.extend(App.TableServerViewMixin, {
   templateName:require('templates/main/host'),
 
   tableName: 'Hosts',
+  updaterBinding: 'App.router.updateController',
+  filterConditions: [],
 
   /**
    * Select/deselect all visible hosts flag
@@ -36,16 +38,15 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
    * Contains all selected hosts on cluster
    */
   selectedHosts: [],
+
   /**
-   * total number of installed hosts
+   * Request error data
    */
-  totalCount: function () {
-    return this.get('controller.hostsCountMap')['TOTAL'] || 0;
-  }.property('controller.hostsCountMap'),
+  requestError: null,
 
-  filteredCount: function () {
-    return this.get('controller.filteredCount');
-  }.property('controller.filteredCount'),
+  colspan: function () {
+    return App.get('supports.stackUpgrade') ? 11 : 10;
+  }.property("App.supports.stackUpgrade"),
 
   /**
    * List of hosts in cluster
@@ -76,20 +77,6 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
   }.observes('requestError'),
 
   /**
-   * Contains content to show on the current page of data page view
-   * @type {Array}
-   */
-  pageContent: function () {
-    var content = this.get('filteredContent');
-    if (content.length > this.get('endIndex') - this.get('startIndex') + 1) {
-      content = content.slice(0, this.get('endIndex') - this.get('startIndex') + 1);
-    }
-    return content.sort(function (a, b) {
-      return a.get('index') - b.get('index');
-    });
-  }.property('filteredCount'),
-
-  /**
    * flag to toggle displaying selected hosts counter
    */
   showSelectedFilter: function () {
@@ -105,6 +92,38 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
   }.property('filteredCount', 'totalCount'),
 
   /**
+   * request latest data filtered by new parameters
+   * called when trigger property(<code>refreshTriggers</code>) is changed
+   */
+  refresh: function () {
+    var self = this;
+    this.set('filteringComplete', false);
+    var updaterMethodName = this.get('updater.tableUpdaterMap')[this.get('tableName')];
+    this.get('updater')[updaterMethodName](function () {
+      self.set('filteringComplete', true);
+      self.propertyDidChange('pageContent');
+    }, function() {
+      self.set('requestError', arguments);
+    });
+    return true;
+  },
+  /**
+   * reset filters value by column to which filter belongs
+   * @param columns {Array}
+   */
+  resetFilterByColumns: function (columns) {
+    var filterConditions = this.get('filterConditions');
+    columns.forEach(function (iColumn) {
+      var filterCondition = filterConditions.findProperty('iColumn', iColumn);
+
+      if (filterCondition) {
+        filterCondition.value = '';
+        this.saveFilterConditions(filterCondition.iColumn, filterCondition.value, filterCondition.type, filterCondition.skipFilter);
+      }
+    }, this);
+  },
+
+  /**
    * Return pagination information displayed on the page
    * @type {String}
    */
@@ -158,7 +177,7 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
       var self = this;
       if (this.get('parentView.startIndex') === 1 || this.get('parentView.startIndex') === 0) {
         Ember.run.next(function () {
-          self.get('parentView').updateViewProperty();
+          self.get('parentView').updatePagination();
         });
       } else {
         Ember.run.next(function () {
@@ -172,24 +191,6 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
     this.set('controller.startIndex', this.get('startIndex'));
   }.observes('startIndex'),
 
-  /**
-   * Calculates default value for startIndex property after applying filter or changing displayLength
-   */
-  updatePaging: function (controller, property) {
-    var displayLength = this.get('displayLength');
-    var filteredContentLength = this.get('filteredCount');
-    if (property == 'displayLength' && this.get('filteringComplete')) {
-      this.set('startIndex', Math.min(1, filteredContentLength));
-    } else if (!filteredContentLength) {
-      this.set('startIndex', 0);
-    } else if (this.get('startIndex') > filteredContentLength) {
-      this.set('startIndex', Math.floor((filteredContentLength - 1) / displayLength) * displayLength + 1);
-    } else if (!this.get('startIndex')) {
-      this.set('startIndex', 1);
-    }
-  },
-
-
   clearFiltersObs: function() {
     var self = this;
     Em.run.next(function() {
@@ -211,14 +212,6 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
   },
 
   /**
-   * get query parameters computed in controller
-   * @param {bool} flag should non-filters params be skipped
-   * @return {Array}
-   */
-  getQueryParameters: function (flag) {
-    return this.get('controller').getQueryParameters(flag);
-  },
-  /**
    * stub for filter function in TableView
    */
   filter: function () {
@@ -232,7 +225,8 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
     this.set('controller.isCountersUpdating', true);
     this.get('controller').updateStatusCounters();
     this.addObserver('filteringComplete', this, this.overlayObserver);
-    this.addObserver('displayLength', this, this.updatePaging);
+    this.addObserver('startIndex', this, 'updatePagination');
+    this.addObserver('displayLength', this, 'updatePagination');
     this.addObserver('filteredCount', this, this.updatePaging);
     this.overlayObserver();
   },
@@ -243,15 +237,6 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
     }
   }.observes('tableFilteringComplete'),
 
-  /**
-   * synchronize properties of view with controller to generate query parameters
-   */
-  updateViewProperty: function () {
-    this.get('controller.viewProperties').findProperty('key', 'displayLength').set('viewValue', this.get('displayLength'));
-    this.get('controller.viewProperties').findProperty('key', 'startIndex').set('viewValue', this.get('startIndex'));
-    this.refresh();
-  }.observes('startIndex'),
-
   willDestroyElement: function() {
     this.set('controller.isCountersUpdating', false);
   },
@@ -383,7 +368,7 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
         }
         break;
       case 'f':
-        queryParams = this.getQueryParameters(true).filter(function (obj) {
+        queryParams = this.get('controller').getQueryParameters(true).filter(function (obj) {
           return !(obj.key == 'page_size' || obj.key == 'from');
         });
         break;
@@ -1081,11 +1066,5 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
    */
   colPropAssoc: function () {
     return this.get('controller.colPropAssoc');
-  }.property('controller.colPropAssoc'),
-
-  resetStartIndex: function () {
-    if (this.get('controller.resetStartIndex') && this.get('filteredCount') > 0) {
-      this.set('startIndex', 1);
-    }
-  }.observes('controller.resetStartIndex')
+  }.property('controller.colPropAssoc')
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/test/mixins/common/table_server_view_mixin_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/table_server_view_mixin_test.js b/ambari-web/test/mixins/common/table_server_view_mixin_test.js
new file mode 100644
index 0000000..51d9d4c
--- /dev/null
+++ b/ambari-web/test/mixins/common/table_server_view_mixin_test.js
@@ -0,0 +1,197 @@
+/**
+ * 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');
+require('mixins/common/table_server_view_mixin');
+
+describe('App.MainConfigHistoryView', function() {
+  var view = Em.View.create(App.TableServerViewMixin, {
+    filteredCount: 0,
+    totalCount: 0,
+    content: [],
+    filteredContent: [],
+    refresh: Em.K,
+    saveFilterConditions: Em.K,
+    controller: Em.Object.create({
+      name: 'mainConfigHistoryController',
+      paginationProps: [
+        {
+          name: 'displayLength',
+          value: '25'
+        },
+        {
+          name: 'startIndex',
+          value: 0
+        }
+      ]
+    })
+  });
+
+  describe('#pageContent', function() {
+    beforeEach(function(){
+      view.propertyDidChange('pageContent');
+    });
+    it('filtered content is empty', function() {
+      view.set('filteredContent', []);
+      expect(view.get('pageContent')).to.be.empty;
+    });
+    it('filtered content contain one item', function() {
+      view.set('filteredCount', 1);
+      view.set('filteredContent', [Em.Object.create()]);
+
+      expect(view.get('pageContent')).to.eql([Em.Object.create()]);
+    });
+    it('filtered content contain two unsorted items', function() {
+      view.set('filteredCount', 2);
+      view.set('filteredContent', [
+        Em.Object.create({index:2}),
+        Em.Object.create({index:1})
+      ]);
+
+      expect(view.get('pageContent')).to.eql([
+        Em.Object.create({index:1}),
+        Em.Object.create({index:2})
+      ]);
+    });
+  });
+
+  describe('#updatePagination', function() {
+    beforeEach(function () {
+      sinon.spy(view, 'refresh');
+      sinon.stub(App.db, 'setDisplayLength', Em.K);
+      sinon.stub(App.db, 'setStartIndex', Em.K);
+    });
+    afterEach(function () {
+      view.refresh.restore();
+      App.db.setStartIndex.restore();
+      App.db.setDisplayLength.restore();
+    });
+
+    it('displayLength is correct', function() {
+      view.set('displayLength', '50');
+      view.set('startIndex', null);
+
+      view.updatePagination();
+
+      expect(view.refresh.calledOnce).to.be.true;
+      expect(App.db.setStartIndex.called).to.be.false;
+      expect(App.db.setDisplayLength.calledWith('mainConfigHistoryController', '50')).to.be.true;
+      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(0);
+      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('50');
+    });
+    it('startIndex is correct', function() {
+      view.set('displayLength', null);
+      view.set('startIndex', 10);
+
+      view.updatePagination();
+
+      expect(view.refresh.calledOnce).to.be.true;
+      expect(App.db.setStartIndex.calledWith('mainConfigHistoryController', 10)).to.be.true;
+      expect(App.db.setDisplayLength.called).to.be.false;
+      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(10);
+      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('50');
+    });
+    it('displayLength and startIndex are correct', function() {
+      view.set('displayLength', '100');
+      view.set('startIndex', 20);
+
+      view.updatePagination();
+
+      expect(view.refresh.calledOnce).to.be.true;
+      expect(App.db.setStartIndex.calledWith('mainConfigHistoryController', 20)).to.be.true;
+      expect(App.db.setDisplayLength.calledWith('mainConfigHistoryController', '100')).to.be.true;
+      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(20);
+      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('100');
+    });
+    it('displayLength and startIndex are null', function() {
+      view.set('displayLength', null);
+      view.set('startIndex', null);
+
+      view.updatePagination();
+
+      expect(view.refresh.calledOnce).to.be.true;
+      expect(App.db.setStartIndex.called).to.be.false;
+      expect(App.db.setDisplayLength.called).to.be.false;
+      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(20);
+      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('100');
+    });
+  });
+
+  describe('#updateFilter()', function() {
+    beforeEach(function () {
+      sinon.stub(view, 'saveFilterConditions', Em.K);
+      sinon.stub(view, 'refresh', Em.K);
+      sinon.spy(view, 'updateFilter');
+    });
+    afterEach(function () {
+      view.saveFilterConditions.restore();
+      view.updateFilter.restore();
+      view.refresh.restore();
+    });
+    it('filteringComplete is false', function() {
+      this.clock = sinon.useFakeTimers();
+
+      view.set('filteringComplete', false);
+      view.updateFilter(1, '', 'string');
+      expect(view.get('controller.resetStartIndex')).to.be.false;
+      expect(view.saveFilterConditions.calledWith(1, '', 'string', false)).to.be.true;
+      view.set('filteringComplete', true);
+      this.clock.tick(view.get('filterWaitingTime'));
+      expect(view.updateFilter.calledWith(1, '', 'string')).to.be.true;
+      this.clock.restore();
+    });
+    it('filteringComplete is true', function() {
+      view.set('filteringComplete', true);
+
+      view.updateFilter(1, '', 'string');
+      expect(view.get('controller.resetStartIndex')).to.be.true;
+      expect(view.refresh.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#resetStartIndex()', function() {
+    it('resetStartIndex is false and filteredCount is 0', function() {
+      view.set('filteredCount', 0);
+      view.set('controller.resetStartIndex', false);
+      view.set('startIndex', 0);
+      view.resetStartIndex();
+      expect(view.get('startIndex')).to.equal(0);
+    });
+    it('resetStartIndex is true and filteredCount is 0', function() {
+      view.set('filteredCount', 0);
+      view.set('controller.resetStartIndex', true);
+      view.set('startIndex', 0);
+      view.resetStartIndex();
+      expect(view.get('startIndex')).to.equal(0);
+    });
+    it('resetStartIndex is false and filteredCount is 5', function() {
+      view.set('filteredCount', 5);
+      view.set('controller.resetStartIndex', false);
+      view.set('startIndex', 0);
+      view.resetStartIndex();
+      expect(view.get('startIndex')).to.equal(0);
+    });
+    it('resetStartIndex is true and filteredCount is 5', function() {
+      view.set('controller.resetStartIndex', true);
+      view.set('filteredCount', 5);
+      view.set('startIndex', 0);
+      view.resetStartIndex();
+      expect(view.get('startIndex')).to.equal(1);
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/87e03ea3/ambari-web/test/views/main/dashboard/config_history_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/dashboard/config_history_view_test.js b/ambari-web/test/views/main/dashboard/config_history_view_test.js
index 7d2ace8..f4cb6bc 100644
--- a/ambari-web/test/views/main/dashboard/config_history_view_test.js
+++ b/ambari-web/test/views/main/dashboard/config_history_view_test.js
@@ -40,108 +40,6 @@ describe('App.MainConfigHistoryView', function() {
   });
   view.removeObserver('controller.resetStartIndex', view, 'resetStartIndex');
 
-  describe('#pageContent', function() {
-    beforeEach(function(){
-      view.propertyDidChange('pageContent');
-    });
-    it('filtered content is empty', function() {
-      view.set('filteredContent', []);
-      expect(view.get('pageContent')).to.be.empty;
-    });
-    it('filtered content contain one item', function() {
-      view.set('filteredCount', 1);
-      view.set('filteredContent', [Em.Object.create()]);
-
-      expect(view.get('pageContent')).to.eql([Em.Object.create()]);
-    });
-    it('filtered content contain two unsorted items', function() {
-      view.set('filteredCount', 2);
-      view.set('filteredContent', [
-        Em.Object.create({index:2}),
-        Em.Object.create({index:1})
-      ]);
-
-      expect(view.get('pageContent')).to.eql([
-        Em.Object.create({index:1}),
-        Em.Object.create({index:2})
-      ]);
-    });
-    it('filtered content contain three items, when two visible', function() {
-      view.set('filteredCount', 2);
-      view.set('filteredContent', [
-        Em.Object.create({index:1}),
-        Em.Object.create({index:2}),
-        Em.Object.create({index:3})
-      ]);
-
-      expect(view.get('pageContent')).to.eql([
-        Em.Object.create({index:1}),
-        Em.Object.create({index:2})
-      ]);
-    });
-  });
-  describe('#updatePagination', function() {
-    beforeEach(function () {
-      sinon.stub(view, 'refresh', Em.K);
-      sinon.stub(App.db, 'setDisplayLength', Em.K);
-      sinon.stub(App.db, 'setStartIndex', Em.K);
-    });
-    afterEach(function () {
-      view.refresh.restore();
-      App.db.setStartIndex.restore();
-      App.db.setDisplayLength.restore();
-    });
-
-    it('displayLength is correct', function() {
-      view.set('displayLength', '50');
-      view.set('startIndex', null);
-
-      view.updatePagination();
-
-      expect(view.refresh.calledOnce).to.be.true;
-      expect(App.db.setStartIndex.called).to.be.false;
-      expect(App.db.setDisplayLength.calledWith('mainConfigHistoryController', '50')).to.be.true;
-      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(0);
-      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('50');
-    });
-    it('startIndex is correct', function() {
-      view.set('displayLength', null);
-      view.set('startIndex', 10);
-
-      view.updatePagination();
-
-      expect(view.refresh.calledOnce).to.be.true;
-      expect(App.db.setStartIndex.calledWith('mainConfigHistoryController', 10)).to.be.true;
-      expect(App.db.setDisplayLength.called).to.be.false;
-      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(10);
-      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('50');
-    });
-    it('displayLength and startIndex are correct', function() {
-      view.set('displayLength', '100');
-      view.set('startIndex', 20);
-
-      view.updatePagination();
-
-      expect(view.refresh.calledOnce).to.be.true;
-      expect(App.db.setStartIndex.calledWith('mainConfigHistoryController', 20)).to.be.true;
-      expect(App.db.setDisplayLength.calledWith('mainConfigHistoryController', '100')).to.be.true;
-      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(20);
-      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('100');
-    });
-    it('displayLength and startIndex are null', function() {
-      view.set('displayLength', null);
-      view.set('startIndex', null);
-
-      view.updatePagination();
-
-      expect(view.refresh.calledOnce).to.be.true;
-      expect(App.db.setStartIndex.called).to.be.false;
-      expect(App.db.setDisplayLength.called).to.be.false;
-      expect(view.get('controller.paginationProps').findProperty('name', 'startIndex').value).to.equal(20);
-      expect(view.get('controller.paginationProps').findProperty('name', 'displayLength').value).to.equal('100');
-    });
-  });
-
   describe('#didInsertElement()', function() {
     it('', function() {
       sinon.stub(view, 'addObserver', Em.K);
@@ -163,39 +61,6 @@ describe('App.MainConfigHistoryView', function() {
       expect(view.get('controller.isPolling')).to.be.false;
     });
   });
-
-  describe('#updateFilter()', function() {
-    beforeEach(function () {
-      sinon.stub(view, 'saveFilterConditions', Em.K);
-      sinon.stub(view, 'refresh', Em.K);
-      sinon.spy(view, 'updateFilter');
-    });
-    afterEach(function () {
-      view.saveFilterConditions.restore();
-      view.updateFilter.restore();
-      view.refresh.restore();
-    });
-    it('filteringComplete is false', function() {
-      this.clock = sinon.useFakeTimers();
-
-      view.set('filteringComplete', false);
-      view.updateFilter(1, '', 'string');
-      expect(view.get('controller.resetStartIndex')).to.be.false;
-      expect(view.saveFilterConditions.calledWith(1, '', 'string', false)).to.be.true;
-      view.set('filteringComplete', true);
-      this.clock.tick(view.get('filterWaitingTime'));
-      expect(view.updateFilter.calledWith(1, '', 'string')).to.be.true;
-      this.clock.restore();
-    });
-    it('filteringComplete is true', function() {
-      view.set('filteringComplete', true);
-
-      view.updateFilter(1, '', 'string');
-      expect(view.get('controller.resetStartIndex')).to.be.true;
-      expect(view.refresh.calledOnce).to.be.true;
-    });
-  });
-
   describe('#refresh()', function() {
     it('', function() {
       sinon.spy(view.get('controller'), 'load');
@@ -205,36 +70,4 @@ describe('App.MainConfigHistoryView', function() {
       view.get('controller').load.restore();
     });
   });
-
-  describe('#resetStartIndex()', function() {
-    it('resetStartIndex is false and filteredCount is 0', function() {
-      view.set('filteredCount', 0);
-      view.set('controller.resetStartIndex', false);
-      view.set('startIndex', 0);
-      view.resetStartIndex();
-      expect(view.get('startIndex')).to.equal(0);
-    });
-    it('resetStartIndex is true and filteredCount is 0', function() {
-      view.set('filteredCount', 0);
-      view.set('controller.resetStartIndex', true);
-      view.set('startIndex', 0);
-      view.resetStartIndex();
-      expect(view.get('startIndex')).to.equal(0);
-    });
-    it('resetStartIndex is false and filteredCount is 5', function() {
-      view.set('filteredCount', 5);
-      view.set('controller.resetStartIndex', false);
-      view.set('startIndex', 0);
-      view.resetStartIndex();
-      expect(view.get('startIndex')).to.equal(0);
-    });
-    it('resetStartIndex is true and filteredCount is 5', function() {
-      view.set('controller.resetStartIndex', true);
-      view.set('filteredCount', 5);
-      view.set('startIndex', 0);
-      view.resetStartIndex();
-      expect(view.get('startIndex')).to.equal(1);
-    });
-  });
-
 });