Kaynağa Gözat

AMBARI-8463. Alerts UI: Manage Notifications dialog needs Edit/Create/Remove/Duplicate ability. (akovalenko)

Aleksandr Kovalenko 10 yıl önce
ebeveyn
işleme
ca7b8fdb89

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

@@ -50,6 +50,7 @@ var files = ['test/init_model_test',
   'test/controllers/main/alerts/definitions_details_controller_test',
   'test/controllers/main/alerts/alert_instances_controller_test',
   'test/controllers/main/alerts/add_alert_definition/step1_controller_test',
+  'test/controllers/main/alerts/manage_alert_notifications_controller_test',
   'test/controllers/main/admin/stack_and_upgrade_controller_test',
   'test/controllers/main/admin/stack_version/stack_version_details_controller_test',
   'test/controllers/main/admin/serviceAccounts_controller_test',
@@ -239,6 +240,7 @@ var files = ['test/init_model_test',
   'test/models/host_stack_version_test',
   //contains test with fake timers that affect Date
   'test/utils/lazy_loading_test'
+
 ];
 App.initialize();
 describe('Ambari Web Unit tests', function() {

+ 246 - 8
ambari-web/app/controllers/main/alerts/manage_alert_notifications_controller.js

@@ -24,6 +24,57 @@ App.ManageAlertNotificationsController = Em.Controller.extend({
 
   isLoaded: false,
 
+  /**
+   * Create/Edit modal popup object
+   * used to hide popup
+   * @type {App.ModalPopup}
+   */
+  createEditPopup: null,
+
+  /**
+   * Map of edit inputs shown in Create/Edit Notification popup
+   * @type {Object}
+   */
+  inputFields: Em.Object.create({
+    name: {
+      label: Em.I18n.t('common.name'),
+      value: '',
+      defaultValue: ''
+    },
+    groups: {
+      label: Em.I18n.t('common.groups'),
+      value: '',
+      defaultValue: ''
+    },
+    method: {
+      label: Em.I18n.t('alerts.actions.manage_alert_notifications_popup.method'),
+      value: '',
+      defaultValue: ''
+    },
+    email: {
+      label: Em.I18n.t('alerts.actions.manage_alert_notifications_popup.email'),
+      value: '',
+      defaultValue: ''
+    },
+    severityFilter: {
+      label: Em.I18n.t('alerts.actions.manage_alert_notifications_popup.severityFilter'),
+      value: [true, true, true, true],
+      defaultValue: [true, true, true, true]
+    },
+    description: {
+      label: Em.I18n.t('common.description'),
+      value: '',
+      defaultValue: ''
+    }
+  }),
+
+  /**
+   * List of available Notification types
+   * used in Type combobox
+   * @type {Array}
+   */
+  methods: ['EMAIL', 'SNMP'],
+
   /**
    * List of all Alert Notifications
    * @type {App.AlertNotification[]}
@@ -47,13 +98,10 @@ App.ManageAlertNotificationsController = Em.Controller.extend({
    * @returns {$.ajax|null}
    */
   loadAlertNotifications: function () {
-    if (this.get('isLoaded')) {
-      return null;
-    }
+    this.set('isLoaded', false);
     return App.ajax.send({
       name: 'alerts.notifications',
       sender: this,
-      data: {},
       success: 'getAlertNotificationsSuccessCallback',
       error: 'getAlertNotificationsErrorCallback'
     });
@@ -77,8 +125,198 @@ App.ManageAlertNotificationsController = Em.Controller.extend({
     this.set('isLoaded', true);
   },
 
-  addAlertNotification: Em.K,
-  deleteAlertNotification: Em.K,
-  editAlertNotification: Em.K,
-  duplicateAlertNotification: Em.K
+  /**
+   * Add Notification button handler
+   */
+  addAlertNotification: function () {
+    var inputFields = this.get('inputFields');
+    Em.keys(inputFields).forEach(function (key) {
+      inputFields.set(key + '.value', inputFields.get(key + '.defaultValue'));
+    });
+    this.showCreateEditPopup(false);
+  },
+
+  /**
+   * Edit Notification button handler
+   */
+  editAlertNotification: function () {
+    this.fillEditCreateInputs();
+    this.showCreateEditPopup(true);
+  },
+
+  /**
+   * Fill inputs of Create/Edit popup form
+   * @param addCopyToName define whether add 'Copy of ' to name
+   */
+  fillEditCreateInputs: function (addCopyToName) {
+    var inputFields = this.get('inputFields');
+    var selectedAlertNotification = this.get('selectedAlertNotification');
+    inputFields.set('name.value', (addCopyToName ? 'Copy of ' : '') + selectedAlertNotification.get('name'));
+    inputFields.set('email.value', selectedAlertNotification.get('properties')['ambari.dispatch.recipients'] ?
+        selectedAlertNotification.get('properties')['ambari.dispatch.recipients'].join(', ') : '');
+    inputFields.set('severityFilter.value', [
+      selectedAlertNotification.get('alertStates').contains('OK'),
+      selectedAlertNotification.get('alertStates').contains('WARNING'),
+      selectedAlertNotification.get('alertStates').contains('CRITICAL'),
+      selectedAlertNotification.get('alertStates').contains('UNKNOWN')
+    ]);
+    inputFields.set('description.value', selectedAlertNotification.get('description'));
+    inputFields.set('method.value', selectedAlertNotification.get('type'));
+  },
+
+  /**
+   * Show Edit or Create Notification popup
+   * @param isEdit
+   * @returns {App.ModalPopup}
+   */
+  showCreateEditPopup: function (isEdit) {
+    var self = this;
+    var createEditPopup = App.ModalPopup.show({
+      header: isEdit ? Em.I18n.t('alerts.actions.manage_alert_notifications_popup.editHeader') : Em.I18n.t('alerts.actions.manage_alert_notifications_popup.addHeader'),
+      bodyClass: Em.View.extend({
+        controller: this,
+        templateName: require('templates/main/alerts/create_alert_notification'),
+        isEmailMethodSelected: function () {
+          return this.get('controller.inputFields.method.value') === 'EMAIL';
+        }.property('controller.inputFields.method.value')
+      }),
+      primary: Em.I18n.t('common.save'),
+      onPrimary: function () {
+        this.set('disablePrimary', true);
+        var apiObject = self.formatNotificationAPIObject();
+        if (isEdit) {
+          self.updateAlertNotification(apiObject);
+        } else {
+          self.createAlertNotification(apiObject);
+        }
+      }
+    });
+    this.set('createEditPopup', createEditPopup);
+    return createEditPopup;
+  },
+
+  /**
+   * Create API-formatted object from data populate by user
+   * @returns {Object}
+   */
+  formatNotificationAPIObject: function () {
+    var inputFields = this.get('inputFields');
+    var alertStates = [];
+    var properties = {};
+    if (inputFields.severityFilter.value[0]) {
+      alertStates.push('OK');
+    }
+    if (inputFields.severityFilter.value[1]) {
+      alertStates.push('WARNING');
+    }
+    if (inputFields.severityFilter.value[2]) {
+      alertStates.push('CRITICAL');
+    }
+    if (inputFields.severityFilter.value[3]) {
+      alertStates.push('UNKNOWN');
+    }
+    if (inputFields.method.value === 'EMAIL') {
+      properties['ambari.dispatch.recipients'] = inputFields.email.value.replace(/\s/g, '').split(',');
+    }
+    return {
+      AlertTarget: {
+        name: inputFields.name.value,
+        description: inputFields.description.value,
+        notification_type: inputFields.method.value,
+        alert_states: alertStates,
+        properties: properties
+      }
+    };
+  },
+
+  /**
+   * Send request to server to create Alert Notification
+   * @param apiObject
+   * @returns {$.ajax}
+   */
+  createAlertNotification: function (apiObject) {
+    return App.ajax.send({
+      name: 'alerts.create_alert_notification',
+      sender: this,
+      data: {
+        data: apiObject
+      },
+      success: 'createAlertNotificationSuccessCallback'
+    });
+  },
+
+  /**
+   * Success callback for <code>createAlertNotification</code>
+   */
+  createAlertNotificationSuccessCallback: function () {
+    this.loadAlertNotifications();
+    var createEditPopup = this.get('createEditPopup');
+    if (createEditPopup) {
+      createEditPopup.hide();
+    }
+  },
+
+  /**
+   * Send request to server to update Alert Notification
+   * @param apiObject
+   * @returns {$.ajax}
+   */
+  updateAlertNotification: function (apiObject) {
+    return App.ajax.send({
+      name: 'alerts.update_alert_notification',
+      sender: this,
+      data: {
+        data: apiObject,
+        id: this.get('selectedAlertNotification.id')
+      },
+      success: 'updateAlertNotificationSuccessCallback'
+    });
+  },
+
+  /**
+   * Success callback for <code>updateAlertNotification</code>
+   */
+  updateAlertNotificationSuccessCallback: function () {
+    this.loadAlertNotifications();
+    var createEditPopup = this.get('createEditPopup');
+    if (createEditPopup) {
+      createEditPopup.hide();
+    }
+  },
+
+  /**
+   * Delete Notification button handler
+   */
+  deleteAlertNotification: function () {
+    var self = this;
+    return App.showConfirmationPopup(function () {
+      App.ajax.send({
+        name: 'alerts.delete_alert_notification',
+        sender: self,
+        data: {
+          id: self.get('selectedAlertNotification.id')
+        },
+        success: 'deleteAlertNotificationSuccessCallback'
+      });
+    });
+  },
+
+  /**
+   * Success callback for <code>deleteAlertNotification</code>
+   */
+  deleteAlertNotificationSuccessCallback: function () {
+    this.loadAlertNotifications();
+    var selectedAlertNotification = this.get('selectedAlertNotification');
+    selectedAlertNotification.deleteRecord();
+    this.set('selectedAlertNotification', null);
+  },
+
+  /**
+   * Duplicate Notification button handler
+   */
+  duplicateAlertNotification: function () {
+    this.fillEditCreateInputs(true);
+    this.showCreateEditPopup();
+  }
+
 });

+ 50 - 19
ambari-web/app/mappers/alert_notification_mapper.js

@@ -1,29 +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.
-  */
+ * 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.alertNotificationMapper = App.QuickDataMapper.create({
-  model : App.AlertNotification,
-  config : {
+  model: App.AlertNotification,
+  config: {
     id: 'AlertTarget.id',
     name: 'AlertTarget.name',
     type: 'AlertTarget.notification_type',
-    description: 'AlertTarget.description',
-    properties: 'AlertTarget.properties'
+    description: 'AlertTarget.description'
+  },
+
+  map: function (json) {
+    if (json.items) {
+      var result = [];
+      var notificationsProperties = {};
+      var notificationsAlertStates = {};
+
+      json.items.forEach(function (item) {
+        result.push(this.parseIt(item, this.config));
+        notificationsProperties[item.AlertTarget.id] = item.AlertTarget.properties;
+        notificationsAlertStates[item.AlertTarget.id] = item.AlertTarget.alert_states;
+      }, this);
+
+      App.store.loadMany(this.get('model'), result);
+      this.setProperties('properties', notificationsProperties);
+      this.setProperties('alertStates', notificationsAlertStates);
+    }
+  },
+
+  /**
+   * Set values from <code>propertyMap</code> for <code>propertyName</code> for each record in model
+   * @param propertyName
+   * @param propertiesMap record_id to value map
+   */
+  setProperties: function (propertyName, propertiesMap) {
+    var modelRecords = this.get('model').find();
+    for (var recordId in propertiesMap) {
+      if (propertiesMap.hasOwnProperty(recordId)) {
+        modelRecords.findProperty('id', +recordId).set(propertyName, propertiesMap[recordId]);
+      }
+    }
   }
 });

+ 3 - 0
ambari-web/app/messages.js

@@ -1791,11 +1791,14 @@ Em.I18n.translations = {
 
   'alerts.actions.manage_alert_notifications_popup.header':'Manage Alert Notifications',
   'alerts.actions.manage_alert_notifications_popup.addButton':'Create new Alert Notification',
+  'alerts.actions.manage_alert_notifications_popup.addHeader':'Create Alert Notification',
   'alerts.actions.manage_alert_notifications_popup.removeButton':'Delete Alert Notification',
   'alerts.actions.manage_alert_notifications_popup.editButton':'Edit Alert Notification',
+  'alerts.actions.manage_alert_notifications_popup.editHeader':'Edit Notification',
   'alerts.actions.manage_alert_notifications_popup.duplicateButton':'Duplicate Alert Notification',
   'alerts.actions.manage_alert_notifications_popup.method':'Method',
   'alerts.actions.manage_alert_notifications_popup.email':'Email',
+  'alerts.actions.manage_alert_notifications_popup.severityFilter':'Severity Filter',
 
   'hosts.host.add':'Add New Hosts',
   'hosts.table.noHosts':'No hosts to display',

+ 4 - 17
ambari-web/app/models/alert_notification.js

@@ -23,22 +23,9 @@ App.AlertNotification = DS.Model.extend({
   name: DS.attr('string'),
   type: DS.attr('string'),
   description: DS.attr('string'),
-  properties: DS.attr('string')
+
+  properties: {},
+  alertStates: []
 });
 
-App.AlertNotification.FIXTURES = [
-  {
-    "description" : "Admins",
-    "id" : 1,
-    "name" : "Administrators",
-    "type" : "EMAIL",
-    "properties" : ""
-  },
-  {
-    "description" : "Operators",
-    "id" : 2,
-    "name" : "Operators",
-    "type" : "SNMP",
-    "properties" : ""
-  }
-];
+App.AlertNotification.FIXTURES = [];

+ 18 - 0
ambari-web/app/styles/alerts.less

@@ -275,6 +275,24 @@
   }
 }
 
+#manage-alert-notifications-error {
+  margin-bottom: 20px
+}
+
+#create-edit-alert-notification {
+  .form-horizontal {
+    .controls {
+      margin-left: 140px;
+    }
+  }
+  label {
+    width: 115px;
+    &.checkbox {
+      width: inherit;
+    }
+  }
+}
+
 /*****start styles for alerts popup*****/
 .alerts-popup {
   .modal-body, .modal-footer, .modal-header {

+ 74 - 0
ambari-web/app/templates/main/alerts/create_alert_notification.hbs

@@ -0,0 +1,74 @@
+{{!
+* 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.
+}}
+
+<div id="create-edit-alert-notification">
+
+  <form class="form-horizontal">
+
+    <div class="control-group">
+      <label class="control-label" for="inputName">{{controller.inputFields.name.label}}</label>
+
+      <div class="controls">
+        {{view Em.TextField valueBinding="controller.inputFields.name.value" id="inputName" class="input-xlarge"}}
+      </div>
+    </div>
+
+    <div class="control-group">
+      <label class="control-label" for="inputGroups">{{controller.inputFields.groups.label}}</label>
+
+      <div class="controls">
+        {{view Em.TextField valueBinding="controller.inputFields.groups.value" id="inputGroups" class="input-xlarge"}}
+      </div>
+    </div>
+
+    <div class="control-group">
+      <label class="control-label" for="inputMethod">{{controller.inputFields.method.label}}</label>
+
+      <div class="controls">
+        {{view Em.Select contentBinding="controller.methods" selectionBinding="controller.inputFields.method.value" id="inputMethod" class="input-xlarge"}}
+      </div>
+    </div>
+
+    {{#if view.isEmailMethodSelected}}
+      <div class="control-group">
+        <label class="control-label" for="inputEmail">{{controller.inputFields.email.label}}</label>
+
+        <div class="controls">
+          {{view Em.TextField valueBinding="controller.inputFields.email.value" id="inputEmail" class="input-xlarge"}}
+        </div>
+      </div>
+    {{/if}}
+
+    <div class="control-group">
+      <label class="control-label">{{controller.inputFields.severityFilter.label}}</label>
+
+      <div class="controls">
+        {{view App.AlertSeverityFilterView selectionBinding="controller.inputFields.severityFilter.value"}}
+      </div>
+    </div>
+
+    <div class="control-group">
+      <label class="control-label" for="inputDescription">{{controller.inputFields.description.label}}</label>
+
+      <div class="controls">
+        {{view Em.TextArea valueBinding="controller.inputFields.description.value" id="inputDescription" rows="4" class="input-xlarge"}}
+      </div>
+    </div>
+
+  </form>
+</div>

+ 2 - 2
ambari-web/app/templates/main/alerts/manage_alert_notifications_popup.hbs

@@ -37,7 +37,7 @@
               {{translateAttr data-original-title="alerts.actions.manage_alert_notifications_popup.removeButton"}}
               {{bindAttr disabled="view.isRemoveButtonDisabled"}}
               {{action deleteAlertNotification target="controller"}}><i class="icon-minus"></i></button>
-            <div class="btn-group dropup">
+            <div class="btn-group">
               <button class="btn dropdown-toggle" data-toggle="dropdown">
                 <i class="icon-cog"></i>&nbsp;<span class="caret"></span>
               </button>
@@ -92,7 +92,7 @@
         </div>
         <div class="clearfix"></div>
         <div class="row-fluid">
-          <div class="span12 text-error" id="manage-config-group-error-div">
+          <div class="span12 text-error" id="manage-alert-notifications-error">
             {{#if controller.errorMessage}}
               {{controller.errorMessage}}
             {{else}}

+ 30 - 0
ambari-web/app/templates/main/alerts/severity_filter.hbs

@@ -0,0 +1,30 @@
+{{!
+* 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.
+}}
+
+<label class="checkbox inline">
+  {{view Em.Checkbox checkedBinding="view.okChecked"}} OK
+</label>
+<label class="checkbox inline">
+  {{view Em.Checkbox checkedBinding="view.warningChecked"}} WARNING
+</label>
+<label class="checkbox inline">
+  {{view Em.Checkbox checkedBinding="view.criticalChecked"}} CRITICAL
+</label>
+<label class="checkbox inline">
+  {{view Em.Checkbox checkedBinding="view.unknownChecked"}} UNKNOWN
+</label>

+ 22 - 1
ambari-web/app/utils/ajax/ajax.js

@@ -386,12 +386,33 @@ var urls = {
   'alerts.delete_alert_definition': {
     'real': '/clusters/{clusterName}/alert_definitions/{id}',
     'mock': '',
+    'type': 'DELETE'
+  },
+  'alerts.create_alert_notification': {
+    'real': '/alert_targets',
+    'mock': '',
     'format': function (data) {
       return {
-        type: 'DELETE'
+        type: 'POST',
+        data: JSON.stringify(data.data)
       }
     }
   },
+  'alerts.update_alert_notification': {
+    'real': '/alert_targets/{id}',
+    'mock': '',
+    'format': function (data) {
+      return {
+        type: 'PUT',
+        data: JSON.stringify(data.data)
+      }
+    }
+  },
+  'alerts.delete_alert_notification': {
+    'real': '/alert_targets/{id}',
+    'mock': '',
+    'type': 'DELETE'
+  },
   'background_operations.get_most_recent': {
     'real': '/clusters/{clusterName}/requests?to=end&page_size={operationsCount}&fields=Requests',
     'mock': '/data/background_operations/list_on_start.json',

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

@@ -53,6 +53,7 @@ require('views/main/alerts/add_alert_definition/step3_view');
 require('views/main/alerts');
 require('views/main/alerts/manage_alert_groups_view');
 require('views/main/alerts/manage_alert_notifications_view');
+require('views/main/alerts/severity_filter_view');
 require('views/main/charts');
 require('views/main/views/details');
 require('views/main/host');

+ 5 - 5
ambari-web/app/views/main/alerts/manage_alert_notifications_view.js

@@ -71,12 +71,11 @@ App.ManageAlertNotificationsView = Em.View.extend({
    */
   onAlertNotificationSelect: function () {
     var selectedAlertNotification = this.get('selectedAlertNotification');
-    var length = selectedAlertNotification.length;
-    if (selectedAlertNotification && length) {
-      this.set('controller.selectedAlertNotification', selectedAlertNotification[length - 1]);
+    if (selectedAlertNotification && selectedAlertNotification.length) {
+      this.set('controller.selectedAlertNotification', selectedAlertNotification[selectedAlertNotification.length - 1]);
     }
-    if (selectedAlertNotification && length > 1) {
-      this.set('selectedAlertNotification', selectedAlertNotification[length - 1]);
+    if (selectedAlertNotification && selectedAlertNotification.length > 1) {
+      this.set('selectedAlertNotification', selectedAlertNotification[selectedAlertNotification.length - 1]);
     }
   }.observes('selectedAlertNotification'),
 
@@ -90,6 +89,7 @@ App.ManageAlertNotificationsView = Em.View.extend({
       var notifications = this.get('controller.alertNotifications');
       if (notifications && notifications.length) {
         this.set('selectedAlertNotification', notifications[0]);
+        this.buttonObserver();
       }  else {
         this.set('selectedAlertNotification', null);
       }

+ 52 - 0
ambari-web/app/views/main/alerts/severity_filter_view.js

@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+
+App.AlertSeverityFilterView = Em.View.extend({
+
+  templateName: require('templates/main/alerts/severity_filter'),
+
+  /**
+   * Array of Boolean values binded to checkboxes
+   * @type {Array}
+   */
+  selection: [],
+
+  didInsertElement: function () {
+    this.set('okChecked', this.get('selection')[0]);
+    this.set('warningChecked', this.get('selection')[1]);
+    this.set('criticalChecked', this.get('selection')[2]);
+    this.set('unknownChecked', this.get('selection')[3]);
+    this.addObserver('okChecked', this, 'onChange');
+    this.addObserver('warningChecked', this, 'onChange');
+    this.addObserver('criticalChecked', this, 'onChange');
+    this.addObserver('unknownChecked', this, 'onChange');
+  },
+
+  okChecked: true,
+
+  warningChecked: true,
+
+  criticalChecked: true,
+
+  unknownChecked: true,
+
+  onChange: function () {
+    this.set('selection', [this.get('okChecked'), this.get('warningChecked'), this.get('criticalChecked'), this.get('unknownChecked')]);
+  }
+
+});

+ 0 - 4
ambari-web/test/controllers/main/alerts/manage_alert_groups_controller_test.js

@@ -17,12 +17,8 @@
  */
 
 var App = require('app');
-var c;
 describe('App.ManageAlertGroupsController', function() {
 
-  beforeEach(function() {
-    c = App.ManageAlertGroupsController.create({});
-  });
   var manageAlertGroupsController = App.ManageAlertGroupsController.create({});
 
   describe('#addAlertGroup', function() {

+ 437 - 0
ambari-web/test/controllers/main/alerts/manage_alert_notifications_controller_test.js

@@ -0,0 +1,437 @@
+/**
+ * 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 controller;
+describe('App.ManageAlertNotificationsController', function () {
+
+  beforeEach(function () {
+    controller = App.ManageAlertNotificationsController.create({});
+    sinon.spy(App.ajax, 'send');
+  });
+
+  afterEach(function () {
+    App.ajax.send.restore();
+  });
+
+  describe('#alertNotifications', function () {
+
+    beforeEach(function () {
+      sinon.stub(App.AlertNotification, 'find', function () {
+        return [1, 2, 3];
+      });
+    });
+
+    afterEach(function () {
+      App.AlertNotification.find.restore();
+    });
+
+    it("should return all alert notifications if controller isLoaded", function () {
+
+      controller.set('isLoaded', true);
+      expect(controller.get('alertNotifications')).to.eql([1, 2, 3]);
+    });
+
+    it("should return [] if controller isLoaded=false", function () {
+
+      controller.set('isLoaded', false);
+      expect(controller.get('alertNotifications')).to.eql([]);
+    });
+
+  });
+
+  describe('#loadAlertNotifications()', function () {
+
+    it("should send ajax request and set isLoaded to false", function () {
+
+      controller.set('isLoaded', true);
+      controller.loadAlertNotifications();
+      expect(controller.get('isLoaded')).to.be.false;
+      expect(App.ajax.send.calledOnce).to.be.true;
+    });
+
+  });
+
+  describe('#getAlertNotificationsSuccessCallback()', function () {
+
+    beforeEach(function () {
+      sinon.spy(App.alertNotificationMapper, 'map');
+    });
+
+    afterEach(function () {
+      App.alertNotificationMapper.map.restore();
+    });
+
+    it("should call mapper and set isLoaded to true", function () {
+
+      controller.set('isLoaded', false);
+      controller.getAlertNotificationsSuccessCallback('test');
+      expect(controller.get('isLoaded')).to.be.true;
+      expect(App.alertNotificationMapper.map.calledWith('test')).to.be.true;
+    });
+
+  });
+
+  describe('#getAlertNotificationsErrorCallback()', function () {
+
+    it("should set isLoaded to true", function () {
+
+      controller.set('isLoaded', false);
+      controller.getAlertNotificationsSuccessCallback('test');
+      expect(controller.get('isLoaded')).to.be.true;
+    });
+
+  });
+
+  describe('#addAlertNotification()', function () {
+
+    beforeEach(function () {
+      sinon.spy(controller, 'showCreateEditPopup');
+    });
+
+    afterEach(function () {
+      controller.showCreateEditPopup.restore();
+    });
+
+    it("should set value for inputFields and call showCreateEditPopup", function () {
+
+      controller.set('inputFields', Em.Object.create({
+        a: {
+          value: '',
+          defaultValue: 'a'
+        },
+        b: {
+          value: '',
+          defaultValue: 'b'
+        },
+        c: {
+          value: '',
+          defaultValue: 'c'
+        },
+        severityFilter: {
+          value: [],
+          defaultValue: [true, true, true, true]
+        }
+      }));
+      controller.addAlertNotification();
+
+      Em.keys(controller.get('inputFields')).forEach(function (key) {
+        expect(controller.get('inputFields.' + key + '.value')).to.equal(controller.get('inputFields.' + key + '.defaultValue'));
+      });
+      expect(controller.showCreateEditPopup.calledOnce).to.be.true;
+    });
+
+  });
+
+  describe('#editAlertNotification()', function () {
+
+    beforeEach(function () {
+      sinon.stub(controller, 'showCreateEditPopup', Em.K);
+      sinon.stub(controller, 'fillEditCreateInputs', Em.K);
+    });
+
+    afterEach(function () {
+      controller.showCreateEditPopup.restore();
+      controller.fillEditCreateInputs.restore();
+    });
+
+    it("should call fillEditCreateInputs and showCreateEditPopup", function () {
+
+      controller.editAlertNotification();
+
+      expect(controller.fillEditCreateInputs.calledOnce).to.be.true;
+      expect(controller.showCreateEditPopup.calledWith(true)).to.be.true;
+    });
+
+  });
+
+  describe('#fillEditCreateInputs()', function () {
+
+    it("should map properties from selectedAlertNotification to inputFields", function () {
+
+      controller.set('selectedAlertNotification', Em.Object.create({
+        name: 'test_name',
+        description: 'test_description',
+        type: 'EMAIL',
+        alertStates: ['OK', 'UNKNOWN'],
+        properties: {
+          'ambari.dispatch.recipients': [
+            'test1@test.test',
+            'test2@test.test'
+          ]
+        }
+      }));
+
+      controller.set('inputFields', Em.Object.create({
+        name: {
+          value: ''
+        },
+        groups: {
+          value: ''
+        },
+        method: {
+          value: ''
+        },
+        email: {
+          value: ''
+        },
+        severityFilter: {
+          value: []
+        },
+        description: {
+          value: ''
+        }
+      }));
+
+      controller.fillEditCreateInputs();
+
+      expect(JSON.stringify(controller.get('inputFields'))).to.equal(JSON.stringify({
+        name: {
+          value: 'test_name'
+        },
+        groups: {
+          value: ''
+        },
+        method: {
+          value: 'EMAIL'
+        },
+        email: {
+          value: 'test1@test.test, test2@test.test'
+        },
+        severityFilter: {
+          value: [true, false, false, true]
+        },
+        description: {
+          value: 'test_description'
+        }
+      }));
+
+    });
+
+  });
+
+  describe("#showCreateEditPopup()", function () {
+
+    before(function () {
+      sinon.stub(App.ModalPopup, 'show', function () {
+        return 'popup';
+      });
+    });
+
+    after(function () {
+      App.ModalPopup.show.restore();
+    });
+
+    it("should open popup and set popup object to createEditPopup", function () {
+
+      controller.set('createEditPopup', null);
+
+      controller.showCreateEditPopup();
+
+      expect(App.ModalPopup.show.calledOnce).to.be.true;
+      expect(controller.get('createEditPopup')).to.equal('popup');
+    });
+
+  });
+
+  describe("#formatNotificationAPIObject()", function () {
+
+    it("should create object with properties from inputFields values", function () {
+
+      controller.set('inputFields', Em.Object.create({
+        name: {
+          value: 'test_name'
+        },
+        groups: {
+          value: ''
+        },
+        method: {
+          value: 'EMAIL'
+        },
+        email: {
+          value: 'test1@test.test, test2@test.test,test3@test.test , test4@test.test'
+        },
+        severityFilter: {
+          value: [true, false, true, false]
+        },
+        description: {
+          value: 'test_description'
+        }
+      }));
+
+      var result = controller.formatNotificationAPIObject();
+
+      expect(result).to.eql({
+        AlertTarget: {
+          name: 'test_name',
+          description: 'test_description',
+          notification_type: 'EMAIL',
+          alert_states: ['OK', 'CRITICAL'],
+          properties: {
+            'ambari.dispatch.recipients': [
+              'test1@test.test',
+              'test2@test.test',
+              'test3@test.test',
+              'test4@test.test'
+            ]
+          }
+        }
+      });
+    });
+
+  });
+
+  describe('#createAlertNotification()', function () {
+
+    it("should send ajax request", function () {
+
+      controller.createAlertNotification();
+      expect(App.ajax.send.calledOnce).to.be.true;
+    });
+
+  });
+
+  describe('#createAlertNotificationSuccessCallback()', function () {
+
+    beforeEach(function () {
+      controller.set('createEditPopup', {
+        hide: Em.K
+      });
+      sinon.stub(controller, 'loadAlertNotifications', Em.K);
+      sinon.spy(controller.createEditPopup, 'hide');
+    });
+
+    afterEach(function () {
+      controller.loadAlertNotifications.restore();
+      controller.createEditPopup.hide.restore();
+    });
+
+    it("should call loadAlertNotifications and createEditPopup.hide", function () {
+
+      controller.createAlertNotificationSuccessCallback();
+
+      expect(controller.loadAlertNotifications.calledOnce).to.be.true;
+      expect(controller.createEditPopup.hide.calledOnce).to.be.true;
+    });
+
+  });
+
+  describe('#updateAlertNotification()', function () {
+
+    it("should send ajax request", function () {
+
+      controller.createAlertNotification();
+      expect(App.ajax.send.calledOnce).to.be.true;
+    });
+
+  });
+
+  describe('#updateAlertNotificationSuccessCallback()', function () {
+
+    beforeEach(function () {
+      controller.set('createEditPopup', {
+        hide: Em.K
+      });
+      sinon.stub(controller, 'loadAlertNotifications', Em.K);
+      sinon.spy(controller.createEditPopup, 'hide');
+    });
+
+    afterEach(function () {
+      controller.loadAlertNotifications.restore();
+      controller.createEditPopup.hide.restore();
+    });
+
+    it("should call loadAlertNotifications and createEditPopup.hide", function () {
+
+      controller.updateAlertNotificationSuccessCallback();
+
+      expect(controller.loadAlertNotifications.calledOnce).to.be.true;
+      expect(controller.createEditPopup.hide.calledOnce).to.be.true;
+    });
+
+  });
+
+  describe('#deleteAlertNotification()', function () {
+
+    beforeEach(function () {
+      sinon.spy(App, 'showConfirmationPopup');
+    });
+
+    afterEach(function () {
+      App.showConfirmationPopup.restore();
+    });
+
+    it("should show popup and send request on confirmation", function () {
+
+      var popup = controller.deleteAlertNotification();
+
+      expect(App.showConfirmationPopup.calledOnce).to.be.true;
+      popup.onPrimary();
+      expect(App.ajax.send.calledOnce).to.be.true;
+    });
+
+  });
+
+
+  describe('#deleteAlertNotificationSuccessCallback()', function () {
+
+    it("should call loadAlertNotifications, selectedAlertNotification.deleteRecord and set null to selectedAlertNotification", function () {
+
+      var mockSelectedAlertNotification = {
+        deleteRecord: Em.K
+      };
+      controller.set('selectedAlertNotification', mockSelectedAlertNotification);
+      sinon.stub(controller, 'loadAlertNotifications', Em.K);
+      sinon.spy(mockSelectedAlertNotification, 'deleteRecord');
+
+      controller.deleteAlertNotificationSuccessCallback();
+
+      expect(controller.loadAlertNotifications.calledOnce).to.be.true;
+      expect(mockSelectedAlertNotification.deleteRecord.calledOnce).to.be.true;
+      expect(controller.get('selectedAlertNotification')).to.equal(null);
+
+      controller.loadAlertNotifications.restore();
+      mockSelectedAlertNotification.deleteRecord.restore();
+    });
+
+  });
+
+  describe('#duplicateAlertNotification()', function () {
+
+    beforeEach(function () {
+      sinon.stub(controller, 'fillEditCreateInputs', Em.K);
+      sinon.stub(controller, 'showCreateEditPopup', Em.K);
+    });
+
+    afterEach(function () {
+      controller.fillEditCreateInputs.restore();
+      controller.showCreateEditPopup.restore();
+    });
+
+    it("should call fillEditCreateInputs and showCreateEditPopup", function () {
+
+      controller.duplicateAlertNotification();
+
+      expect(controller.fillEditCreateInputs.calledWith(true)).to.be.true;
+      expect(controller.showCreateEditPopup.calledOnce).to.be.true;
+    });
+
+  });
+
+});
+