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/08/04 12:35:43 UTC

git commit: AMBARI-6726 Config History: E2E Integration of Config Version History Table. (atkach)

Repository: ambari
Updated Branches:
  refs/heads/trunk 9d665447e -> 80b2a1f2e


AMBARI-6726 Config History: E2E Integration of Config Version History Table. (atkach)


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

Branch: refs/heads/trunk
Commit: 80b2a1f2e27294f4228bf83ded3d755d120ad0d2
Parents: 9d66544
Author: atkach <at...@hortonworks.com>
Authored: Mon Aug 4 13:33:05 2014 +0300
Committer: atkach <at...@hortonworks.com>
Committed: Mon Aug 4 13:33:05 2014 +0300

----------------------------------------------------------------------
 .../main/dashboard/config_history_controller.js | 139 +++++++++++++++--
 ambari-web/app/controllers/main/host.js         |   2 +
 .../mappers/service_config_version_mapper.js    |  23 ++-
 ambari-web/app/messages.js                      |   2 +-
 ambari-web/app/mixins.js                        |   1 +
 .../app/mixins/common/tableServerProvider.js    |  25 ----
 .../app/mixins/common/table_server_mixin.js     | 148 +++++++++++++++++++
 ambari-web/app/models/service_config_version.js |  20 ++-
 .../templates/main/dashboard/config_history.hbs |  38 +++--
 .../app/views/common/custom_date_popup.js       | 127 ++++++++++++++++
 ambari-web/app/views/common/filter_view.js      |  15 +-
 ambari-web/app/views/common/table_view.js       |  31 ++--
 .../views/main/dashboard/config_history_view.js | 104 ++++++++++---
 ambari-web/app/views/main/host.js               |   5 -
 14 files changed, 578 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/controllers/main/dashboard/config_history_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/dashboard/config_history_controller.js b/ambari-web/app/controllers/main/dashboard/config_history_controller.js
index 493f499..8989b56 100644
--- a/ambari-web/app/controllers/main/dashboard/config_history_controller.js
+++ b/ambari-web/app/controllers/main/dashboard/config_history_controller.js
@@ -16,28 +16,124 @@
  * limitations under the License.
  */
 
-App.MainConfigHistoryController = Em.ArrayController.extend({
+var customDatePopup = require('/views/common/custom_date_popup');
+
+App.MainConfigHistoryController = Em.ArrayController.extend(App.TableServerMixin, {
   name: 'mainConfigHistoryController',
 
   dataSource: App.ServiceConfigVersion.find(),
   content: function () {
-    return this.get('dataSource').toArray();
-  }.property('dataSource.@each.isLoaded'),
-  isLoaded: false,
+    return this.get('dataSource').filterProperty('isRequested');
+  }.property('dataSource.@each.isRequested'),
   isPolling: false,
+  filteredCount: 0,
+  mockUrl: '/data/configurations/service_versions.json',
+  realUrl: function () {
+    return App.apiPrefix + '/clusters/' + App.get('clusterName') + '/configurations/serviceconfigversions?<parameters>fields=serviceconfigversion,user,appliedtime,createtime,service_name&minimal_response=true';
+  }.property('App.clusterName'),
 
   /**
-   * initial data load
+   * associations between host property and column index
+   * @type {Array}
    */
-  load: function () {
-    var self = this;
+  colPropAssoc: function () {
+    var associations = [];
+    associations[1] = 'serviceVersion';
+    associations[2] = 'appliedTime';
+    associations[3] = 'author';
+    associations[4] = 'notes';
+    return associations;
+  }.property(),
 
-    this.set('isLoaded', false);
-    this.loadHistoryToModel().done(function () {
-      self.set('isLoaded', true);
-      self.doPolling();
-    });
-  },
+  filterProps: [
+    {
+      name: 'serviceVersion',
+      key: 'service_name',
+      type: 'EQUAL'
+    },
+    {
+      name: 'appliedTime',
+      key: 'appliedtime',
+      type: 'MORE'
+    },
+    {
+      name: 'author',
+      key: 'user',
+      type: 'MATCH'
+    }
+    //TODO uncomment when API contains "notes" property
+    /*,
+    {
+      name: 'notes',
+      key: '',
+      type: 'MATCH'
+    }*/
+  ],
+
+  sortProps: [
+    {
+      name: 'serviceVersion',
+      key: 'service_name'
+    },
+    {
+      name: 'appliedTime',
+      key: 'appliedtime'
+    },
+    {
+      name: 'author',
+      key: 'user'
+    }/*,
+    {
+      name: 'notes',
+      key: ''
+    }*/
+  ],
+
+  modifiedFilter: Em.Object.create({
+    optionValue: 'Any',
+    filterModified: function () {
+      var time = "";
+      var curTime = new Date().getTime();
+
+      switch (this.get('optionValue')) {
+        case 'Past 1 hour':
+          time = curTime - 3600000;
+          break;
+        case 'Past 1 Day':
+          time = curTime - 86400000;
+          break;
+        case 'Past 2 Days':
+          time = curTime - 172800000;
+          break;
+        case 'Past 7 Days':
+          time = curTime - 604800000;
+          break;
+        case 'Past 14 Days':
+          time = curTime - 1209600000;
+          break;
+        case 'Past 30 Days':
+          time = curTime - 2592000000;
+          break;
+        case 'Custom':
+          customDatePopup.showCustomDatePopup(this, this.get('actualValues'));
+          break;
+        case 'Any':
+          time = "";
+          break;
+      }
+      if (this.get('modified') !== "Custom") {
+        this.set("actualValues.startTime", time);
+        this.set("actualValues.endTime", '');
+      }
+    }.observes('optionValue'),
+    cancel: function () {
+      this.set('optionValue', 'Any');
+    },
+    actualValues: Em.Object.create({
+      startTime: "",
+      endTime: ""
+    })
+  }),
 
   /**
    * get data from server and push it to model
@@ -45,10 +141,9 @@ App.MainConfigHistoryController = Em.ArrayController.extend({
    */
   loadHistoryToModel: function () {
     var dfd = $.Deferred();
+    var queryParams = this.getQueryParameters();
 
-    var url = '/data/configurations/service_versions.json';
-
-    App.HttpClient.get(url, App.serviceConfigVersionsMapper, {
+    App.HttpClient.get(this.getUrl(queryParams), App.serviceConfigVersionsMapper, {
       complete: function () {
         dfd.resolve();
       }
@@ -56,6 +151,18 @@ App.MainConfigHistoryController = Em.ArrayController.extend({
     return dfd.promise();
   },
 
+  getUrl: function (queryParams) {
+    var params = '';
+    if (App.get('testMode')) {
+      return this.get('mockUrl');
+    } else {
+      if (queryParams) {
+        params = App.router.get('updateController').computeParameters(queryParams);
+      }
+      return this.get('realUrl').replace('<parameters>', params);
+    }
+  },
+
   /**
    * request latest data from server and update content
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/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 6b9210a..227a091 100644
--- a/ambari-web/app/controllers/main/host.js
+++ b/ambari-web/app/controllers/main/host.js
@@ -79,6 +79,8 @@ App.MainHostController = Em.ArrayController.extend({
    * filterProperties support follow types of filter:
    * MATCH - match of RegExp
    * EQUAL - equality "="
+   * LESS - "<"
+   * MORE - ">"
    * MULTIPLE - multiple values to compare
    * CUSTOM - substitute values with keys "{#}" in alias
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/mappers/service_config_version_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/service_config_version_mapper.js b/ambari-web/app/mappers/service_config_version_mapper.js
index 93fc2b8..1dbf99c 100644
--- a/ambari-web/app/mappers/service_config_version_mapper.js
+++ b/ambari-web/app/mappers/service_config_version_mapper.js
@@ -21,23 +21,38 @@ var App = require('app');
 App.serviceConfigVersionsMapper = App.QuickDataMapper.create({
   model: App.ServiceConfigVersion,
   config: {
-    service_name: 'servicename',
+    service_name: 'service_name',
+    service_id: 'service_name',
     version: "serviceconfigversion",
     create_time: 'createtime',
     applied_time: 'appliedtime',
-    author: 'author',
-    notes: 'notes'
+    author: 'user',
+    notes: 'notes',
+    index: 'index'
   },
   map: function (json) {
     var result = [];
+    var itemIds = {};
 
     if (json && json.items) {
-      json.items.forEach(function (item) {
+      json.items.forEach(function (item, index) {
         var parsedItem = this.parseIt(item, this.get('config'));
         parsedItem.id = parsedItem.service_name + '_' + parsedItem.version;
+        parsedItem.is_requested = true;
+        itemIds[parsedItem.id] = true;
+        parsedItem.index = index;
         result.push(parsedItem);
       }, this);
 
+      this.get('model').find().forEach(function (item) {
+        if (!itemIds[item.get('id')]) {
+          item.set('isRequested', false);
+        }
+      });
+      var itemTotal = parseInt(json.itemTotal);
+      if (!isNaN(itemTotal)) {
+        App.router.set('mainConfigHistoryController.filteredCount', itemTotal);
+      }
       App.store.loadMany(this.get('model'), result);
     }
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 42724d3..68e9d51 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -215,6 +215,7 @@ Em.I18n.translations = {
   'common.notes': 'Notes',
   'common.view': 'View',
   'common.compare': 'Compare',
+  'common.latest': 'Latest',
 
   'passiveState.turnOn':'Turn On Maintenance Mode',
   'passiveState.turnOff':'Turn Off Maintenance Mode',
@@ -1961,7 +1962,6 @@ Em.I18n.translations = {
   'dashboard.configHistory.table.modified.title' : 'Modified',
   'dashboard.configHistory.table.empty' : 'No history to display',
   'dashboard.configHistory.table.filteredHostsInfo': '{0} of {1} versions showing',
-  'dashboard.configHistory.table.current': '(Current)',
   'dashboard.configHistory.info-bar.authoredOn': 'authored on',
   'dashboard.configHistory.info-bar.changesToHandle': 'Changes to handle',
   'dashboard.configHistory.info-bar.showMore': 'Show more',

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/mixins.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js
index ba5d51f..98cc3d8 100644
--- a/ambari-web/app/mixins.js
+++ b/ambari-web/app/mixins.js
@@ -23,5 +23,6 @@ require('mixins/common/localStorage');
 require('mixins/common/userPref');
 require('mixins/models/service_mixin');
 require('mixins/common/tableServerProvider');
+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/80b2a1f2/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
index b1942ce..4ba45d1 100644
--- a/ambari-web/app/mixins/common/tableServerProvider.js
+++ b/ambari-web/app/mixins/common/tableServerProvider.js
@@ -96,30 +96,5 @@ App.TableServerProvider = Em.Mixin.create({
         this.refresh();
       }
     }
-  },
-
-  /**
-   * save filter conditions to local storage
-   * @param iColumn {Number}
-   * @param value {String|Array}
-   * @param type {String}
-   * @param skipFilter {Boolean}
-   */
-  saveFilterConditions: function (iColumn, value, type, skipFilter) {
-    var filterCondition = this.get('filterConditions').findProperty('iColumn', iColumn);
-
-    if (filterCondition) {
-      filterCondition.value = value;
-      filterCondition.skipFilter = skipFilter;
-    } else {
-      filterCondition = {
-        skipFilter: skipFilter,
-        iColumn: iColumn,
-        value: value,
-        type: type
-      };
-      this.get('filterConditions').push(filterCondition);
-    }
-    App.db.setFilterConditions(this.get('controller.name'), this.get('filterConditions'));
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/mixins/common/table_server_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/table_server_mixin.js b/ambari-web/app/mixins/common/table_server_mixin.js
new file mode 100644
index 0000000..d449125
--- /dev/null
+++ b/ambari-web/app/mixins/common/table_server_mixin.js
@@ -0,0 +1,148 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var validator = require('utils/validator');
+
+//TODO integrate mixin into mainHostController to avoid code duplication
+App.TableServerMixin = Em.Mixin.create({
+  queryParams: [],
+  resetStartIndex: false,
+  /**
+   * filterProps support follow types of filter:
+   * MATCH - match of RegExp
+   * EQUAL - equality "="
+   * LESS - "<"
+   * MORE - ">"
+   * MULTIPLE - multiple values to compare
+   * CUSTOM - substitute values with keys "{#}" in alias
+   */
+  filterProps: [],
+  /**
+   * include "from" nad "page_size"
+   */
+  paginationProps: [
+    {
+      name: 'displayLength',
+      key: 'page_size',
+      value: '25',
+      type: 'EQUAL'
+    },
+    {
+      name: 'startIndex',
+      key: 'from',
+      value: 0,
+      type: 'EQUAL'
+    }
+  ],
+  sortProps: [],
+
+  /**
+   * update values of pagination properties from local db and return them
+   * @return {array}
+   */
+  getPaginationProps: function () {
+    var displayLength = App.db.getDisplayLength(this.get('name'));
+    if (displayLength) {
+      this.get('paginationProps').findProperty('name', 'displayLength').value = displayLength;
+    }
+
+    var startIndex = App.db.getStartIndex(this.get('name'));
+    if (!Em.isNone(startIndex)) {
+      startIndex = (startIndex > 0) ? startIndex - 1 : startIndex;
+      if (this.get('resetStartIndex')) {
+        startIndex = 0;
+      }
+      this.get('paginationProps').findProperty('name', 'startIndex').value = startIndex;
+    }
+    return this.get('paginationProps');
+  },
+
+  /**
+   * get sort properties from local db
+   * @return {Array}
+   */
+  getSortProps: function () {
+    var savedSortConditions = App.db.getSortingStatuses(this.get('name')) || [],
+      sortProperties = this.get('sortProps'),
+      sortParams = [];
+
+    savedSortConditions.forEach(function (sort) {
+      var property = sortProperties.findProperty('name', sort.name);
+
+      if (property && (sort.status === 'sorting_asc' || sort.status === 'sorting_desc')) {
+        property.value = sort.status.replace('sorting_', '');
+        property.type = 'SORT';
+        sortParams.push(property);
+      }
+    });
+    return sortParams;
+  },
+  /**
+   * get filter properties from local db
+   * @return {Array}
+   */
+  getFilterProps: function () {
+    var savedFilterConditions = App.db.getFilterConditions(this.get('name')) || [],
+      filterProperties = this.get('filterProps'),
+      filterParams = [],
+      colPropAssoc = this.get('colPropAssoc');
+
+    savedFilterConditions.forEach(function (filter) {
+      var property = filterProperties.findProperty('name', colPropAssoc[filter.iColumn]);
+
+      if (property && filter.value.length > 0 && !filter.skipFilter) {
+        property.isFilter = true;
+
+        if (filter.type === 'range') {
+          //range value should contain array of two element with start and end values accordingly
+          filter.value.forEach(function (val, index) {
+            if (val) {
+              property.type = (index === 0) ? "MORE" : "LESS";
+              property.value = val;
+              filterParams.push(property);
+            }
+          });
+        } else if (filter.type === 'string') {
+          property.value = this.getRegExp(filter.value);
+        } else {
+          property.value = filter.value;
+          filterParams.push(property);
+        }
+      }
+    }, this);
+    return filterParams;
+  },
+
+  getQueryParameters: function () {
+    var queryParams = [];
+
+    queryParams.pushObjects(this.getPaginationProps());
+    queryParams.pushObjects(this.getSortProps());
+    queryParams.pushObjects(this.getFilterProps());
+    this.set('queryParams', queryParams);
+    return queryParams;
+  },
+
+  getRegExp: function (value) {
+    value = validator.isValidMatchesRegexp(value) ? value.replace(/(\.+\*?|(\.\*)+)$/, '') + '.*' : '^$';
+    value = /^\.\*/.test(value) || value == '^$' ? value : '.*' + value;
+    return value;
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/models/service_config_version.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/service_config_version.js b/ambari-web/app/models/service_config_version.js
index 10552fd..2c52d22 100644
--- a/ambari-web/app/models/service_config_version.js
+++ b/ambari-web/app/models/service_config_version.js
@@ -28,13 +28,23 @@ App.ServiceConfigVersion = DS.Model.extend({
   appliedTime: DS.attr('number'),
   author: DS.attr('string'),
   notes: DS.attr('string'),
-  serviceVersion: function(){
-    return this.get('serviceName') + ': '+ this.get('version');
+  service: DS.belongsTo('App.Service'),
+  index: DS.attr('number'),
+  serviceVersion: function () {
+    return this.get('serviceName') + ': ' + this.get('version');
   }.property('serviceName', 'version'),
-  modifiedDate: function() {
-    return dateUtil.dateFormat(this.get('createTime'));
+  modifiedDate: function () {
+    return dateUtil.dateFormat(this.get('appliedTime'));
   }.property('createTime'),
-  isCurrent: true
+  //TODO set isCurrent value from API response
+  isCurrent: false,
+  /**
+   * determine whether ServiceConfigVersion is requested from server
+   */
+  isRequested: DS.attr('boolean'),
+  isRestartRequired: function () {
+    return this.get('service.isRestartRequired');
+  }.property('service.isRestartRequired')
 });
 
 App.ServiceConfigVersion.FIXTURES = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/templates/main/dashboard/config_history.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/dashboard/config_history.hbs b/ambari-web/app/templates/main/dashboard/config_history.hbs
index 88ee4b9..6306078 100644
--- a/ambari-web/app/templates/main/dashboard/config_history.hbs
+++ b/ambari-web/app/templates/main/dashboard/config_history.hbs
@@ -27,30 +27,36 @@
         {{/view}}
 
         <tr class="filter-row">
-            <th class="first">{{view view.versionFilterView}}</th>
+            <th class="first">{{view view.serviceFilterView}}</th>
             <th>{{view view.modifiedFilterView}}</th>
             <th>{{view view.authorFilterView}}</th>
             <th>{{view view.notesFilterView}}</th>
         </tr>
         </thead>
-        <tbody>
-        {{#if view.pageContent}}
-          {{#each item in view.pageContent}}
+        <tbody class="services-menu">
+        {{#if view.filteringComplete}}
+          {{#if view.pageContent}}
+            {{#each item in view.pageContent}}
+                <tr>
+                    <td class="first"><a {{action goToServiceConfigs item.serviceName}}>
+                      {{item.serviceVersion}}
+                      {{#if item.isCurrent}}&nbsp;({{t common.latest}}){{/if}}
+                        <i {{bindAttr class=":icon-refresh :restart-required-service item.isRestartRequired::hidden"}}></i>
+                    </a></td>
+                    <td>{{item.modifiedDate}}</td>
+                    <td>{{item.author}}</td>
+                    <td>{{item.notes}}</td>
+                </tr>
+            {{/each}}
+          {{else}}
               <tr>
-                  <td class="first"><a {{action goToServiceConfigs item.serviceName}}>
-                    {{item.serviceVersion}}{{#if item.isCurrent}}&nbsp;{{t dashboard.configHistory.table.current}}{{/if}}
-                  </a></td>
-                  <td>{{item.modifiedDate}}</td>
-                  <td>{{item.author}}</td>
-                  <td>{{item.notes}}</td>
+                  <td class="first" colspan="4">
+                    {{t dashboard.configHistory.table.empty}}
+                  </td>
               </tr>
-          {{/each}}
+          {{/if}}
         {{else}}
-            <tr>
-                <td class="first" colspan="4">
-                  {{t dashboard.configHistory.table.empty}}
-                </td>
-            </tr>
+          <tr><td colspan="4"><div class="spinner"></div></td></tr>
         {{/if}}
         </tbody>
     </table>

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/views/common/custom_date_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/custom_date_popup.js b/ambari-web/app/views/common/custom_date_popup.js
new file mode 100644
index 0000000..d018e07
--- /dev/null
+++ b/ambari-web/app/views/common/custom_date_popup.js
@@ -0,0 +1,127 @@
+/**
+ * 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');
+
+module.exports = Em.Object.create({
+
+  // Fields values from Select Custom Dates form
+  customDateFormFields: Ember.Object.create({
+    startDate: null,
+    hoursForStart: null,
+    minutesForStart: null,
+    middayPeriodForStart: null,
+    endDate: null,
+    hoursForEnd: null,
+    minutesForEnd: null,
+    middayPeriodForEnd: null
+  }),
+
+  errors: Ember.Object.create({
+    isStartDateError: false,
+    isEndDateError: false
+  }),
+
+  errorMessages: Ember.Object.create({
+    startDate: '',
+    endDate: ''
+  }),
+
+  showCustomDatePopup: function (context, valueObject) {
+    var self = this;
+
+    App.ModalPopup.show({
+      header: Em.I18n.t('jobs.table.custom.date.header'),
+      onPrimary: function () {
+        self.validate();
+        if(self.get('errors.isStartDateError') || self.get('errors.isEndDateError')){
+          return false;
+        }
+
+        var windowStart = self.createCustomStartDate();
+        var windowEnd = self.createCustomEndDate();
+
+        valueObject.set("endTime", windowEnd.getTime());
+        valueObject.set("startTime", windowStart.getTime());
+        this.hide();
+      },
+      onSecondary: function () {
+        context.cancel();
+        this.hide();
+      },
+      bodyClass: App.JobsCustomDatesSelectView.extend({
+        controller: self
+      })
+    });
+  },
+
+  createCustomStartDate : function () {
+    var startDate = this.get('customDateFormFields.startDate');
+    var hoursForStart = this.get('customDateFormFields.hoursForStart');
+    var minutesForStart = this.get('customDateFormFields.minutesForStart');
+    var middayPeriodForStart = this.get('customDateFormFields.middayPeriodForStart');
+    if (startDate && hoursForStart && minutesForStart && middayPeriodForStart) {
+      return new Date(startDate + ' ' + hoursForStart + ':' + minutesForStart + ' ' + middayPeriodForStart);
+    }
+    return null;
+  },
+
+  createCustomEndDate : function () {
+    var endDate = this.get('customDateFormFields.endDate');
+    var hoursForEnd = this.get('customDateFormFields.hoursForEnd');
+    var minutesForEnd = this.get('customDateFormFields.minutesForEnd');
+    var middayPeriodForEnd = this.get('customDateFormFields.middayPeriodForEnd');
+    if (endDate && hoursForEnd && minutesForEnd && middayPeriodForEnd) {
+      return new Date(endDate + ' ' + hoursForEnd + ':' + minutesForEnd + ' ' + middayPeriodForEnd);
+    }
+    return null;
+  },
+
+  clearErrors: function () {
+    var errorMessages = this.get('errorMessages');
+    Em.keys(errorMessages).forEach(function (key) {
+      errorMessages.set(key, '');
+    }, this);
+    var errors = this.get('errors');
+    Em.keys(errors).forEach(function (key) {
+      errors.set(key, false);
+    }, this);
+  },
+
+  // Validation for every field in customDateFormFields
+  validate: function () {
+    var formFields = this.get('customDateFormFields');
+    var errors = this.get('errors');
+    var errorMessages = this.get('errorMessages');
+    this.clearErrors();
+    // Check if feild is empty
+    Em.keys(errorMessages).forEach(function (key) {
+      if (!formFields.get(key)) {
+        errors.set('is' + key.capitalize() + 'Error', true);
+        errorMessages.set(key, Em.I18n.t('jobs.customDateFilter.error.required'));
+      }
+    }, this);
+    // Check that endDate is after startDate
+    var startDate = this.createCustomStartDate();
+    var endDate = this.createCustomEndDate();
+    if (startDate && endDate && (startDate > endDate)) {
+      errors.set('isEndDateError', true);
+      errorMessages.set('endDate', Em.I18n.t('jobs.customDateFilter.error.date.order'));
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/views/common/filter_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/filter_view.js b/ambari-web/app/views/common/filter_view.js
index 851557d..93c2d90 100644
--- a/ambari-web/app/views/common/filter_view.js
+++ b/ambari-web/app/views/common/filter_view.js
@@ -72,11 +72,18 @@ var wrapperView = Ember.View.extend({
   emptyValue: '',
 
   /**
+   * reset value to empty string if emptyValue selected
+   */
+  actualValue: function () {
+    return this.get('value') === this.get('emptyValue') ? "" : this.get('value');
+  }.property('value'),
+
+  /**
    * Whether our <code>value</code> is empty or not
    * @return {Boolean}
    */
   isEmpty: function(){
-    if(this.get('value') === null){
+    if (Em.isNone(this.get('value'))) {
       return true;
     }
     return this.get('value').toString() === this.get('emptyValue').toString();
@@ -486,6 +493,12 @@ module.exports = {
           return true;
         };
         break;
+      case 'range':
+        return function (origin, compareValue){
+          //TODO add filter by range value
+          return true;
+        };
+        break;
       case 'string':
       default:
         return function(origin, compareValue){

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/ambari-web/app/views/common/table_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/table_view.js b/ambari-web/app/views/common/table_view.js
index 2121d8a..a234675 100644
--- a/ambari-web/app/views/common/table_view.js
+++ b/ambari-web/app/views/common/table_view.js
@@ -96,7 +96,11 @@ App.TableView = Em.View.extend(App.UserPref, {
       filterConditions.forEach(function (condition, index, filteredConditions) {
         var view = !Em.isNone(condition.iColumn) && childViews.findProperty('column', condition.iColumn);
         if (view) {
-          view.set('value', condition.value);
+          if (view.get('emptyValue')) {
+            view.set('value', view.get('emptyValue'));
+          } else {
+            view.set('value', condition.value);
+          }
           Em.run.next(function () {
             view.showClearFilter();
             // check if it is the last iteration
@@ -320,24 +324,33 @@ App.TableView = Em.View.extend(App.UserPref, {
    * @param {String} type
    */
   updateFilter: function (iColumn, value, type) {
+    this.saveFilterConditions(iColumn, value, type, false);
+    this.filtersUsedCalc();
+    this.filter();
+  },
+
+  /**
+   * save filter conditions to local storage
+   * @param iColumn {Number}
+   * @param value {String|Array}
+   * @param type {String}
+   * @param skipFilter {Boolean}
+   */
+  saveFilterConditions: function(iColumn, value, type, skipFilter) {
     var filterCondition = this.get('filterConditions').findProperty('iColumn', iColumn);
+
     if (filterCondition) {
       filterCondition.value = value;
-    }
-    else {
+      filterCondition.skipFilter = skipFilter;
+    } else {
       filterCondition = {
+        skipFilter: skipFilter,
         iColumn: iColumn,
         value: value,
         type: type
       };
       this.get('filterConditions').push(filterCondition);
     }
-    this.saveFilterConditions();
-    this.filtersUsedCalc();
-    this.filter();
-  },
-
-  saveFilterConditions: function() {
     App.db.setFilterConditions(this.get('controller.name'), this.get('filterConditions'));
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/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 b93a229..d302e69 100644
--- a/ambari-web/app/views/main/dashboard/config_history_view.js
+++ b/ambari-web/app/views/main/dashboard/config_history_view.js
@@ -24,23 +24,60 @@ App.MainConfigHistoryView = App.TableView.extend({
   templateName: require('templates/main/dashboard/config_history'),
 
   controllerBinding: 'App.router.mainConfigHistoryController',
+  filteringComplete: true,
+  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('filteredCount'),
+
+  filteredCount: function () {
+    return this.get('controller.filteredCount');
+  }.property('controller.filteredCount'),
+
+  totalCount: function () {
+    //TODO change to totalCount when property provided by API
+    return this.get('controller.filteredCount');
+  }.property('controller.filteredCount'),
   /**
    * return filtered number of all content number information displayed on the page footer bar
    * @returns {String}
    */
   filteredContentInfo: function () {
-    return this.t('hosts.filters.filteredHostsInfo').format(this.get('filteredCount'), this.get('content.length'));
+    return this.t('hosts.filters.filteredHostsInfo').format(this.get('filteredCount'), this.get('totalCount'));
   }.property('filteredCount', 'totalCount'),
 
+  /**
+   * synchronize properties of view with controller to generate query parameters
+   */
+  updatePagination: function () {
+    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');
+    }
+
+    this.refresh();
+  },
 
   didInsertElement: function () {
+    this.addObserver('startIndex', this, 'updatePagination');
+    this.addObserver('displayLength', this, 'updatePagination');
     this.set('controller.isPolling', true);
-    this.get('controller').load();
+    this.refresh();
   },
 
   /**
@@ -59,7 +96,7 @@ App.MainConfigHistoryView = App.TableView.extend({
   }),
   modifiedSort: sort.fieldView.extend({
     column: 2,
-    name: 'createTime',
+    name: 'appliedTime',
     displayName: Em.I18n.t('dashboard.configHistory.table.modified.title')
   }),
   authorSort: sort.fieldView.extend({
@@ -73,13 +110,14 @@ App.MainConfigHistoryView = App.TableView.extend({
     displayName: Em.I18n.t('common.notes')
   }),
 
-  versionFilterView: filters.createSelectView({
+  serviceFilterView: filters.createSelectView({
     column: 1,
     fieldType: 'filter-input-width',
-    content: ['All'],
-    valueBinding: "controller.filterObject.version",
+    content: function () {
+      return ['All'].concat(App.Service.find().mapProperty('serviceName'));
+    }.property('App.router.clusterController.isLoaded'),
     onChangeValue: function () {
-      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
+      this.get('parentView').updateFilter(this.get('column'), this.get('actualValue'), 'select');
     },
     emptyValue: Em.I18n.t('common.all')
   }),
@@ -87,11 +125,13 @@ App.MainConfigHistoryView = App.TableView.extend({
   modifiedFilterView: filters.createSelectView({
     column: 2,
     fieldType: 'filter-input-width',
-    content: ['Any'],
-    valueBinding: "controller.filterObject.modified",
-    onChangeValue: function () {
-      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
-    }
+    content: ['Any', 'Past 1 hour',  'Past 1 Day', 'Past 2 Days', 'Past 7 Days', 'Past 14 Days', 'Past 30 Days', 'Custom'],
+    valueBinding: "controller.modifiedFilter.optionValue",
+    startTimeBinding: "controller.modifiedFilter.actualValues.startTime",
+    endTimeBinding: "controller.modifiedFilter.actualValues.endTime",
+    onTimeChange: function () {
+      this.get('parentView').updateFilter(this.get('column'), [this.get('startTime'), this.get('endTime')], 'range');
+    }.observes('startTime')
   }),
 
   authorFilterView: filters.createTextView({
@@ -110,11 +150,34 @@ 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();
+    }
+  },
+
   /**
    * sort content
    */
   refresh: function () {
-    this.sortContent();
+    var self = this;
+
+    this.set('filteringComplete', false);
+    this.get('controller').loadHistoryToModel().done(function(){
+      self.set('filteringComplete', true);
+      self.propertyDidChange('pageContent');
+    });
   },
 
   /**
@@ -122,12 +185,13 @@ App.MainConfigHistoryView = App.TableView.extend({
    * @type {Array}
    */
   colPropAssoc: function () {
-    var associations = [];
-    associations[1] = 'serviceVersion';
-    associations[2] = 'createTime';
-    associations[3] = 'author';
-    associations[4] = 'notes';
-    return associations;
-  }.property()
+    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')
 
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/80b2a1f2/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 d267a2b..e9c90fd 100644
--- a/ambari-web/app/views/main/host.js
+++ b/ambari-web/app/views/main/host.js
@@ -85,11 +85,6 @@ App.MainHostView = App.TableView.extend(App.TableServerProvider, {
   }.property('filteredCount'),
 
   /**
-   * Stub function
-   */
-  updatePaging: function () {},
-
-  /**
    * flag to toggle displaying selected hosts counter
    */
   showSelectedFilter: function () {