Repository: ambari
Updated Branches:
  refs/heads/trunk 0af0d9b3c -> 6fc532bc4


AMBARI-8072. Alerts UI: Summary Page. Create basic table with mock data. 
(onechiporenko)


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

Branch: refs/heads/trunk
Commit: 6fc532bc4a7cab0a667229be68491c9dc83e9dc8
Parents: 0af0d9b
Author: Oleg Nechiporenko <onechipore...@apache.org>
Authored: Fri Oct 31 19:28:29 2014 +0200
Committer: Oleg Nechiporenko <onechipore...@apache.org>
Committed: Fri Oct 31 19:28:29 2014 +0200

----------------------------------------------------------------------
 ambari-web/app/controllers.js                   |   1 +
 .../main/alert_definitions_controller.js        | 108 ++++++++
 .../app/mappers/alert_definitions_mapper.js     |   3 +
 ambari-web/app/messages.js                      |   4 +
 ambari-web/app/models/alertDefinition.js        |  13 +-
 ambari-web/app/models/alert_instance.js         |  92 ++++++-
 ambari-web/app/routes/main.js                   |   6 +-
 ambari-web/app/styles/alerts.less               |  87 +++++++
 ambari-web/app/styles/application.less          |  11 +-
 ambari-web/app/styles/common.less               |  29 +++
 ambari-web/app/templates/main/alerts.hbs        |  61 ++++-
 ambari-web/app/utils/data_manipulation.js       |  97 ++++++++
 ambari-web/app/views.js                         |   2 +-
 ambari-web/app/views/common/filter_view.js      |   3 +-
 .../app/views/main/alert_definitions_view.js    | 244 +++++++++++++++++++
 ambari-web/test/utils/data_manipulation_test.js |  89 +++++++
 16 files changed, 823 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/controllers.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers.js b/ambari-web/app/controllers.js
index aeec3ee..1690f94 100644
--- a/ambari-web/app/controllers.js
+++ b/ambari-web/app/controllers.js
@@ -66,6 +66,7 @@ require('controllers/main/admin/security/add/step3');
 require('controllers/main/admin/security/add/step4');
 require('controllers/main/admin/authentication');
 require('controllers/main/alerts_controller');
+require('controllers/main/alert_definitions_controller');
 require('controllers/main/service');
 require('controllers/main/service/item');
 require('controllers/main/service/info/summary');

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/controllers/main/alert_definitions_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/alert_definitions_controller.js 
b/ambari-web/app/controllers/main/alert_definitions_controller.js
new file mode 100644
index 0000000..d41ff91
--- /dev/null
+++ b/ambari-web/app/controllers/main/alert_definitions_controller.js
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var customDatePopup = require('/views/common/custom_date_popup');
+
+App.MainAlertDefinitionsController = Em.ArrayController.extend({
+
+  name: 'mainAlertDefinitionsController',
+
+  /**
+   * Timestamp when <code>App.alertDefinitionsMapper</code> run last time
+   * Current <code>content</code> is updated on when it changed
+   * @type {number|null}
+   */
+  mapperTimestamp: null,
+
+  /**
+   * List of all <code>App.AlertDefinition</code>
+   * Consists of:
+   * <ul>
+   *   <li>App.PortAlertDefinition</li>
+   *   <li>App.MetricsAlertDefinition</li>
+   *   <li>App.WebAlertDefinition</li>
+   *   <li>App.AggregateAlertDefinition</li>
+   *   <li>App.ScriptAlertDefinition</li>
+   * </ul>
+   * @type {App.AlertDefinition[]}
+   */
+  content: function() {
+    return Array.prototype.concat.call(Array.prototype, 
App.PortAlertDefinition.find().toArray(),
+      App.MetricsAlertDefinition.find().toArray(),
+      App.WebAlertDefinition.find().toArray(),
+      App.AggregateAlertDefinition.find().toArray(),
+      App.ScriptAlertDefinition.find().toArray());
+  }.property('mapperTimestamp'),
+
+  /**
+   * Object for lastTriggered-filter
+   * @type {Em.Object}
+   */
+  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':
+        case 'Custom2':
+          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: ""
+    })
+  }),
+
+  toggleState: Em.K,
+
+  gotoAlertDetails: Em.K
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/mappers/alert_definitions_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/alert_definitions_mapper.js 
b/ambari-web/app/mappers/alert_definitions_mapper.js
index b75c195..196c649 100644
--- a/ambari-web/app/mappers/alert_definitions_mapper.js
+++ b/ambari-web/app/mappers/alert_definitions_mapper.js
@@ -152,6 +152,9 @@ App.alertDefinitionsMapper = App.QuickDataMapper.create({
       App.store.loadMany(App.WebAlertDefinition, webAlertDefinitions);
       App.store.loadMany(App.AggregateAlertDefinition, 
aggregateAlertDefinitions);
       App.store.loadMany(App.ScriptAlertDefinition, scriptAlertDefinitions);
+      if (App.router.get('mainAlertDefinitionsController')) {
+         App.router.set('mainAlertDefinitionsController.mapperTimestamp', (new 
Date()).getTime());
+      }
     }
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 65a0260..9e854be 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -819,6 +819,10 @@ Em.I18n.translations = {
   'form.validator.configGroupName':'Invalid Group Name. Only alphanumerics, 
hyphens, spaces and underscores are allowed.',
   'form.validator.configKey.specific':'"{0}" is invalid Key. Only 
alphanumerics, hyphens, underscores, asterisks and periods are allowed.',
 
+  'alerts.table.noAlerts': 'No Alerts to display',
+  'alerts.table.header.lastTriggered': 'Last Triggered',
+  'alerts.filters.filteredAlertsInfo': '{0} of {1} alerts showing',
+
   'admin.advanced.caution':'This section is for advanced user 
only.<br/>Proceed with caution.',
   'admin.advanced.button.uninstallIncludingData':'Uninstall cluster including 
all data.',
   'admin.advanced.button.uninstallKeepData':'Uninstall cluster but keep data.',

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/models/alertDefinition.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alertDefinition.js 
b/ambari-web/app/models/alertDefinition.js
index e5c18ab..e5441ea 100644
--- a/ambari-web/app/models/alertDefinition.js
+++ b/ambari-web/app/models/alertDefinition.js
@@ -17,6 +17,7 @@
  */
 
 var App = require('app');
+var dateUtils = require('utils/date');
 
 App.AlertDefinition = DS.Model.extend({
 
@@ -28,7 +29,17 @@ App.AlertDefinition = DS.Model.extend({
   scope: DS.attr('string'),
   interval: DS.attr('number'),
   type: DS.attr('string'),
-  reporting: DS.hasMany('App.AlertReportDefinition')
+  reporting: DS.hasMany('App.AlertReportDefinition'),
+  alerts: DS.hasMany('App.AlertInstance'),
+
+  /**
+   * Formatted timestamp for latest alert triggering for current 
alertDefinition
+   * @type {string}
+   */
+  lastTriggered: function() {
+    return dateUtils.dateFormat(Math.max.apply(Math, 
this.get('alerts').mapProperty('latestTimestamp')));
+  }.property('alerts.@each.latestTimestamp')
+
 });
 
 App.AlertReportDefinition = DS.Model.extend({

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/models/alert_instance.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alert_instance.js 
b/ambari-web/app/models/alert_instance.js
index 1d6c99c..b13b952 100644
--- a/ambari-web/app/models/alert_instance.js
+++ b/ambari-web/app/models/alert_instance.js
@@ -21,7 +21,7 @@ var App = require('app');
 App.AlertInstance = DS.Model.extend({
   id: DS.attr('number'),
   label: DS.attr('string'),
-  alertDefinition: DS.attr('string'),
+  alertDefinition: DS.belongsTo('App.AlertDefinition'),
   serviceName: DS.attr('string'),
   componentName: DS.attr('string'),
   hostName: DS.attr('string'),
@@ -32,4 +32,92 @@ App.AlertInstance = DS.Model.extend({
   instance: DS.attr('string'),
   state: DS.attr('string'),
   text: DS.attr('string')
-});
\ No newline at end of file
+});
+
+App.AlertInstance.FIXTURES = [
+  {
+    "id": 1,
+    "cluster_name": "tdk",
+    "component_name": "SECONDARY_NAMENODE",
+    "host_name": "tr-2.c.pramod-thangali.internal",
+    "instance": null,
+    "label": "Secondary NameNode Process",
+    "latest_timestamp": 1414664775337,
+    "maintenance_state": "OFF",
+    "name": "secondary_namenode_process",
+    "original_timestamp": 1414585335334,
+    "scope": "ANY",
+    "service_name": "HDFS",
+    "state": "CRITICAL",
+    "text": "Connection failed: [Errno 111] Connection refused on host 
tr-2.c.pramod-thangali.internal:50090",
+    "alert_definition": 1
+  },
+  {
+    "cluster_name" : "tdk",
+    "component_name" : "DATANODE",
+    "host_name" : "tr-3.c.pramod-thangali.internal",
+    "id" : 2,
+    "instance" : null,
+    "label" : "DataNode Web UI",
+    "latest_timestamp" : 1414666905645,
+    "maintenance_state" : "OFF",
+    "name" : "datanode_webui",
+    "original_timestamp" : 1414585365674,
+    "scope" : "HOST",
+    "service_name" : "HDFS",
+    "state" : "CRITICAL",
+    "text" : "Connection failed to 0.0.0.0:50075",
+    "alert_definition": 2
+  },
+  {
+    "cluster_name": "tdk",
+    "component_name": "ZOOKEEPER_SERVER",
+    "host_name": "tr-1.c.pramod-thangali.internal",
+    "id": 3,
+    "instance": null,
+    "label": "ZooKeeper Server Process",
+    "latest_timestamp": 1414665174611,
+    "maintenance_state": "OFF",
+    "name": "zookeeper_server_process",
+    "original_timestamp": 1414585014606,
+    "scope": "ANY",
+    "service_name": "ZOOKEEPER",
+    "state": "CRITICAL",
+    "text": "TCP OK - 0.0000 response on port 2181",
+    "alert_definition": 3
+  },
+  {
+    "cluster_name": "tdk",
+    "component_name": "ZOOKEEPER_SERVER",
+    "host_name": "tr-2.c.pramod-thangali.internal",
+    "id": 4,
+    "instance": null,
+    "label": "ZooKeeper Server Process",
+    "latest_timestamp": 1414665135341,
+    "maintenance_state": "OFF",
+    "name": "zookeeper_server_process",
+    "original_timestamp": 1414585035316,
+    "scope": "ANY",
+    "service_name": "ZOOKEEPER",
+    "state": "OK",
+    "text": "TCP OK - 0.0000 response on port 2181",
+    "alert_definition": 3
+  },
+  {
+    "cluster_name": "tdk",
+    "component_name": "ZOOKEEPER_SERVER",
+    "host_name": "tr-3.c.pramod-thangali.internal",
+    "id": 5,
+    "instance": null,
+    "label": "ZooKeeper Server Process",
+    "latest_timestamp": 1414665165640,
+    "maintenance_state": "OFF",
+    "name": "zookeeper_server_process",
+    "original_timestamp": 1414585065616,
+    "scope": "ANY",
+    "service_name": "ZOOKEEPER",
+    "state": "OK",
+    "text": "TCP OK - 0.0000 response on port 2181",
+    "alert_definition": 3
+  }
+];

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/routes/main.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/routes/main.js b/ambari-web/app/routes/main.js
index 41536cc..787e1db 100644
--- a/ambari-web/app/routes/main.js
+++ b/ambari-web/app/routes/main.js
@@ -315,10 +315,10 @@ module.exports = Em.Route.extend({
 
   alerts: Em.Route.extend({
     route: '/alerts',
-    index: Ember.Route.extend({
+    index: Em.Route.extend({
       route: '/',
       connectOutlets: function (router, context) {
-        router.get('mainController').connectOutlet('mainAlerts');
+        router.get('mainController').connectOutlet('mainAlertDefinitions');
       }
     }),
 
@@ -327,7 +327,7 @@ module.exports = Em.Route.extend({
       connectOutlets: function (router, host) {
       },
 
-      index: Ember.Route.extend({
+      index: Em.Route.extend({
         route: '/'
       })
     })

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/styles/alerts.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/alerts.less 
b/ambari-web/app/styles/alerts.less
new file mode 100644
index 0000000..d263f54
--- /dev/null
+++ b/ambari-web/app/styles/alerts.less
@@ -0,0 +1,87 @@
+/**
+ * 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.
+ */
+
+@import 'common.less';
+
+.alert-state-OK {color: @health-status-green;}
+.alert-state-WARNING {color: @health-status-red;}
+.alert-state-CRITICAL {color: @health-status-red;}
+.alert-state-DISABLED {color: #000;}
+.alert-state-UNKNOWN {color: @health-status-yellow;}
+
+#alerts-table {
+
+  margin-top: 10px;
+  margin-bottom: 10px;
+  font-size: 13px\9;
+
+  .filter-row {
+    th {
+      padding: 0px;
+      padding-left: 4px;
+    }
+  }
+  thead {
+    background: none repeat scroll 0 0 #F8F8F8;
+    th {
+      padding: 8px;
+    }
+  }
+
+  a {
+    &.disabled {
+      color: #000;
+    }
+  }
+
+  .col0,
+  td:first-child,
+  th:first-child {
+    width: 30%;
+  }
+
+  .col1,
+  td:first-child + td,
+  th:first-child + th{
+    width: 18%;
+    .filter-input-width{
+      width:90%;
+    }
+  }
+
+  .col2,
+  td:first-child + td + td,
+  th:first-child + th + th{
+    width: 17%;
+    .filter-input-width{
+      width:90%;
+    }
+  }
+  .col3,
+  td:first-child + td + td + td,
+  th:first-child + th + th + th {
+    width: 25%
+  }
+
+  .col4,
+  td:first-child + td + td + td + td,
+  th:first-child + th + th + th + th{
+    width: 10%;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less 
b/ambari-web/app/styles/application.less
index 8d2bd6f..d1ecd1f 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+@import 'common.less';
 
 @space-s: 5px;
 @space-m: 10px;
@@ -493,16 +494,6 @@ h1 {
 .show {
   visibility: visible;
 }
-/************************************************************************
-* Health status(service/host/host component health)icon colors
-***********************************************************************/
-@health-status-red: red;
-@health-status-green: #5AB400;
-@health-status-yellow: #FFD13D;
-@health-status-orange: #FF8E00;
-/************************************************************************
-* Health status(service/host/host component health)icon colors ends
-***********************************************************************/
 
 /***************
  * Ambari wide icon colors

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/styles/common.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/common.less 
b/ambari-web/app/styles/common.less
new file mode 100644
index 0000000..e003989
--- /dev/null
+++ b/ambari-web/app/styles/common.less
@@ -0,0 +1,29 @@
+/**
+ * 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.
+ */
+
+/************************************************************************
+* Health status(service/host/host component health)icon colors
+***********************************************************************/
+@health-status-red: red;
+@health-status-green: #5AB400;
+@health-status-yellow: #FFD13D;
+@health-status-orange: #FF8E00;
+/************************************************************************
+* Health status(service/host/host component health)icon colors ends
+***********************************************************************/
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/templates/main/alerts.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/alerts.hbs 
b/ambari-web/app/templates/main/alerts.hbs
index d4120ed..5e71eb6 100644
--- a/ambari-web/app/templates/main/alerts.hbs
+++ b/ambari-web/app/templates/main/alerts.hbs
@@ -19,20 +19,65 @@
 <div id="alerts">
   <div class="box-header row">
   </div>
-  <table class="table advanced-header-table table-bordered table-striped" 
id="">
+  <table class="table advanced-header-table table-bordered table-striped" 
id="alerts-table">
     <thead>
-      <tr class="filter-row">
-      </tr>
+      {{#view view.sortView classNames="label-row" 
contentBinding="view.filteredContent"}}
+        {{view view.parentView.nameSort class="first"}}
+        {{view view.parentView.statusSort}}
+        {{view view.parentView.serviceSort}}
+        {{view view.parentView.lastTriggeredSort}}
+      <th><span class="icon-off"></span></th>
+      {{/view}}
+    <tr class="filter-row">
+      <th class="first">{{view view.nameFilterView}}</th>
+      <th>{{view view.stateFilterView}}</th>
+      <th>{{view view.serviceFilterView}}</th>
+      <th>{{view view.triggeredFilterView}}</th>
+      <th></th>
+    </tr>
     </thead>
     <tbody>
+      {{#if view.pageContent}}
+        {{#each alertDefinition in view.pageContent}}
+          {{#view view.AlertDefinitionView contentBinding="alertDefinition"}}
+            <td class="first">
+              <a href="#" {{action "gotoAlertDetails" alertDefinition 
target="controller"}}>{{alertDefinition.label}}</a>
+            </td>
+            <td>{{{view.status}}}</td>
+            <td>{{alertDefinition.service.serviceName}}</td>
+            <td>{{alertDefinition.lastTriggered}}</td>
+            <td class="last">
+              <a href="#" {{action "toggleState" alertDefinition 
target="controller"}} {{bindAttr 
class="alertDefinition.enabled:enabled:disabled"}}>
+                <span class="icon-off"></span>
+              </a>
+            </td>
+          {{/view}}
+        {{/each}}
+      {{else}}
+      <tr>
+        <td class="first"></td>
+        <td colspan="4">
+          {{t alerts.table.noAlerts}}
+        </td>
+      </tr>
+      {{/if}}
     </tbody>
   </table>
 
-  <div>
-    <div class="spinner"></div>
-  </div>
-
   <div class="page-bar">
+    <div class="filtered-info span4">
+      <label>{{view.filteredContentInfo}} - <a {{action clearFilters 
target="view"}}
+          href="#">{{t tableView.filters.clearAllFilters}}</a></label>
+    </div>
+    <div class="selected-hosts-info span4">
+    </div>
+    <div class="items-on-page">
+      <label>{{t common.show}}: {{view view.rowsPerPageSelectView 
selectionBinding="view.displayLength"}}</label>
+    </div>
+    <div class="info">{{view.paginationInfo}}</div>
+    <div class="paging_two_button">
+      <a {{bindAttr class="view.paginationLeftClass"}}{{action previousPage 
target="view"}}><i class="icon-arrow-left"></i></a>
+      <a {{bindAttr class="view.paginationRightClass"}}{{action nextPage 
target="view"}}><i class="icon-arrow-right"></i></a>
+    </div>
   </div>
 </div>
-

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/utils/data_manipulation.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/data_manipulation.js 
b/ambari-web/app/utils/data_manipulation.js
new file mode 100644
index 0000000..7aab73b
--- /dev/null
+++ b/ambari-web/app/utils/data_manipulation.js
@@ -0,0 +1,97 @@
+/**
+ * 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 = {
+
+  /**
+   * Exclude objects from <code>collection</code> if its <code>key</code> 
exist in <code>valuesToReject</code>
+   * Example:
+   * <code>
+   *   var collection = [{n: 'v1'}, {n: 'v2'}, {n: 'v3'}, {n: 'v4'}],
+   *   key = 'n',
+   *   valuesToReject = ['v2', 'v3'];
+   *   var result = rejectPropertyValues(collection, key, valuesToReject); // 
[{n: 'v1'}, {n: 'v4'}]
+   * </code>
+   * @param {Object[]} collection array of objects
+   * @param {String} key property name of each object to be checked
+   * @param {String[]} valuesToReject list of <code>key</code> values. Objects 
with <code>key</code>,
+   *                                    equal to one of them, will be excluded
+   * @returns {Object[]} Rejected array
+   */
+  rejectPropertyValues: function(collection, key, valuesToReject) {
+    return collection.filter(function (item) {
+      var propertyValue = Em.get(item, key);
+      return valuesToReject.contains(propertyValue);
+    });
+  },
+
+  /**
+   * Wrapper to <code>filterProperty</code>-method that allows using list of 
values to filter
+   * Example:
+   * <code>
+   *   var collection = [{n: 'v1'}, {n: 'v2'}, {n: 'v3'}, {n: 'v4'}],
+   *   key = 'n',
+   *   valuesToFilter = ['v2', 'v3'];
+   *   var result = filterPropertyValues(collection, key, valuesToFilter); // 
[{n: 'v2'}, {n: 'v3'}]
+   * </code>
+   * @param {Object[]} collection array of objects
+   * @param {String} key property name of each object to be filtered
+   * @param {String|String[]} valuesToFilter array of values (or one 
value-string) to filter
+   * @returns {Object[]} Filtered array
+   */
+  filterPropertyValues: function(collection, key, valuesToFilter) {
+    var type = Em.typeOf(valuesToFilter);
+    if ('string' === type)
+      return collection.filterProperty(key, valuesToFilter);
+
+    var ret = [];
+    if ('array' === type) {
+      valuesToFilter.forEach(function (value) {
+        ret = ret.concat(collection.filterProperty(key, value));
+      });
+    }
+    return ret;
+  },
+
+  /**
+   * Group <code>collection</code> items by <code>key</code> value
+   * Example:
+   * <code>
+   *   var collection = [{n: 'v1'}, {n: 'v2'}, {n: 'v2'}, {n: 'v4'}],
+   *   key = 'n';
+   *   var result = groupPropertyValues(collection, key); // {v1: [{n: 'v1'}], 
v2: [{n: 'v2'}, {n: 'v2'}], v4: [{n: 'v4'}]}
+   * </code>
+   * @param {Object[]} collection array of objects
+   * @param {String} key property name of each object to be grouped
+   * @returns {*}
+   */
+  groupPropertyValues: function(collection, key) {
+    var group = {};
+    collection.forEach(function(item) {
+      var value = Em.get(item, key);
+      if (Em.isNone(group[value])) {
+        group[value] = [item];
+      }
+      else {
+        group[value].pushObject(item);
+      }
+    });
+    return group;
+  }
+
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index 736b4ca..7990f5e 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -41,7 +41,7 @@ require('views/common/table_view');
 require('views/login');
 require('views/main');
 require('views/main/menu');
-require('views/main/alerts');
+require('views/main/alert_definitions_view');
 require('views/main/charts');
 require('views/main/views/details');
 require('views/main/host');

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/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 a730a05..6e28209 100644
--- a/ambari-web/app/views/common/filter_view.js
+++ b/ambari-web/app/views/common/filter_view.js
@@ -569,8 +569,7 @@ module.exports = {
         break;
       case 'select':
         return function (origin, compareValue){
-          //TODO add filter by select value
-          return true;
+          return origin == compareValue;
         };
         break;
       case 'range':

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/views/main/alert_definitions_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/alert_definitions_view.js 
b/ambari-web/app/views/main/alert_definitions_view.js
new file mode 100644
index 0000000..22272ea
--- /dev/null
+++ b/ambari-web/app/views/main/alert_definitions_view.js
@@ -0,0 +1,244 @@
+/**
+ * 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 filters = require('views/common/filter_view'),
+  sort = require('views/common/sort_view'),
+  date = require('utils/date'),
+  dataUtils = require('utils/data_manipulation');
+
+App.MainAlertDefinitionsView = App.TableView.extend({
+
+  templateName: require('templates/main/alerts'),
+
+  content: function() {
+    return this.get('controller.content');
+  }.property('controller.content.@each'),
+
+  /**
+   * @type {number}
+   */
+  totalCount: function () {
+    return this.get('content.length');
+  }.property('content.length'),
+
+  colPropAssoc: ['', 'label', 'state', 'service.serviceName', 'lastTriggered'],
+
+  /**
+   * List of css-classes for alert types
+   * @type {object}
+   */
+  typeIcons: {
+    'OK': 'icon-ok-sign',
+    'WARNING': 'icon-warning-sign',
+    'CRITICAL': 'icon-remove',
+    'DISABLED': 'icon-off',
+    'UNKNOWN': 'icon-question-sign'
+  },
+
+  sortView: sort.wrapperView,
+
+  /**
+   * Sorting header for <label>alertDefinition.label</label>
+   * @type {Em.View}
+   */
+  nameSort: sort.fieldView.extend({
+    column: 1,
+    name: 'label',
+    displayName: Em.I18n.t('common.name')
+  }),
+
+  /**
+   * Sorting header for <label>alertDefinition.status</label>
+   * @type {Em.View}
+   */
+  statusSort: sort.fieldView.extend({
+    column: 2,
+    name: 'status',
+    displayName: Em.I18n.t('common.status'),
+    type: 'string'
+  }),
+
+  /**
+   * Sorting header for <label>alertDefinition.service.serviceName</label>
+   * @type {Em.View}
+   */
+  serviceSort: sort.fieldView.extend({
+    column: 3,
+    name: 'service.serviceName',
+    displayName: Em.I18n.t('common.service'),
+    type: 'string'
+  }),
+
+  /**
+   * Sorting header for <label>alertDefinition.lastTriggeredSort</label>
+   * @type {Em.View}
+   */
+  lastTriggeredSort: sort.fieldView.extend({
+    column: 4,
+    name: 'memory',
+    displayName: Em.I18n.t('alerts.table.header.lastTriggered'),
+    type: 'date'
+  }),
+
+  /**
+   * Filtering header for <label>alertDefinition.label</label>
+   * @type {Em.View}
+   */
+  nameFilterView: filters.createTextView({
+    column: 1,
+    fieldType: 'filter-input-width',
+    onChangeValue: function(){
+      this.get('parentView').updateFilter(this.get('column'), 
this.get('value'), 'string');
+    }
+  }),
+
+  /**
+   * Filtering header for <label>alertDefinition.status</label>
+   * @type {Em.View}
+   */
+  stateFilterView: filters.createSelectView({
+    column: 2,
+    fieldType: 'filter-input-width',
+    content: ['All', 'OK', 'WARNING', 'CRITICAL', 'DISABLED', 'UNKNOWN'],
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), 
this.get('actualValue'), 'select');
+    },
+    emptyValue: Em.I18n.t('common.all')
+  }),
+
+  /**
+   * Filtering header for <label>alertDefinition.service.serviceName</label>
+   * @type {Em.View}
+   */
+  serviceFilterView: filters.createSelectView({
+    column: 3,
+    fieldType: 'filter-input-width',
+    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('actualValue'), 'select');
+    },
+    emptyValue: Em.I18n.t('common.all')
+  }),
+
+  /**
+   * Filtering header for <label>alertDefinition.lastTriggered</label>
+   * @type {Em.View}
+   */
+  triggeredFilterView: filters.createSelectView({
+    column: 4,
+    triggeredOnSameValue: [
+      {
+        values: ['Custom', 'Custom2'],
+        displayAs: 'Custom'
+      }
+    ],
+    appliedEmptyValue: ["", ""],
+    fieldType: 'filter-input-width,modified-filter',
+    content: ['Any', 'Past 1 hour',  'Past 1 Day', 'Past 2 Days', 'Past 7 
Days', 'Past 14 Days', 'Past 30 Days', 'Custom', 'Custom2'],
+    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('controller.modifiedFilter.actualValues.startTime'), 
this.get('controller.modifiedFilter.actualValues.endTime')], 'range');
+    }.observes('controller.modifiedFilter.actualValues.startTime', 
'controller.modifiedFilter.actualValues.endTime')
+  }),
+
+  /**
+   * Filtered number of all content number information displayed on the page 
footer bar
+   * @returns {String}
+   */
+  filteredContentInfo: function () {
+    return 
this.t('alerts.filters.filteredAlertsInfo').format(this.get('filteredCount'), 
this.get('totalCount'));
+  }.property('filteredCount', 'totalCount'),
+
+  /**
+   * Determines how display "back"-link - as link or text
+   * @type {string}
+   */
+  paginationLeftClass: function () {
+    if (this.get("startIndex") > 1) {
+      return "paginate_previous";
+    }
+    return "paginate_disabled_previous";
+  }.property("startIndex", 'filteredCount'),
+
+  /**
+   * Determines how display "next"-link - as link or text
+   * @type {string}
+   */
+  paginationRightClass: function () {
+    if ((this.get("endIndex")) < this.get("filteredCount")) {
+      return "paginate_next";
+    }
+    return "paginate_disabled_next";
+  }.property("endIndex", 'filteredCount'),
+
+  /**
+   * Show previous-page if user not in the first page
+   * @method previousPage
+   */
+  previousPage: function () {
+    if (this.get('paginationLeftClass') === 'paginate_previous') {
+      this._super();
+    }
+  },
+
+  /**
+   * Show next-page if user not in the last page
+   * @method nextPage
+   */
+  nextPage: function () {
+    if (this.get('paginationRightClass') === 'paginate_next') {
+      this._super();
+    }
+  },
+
+  /**
+   * View for each table row with <code>alertDefinition</code>
+   * @type {Em.View}
+   */
+  AlertDefinitionView: Em.View.extend({
+
+    tagName: 'tr',
+
+    /**
+     * Status generates from child-alerts
+     * Format: 1 OK / 2 WARN / 1 CRIT / 1 DISABLED / 1 UNKNOWN
+     * If some there are no alerts with some state, this state isn't shown
+     * Order is equal to example
+     * @type {string}
+     */
+    status: function () {
+      var typeIcons = this.get('parentView.typeIcons'),
+        ordered = ['OK', 'WARNING', 'CRITICAL', 'DISABLED', 'UNKNOWN'],
+        grouped = dataUtils.groupPropertyValues(this.get('content.alerts'), 
'state');
+      return ordered.map(function (state) {
+        if (grouped[state]) {
+          return grouped[state].length + ' <span class="' + typeIcons[state] + 
' alert-state-' + state + '"></span>';
+        }
+        return null;
+      }).compact().join(' / ');
+    }.property('content.alerts.@each.status')
+
+  })
+
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/test/utils/data_manipulation_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/utils/data_manipulation_test.js 
b/ambari-web/test/utils/data_manipulation_test.js
new file mode 100644
index 0000000..16f834f
--- /dev/null
+++ b/ambari-web/test/utils/data_manipulation_test.js
@@ -0,0 +1,89 @@
+/**
+ * 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 data_manipulation = require('utils/data_manipulation');
+
+describe('data_manipulation', function () {
+
+  describe('#rejectPropertyValues', function () {
+
+    it('Basic test', function () {
+      var collection = [
+          {n: 'v1'},
+          {n: 'v2'},
+          {n: 'v3'},
+          {n: 'v4'}
+        ],
+        key = 'n',
+        valuesToReject = ['v2', 'v3'];
+      var result = data_manipulation.rejectPropertyValues(collection, key, 
valuesToReject);
+      expect(result).to.eql([
+        {n: 'v1'},
+        {n: 'v4'}
+      ]);
+    });
+
+  });
+
+  describe('#filterPropertyValues', function () {
+
+    it('Basic test', function () {
+      var collection = [
+          {n: 'v1'},
+          {n: 'v2'},
+          {n: 'v3'},
+          {n: 'v4'}
+        ],
+        key = 'n',
+        valuesToFilter = ['v2', 'v3'];
+      var result = data_manipulation.filterPropertyValues(collection, key, 
valuesToFilter);
+      expect(result).to.eql([
+        {n: 'v2'},
+        {n: 'v3'}
+      ]);
+    });
+
+  });
+
+  describe('#groupPropertyValues', function () {
+
+    it('Basic test', function () {
+      var collection = [
+          {n: 'v1'},
+          {n: 'v2'},
+          {n: 'v2'},
+          {n: 'v4'}
+        ],
+        key = 'n';
+      var result = data_manipulation.groupPropertyValues(collection, key);
+      expect(result).to.eql({
+        v1: [
+          {n: 'v1'}
+        ],
+        v2: [
+          {n: 'v2'},
+          {n: 'v2'}
+        ],
+        v4: [
+          {n: 'v4'}
+        ]});
+    });
+
+  });
+
+});
\ No newline at end of file

Reply via email to