Browse Source

AMBARI-8308. Alerts UI: Summary Page. Fill Status and Last Triggered columns (onechiporenko)

Oleg Nechiporenko 10 years ago
parent
commit
93b6119cfb

+ 93 - 0
ambari-web/app/assets/data/alerts/alert_summary.json

@@ -0,0 +1,93 @@
+{
+  "href" : "http://localhost:8080/api/v1/clusters/c1/alerts?format=groupedSummary",
+  "alerts_summary_grouped" : [
+    {
+      "definition_id" : 34,
+      "definition_name" : "yarn_resourcemanager_webui",
+      "summary" : {
+        "OK" : {
+          "count" : 1,
+          "original_timestamp" : 1415732283450
+        },
+        "WARNING" : {
+          "count" : 0,
+          "original_timestamp" : 0
+        },
+        "CRITICAL" : {
+          "count" : 0,
+          "original_timestamp" : 0
+        },
+        "UNKNOWN" : {
+          "count" : 0,
+          "original_timestamp" : 0
+        }
+      }
+    },
+    {
+      "definition_id" : 12,
+      "definition_name" : "ganglia_monitor_mapreduce_history_server",
+      "summary" : {
+        "OK" : {
+          "count" : 1,
+          "original_timestamp" : 1415735183450
+        },
+        "WARNING" : {
+          "count" : 3,
+          "original_timestamp" : 1415735183450
+        },
+        "CRITICAL" : {
+          "count" : 1,
+          "original_timestamp" : 1415755183450
+        },
+        "UNKNOWN" : {
+          "count" : 2,
+          "original_timestamp" : 1412335183450
+        }
+      }
+    },
+    {
+      "definition_id" : 1,
+      "definition_name" : "ganglia_monitor_hdfs_namenode",
+      "summary" : {
+        "OK" : {
+          "count" : 2,
+          "original_timestamp" : 1415735183450
+        },
+        "WARNING" : {
+          "count" : 1,
+          "original_timestamp" : 1415735183450
+        },
+        "CRITICAL" : {
+          "count" : 3,
+          "original_timestamp" : 1415855183450
+        },
+        "UNKNOWN" : {
+          "count" : 2,
+          "original_timestamp" : 1412335183450
+        }
+      }
+    },
+    {
+      "definition_id" : 1,
+      "definition_name" : "ganglia_monitor_hbase_master",
+      "summary" : {
+        "OK" : {
+          "count" : 2,
+          "original_timestamp" : 1415735183450
+        },
+        "WARNING" : {
+          "count" : 2,
+          "original_timestamp" : 1415735183450
+        },
+        "CRITICAL" : {
+          "count" : 3,
+          "original_timestamp" : 1415355183450
+        },
+        "UNKNOWN" : {
+          "count" : 2,
+          "original_timestamp" : 1412335183450
+        }
+      }
+    }
+  ]
+}

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

@@ -103,6 +103,7 @@ var files = ['test/init_model_test',
   'test/controllers/wizard/stack_upgrade/step3_controller_test',
   'test/controllers/wizard/stack_upgrade/step3_controller_test',
   'test/login_test',
   'test/login_test',
   'test/router_test',
   'test/router_test',
+  'test/mappers/alert_definition_summary_mapper_test',
   'test/mappers/server_data_mapper_test',
   'test/mappers/server_data_mapper_test',
   'test/mappers/hosts_mapper_test',
   'test/mappers/hosts_mapper_test',
   'test/mappers/service_mapper_test',
   'test/mappers/service_mapper_test',
@@ -138,6 +139,7 @@ var files = ['test/init_model_test',
   'test/views/common/quick_link_view_test',
   'test/views/common/quick_link_view_test',
   'test/views/common/rolling_restart_view_test',
   'test/views/common/rolling_restart_view_test',
   'test/views/common/modal_popup_test',
   'test/views/common/modal_popup_test',
+  'test/views/common/sort_view_test',
   'test/views/common/configs/config_history_flow_test',
   'test/views/common/configs/config_history_flow_test',
   'test/views/main/dashboard_test',
   'test/views/main/dashboard_test',
   'test/views/main/menu_test',
   'test/views/main/menu_test',
@@ -192,6 +194,7 @@ var files = ['test/init_model_test',
   'test/models/service/hdfs_test',
   'test/models/service/hdfs_test',
   'test/models/service/yarn_test',
   'test/models/service/yarn_test',
   'test/models/alert_test',
   'test/models/alert_test',
+  'test/models/alert_definition_test',
   'test/models/authentication_test',
   'test/models/authentication_test',
   'test/models/cluster_states_test',
   'test/models/cluster_states_test',
   'test/models/config_group_test',
   'test/models/config_group_test',

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

@@ -341,7 +341,9 @@ App.ClusterController = Em.Controller.extend({
           self.updateLoadStatus('serviceMetrics');
           self.updateLoadStatus('serviceMetrics');
 
 
           updater.updateAlertDefinitions(function () {
           updater.updateAlertDefinitions(function () {
-            self.updateLoadStatus('alertDefinitions');
+            updater.updateAlertDefinitionSummary(function() {
+              self.updateLoadStatus('alertDefinitions');
+            });
           });
           });
         });
         });
       });
       });

+ 12 - 0
ambari-web/app/controllers/global/update_controller.js

@@ -129,6 +129,7 @@ App.UpdateController = Em.Controller.extend({
       App.updater.run(this, 'graphsUpdate', 'isWorking');
       App.updater.run(this, 'graphsUpdate', 'isWorking');
       App.updater.run(this, 'updateComponentConfig', 'isWorking');
       App.updater.run(this, 'updateComponentConfig', 'isWorking');
       App.updater.run(this, 'updateAlertDefinitions', 'isWorking', App.alertDefinitionsUpdateInterval);
       App.updater.run(this, 'updateAlertDefinitions', 'isWorking', App.alertDefinitionsUpdateInterval);
+      App.updater.run(this, 'updateAlertDefinitionSummary', 'isWorking', App.alertDefinitionsUpdateInterval);
     }
     }
   }.observes('isWorking'),
   }.observes('isWorking'),
   /**
   /**
@@ -453,5 +454,16 @@ App.UpdateController = Em.Controller.extend({
     App.HttpClient.get(url, App.alertDefinitionsMapper, {
     App.HttpClient.get(url, App.alertDefinitionsMapper, {
       complete: callback
       complete: callback
     });
     });
+  },
+
+  updateAlertDefinitionSummary: function(callback) {
+    var testUrl = '/data/alerts/alert_summary.json';
+    var realUrl = '/alerts?format=groupedSummary';
+    var url = this.getUrl(testUrl, realUrl);
+
+    App.HttpClient.get(url, App.alertDefinitionSummaryMapper, {
+      complete: callback
+    });
   }
   }
+
 });
 });

+ 0 - 51
ambari-web/app/controllers/main/alert_definitions_controller.js

@@ -50,57 +50,6 @@ App.MainAlertDefinitionsController = Em.ArrayController.extend({
       App.ScriptAlertDefinition.find().toArray());
       App.ScriptAlertDefinition.find().toArray());
   }.property('mapperTimestamp'),
   }.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
   toggleState: Em.K
 
 
 });
 });

+ 1 - 0
ambari-web/app/mappers.js

@@ -35,5 +35,6 @@ require('mappers/component_config_mapper');
 require('mappers/components_state_mapper');
 require('mappers/components_state_mapper');
 require('mappers/service_config_version_mapper');
 require('mappers/service_config_version_mapper');
 require('mappers/alert_definitions_mapper');
 require('mappers/alert_definitions_mapper');
+require('mappers/alert_definition_summary_mapper');
 require('mappers/alert_instances_mapper');
 require('mappers/alert_instances_mapper');
 require('mappers/root_service_mapper');
 require('mappers/root_service_mapper');

+ 51 - 0
ambari-web/app/mappers/alert_definition_summary_mapper.js

@@ -0,0 +1,51 @@
+/**
+ * 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.alertDefinitionSummaryMapper = App.QuickDataMapper.create({
+
+  config: {},
+
+  map: function(data) {
+    if (!data.alerts_summary_grouped) return;
+    var alertDefinitions = 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()
+    );
+    data.alerts_summary_grouped.forEach(function(alertDefinitionSummary) {
+      var alertDefinition = alertDefinitions.findProperty('id', alertDefinitionSummary.definition_id);
+      if (alertDefinition) {
+        var summary = {},
+          timestamp = 0;
+        Em.keys(alertDefinitionSummary.summary).forEach(function(status) {
+          summary[status] = alertDefinitionSummary.summary[status].count;
+          if (alertDefinitionSummary.summary[status].original_timestamp > timestamp) {
+            timestamp = alertDefinitionSummary.summary[status].original_timestamp;
+          }
+        });
+        alertDefinition.setProperties({
+          summary: summary,
+          lastTriggered: parseInt(timestamp)
+        });
+      }
+    });
+  }
+});

+ 15 - 1
ambari-web/app/mappers/alert_definitions_mapper.js

@@ -27,7 +27,7 @@ App.alertDefinitionsMapper = App.QuickDataMapper.create({
   metricsUriModel: App.AlertMetricsUriDefinition,
   metricsUriModel: App.AlertMetricsUriDefinition,
 
 
   config: {
   config: {
-    id: 'AlertDefinition.name',
+    id: 'AlertDefinition.id',
     name: 'AlertDefinition.name',
     name: 'AlertDefinition.name',
     label: 'AlertDefinition.label',
     label: 'AlertDefinition.label',
     service_id: 'AlertDefinition.service_name',
     service_id: 'AlertDefinition.service_name',
@@ -75,6 +75,13 @@ App.alertDefinitionsMapper = App.QuickDataMapper.create({
       var alertReportDefinitions = [];
       var alertReportDefinitions = [];
       var alertMetricsSourceDefinitions = [];
       var alertMetricsSourceDefinitions = [];
       var alertMetricsUriDefinitions = [];
       var alertMetricsUriDefinitions = [];
+      var alertDefinitions = 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()
+      );
 
 
       json.items.forEach(function (item) {
       json.items.forEach(function (item) {
         var convertedReportDefinitions = [];
         var convertedReportDefinitions = [];
@@ -94,6 +101,13 @@ App.alertDefinitionsMapper = App.QuickDataMapper.create({
         item.reporting = convertedReportDefinitions;
         item.reporting = convertedReportDefinitions;
         var alertDefinition = this.parseIt(item, this.get('config'));
         var alertDefinition = this.parseIt(item, this.get('config'));
 
 
+        var oldAlertDefinition = alertDefinitions.findProperty('id', alertDefinition.id);
+        if (oldAlertDefinition) {
+          // new values will be parsed in the another mapper, so for now just use old values
+          alertDefinition.summary = oldAlertDefinition.get('summary');
+          alertDefinition.last_triggered = oldAlertDefinition.get('lastTriggered');
+        }
+
         // map properties dependent on Alert Definition type
         // map properties dependent on Alert Definition type
         switch (item.AlertDefinition.source.type) {
         switch (item.AlertDefinition.source.type) {
           case 'PORT':
           case 'PORT':

+ 28 - 12
ambari-web/app/models/alert_definition.js

@@ -18,7 +18,6 @@
 
 
 var App = require('app');
 var App = require('app');
 var dateUtils = require('utils/date');
 var dateUtils = require('utils/date');
-var dataUtils = require('utils/data_manipulation');
 
 
 App.AlertDefinition = DS.Model.extend({
 App.AlertDefinition = DS.Model.extend({
 
 
@@ -31,34 +30,49 @@ App.AlertDefinition = DS.Model.extend({
   interval: DS.attr('number'),
   interval: DS.attr('number'),
   type: DS.attr('string'),
   type: DS.attr('string'),
   reporting: DS.hasMany('App.AlertReportDefinition'),
   reporting: DS.hasMany('App.AlertReportDefinition'),
-  alerts: DS.hasMany('App.AlertInstance'),
+  lastTriggered: DS.attr('number'),
+
+  /**
+   * Counts of alert grouped by their status
+   * Format:
+   * <code>
+   *   {
+   *    "CRITICAL": 1,
+   *    "OK": 1,
+   *    "UNKNOWN": 0,
+   *    "WARN": 0
+   *   }
+   * </code>
+   * @type {object}
+   */
+  summary: {},
 
 
   /**
   /**
    * Formatted timestamp for latest alert triggering for current alertDefinition
    * Formatted timestamp for latest alert triggering for current alertDefinition
    * @type {string}
    * @type {string}
    */
    */
-  lastTriggered: function () {
-    return dateUtils.dateFormat(Math.max.apply(Math, this.get('alerts').mapProperty('latestTimestamp')));
-  }.property('alerts.@each.latestTimestamp'),
+  lastTriggeredFormatted: function () {
+    return dateUtils.dateFormat(this.get('lastTriggered'));
+  }.property('lastTriggered'),
 
 
   /**
   /**
    * Status generates from child-alerts
    * Status generates from child-alerts
-   * Format: 1 OK / 2 WARN / 1 CRIT / 1 DISABLED / 1 UNKNOWN
+   * Format: 1 OK / 2 WARN / 1 CRIT / 1 UNKNOWN
    * If some there are no alerts with some state, this state isn't shown
    * If some there are no alerts with some state, this state isn't shown
    * Order is equal to example
    * Order is equal to example
    * @type {string}
    * @type {string}
    */
    */
   status: function () {
   status: function () {
     var typeIcons = this.get('typeIcons'),
     var typeIcons = this.get('typeIcons'),
-        ordered = ['OK', 'WARNING', 'CRITICAL', 'DISABLED', 'UNKNOWN'],
-        grouped = dataUtils.groupPropertyValues(this.get('alerts'), 'state');
-    return ordered.map(function (state) {
-      if (grouped[state]) {
-        return grouped[state].length + ' <span class="' + typeIcons[state] + ' alert-state-' + state + '"></span>';
+        order = this.get('order'),
+        summary = this.get('summary');
+    return order.map(function (state) {
+      if (summary[state]) {
+        return summary[state] + ' <span class="' + typeIcons[state] + ' alert-state-' + state + '"></span>';
       }
       }
       return null;
       return null;
     }).compact().join(' / ');
     }).compact().join(' / ');
-  }.property('alerts.@each.state'),
+  }.property('summary'),
 
 
   /**
   /**
    * List of css-classes for alert types
    * List of css-classes for alert types
@@ -72,6 +86,8 @@ App.AlertDefinition = DS.Model.extend({
     'UNKNOWN': 'icon-question-sign'
     'UNKNOWN': 'icon-question-sign'
   },
   },
 
 
+  order: ['OK', 'WARNING', 'CRITICAL', 'UNKNOWN'],
+
   // todo: in future be mapped from server response
   // todo: in future be mapped from server response
   description: 'Description for the Alert Definition.',
   description: 'Description for the Alert Definition.',
   // todo: in future be mapped from server response
   // todo: in future be mapped from server response

+ 1 - 1
ambari-web/app/templates/main/alerts.hbs

@@ -49,7 +49,7 @@
           </td>
           </td>
           <td>{{{alertDefinition.status}}}</td>
           <td>{{{alertDefinition.status}}}</td>
           <td>{{alertDefinition.service.serviceName}}</td>
           <td>{{alertDefinition.service.serviceName}}</td>
-          <td>{{alertDefinition.lastTriggered}}</td>
+          <td>{{alertDefinition.lastTriggeredFormatted}}</td>
           <td class="last">
           <td class="last">
             <a href="#" {{action "toggleState" alertDefinition target="controller"}} {{bindAttr class="alertDefinition.enabled:enabled:disabled"}}>
             <a href="#" {{action "toggleState" alertDefinition target="controller"}} {{bindAttr class="alertDefinition.enabled:enabled:disabled"}}>
               <span class="icon-off"></span>
               <span class="icon-off"></span>

+ 9 - 0
ambari-web/app/views/common/filter_view.js

@@ -593,6 +593,15 @@ module.exports = {
           return true;
           return true;
         };
         };
         break;
         break;
+      case 'alert_status':
+        /**
+         * origin - alertDefinition.summary
+         * compareValue - "OK|WARN..."
+         */
+        return function (origin, compareValue) {
+          return !!origin[compareValue] && origin[compareValue] > 0;
+        };
+        break;
       case 'string':
       case 'string':
       default:
       default:
         return function (origin, compareValue) {
         return function (origin, compareValue) {

+ 23 - 1
ambari-web/app/views/common/sort_view.js

@@ -168,6 +168,28 @@ var wrapperView = Em.View.extend({
           }
           }
         };
         };
         break;
         break;
+      case 'alert_status':
+        func = function(a, b) {
+          var a_summary = a.get('summary'),
+            b_summary = b.get('summary'),
+            st_order = a.get('order'),
+            ret = 0;
+          for(var i = 0; i < st_order.length; i++) {
+            var a_v = Em.isNone(a_summary[st_order[i]]) ? 0 : a_summary[st_order[i]],
+              b_v = Em.isNone(b_summary[st_order[i]]) ? 0 : b_summary[st_order[i]];
+            ret = b_v - a_v;
+            if (ret !== 0) {
+              break;
+            }
+          }
+          if (order) {
+            return ret;
+          }
+          else {
+            return -ret;
+          }
+        };
+        break;
       default:
       default:
         func = function (a, b) {
         func = function (a, b) {
           if (order) {
           if (order) {
@@ -183,7 +205,7 @@ var wrapperView = Em.View.extend({
               return 1;
               return 1;
             return 0;
             return 0;
           }
           }
-        }
+        };
     }
     }
     return func;
     return func;
   }
   }

+ 9 - 26
ambari-web/app/views/main/alert_definitions_view.js

@@ -36,7 +36,7 @@ App.MainAlertDefinitionsView = App.TableView.extend({
     return this.get('content.length');
     return this.get('content.length');
   }.property('content.length'),
   }.property('content.length'),
 
 
-  colPropAssoc: ['', 'label', 'state', 'service.serviceName', 'lastTriggered'],
+  colPropAssoc: ['', 'label', 'summary', 'service.serviceName', 'lastTriggered'],
 
 
   sortView: sort.wrapperView,
   sortView: sort.wrapperView,
 
 
@@ -56,9 +56,9 @@ App.MainAlertDefinitionsView = App.TableView.extend({
    */
    */
   statusSort: sort.fieldView.extend({
   statusSort: sort.fieldView.extend({
     column: 2,
     column: 2,
-    name: 'status',
+    name: 'summary',
     displayName: Em.I18n.t('common.status'),
     displayName: Em.I18n.t('common.status'),
-    type: 'string'
+    type: 'alert_status'
   }),
   }),
 
 
   /**
   /**
@@ -78,9 +78,9 @@ App.MainAlertDefinitionsView = App.TableView.extend({
    */
    */
   lastTriggeredSort: sort.fieldView.extend({
   lastTriggeredSort: sort.fieldView.extend({
     column: 4,
     column: 4,
-    name: 'memory',
+    name: 'lastTriggered',
     displayName: Em.I18n.t('alerts.table.header.lastTriggered'),
     displayName: Em.I18n.t('alerts.table.header.lastTriggered'),
-    type: 'date'
+    type: 'number'
   }),
   }),
 
 
   /**
   /**
@@ -129,7 +129,7 @@ App.MainAlertDefinitionsView = App.TableView.extend({
       }
       }
     ],
     ],
     onChangeValue: function () {
     onChangeValue: function () {
-      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'alert_status');
     }
     }
   }),
   }),
 
 
@@ -164,12 +164,6 @@ App.MainAlertDefinitionsView = App.TableView.extend({
    */
    */
   triggeredFilterView: filters.createSelectView({
   triggeredFilterView: filters.createSelectView({
     column: 4,
     column: 4,
-    triggeredOnSameValue: [
-      {
-        values: ['Custom', 'Custom2'],
-        displayAs: 'Custom'
-      }
-    ],
     appliedEmptyValue: ["", ""],
     appliedEmptyValue: ["", ""],
     fieldType: 'filter-input-width,modified-filter',
     fieldType: 'filter-input-width,modified-filter',
     content: [
     content: [
@@ -200,23 +194,12 @@ App.MainAlertDefinitionsView = App.TableView.extend({
       {
       {
         value: 'Past 30 Days',
         value: 'Past 30 Days',
         label: 'Past 30 Days'
         label: 'Past 30 Days'
-      },
-      {
-        value: 'Custom',
-        label: 'Custom'
-      },
-      {
-        value: 'Custom2',
-        label: 'Custom2'
       }
       }
     ],
     ],
     emptyValue: 'Any',
     emptyValue: 'Any',
-    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')
+    onChangeValue: function () {
+      this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'date');
+    }
   }),
   }),
 
 
   /**
   /**

+ 1 - 1
ambari-web/test/controllers/global/update_controller_test.js

@@ -62,7 +62,7 @@ describe('App.UpdateController', function () {
 
 
     it('isWorking = true', function () {
     it('isWorking = true', function () {
       controller.set('isWorking', true);
       controller.set('isWorking', true);
-      expect(App.updater.run.callCount).to.equal(7);
+      expect(App.updater.run.callCount).to.equal(8);
     });
     });
   });
   });
 
 

+ 125 - 0
ambari-web/test/mappers/alert_definition_summary_mapper_test.js

@@ -0,0 +1,125 @@
+/**
+ * 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('mappers/alert_definition_summary_mapper');
+
+describe('App.alertDefinitionSummaryMapper', function () {
+
+  describe('#map', function() {
+
+    var testModels = [
+        App.PortAlertDefinition.createRecord({id: 1, type: 'PORT'}),
+        App.MetricsAlertDefinition.createRecord({id: 2, type: 'METRICS'}),
+        App.WebAlertDefinition.createRecord({id: 3, type: 'WEB'}),
+        App.AggregateAlertDefinition.createRecord({id: 4, type: 'AGGREGATE'}),
+        App.ScriptAlertDefinition.createRecord({id: 5, type: 'SCRIPT'})
+      ],
+      dataToMap = {
+        alerts_summary_grouped: [
+          {
+            definition_id: 1,
+            summary: {
+              OK: {count: 1, original_timestamp: 1},
+              WARNING: {count: 1, original_timestamp: 2},
+              CRITICAL: {count: 0, original_timestamp: 0},
+              UNKNOWN: {count: 0, original_timestamp: 0}
+            }
+          },
+          {
+            definition_id: 2,
+            summary: {
+              OK: {count: 1, original_timestamp: 1},
+              WARNING: {count: 5, original_timestamp: 2},
+              CRITICAL: {count: 1, original_timestamp: 1},
+              UNKNOWN: {count: 1, original_timestamp: 3}
+            }
+          },
+          {
+            definition_id: 3,
+            summary: {
+              OK: {count: 1, original_timestamp: 1},
+              WARNING: {count: 2, original_timestamp: 2},
+              CRITICAL: {count: 3, original_timestamp: 4},
+              UNKNOWN: {count: 4, original_timestamp: 3}
+            }
+          },
+          {
+            definition_id: 4,
+            summary: {
+              OK: {count: 4, original_timestamp: 1},
+              WARNING: {count: 3, original_timestamp: 2},
+              CRITICAL: {count: 2, original_timestamp: 1},
+              UNKNOWN: {count: 1, original_timestamp: 2}
+            }
+          },
+          {
+            definition_id: 5,
+            summary: {
+              OK: {count: 1, original_timestamp: 1},
+              WARNING: {count: 1, original_timestamp: 2},
+              CRITICAL: {count: 1, original_timestamp: 3},
+              UNKNOWN: {count: 1, original_timestamp: 4}
+            }
+          }
+        ]
+      };
+
+    beforeEach(function() {
+
+      sinon.stub(App.PortAlertDefinition, 'find', function() {return testModels.filterProperty('type', 'PORT');});
+      sinon.stub(App.MetricsAlertDefinition, 'find', function() {return testModels.filterProperty('type', 'METRICS');});
+      sinon.stub(App.WebAlertDefinition, 'find', function() {return testModels.filterProperty('type', 'WEB');});
+      sinon.stub(App.AggregateAlertDefinition, 'find', function() {return testModels.filterProperty('type', 'AGGREGATE');});
+      sinon.stub(App.ScriptAlertDefinition, 'find', function() {return testModels.filterProperty('type', 'SCRIPT');});
+
+    });
+
+    afterEach(function() {
+
+      App.PortAlertDefinition.find.restore();
+      App.MetricsAlertDefinition.find.restore();
+      App.WebAlertDefinition.find.restore();
+      App.AggregateAlertDefinition.find.restore();
+      App.ScriptAlertDefinition.find.restore();
+
+    });
+
+    it('should map summary info for each alert', function() {
+
+      App.alertDefinitionSummaryMapper.map(dataToMap);
+      expect(App.PortAlertDefinition.find().findProperty('id', 1).get('lastTriggered')).to.equal(2);
+      expect(App.PortAlertDefinition.find().findProperty('id', 1).get('summary')).to.eql({OK: 1, WARNING: 1, CRITICAL: 0, UNKNOWN: 0});
+
+      expect(App.MetricsAlertDefinition.find().findProperty('id', 2).get('lastTriggered')).to.equal(3);
+      expect(App.MetricsAlertDefinition.find().findProperty('id', 2).get('summary')).to.eql({OK: 1, WARNING: 5, CRITICAL: 1, UNKNOWN: 1});
+
+      expect(App.WebAlertDefinition.find().findProperty('id', 3).get('lastTriggered')).to.equal(4);
+      expect(App.WebAlertDefinition.find().findProperty('id', 3).get('summary')).to.eql({OK: 1, WARNING: 2, CRITICAL: 3, UNKNOWN: 4});
+
+      expect(App.AggregateAlertDefinition.find().findProperty('id', 4).get('lastTriggered')).to.equal(2);
+      expect(App.AggregateAlertDefinition.find().findProperty('id', 4).get('summary')).to.eql({OK: 4, WARNING: 3, CRITICAL: 2, UNKNOWN: 1});
+
+      expect(App.ScriptAlertDefinition.find().findProperty('id', 5).get('lastTriggered')).to.equal(4);
+      expect(App.ScriptAlertDefinition.find().findProperty('id', 5).get('summary')).to.eql({OK: 1, WARNING: 1, CRITICAL: 1, UNKNOWN: 1});
+
+    });
+
+  });
+
+});

+ 60 - 0
ambari-web/test/models/alert_definition_test.js

@@ -0,0 +1,60 @@
+/**
+ * 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('models/alert_definition');
+
+var model;
+
+describe('App.AlertDefinition', function() {
+
+  beforeEach(function() {
+
+    model = App.AlertDefinition.createRecord();
+
+  });
+
+  describe('#status', function() {
+
+    Em.A([
+      {
+        summary: {OK: 1, UNKNOWN: 1, WARNING: 2},
+        m: 'No CRITICAL',
+        e: '1 <span class="icon-ok-sign alert-state-OK"></span> / ' +
+          '2 <span class="icon-warning-sign alert-state-WARNING"></span> / ' +
+          '1 <span class="icon-question-sign alert-state-UNKNOWN"></span>'
+      },
+      {
+        summary: {WARNING: 2, CRITICAL: 3, UNKNOWN: 1, OK: 1},
+        m: 'All states exists',
+        e: '1 <span class="icon-ok-sign alert-state-OK"></span> / ' +
+          '2 <span class="icon-warning-sign alert-state-WARNING"></span> / ' +
+          '3 <span class="icon-remove alert-state-CRITICAL"></span> / ' +
+          '1 <span class="icon-question-sign alert-state-UNKNOWN"></span>'
+      }
+    ]).forEach(function(test) {
+        it(test.m, function() {
+          model.set('summary', test.summary);
+          expect(model.get('status')).to.equal(test.e);
+        });
+      });
+
+  });
+
+});

+ 29 - 1
ambari-web/test/views/common/filter_view_test.js

@@ -22,7 +22,6 @@ require('utils/helper');
 
 
 describe('filters.getFilterByType', function () {
 describe('filters.getFilterByType', function () {
 
 
-
   describe('ambari-bandwidth', function () {
   describe('ambari-bandwidth', function () {
 
 
     var filter = filters.getFilterByType('ambari-bandwidth');
     var filter = filters.getFilterByType('ambari-bandwidth');
@@ -465,4 +464,33 @@ describe('filters.getFilterByType', function () {
       })
       })
     });
     });
   });
   });
+
+  describe('alert_status', function () {
+
+    var filter = filters.getFilterByType('alert_status');
+
+    Em.A([
+      {
+        origin: {OK: 1},
+        compareValue: 'OK',
+        e: true
+      },
+      {
+        origin: {WARN: 1},
+        compareValue: 'OK',
+        e: false
+      },
+      {
+        origin: {WARN: 0},
+        compareValue: 'WARN',
+        e: false
+      }
+    ]).forEach(function(test, i) {
+        it('test #' + (i + 1), function() {
+          expect(filter(test.origin, test.compareValue)).to.equal(test.e);
+        });
+      });
+
+  });
+
 });
 });

+ 68 - 0
ambari-web/test/views/common/sort_view_test.js

@@ -0,0 +1,68 @@
+/**
+ * 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 sort = require('views/common/sort_view');
+require('utils/misc');
+require('utils/string_utils');
+
+describe('#wrapperView', function () {
+
+  describe('#getSortFunc', function () {
+
+    describe('alert_status', function () {
+
+      var property = Em.Object.create({type: 'alert_status'});
+
+      Em.A([
+        {
+          a: App.AlertDefinition.createRecord({summary: {OK: 1, WARNING: 1}}),
+          b: App.AlertDefinition.createRecord({summary: {WARNING: 1}}),
+          order: true,
+          e: -1
+        },
+        {
+          a: App.AlertDefinition.createRecord({summary: {OK: 1, WARNING: 2}}),
+          b: App.AlertDefinition.createRecord({summary: {OK: 1, WARNING: 1}}),
+          order: true,
+          e: -1
+        },
+        {
+          a: App.AlertDefinition.createRecord({summary: {OK: 1, WARNING: 1}}),
+          b: App.AlertDefinition.createRecord({summary: {WARNING: 1}}),
+          order: false,
+          e: 1
+        },
+        {
+          a: App.AlertDefinition.createRecord({summary: {OK: 1, WARNING: 2}}),
+          b: App.AlertDefinition.createRecord({summary: {OK: 1, WARNING: 1}}),
+          order: false,
+          e: 1
+        }
+      ]).forEach(function(test, i) {
+          it('test #' + (i + 1), function () {
+            var func = sort.wrapperView.create().getSortFunc(property, test.order);
+            expect(func(test.a, test.b)).to.equal(test.e);
+          });
+        });
+
+    });
+
+  });
+
+});