You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by on...@apache.org on 2015/08/21 12:10:14 UTC

[1/2] ambari git commit: AMBARI-12845. Alert Instances overriding issue (onehiporenko)

Repository: ambari
Updated Branches:
  refs/heads/trunk 995343371 -> 66d0b6e2c


http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alerts/alert_notification.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alerts/alert_notification.js b/ambari-web/app/models/alerts/alert_notification.js
new file mode 100644
index 0000000..c2d7570
--- /dev/null
+++ b/ambari-web/app/models/alerts/alert_notification.js
@@ -0,0 +1,33 @@
+/**
+ * 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.AlertNotification = DS.Model.extend({
+  id: DS.attr('number'),
+  name: DS.attr('string'),
+  type: DS.attr('string'),
+  description: DS.attr('string'),
+  groups: DS.hasMany('App.AlertGroup'),
+  global: DS.attr('boolean'),
+
+  properties: {},
+  alertStates: []
+});
+
+App.AlertNotification.FIXTURES = [];
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/views/main/alerts/definition_details_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/alerts/definition_details_view.js b/ambari-web/app/views/main/alerts/definition_details_view.js
index cbbded9..12b563c 100644
--- a/ambari-web/app/views/main/alerts/definition_details_view.js
+++ b/ambari-web/app/views/main/alerts/definition_details_view.js
@@ -163,7 +163,7 @@ App.AlertInstanceServiceHostView = Em.View.extend({
    * Define whether show link for transition to service page
    */
   serviceIsLink: function () {
-    return App.Service.find().someProperty('serviceName', this.get('instance.service.serviceName'));
+    return App.get('services.all').contains(this.get('instance.service.serviceName'));
   }.property('instance.service.serviceName'),
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/views/main/host/host_alerts_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/host_alerts_view.js b/ambari-web/app/views/main/host/host_alerts_view.js
index 54b4e7c..656787b 100644
--- a/ambari-web/app/views/main/host/host_alerts_view.js
+++ b/ambari-web/app/views/main/host/host_alerts_view.js
@@ -253,10 +253,15 @@ App.MainHostAlertsView = App.TableView.extend({
    * @method tooltipsUpdater
    */
   tooltipsUpdater: function () {
+    Em.run.once(this,this.tooltipsUpdaterOnce);
+  }.observes('pageContent.@each'),
+
+  tooltipsUpdaterOnce: function() {
+    var self = this;
     Em.run.next(this, function () {
-      App.tooltip($(".enable-disable-button, .timeago, .alert-text"));
+      App.tooltip(self.$(".enable-disable-button, .timeago, .alert-text"));
     });
-  }.observes('pageContent.@each'),
+  },
 
   /**
    * Run <code>clearFilter</code> in the each child filterView
@@ -268,6 +273,10 @@ App.MainHostAlertsView = App.TableView.extend({
         childView.clearFilter();
       }
     });
+  },
+
+  willDestroyElement: function() {
+    this.$(".enable-disable-button, .timeago, .alert-text").tooltip('destroy');
   }
 
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/test/models/alert_config_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/alert_config_test.js b/ambari-web/test/models/alert_config_test.js
deleted file mode 100644
index 31f2ce9..0000000
--- a/ambari-web/test/models/alert_config_test.js
+++ /dev/null
@@ -1,332 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-require('models/alert_config');
-
-var model;
-
-describe('App.AlertConfigProperties', function () {
-
-  describe('Threshold', function () {
-
-    beforeEach(function () {
-      model = App.AlertConfigProperties.Threshold.create({});
-    });
-
-    describe('#apiFormattedValue', function () {
-
-      it('should be based on showInputForValue and showInputForText', function () {
-
-        model.setProperties({
-          value: 'value',
-          text: 'text',
-          showInputForValue: false,
-          showInputForText: false
-        });
-        expect(model.get('apiFormattedValue')).to.eql([]);
-
-        model.set('showInputForValue', true);
-        expect(model.get('apiFormattedValue')).to.eql(['value']);
-
-        model.set('showInputForText', true);
-        expect(model.get('apiFormattedValue')).to.eql(['value', 'text']);
-
-      });
-
-    });
-
-    describe('#valueWasChanged', function () {
-
-      it('value change should effect displayValue for AGGREGATE type', function () {
-
-        model = App.AlertConfigProperties.Threshold.create(App.AlertConfigProperties.Thresholds.PercentageMixin, {
-          value: '0.4',
-          valueMetric: '%',
-          text: 'text',
-          showInputForValue: false,
-          showInputForText: false
-        });
-
-        expect(model.get('displayValue')).to.eql('40');
-      });
-
-      it('value change should not effect displayValue for not AGGREGATE type', function () {
-
-        model = App.AlertConfigProperties.Threshold.create({
-          value: '0.4',
-          valueMetric: '%',
-          text: 'text',
-          showInputForValue: false,
-          showInputForText: false
-        });
-
-        expect(model.get('displayValue')).to.eql('0.4');
-      });
-
-    });
-
-    describe('#badgeCssClass', function () {
-
-      it ('should be based on badge', function () {
-
-        model.set('badge', 'OK');
-        expect(model.get('badgeCssClass')).to.equal('alert-state-OK');
-
-      });
-
-    });
-
-    describe('#wasChanged', function () {
-
-      Em.A([
-          {
-            p: {
-              previousValue: null,
-              previousText: null,
-              value: '',
-              text: ''
-            },
-            e: false
-          },
-          {
-            p: {
-              previousValue: 'not null',
-              previousText: null,
-              value: '',
-              text: ''
-            },
-            e: true
-          },
-          {
-            p: {
-              previousValue: null,
-              previousText: 'not null',
-              value: '',
-              text: ''
-            },
-            e: true
-          },
-          {
-            p: {
-              previousValue: 'not null',
-              previousText: 'not null',
-              value: '',
-              text: ''
-            },
-            e: true
-          }
-        ]).forEach(function (test, i) {
-        it('test #' + (i + 1), function () {
-          model.setProperties(test.p);
-          expect(model.get('wasChanged')).to.equal(test.e);
-        });
-      });
-
-    });
-
-    describe('#isValid', function () {
-
-      it('should be true if showInputForValue is false', function () {
-        model.set('showInputForValue', false);
-        expect(model.get('isValid')).to.be.true;
-      });
-
-      it('should be false if displayValue is null', function () {
-        model.set('displayValue', null);
-        expect(model.get('isValid')).to.be.false;
-
-        model.set('displayValue', undefined);
-        expect(model.get('isValid')).to.be.false;
-      });
-
-      it('should be false if METRIC displayValue is not valid float', function () {
-        model.set('displayValue', '$1234.444');
-        expect(model.get('isValid')).to.be.false;
-
-        model.set('displayValue', 'hello-world!');
-        expect(model.get('isValid')).to.be.false;
-      });
-
-      it('should be true if METRIC displayValue is valid float with at most one decimal', function () {
-        model.set('displayValue', '123.4');
-        expect(model.get('isValid')).to.be.true;
-
-        model.set('displayValue', '123.0');
-        expect(model.get('isValid')).to.be.true;
-
-        model.set('displayValue', '666');
-        expect(model.get('isValid')).to.be.true;
-      });
-
-      it('should be false if METRIC displayValue is valid float with more than one decimal', function () {
-        model.set('displayValue', '123.48');
-        expect(model.get('isValid')).to.be.false;
-      });
-
-      it('should be true for AGGREGATE percentage with precision of 1', function () {
-        model = Em.Object.create(App.AlertConfigProperties.Thresholds.PercentageMixin, {
-          displayValue: '1',
-          showInputForValue: true
-        });
-
-        expect(model.get('isValid')).to.be.true;
-
-        model.set('displayValue', '88');
-        expect(model.get('isValid')).to.be.true;
-      });
-
-      it('should be false for AGGREGATE percentage values with precision smaller than 1', function () {
-        model = Em.Object.create(App.AlertConfigProperties.Thresholds.PercentageMixin, {
-          displayValue: '70.01',
-          showInputForValue: true
-        });
-
-        expect(model.get('isValid')).to.be.false;
-
-        model.set('displayValue', '70.0');
-        expect(model.get('isValid')).to.be.false;
-
-        model.set('displayValue', '80.000');
-        expect(model.get('isValid')).to.be.false;
-      });
-
-      it('should be true for PORT percentage values with precision of 1/10th', function () {
-        model = App.AlertConfigProperties.Threshold.create({
-          value: '0.4',
-          showInputForValue: true
-        })
-
-        expect(model.get('isValid')).to.be.true;
-
-        model.set('value', '3');
-        expect(model.get('isValid')).to.be.true;
-
-        model.set('value', '33.0');
-        expect(model.get('isValid')).to.be.true;
-      });
-
-      it('should be false for PORT percentage values with precision greater than 1/10th', function() {
-        model = App.AlertConfigProperties.Threshold.create({
-          value: '4.234',
-          showInputForValue: true
-        });
-
-        expect(model.get('isValid')).to.be.false;
-
-        model.set('value', '44.001');
-        expect(model.get('isValid')).to.be.false;
-
-        model.set('value', '112.01');
-        expect(model.get('isValid')).to.be.false;
-      });
-
-    });
-
-  });
-
-  describe('App.AlertConfigProperties.Thresholds', function () {
-
-    describe('OkThreshold', function () {
-
-      beforeEach(function () {
-        model = App.AlertConfigProperties.Thresholds.OkThreshold.create();
-      });
-
-      describe('#apiProperty', function () {
-
-        it('should be based on showInputForValue and showInputForText', function () {
-
-          model.setProperties({
-            showInputForValue: false,
-            showInputForText: false
-          });
-          expect(model.get('apiProperty')).to.eql([]);
-
-          model.set('showInputForValue', true);
-          expect(model.get('apiProperty')).to.eql(['source.reporting.ok.value']);
-
-          model.set('showInputForText', true);
-          expect(model.get('apiProperty')).to.eql(['source.reporting.ok.value', 'source.reporting.ok.text']);
-
-        });
-
-      });
-
-    });
-
-    describe('WarningThreshold', function () {
-
-      beforeEach(function () {
-        model = App.AlertConfigProperties.Thresholds.WarningThreshold.create();
-      });
-
-      describe('#apiProperty', function () {
-
-        it('should be based on showInputForValue and showInputForText', function () {
-
-          model.setProperties({
-            showInputForValue: false,
-            showInputForText: false
-          });
-          expect(model.get('apiProperty')).to.eql([]);
-
-          model.set('showInputForValue', true);
-          expect(model.get('apiProperty')).to.eql(['source.reporting.warning.value']);
-
-          model.set('showInputForText', true);
-          expect(model.get('apiProperty')).to.eql(['source.reporting.warning.value', 'source.reporting.warning.text']);
-
-        });
-
-      });
-
-    });
-
-    describe('CriticalThreshold', function () {
-
-      beforeEach(function () {
-        model = App.AlertConfigProperties.Thresholds.CriticalThreshold.create();
-      });
-
-      describe('#apiProperty', function () {
-
-        it('should be based on showInputForValue and showInputForText', function () {
-
-          model.setProperties({
-            showInputForValue: false,
-            showInputForText: false
-          });
-          expect(model.get('apiProperty')).to.eql([]);
-
-          model.set('showInputForValue', true);
-          expect(model.get('apiProperty')).to.eql(['source.reporting.critical.value']);
-
-          model.set('showInputForText', true);
-          expect(model.get('apiProperty')).to.eql(['source.reporting.critical.value', 'source.reporting.critical.text']);
-
-        });
-
-      });
-
-    });
-
-  });
-
-});

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/test/models/alert_definition_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/alert_definition_test.js b/ambari-web/test/models/alert_definition_test.js
deleted file mode 100644
index 1e349b1..0000000
--- a/ambari-web/test/models/alert_definition_test.js
+++ /dev/null
@@ -1,212 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-require('models/alert_definition');
-
-var model;
-
-describe('App.AlertDefinition', function () {
-
-  beforeEach(function () {
-
-    model = App.AlertDefinition.createRecord();
-
-  });
-
-  describe('#status', function () {
-
-    Em.A([
-      {
-        summary: {OK: {count: 1, maintenanceCount: 0}, UNKNOWN: {count: 1, maintenanceCount: 0}, WARNING: {count: 2, maintenanceCount: 0}, CRITICAL: {count: 0, maintenanceCount: 0}},
-        m: 'No CRITICAL',
-        e: '<span class="alert-state-single-host label alert-state-OK">OK (1)</span> ' +
-        '<span class="alert-state-single-host label alert-state-WARNING">WARN (2)</span> ' +
-        '<span class="alert-state-single-host label alert-state-UNKNOWN">UNKWN (1)</span>'
-      },
-      {
-        summary: {WARNING: {count: 2, maintenanceCount: 0}, CRITICAL: {count: 3, maintenanceCount: 0}, UNKNOWN: {count: 1, maintenanceCount: 0}, OK: {count: 1, maintenanceCount: 0}},
-        m: 'All states exists',
-        e: '<span class="alert-state-single-host label alert-state-OK">OK (1)</span> ' +
-        '<span class="alert-state-single-host label alert-state-WARNING">WARN (2)</span> ' +
-        '<span class="alert-state-single-host label alert-state-CRITICAL">CRIT (3)</span> ' +
-        '<span class="alert-state-single-host label alert-state-UNKNOWN">UNKWN (1)</span>'
-      },
-      {
-        summary: {OK: {count: 1, maintenanceCount: 0}, UNKNOWN: {count: 0, maintenanceCount: 0}, WARNING: {count: 0, maintenanceCount: 0}, CRITICAL: {count: 0, maintenanceCount: 0}},
-        m: 'Single host',
-        e: '<span class="alert-state-single-host label alert-state-OK">OK</span>'
-      },
-      {
-        summary: {OK: {count: 0, maintenanceCount: 1}, UNKNOWN: {count: 0, maintenanceCount: 0}, WARNING: {count: 0, maintenanceCount: 0}, CRITICAL: {count: 0, maintenanceCount: 0}},
-        m: 'Maintenance OK alert',
-        e: '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> OK</span>'
-      },
-      {
-        summary: {},
-        m: 'Pending',
-        e: '<span class="alert-state-single-host label alert-state-PENDING">NONE</span>'
-      }
-    ]).forEach(function (test) {
-      it(test.m, function () {
-        model.set('summary', test.summary);
-        expect(model.get('status')).to.equal(test.e);
-      });
-    });
-
-  });
-
-  describe('#isCriticalOrWarning', function () {
-
-    Em.A([
-      {summary: {CRITICAL: {count: 1, maintenanceCount: 0}}, e: true},
-      {summary: {CRITICAL: {count: 0, maintenanceCount: 1}}, e: false},
-      {summary: {CRITICAL: {count: 1, maintenanceCount: 1}}, e: true},
-      {summary: {WARNING: {count: 1, maintenanceCount: 0}}, e: true},
-      {summary: {OK: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {UNKNOWN: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {}, e: false}
-    ]).forEach(function (test, i) {
-      it('test ' + (i + 1), function () {
-        model.set('summary', test.summary);
-        expect(model.get('isCriticalOrWarning')).to.equal(test.e);
-      });
-    });
-
-  });
-
-  describe('#isCritical', function () {
-
-    Em.A([
-      {summary: {CRITICAL: {count: 1, maintenanceCount: 0}}, e: true},
-      {summary: {WARNING: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {OK: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {UNKNOWN: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {}, e: false}
-    ]).forEach(function (test, i) {
-      it('test ' + (i + 1), function () {
-        model.set('summary', test.summary);
-        expect(model.get('isCritical')).to.equal(test.e);
-      });
-    });
-
-  });
-
-  describe('#isWarning', function () {
-
-    Em.A([
-      {summary: {CRITICAL: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {WARNING: {count: 1, maintenanceCount: 0}}, e: true},
-      {summary: {OK: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {UNKNOWN: {count: 1, maintenanceCount: 0}}, e: false},
-      {summary: {}, e: false}
-    ]).forEach(function (test, i) {
-      it('test ' + (i + 1), function () {
-        model.set('summary', test.summary);
-        expect(model.get('isWarning')).to.equal(test.e);
-      });
-    });
-
-  });
-
-  describe('#lastTriggeredAgoFormatted', function () {
-
-    it('should be empty', function () {
-      model.set('lastTriggered', 0);
-      expect(model.get('lastTriggeredAgoFormatted')).to.equal('');
-    });
-
-    it('should not be empty', function () {
-      model.set('lastTriggered', new Date().getTime() - 61000);
-      expect(model.get('lastTriggeredAgoFormatted')).to.equal('about a minute ago');
-    });
-
-  });
-
-  describe('#serviceDisplayName', function () {
-
-    it('should get name for non-existing service', function () {
-      model.set('serviceName', 'FOOBAR');
-      expect(model.get('serviceDisplayName')).to.equal('Foobar');
-    });
-
-  });
-
-  describe('#componentNameFormatted', function () {
-
-    beforeEach(function () {
-      sinon.stub(App.format, 'role', function (a) {
-        return 'role ' + a;
-      });
-    });
-
-    it('should wrap component name by App.format.role method', function () {
-      model.set('componentName', 'test');
-      var result = model.get('componentNameFormatted');
-      expect(result).to.equal('role test');
-    });
-
-    afterEach(function () {
-      App.format.role.restore();
-    });
-
-
-  });
-
-  describe('REOPEN', function () {
-
-    describe('#getSortDefinitionsByStatus', function () {
-
-      Em.A([
-          {
-            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
-            b: App.AlertDefinition.createRecord({summary: {WARNING: {count: 1, maintenanceCount: 0}}}),
-            order: true,
-            e: -1
-          },
-          {
-            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 2, maintenanceCount: 0}}}),
-            b: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
-            order: true,
-            e: -1
-          },
-          {
-            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
-            b: App.AlertDefinition.createRecord({summary: {WARNING: {count: 1, maintenanceCount: 0}}}),
-            order: false,
-            e: 1
-          },
-          {
-            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 2, maintenanceCount: 0}}}),
-            b: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
-            order: false,
-            e: 1
-          }
-        ]).forEach(function(test, i) {
-          it('test #' + (i + 1), function () {
-            var func = App.AlertDefinition.getSortDefinitionsByStatus(test.order);
-            expect(func(test.a, test.b)).to.equal(test.e);
-          });
-        });
-
-    });
-
-  });
-
-});

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/test/models/alert_instance_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/alert_instance_test.js b/ambari-web/test/models/alert_instance_test.js
deleted file mode 100644
index dc692e2..0000000
--- a/ambari-web/test/models/alert_instance_test.js
+++ /dev/null
@@ -1,94 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-require('models/alert_instance');
-
-var model;
-
-describe('App.AlertInstance', function () {
-
-  beforeEach(function () {
-
-    model = App.AlertInstance.createRecord();
-
-  });
-
-  describe('#serviceDisplayName', function () {
-
-    it('should get name for non-existing service', function () {
-      model.set('serviceName', 'FOOBAR');
-      expect(model.get('serviceDisplayName')).to.equal('Foobar');
-    });
-
-  });
-
-  describe('#statusChangedAndLastCheckedFormatted', function () {
-
-    it('should Status Changed before Last Checked', function () {
-
-      var lastCheckedFormatted = '123',
-        lastTriggeredFormatted = '321';
-
-      model.reopen({
-        lastCheckedFormatted: lastCheckedFormatted,
-        lastTriggeredFormatted: lastTriggeredFormatted
-      });
-      var status = model.get('statusChangedAndLastCheckedFormatted');
-      expect(status.indexOf(lastCheckedFormatted) > status.indexOf(lastTriggeredFormatted)).to.be.true;
-
-    });
-
-  });
-
-  describe('#status', function () {
-
-    it('should show maint mode icon', function () {
-
-      model.set('maintenanceState', 'ON');
-      model.set('state', 'OK');
-      var status = model.get('status');
-
-      expect(status).to.equal('<div class="label alert-state-single-host alert-state-PENDING"><span class="icon-medkit"></span> OK</div>');
-
-    });
-
-    it('should not show maint mode icon', function () {
-
-      model.set('maintenanceState', 'OFF');
-      model.set('state', 'OK');
-      var status = model.get('status');
-
-      expect(status).to.equal('<div class="label alert-state-single-host alert-state-OK">OK</div>');
-
-    });
-
-  });
-
-  describe('#escapeSpecialCharactersFromTooltip', function () {
-    it('it Should Display Alert Without special characters "<" and ">"', function () {
-
-      model.set('text', '<urlopen error [Errno 111] Connection refused>');
-      var resultedText = model.get('escapeSpecialCharactersFromTooltip');
-
-      expect(resultedText).to.equal('urlopen error [Errno 111] Connection refused');
-    });
-  });
-
-});

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/test/models/alerts/alert_config_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/alerts/alert_config_test.js b/ambari-web/test/models/alerts/alert_config_test.js
new file mode 100644
index 0000000..ae3f859
--- /dev/null
+++ b/ambari-web/test/models/alerts/alert_config_test.js
@@ -0,0 +1,332 @@
+/**
+ * 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/alerts/alert_config');
+
+var model;
+
+describe('App.AlertConfigProperties', function () {
+
+  describe('Threshold', function () {
+
+    beforeEach(function () {
+      model = App.AlertConfigProperties.Threshold.create({});
+    });
+
+    describe('#apiFormattedValue', function () {
+
+      it('should be based on showInputForValue and showInputForText', function () {
+
+        model.setProperties({
+          value: 'value',
+          text: 'text',
+          showInputForValue: false,
+          showInputForText: false
+        });
+        expect(model.get('apiFormattedValue')).to.eql([]);
+
+        model.set('showInputForValue', true);
+        expect(model.get('apiFormattedValue')).to.eql(['value']);
+
+        model.set('showInputForText', true);
+        expect(model.get('apiFormattedValue')).to.eql(['value', 'text']);
+
+      });
+
+    });
+
+    describe('#valueWasChanged', function () {
+
+      it('value change should effect displayValue for AGGREGATE type', function () {
+
+        model = App.AlertConfigProperties.Threshold.create(App.AlertConfigProperties.Thresholds.PercentageMixin, {
+          value: '0.4',
+          valueMetric: '%',
+          text: 'text',
+          showInputForValue: false,
+          showInputForText: false
+        });
+
+        expect(model.get('displayValue')).to.eql('40');
+      });
+
+      it('value change should not effect displayValue for not AGGREGATE type', function () {
+
+        model = App.AlertConfigProperties.Threshold.create({
+          value: '0.4',
+          valueMetric: '%',
+          text: 'text',
+          showInputForValue: false,
+          showInputForText: false
+        });
+
+        expect(model.get('displayValue')).to.eql('0.4');
+      });
+
+    });
+
+    describe('#badgeCssClass', function () {
+
+      it ('should be based on badge', function () {
+
+        model.set('badge', 'OK');
+        expect(model.get('badgeCssClass')).to.equal('alert-state-OK');
+
+      });
+
+    });
+
+    describe('#wasChanged', function () {
+
+      Em.A([
+          {
+            p: {
+              previousValue: null,
+              previousText: null,
+              value: '',
+              text: ''
+            },
+            e: false
+          },
+          {
+            p: {
+              previousValue: 'not null',
+              previousText: null,
+              value: '',
+              text: ''
+            },
+            e: true
+          },
+          {
+            p: {
+              previousValue: null,
+              previousText: 'not null',
+              value: '',
+              text: ''
+            },
+            e: true
+          },
+          {
+            p: {
+              previousValue: 'not null',
+              previousText: 'not null',
+              value: '',
+              text: ''
+            },
+            e: true
+          }
+        ]).forEach(function (test, i) {
+        it('test #' + (i + 1), function () {
+          model.setProperties(test.p);
+          expect(model.get('wasChanged')).to.equal(test.e);
+        });
+      });
+
+    });
+
+    describe('#isValid', function () {
+
+      it('should be true if showInputForValue is false', function () {
+        model.set('showInputForValue', false);
+        expect(model.get('isValid')).to.be.true;
+      });
+
+      it('should be false if displayValue is null', function () {
+        model.set('displayValue', null);
+        expect(model.get('isValid')).to.be.false;
+
+        model.set('displayValue', undefined);
+        expect(model.get('isValid')).to.be.false;
+      });
+
+      it('should be false if METRIC displayValue is not valid float', function () {
+        model.set('displayValue', '$1234.444');
+        expect(model.get('isValid')).to.be.false;
+
+        model.set('displayValue', 'hello-world!');
+        expect(model.get('isValid')).to.be.false;
+      });
+
+      it('should be true if METRIC displayValue is valid float with at most one decimal', function () {
+        model.set('displayValue', '123.4');
+        expect(model.get('isValid')).to.be.true;
+
+        model.set('displayValue', '123.0');
+        expect(model.get('isValid')).to.be.true;
+
+        model.set('displayValue', '666');
+        expect(model.get('isValid')).to.be.true;
+      });
+
+      it('should be false if METRIC displayValue is valid float with more than one decimal', function () {
+        model.set('displayValue', '123.48');
+        expect(model.get('isValid')).to.be.false;
+      });
+
+      it('should be true for AGGREGATE percentage with precision of 1', function () {
+        model = Em.Object.create(App.AlertConfigProperties.Thresholds.PercentageMixin, {
+          displayValue: '1',
+          showInputForValue: true
+        });
+
+        expect(model.get('isValid')).to.be.true;
+
+        model.set('displayValue', '88');
+        expect(model.get('isValid')).to.be.true;
+      });
+
+      it('should be false for AGGREGATE percentage values with precision smaller than 1', function () {
+        model = Em.Object.create(App.AlertConfigProperties.Thresholds.PercentageMixin, {
+          displayValue: '70.01',
+          showInputForValue: true
+        });
+
+        expect(model.get('isValid')).to.be.false;
+
+        model.set('displayValue', '70.0');
+        expect(model.get('isValid')).to.be.false;
+
+        model.set('displayValue', '80.000');
+        expect(model.get('isValid')).to.be.false;
+      });
+
+      it('should be true for PORT percentage values with precision of 1/10th', function () {
+        model = App.AlertConfigProperties.Threshold.create({
+          value: '0.4',
+          showInputForValue: true
+        })
+
+        expect(model.get('isValid')).to.be.true;
+
+        model.set('value', '3');
+        expect(model.get('isValid')).to.be.true;
+
+        model.set('value', '33.0');
+        expect(model.get('isValid')).to.be.true;
+      });
+
+      it('should be false for PORT percentage values with precision greater than 1/10th', function() {
+        model = App.AlertConfigProperties.Threshold.create({
+          value: '4.234',
+          showInputForValue: true
+        });
+
+        expect(model.get('isValid')).to.be.false;
+
+        model.set('value', '44.001');
+        expect(model.get('isValid')).to.be.false;
+
+        model.set('value', '112.01');
+        expect(model.get('isValid')).to.be.false;
+      });
+
+    });
+
+  });
+
+  describe('App.AlertConfigProperties.Thresholds', function () {
+
+    describe('OkThreshold', function () {
+
+      beforeEach(function () {
+        model = App.AlertConfigProperties.Thresholds.OkThreshold.create();
+      });
+
+      describe('#apiProperty', function () {
+
+        it('should be based on showInputForValue and showInputForText', function () {
+
+          model.setProperties({
+            showInputForValue: false,
+            showInputForText: false
+          });
+          expect(model.get('apiProperty')).to.eql([]);
+
+          model.set('showInputForValue', true);
+          expect(model.get('apiProperty')).to.eql(['source.reporting.ok.value']);
+
+          model.set('showInputForText', true);
+          expect(model.get('apiProperty')).to.eql(['source.reporting.ok.value', 'source.reporting.ok.text']);
+
+        });
+
+      });
+
+    });
+
+    describe('WarningThreshold', function () {
+
+      beforeEach(function () {
+        model = App.AlertConfigProperties.Thresholds.WarningThreshold.create();
+      });
+
+      describe('#apiProperty', function () {
+
+        it('should be based on showInputForValue and showInputForText', function () {
+
+          model.setProperties({
+            showInputForValue: false,
+            showInputForText: false
+          });
+          expect(model.get('apiProperty')).to.eql([]);
+
+          model.set('showInputForValue', true);
+          expect(model.get('apiProperty')).to.eql(['source.reporting.warning.value']);
+
+          model.set('showInputForText', true);
+          expect(model.get('apiProperty')).to.eql(['source.reporting.warning.value', 'source.reporting.warning.text']);
+
+        });
+
+      });
+
+    });
+
+    describe('CriticalThreshold', function () {
+
+      beforeEach(function () {
+        model = App.AlertConfigProperties.Thresholds.CriticalThreshold.create();
+      });
+
+      describe('#apiProperty', function () {
+
+        it('should be based on showInputForValue and showInputForText', function () {
+
+          model.setProperties({
+            showInputForValue: false,
+            showInputForText: false
+          });
+          expect(model.get('apiProperty')).to.eql([]);
+
+          model.set('showInputForValue', true);
+          expect(model.get('apiProperty')).to.eql(['source.reporting.critical.value']);
+
+          model.set('showInputForText', true);
+          expect(model.get('apiProperty')).to.eql(['source.reporting.critical.value', 'source.reporting.critical.text']);
+
+        });
+
+      });
+
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/test/models/alerts/alert_definition_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/alerts/alert_definition_test.js b/ambari-web/test/models/alerts/alert_definition_test.js
new file mode 100644
index 0000000..0e8cc79
--- /dev/null
+++ b/ambari-web/test/models/alerts/alert_definition_test.js
@@ -0,0 +1,212 @@
+/**
+ * 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/alerts/alert_definition');
+
+var model;
+
+describe('App.AlertDefinition', function () {
+
+  beforeEach(function () {
+
+    model = App.AlertDefinition.createRecord();
+
+  });
+
+  describe('#status', function () {
+
+    Em.A([
+      {
+        summary: {OK: {count: 1, maintenanceCount: 0}, UNKNOWN: {count: 1, maintenanceCount: 0}, WARNING: {count: 2, maintenanceCount: 0}, CRITICAL: {count: 0, maintenanceCount: 0}},
+        m: 'No CRITICAL',
+        e: '<span class="alert-state-single-host label alert-state-OK">OK (1)</span> ' +
+        '<span class="alert-state-single-host label alert-state-WARNING">WARN (2)</span> ' +
+        '<span class="alert-state-single-host label alert-state-UNKNOWN">UNKWN (1)</span>'
+      },
+      {
+        summary: {WARNING: {count: 2, maintenanceCount: 0}, CRITICAL: {count: 3, maintenanceCount: 0}, UNKNOWN: {count: 1, maintenanceCount: 0}, OK: {count: 1, maintenanceCount: 0}},
+        m: 'All states exists',
+        e: '<span class="alert-state-single-host label alert-state-OK">OK (1)</span> ' +
+        '<span class="alert-state-single-host label alert-state-WARNING">WARN (2)</span> ' +
+        '<span class="alert-state-single-host label alert-state-CRITICAL">CRIT (3)</span> ' +
+        '<span class="alert-state-single-host label alert-state-UNKNOWN">UNKWN (1)</span>'
+      },
+      {
+        summary: {OK: {count: 1, maintenanceCount: 0}, UNKNOWN: {count: 0, maintenanceCount: 0}, WARNING: {count: 0, maintenanceCount: 0}, CRITICAL: {count: 0, maintenanceCount: 0}},
+        m: 'Single host',
+        e: '<span class="alert-state-single-host label alert-state-OK">OK</span>'
+      },
+      {
+        summary: {OK: {count: 0, maintenanceCount: 1}, UNKNOWN: {count: 0, maintenanceCount: 0}, WARNING: {count: 0, maintenanceCount: 0}, CRITICAL: {count: 0, maintenanceCount: 0}},
+        m: 'Maintenance OK alert',
+        e: '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> OK</span>'
+      },
+      {
+        summary: {},
+        m: 'Pending',
+        e: '<span class="alert-state-single-host label alert-state-PENDING">NONE</span>'
+      }
+    ]).forEach(function (test) {
+      it(test.m, function () {
+        model.set('summary', test.summary);
+        expect(model.get('status')).to.equal(test.e);
+      });
+    });
+
+  });
+
+  describe('#isCriticalOrWarning', function () {
+
+    Em.A([
+      {summary: {CRITICAL: {count: 1, maintenanceCount: 0}}, e: true},
+      {summary: {CRITICAL: {count: 0, maintenanceCount: 1}}, e: false},
+      {summary: {CRITICAL: {count: 1, maintenanceCount: 1}}, e: true},
+      {summary: {WARNING: {count: 1, maintenanceCount: 0}}, e: true},
+      {summary: {OK: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {UNKNOWN: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {}, e: false}
+    ]).forEach(function (test, i) {
+      it('test ' + (i + 1), function () {
+        model.set('summary', test.summary);
+        expect(model.get('isCriticalOrWarning')).to.equal(test.e);
+      });
+    });
+
+  });
+
+  describe('#isCritical', function () {
+
+    Em.A([
+      {summary: {CRITICAL: {count: 1, maintenanceCount: 0}}, e: true},
+      {summary: {WARNING: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {OK: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {UNKNOWN: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {}, e: false}
+    ]).forEach(function (test, i) {
+      it('test ' + (i + 1), function () {
+        model.set('summary', test.summary);
+        expect(model.get('isCritical')).to.equal(test.e);
+      });
+    });
+
+  });
+
+  describe('#isWarning', function () {
+
+    Em.A([
+      {summary: {CRITICAL: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {WARNING: {count: 1, maintenanceCount: 0}}, e: true},
+      {summary: {OK: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {UNKNOWN: {count: 1, maintenanceCount: 0}}, e: false},
+      {summary: {}, e: false}
+    ]).forEach(function (test, i) {
+      it('test ' + (i + 1), function () {
+        model.set('summary', test.summary);
+        expect(model.get('isWarning')).to.equal(test.e);
+      });
+    });
+
+  });
+
+  describe('#lastTriggeredAgoFormatted', function () {
+
+    it('should be empty', function () {
+      model.set('lastTriggered', 0);
+      expect(model.get('lastTriggeredAgoFormatted')).to.equal('');
+    });
+
+    it('should not be empty', function () {
+      model.set('lastTriggered', new Date().getTime() - 61000);
+      expect(model.get('lastTriggeredAgoFormatted')).to.equal('about a minute ago');
+    });
+
+  });
+
+  describe('#serviceDisplayName', function () {
+
+    it('should get name for non-existing service', function () {
+      model.set('serviceName', 'FOOBAR');
+      expect(model.get('serviceDisplayName')).to.equal('Foobar');
+    });
+
+  });
+
+  describe('#componentNameFormatted', function () {
+
+    beforeEach(function () {
+      sinon.stub(App.format, 'role', function (a) {
+        return 'role ' + a;
+      });
+    });
+
+    it('should wrap component name by App.format.role method', function () {
+      model.set('componentName', 'test');
+      var result = model.get('componentNameFormatted');
+      expect(result).to.equal('role test');
+    });
+
+    afterEach(function () {
+      App.format.role.restore();
+    });
+
+
+  });
+
+  describe('REOPEN', function () {
+
+    describe('#getSortDefinitionsByStatus', function () {
+
+      Em.A([
+          {
+            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
+            b: App.AlertDefinition.createRecord({summary: {WARNING: {count: 1, maintenanceCount: 0}}}),
+            order: true,
+            e: -1
+          },
+          {
+            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 2, maintenanceCount: 0}}}),
+            b: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
+            order: true,
+            e: -1
+          },
+          {
+            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
+            b: App.AlertDefinition.createRecord({summary: {WARNING: {count: 1, maintenanceCount: 0}}}),
+            order: false,
+            e: 1
+          },
+          {
+            a: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 2, maintenanceCount: 0}}}),
+            b: App.AlertDefinition.createRecord({summary: {OK: {count: 1, maintenanceCount: 0}, WARNING: {count: 1, maintenanceCount: 0}}}),
+            order: false,
+            e: 1
+          }
+        ]).forEach(function(test, i) {
+          it('test #' + (i + 1), function () {
+            var func = App.AlertDefinition.getSortDefinitionsByStatus(test.order);
+            expect(func(test.a, test.b)).to.equal(test.e);
+          });
+        });
+
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/test/models/alerts/alert_instance_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/alerts/alert_instance_test.js b/ambari-web/test/models/alerts/alert_instance_test.js
new file mode 100644
index 0000000..19838a4
--- /dev/null
+++ b/ambari-web/test/models/alerts/alert_instance_test.js
@@ -0,0 +1,94 @@
+/**
+ * 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/alerts/alert_instance');
+
+var model;
+
+describe('App.AlertInstance', function () {
+
+  beforeEach(function () {
+
+    model = App.AlertInstance.createRecord();
+
+  });
+
+  describe('#serviceDisplayName', function () {
+
+    it('should get name for non-existing service', function () {
+      model.set('serviceName', 'FOOBAR');
+      expect(model.get('serviceDisplayName')).to.equal('Foobar');
+    });
+
+  });
+
+  describe('#statusChangedAndLastCheckedFormatted', function () {
+
+    it('should Status Changed before Last Checked', function () {
+
+      var lastCheckedFormatted = '123',
+        lastTriggeredFormatted = '321';
+
+      model.reopen({
+        lastCheckedFormatted: lastCheckedFormatted,
+        lastTriggeredFormatted: lastTriggeredFormatted
+      });
+      var status = model.get('statusChangedAndLastCheckedFormatted');
+      expect(status.indexOf(lastCheckedFormatted) > status.indexOf(lastTriggeredFormatted)).to.be.true;
+
+    });
+
+  });
+
+  describe('#status', function () {
+
+    it('should show maint mode icon', function () {
+
+      model.set('maintenanceState', 'ON');
+      model.set('state', 'OK');
+      var status = model.get('status');
+
+      expect(status).to.equal('<div class="label alert-state-single-host alert-state-PENDING"><span class="icon-medkit"></span> OK</div>');
+
+    });
+
+    it('should not show maint mode icon', function () {
+
+      model.set('maintenanceState', 'OFF');
+      model.set('state', 'OK');
+      var status = model.get('status');
+
+      expect(status).to.equal('<div class="label alert-state-single-host alert-state-OK">OK</div>');
+
+    });
+
+  });
+
+  describe('#escapeSpecialCharactersFromTooltip', function () {
+    it('it Should Display Alert Without special characters "<" and ">"', function () {
+
+      model.set('text', '<urlopen error [Errno 111] Connection refused>');
+      var resultedText = model.get('escapeSpecialCharactersFromTooltip');
+
+      expect(resultedText).to.equal('urlopen error [Errno 111] Connection refused');
+    });
+  });
+
+});


[2/2] ambari git commit: AMBARI-12845. Alert Instances overriding issue (onehiporenko)

Posted by on...@apache.org.
AMBARI-12845. Alert Instances overriding issue (onehiporenko)


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

Branch: refs/heads/trunk
Commit: 66d0b6e2c136335841fb99a47d5122ebde18d046
Parents: 9953433
Author: Oleg Nechiporenko <on...@apache.org>
Authored: Fri Aug 21 13:07:57 2015 +0300
Committer: Oleg Nechiporenko <on...@apache.org>
Committed: Fri Aug 21 13:07:57 2015 +0300

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   6 +-
 .../main/alerts/alert_instances_controller.js   |  13 +-
 .../alerts/definition_details_controller.js     |   2 +-
 .../main/host/host_alerts_controller.js         |   2 +-
 .../app/mappers/alert_instances_mapper.js       |  14 +-
 ambari-web/app/models.js                        |  11 +-
 ambari-web/app/models/alert_config.js           | 619 -------------------
 ambari-web/app/models/alert_definition.js       | 344 -----------
 ambari-web/app/models/alert_group.js            |  79 ---
 ambari-web/app/models/alert_instance.js         | 165 -----
 ambari-web/app/models/alert_notification.js     |  33 -
 ambari-web/app/models/alerts/alert_config.js    | 619 +++++++++++++++++++
 .../app/models/alerts/alert_definition.js       | 344 +++++++++++
 ambari-web/app/models/alerts/alert_group.js     |  79 +++
 ambari-web/app/models/alerts/alert_instance.js  | 165 +++++
 .../app/models/alerts/alert_instance_local.js   |  30 +
 .../app/models/alerts/alert_notification.js     |  33 +
 .../main/alerts/definition_details_view.js      |   2 +-
 .../app/views/main/host/host_alerts_view.js     |  13 +-
 ambari-web/test/models/alert_config_test.js     | 332 ----------
 ambari-web/test/models/alert_definition_test.js | 212 -------
 ambari-web/test/models/alert_instance_test.js   |  94 ---
 .../test/models/alerts/alert_config_test.js     | 332 ++++++++++
 .../test/models/alerts/alert_definition_test.js | 212 +++++++
 .../test/models/alerts/alert_instance_test.js   |  94 +++
 25 files changed, 1944 insertions(+), 1905 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index 56f9444..1888690 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -275,9 +275,9 @@ var files = ['test/init_model_test',
   'test/models/service/flume_test',
   'test/models/service/hdfs_test',
   'test/models/service/yarn_test',
-  'test/models/alert_config_test',
-  'test/models/alert_definition_test',
-  'test/models/alert_instance_test',
+  'test/models/alerts/alert_config_test',
+  'test/models/alerts/alert_definition_test',
+  'test/models/alerts/alert_instance_test',
   'test/models/authentication_test',
   'test/models/cluster_states_test',
   'test/models/config_group_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/controllers/main/alerts/alert_instances_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/alerts/alert_instances_controller.js b/ambari-web/app/controllers/main/alerts/alert_instances_controller.js
index 9e07726..234fba0 100644
--- a/ambari-web/app/controllers/main/alerts/alert_instances_controller.js
+++ b/ambari-web/app/controllers/main/alerts/alert_instances_controller.js
@@ -182,7 +182,7 @@ App.MainAlertInstancesController = Em.Controller.extend({
    * @method getAlertInstancesSuccessCallback
    */
   getAlertInstancesSuccessCallback: function (json) {
-    App.alertInstanceMapper.map(json);
+    App.alertInstanceMapper.mapLocal(json);
     this.set('isLoaded', true);
     this.toggleProperty('reload');
   },
@@ -309,15 +309,6 @@ App.MainAlertInstancesController = Em.Controller.extend({
           this.set('filteredContent', this.get('content'));
         }.observes('content.length'),
 
-        refreshTooltips: function () {
-          this.ensureTooltip();
-        }.observes('contents.[]', 'filteringComplete'),
-
-        ensureTooltip: function () {
-          Em.run.next(this, function () {
-            App.tooltip($(".timeago"));
-          });
-        },
         /**
          * Router transition to alert definition details page
          * @param event
@@ -354,8 +345,6 @@ App.MainAlertInstancesController = Em.Controller.extend({
 
         didInsertElement: function () {
           this.filter();
-          this.addObserver('filteringComplete', this, this.overlayObserver);
-          this.overlayObserver();
           return this._super();
         }
       })

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/controllers/main/alerts/definition_details_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/alerts/definition_details_controller.js b/ambari-web/app/controllers/main/alerts/definition_details_controller.js
index 27e6a4a..336033e 100644
--- a/ambari-web/app/controllers/main/alerts/definition_details_controller.js
+++ b/ambari-web/app/controllers/main/alerts/definition_details_controller.js
@@ -21,7 +21,7 @@ App.MainAlertDefinitionDetailsController = Em.Controller.extend({
   name: 'mainAlertDefinitionDetailsController',
 
   alerts: function () {
-    return App.AlertInstance.find().toArray()
+    return App.AlertInstanceLocal.find().toArray()
         .filterProperty('definitionId', this.get('content.id'));
   }.property('App.router.mainAlertInstancesController.isLoaded', 'App.router.mainAlertInstancesController.reload'),
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/controllers/main/host/host_alerts_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/host/host_alerts_controller.js b/ambari-web/app/controllers/main/host/host_alerts_controller.js
index 2a47914..a44aa26 100644
--- a/ambari-web/app/controllers/main/host/host_alerts_controller.js
+++ b/ambari-web/app/controllers/main/host/host_alerts_controller.js
@@ -30,7 +30,7 @@ App.MainHostAlertsController = Em.ArrayController.extend({
    * @type {App.AlertInstance[]}
    */
   content: function () {
-    return App.AlertInstance.find().toArray().filterProperty('host', this.get('selectedHost'));
+    return App.AlertInstanceLocal.find().toArray().filterProperty('host', this.get('selectedHost'));
   }.property('App.router.mainAlertInstancesController.isLoaded', 'selectedHost'),
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/mappers/alert_instances_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/alert_instances_mapper.js b/ambari-web/app/mappers/alert_instances_mapper.js
index af6804f..cd1f8bd 100644
--- a/ambari-web/app/mappers/alert_instances_mapper.js
+++ b/ambari-web/app/mappers/alert_instances_mapper.js
@@ -21,6 +21,8 @@ App.alertInstanceMapper = App.QuickDataMapper.create({
 
   model : App.AlertInstance,
 
+  modelLocal: App.AlertInstanceLocal,
+
   config : {
     id: 'Alert.id',
     label: 'Alert.label',
@@ -41,10 +43,17 @@ App.alertInstanceMapper = App.QuickDataMapper.create({
   },
 
   map: function(json) {
+    return this.parse(json, this.get('model'));
+  },
+
+  mapLocal: function(json) {
+    return this.parse(json, this.get('modelLocal'));
+  },
+
+  parse: function(json, model) {
     console.time('App.alertInstanceMapper execution time');
     if (json.items) {
       var alertInstances = [];
-      var model = this.get('model');
       var alertsToDelete = model.find().mapProperty('id');
 
       json.items.forEach(function (item) {
@@ -57,8 +66,9 @@ App.alertInstanceMapper = App.QuickDataMapper.create({
         model.find().clear();
       }
 
-      App.store.loadMany(this.get('model'), alertInstances);
+      App.store.loadMany(model, alertInstances);
       console.timeEnd('App.alertInstanceMapper execution time');
     }
   }
+
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models.js b/ambari-web/app/models.js
index 64860ea..6bffb95 100644
--- a/ambari-web/app/models.js
+++ b/ambari-web/app/models.js
@@ -42,11 +42,12 @@ require('models/service/mapreduce2');
 require('models/service/hbase');
 require('models/service/flume');
 require('models/service/storm');
-require('models/alert_definition');
-require('models/alert_instance');
-require('models/alert_notification');
-require('models/alert_config');
-require('models/alert_group');
+require('models/alerts/alert_definition');
+require('models/alerts/alert_instance');
+require('models/alerts/alert_instance_local');
+require('models/alerts/alert_notification');
+require('models/alerts/alert_config');
+require('models/alerts/alert_group');
 require('models/user');
 require('models/host');
 require('models/rack');

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alert_config.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alert_config.js b/ambari-web/app/models/alert_config.js
deleted file mode 100644
index df2e579..0000000
--- a/ambari-web/app/models/alert_config.js
+++ /dev/null
@@ -1,619 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-var validator = require('utils/validator');
-var numericUtils = require('utils/number_utils');
-
-App.AlertConfigProperty = Ember.Object.extend({
-
-  /**
-   * label to be shown for config property
-   * @type {String}
-   */
-  label: '',
-
-  /**
-   * PORT|METRIC|AGGREGATE
-   * @type {String}
-   */
-  type: '',
-
-  /**
-   * config property value
-   * @type {*}
-   */
-  value: null,
-
-  /**
-   * property value cache to realise undo function
-   * @type {*}
-   */
-  previousValue: null,
-
-  /**
-   * define either input is disabled or enabled
-   * @type {Boolean}
-   */
-  isDisabled: false,
-
-  /**
-   * options that Select list will have
-   * @type {Array}
-   */
-  options: [],
-
-  /**
-   * input displayType
-   * one of 'textFields', 'textArea', 'select' or 'threshold'
-   * @type {String}
-   */
-  displayType: '',
-
-  /**
-   * unit to be shown with value
-   * @type {String}
-   */
-  unit: null,
-
-  /**
-   * space separated list of css class names to use
-   * @type {String}
-   */
-  classNames: '',
-
-  /**
-   * define whether row with property should be shifted right
-   * @type {Boolean}
-   */
-  isShifted: false,
-
-  /**
-   * name or names of properties related to config
-   * may be either string for one property or array of strings for multiple properties
-   * if this property is array, then <code>apiFormattedValue</code> should also be an array
-   * example: <code>apiProperty[0]</code> relates to <code>apiFormattedValue[0]</code>
-   * @type {String|Array}
-   */
-  apiProperty: '',
-
-  /**
-   * for some metrics properties may be set true or false
-   * depending on what property is related to (JMX or Ganglia)
-   */
-  isJMXMetric: null,
-
-  /**
-   * define place to show label
-   * if true - label is shown before input
-   * if false - label is shown after input
-   * @type {Boolean}
-   */
-  isPreLabeled: function () {
-    var afterLabeledTypes = ['radioButton'];
-    return !afterLabeledTypes.contains(this.get('displayType'));
-  }.property('displayType'),
-
-  /**
-   * value converted to appropriate format for sending to server
-   * should be computed property
-   * should be defined in child class
-   * @type {*}
-   */
-  apiFormattedValue: function () {
-    return this.get('value');
-  }.property('value'),
-
-  /**
-   * define if property was changed by user
-   * @type {Boolean}
-   */
-  wasChanged: function () {
-    return this.get('previousValue') !== null && this.get('value') !== this.get('previousValue');
-  }.property('value', 'previousValue'),
-
-  /**
-   * view class according to <code>displayType</code>
-   * @type {Em.View}
-   */
-  viewClass: function () {
-    var displayType = this.get('displayType');
-    switch (displayType) {
-      case 'textField':
-        return App.AlertConfigTextFieldView;
-      case 'textArea':
-        return App.AlertConfigTextAreaView;
-      case 'select':
-        return App.AlertConfigSelectView;
-      case 'threshold':
-        return App.AlertConfigThresholdView;
-      case 'radioButton':
-        return App.AlertConfigRadioButtonView;
-      default:
-        console.error('Unable to find viewClass for displayType ', displayType);
-    }
-  }.property('displayType'),
-
-  /**
-   * Define whether property is valid
-   * Computed property
-   * Should be defined in child class
-   * @type {Boolean}
-   */
-  isValid: function () {
-    return true;
-  }.property()
-
-});
-
-App.AlertConfigProperties = {
-
-  AlertName: App.AlertConfigProperty.extend({
-    name: 'alert_name',
-    label: 'Alert Name',
-    displayType: 'textField',
-    classNames: 'alert-text-input',
-    apiProperty: 'name'
-  }),
-
-  AlertNameSelected: App.AlertConfigProperty.extend({
-    name: 'alert_name',
-    label: 'Alert Name',
-    displayType: 'select',
-    apiProperty: 'name'
-  }),
-
-  ServiceAlertType: App.AlertConfigProperty.extend({
-    name: 'alert_type_service',
-    label: 'Service Alert Definition',
-    displayType: 'radioButton',
-    group: 'alert_type'
-  }),
-
-  HostAlertType: App.AlertConfigProperty.extend({
-    name: 'alert_type_host',
-    label: 'Host Alert Definition',
-    displayType: 'radioButton',
-    group: 'alert_type'
-  }),
-
-  Service: App.AlertConfigProperty.extend({
-    name: 'service',
-    label: 'Service',
-    displayType: 'select',
-    apiProperty: 'service_name',
-    apiFormattedValue: function () {
-      return App.StackService.find().findProperty('displayName', this.get('value')).get('serviceName');
-    }.property('value')
-  }),
-
-  Component: App.AlertConfigProperty.extend({
-    name: 'component',
-    label: 'Component',
-    displayType: 'select',
-    apiProperty: 'component_name',
-    apiFormattedValue: function () {
-      return App.StackServiceComponent.find().findProperty('displayName', this.get('value')).get('componentName');
-    }.property('value')
-  }),
-
-  Scope: App.AlertConfigProperty.extend({
-    name: 'scope',
-    label: 'Scope',
-    displayType: 'select',
-    apiProperty: 'scope',
-    apiFormattedValue: function () {
-      return this.get('value').toUpperCase();
-    }.property('value')
-  }),
-
-  Description: App.AlertConfigProperty.extend({
-    name: 'description',
-    label: 'Description',
-    displayType: 'textArea',
-    classNames: 'alert-config-text-area',
-    // todo: check value after API will be provided
-    apiProperty: 'description'
-  }),
-
-  Interval: App.AlertConfigProperty.extend({
-    name: 'interval',
-    label: 'Check Interval',
-    displayType: 'textField',
-    unit: 'Minute',
-    classNames: 'alert-interval-input',
-    apiProperty: 'interval',
-    isValid: function () {
-      var value = this.get('value');
-      if (!value) return false;
-      return String(value) === String(parseInt(value, 10)) && value >= 1;
-    }.property('value')
-  }),
-
-  /**
-   * Implements threshold
-   * Main difference from other alertConfigProperties:
-   * it has two editable parts - <code>value</code> and <code>text</code>
-   * User may configure it to edit only one of them (use flags <code>showInputForValue</code> and <code>showInputForText</code>)
-   * This flags also determines update value and text in the API-request or not (see <code>App.AlertConfigProperties.Thresholds</code> for more examples)
-   *
-   * @type {App.AlertConfigProperty.Threshold}
-   */
-  Threshold: App.AlertConfigProperty.extend({
-
-    name: 'threshold',
-
-    /**
-     * Property text cache to realise undo function
-     * @type {*}
-     */
-    previousText: null,
-
-    label: '',
-
-    /**
-     * OK|WARNING|CRITICAL
-     * @type {string}
-     */
-    badge: '',
-
-    /**
-     * threshold-value
-     * @type {string}
-     */
-    value: '',
-
-    /**
-     * Type of value. This will be a fixed set of types (like %).
-     */
-    valueMetric: null,
-
-    /**
-     * Value actually displayed to the user. This value is transformed
-     * based on the limited types of 'valueMetric's. Mappings from
-     * 'value' to 'displayValue' is handled by observers.
-     */
-    displayValue: '',
-
-    /**
-     * threshold-text
-     * @type {string}
-     */
-    text: '',
-
-    displayType: 'threshold',
-
-    classNames: 'alert-thresholds-input',
-
-    apiProperty: [],
-
-    init: function () {
-      this.set('displayValue', this.getNewValue());
-      this._super();
-    },
-
-    /**
-     * @type {string[]}
-     */
-    apiFormattedValue: function () {
-      var ret = [];
-      if (this.get('showInputForValue')) {
-        ret.push(this.get('value'));
-      }
-      if (this.get('showInputForText')) {
-        ret.push(this.get('text'));
-      }
-      return ret;
-    }.property('value', 'text', 'showInputForValue', 'showInputForText'),
-
-    /**
-     * Determines if <code>value</code> should be visible and editable (if not - won't update API-value)
-     * @type {bool}
-     */
-    showInputForValue: true,
-
-    /**
-     * Determines if <code>text</code> should be visible and editable (if not - won't update API-text)
-     * @type {bool}
-     */
-    showInputForText: true,
-
-    /**
-     * Custom css-class for different badges
-     * type {string}
-     */
-    badgeCssClass: function () {
-      return 'alert-state-' + this.get('badge');
-    }.property('badge'),
-
-    /**
-     * Determines if <code>value</code> or <code>text</code> were changed
-     * @type {bool}
-     */
-    wasChanged: function () {
-      return (this.get('previousValue') !== null && this.get('value') !== this.get('previousValue')) ||
-      (this.get('previousText') !== null && this.get('text') !== this.get('previousText'));
-    }.property('value', 'text', 'previousValue', 'previousText'),
-
-    /**
-     * May be redefined in child-models, mixins etc
-     * @method getValue
-     * @returns {string}
-     */
-    getNewValue: function () {
-      return this.get('value');
-    },
-
-    valueWasChanged: function () {
-      var displayValue = this.get('displayValue');
-      var newDisplayValue = this.getNewValue();
-      if (newDisplayValue !== displayValue && !(isNaN(newDisplayValue) ||isNaN(displayValue))) {
-        this.set('displayValue', newDisplayValue);
-      }
-    }.observes('value'),
-
-    /**
-     * May be redefined in child-models, mixins etc
-     * @method getDisplayValue
-     * @returns {string}
-     */
-    getNewDisplayValue: function () {
-      return this.get('displayValue');
-    },
-
-    displayValueWasChanged: function () {
-      var value = this.get('value');
-      var newValue = this.getNewDisplayValue();
-      if (newValue !== value && !(isNaN(newValue) ||isNaN(value))) {
-        this.set('value', newValue);
-      }
-    }.observes('displayValue'),
-
-    /**
-     * Check if <code>displayValue</code> is valid float number
-     * If this value isn't shown (see <code>showInputForValue</code>), result is always true
-     * @return {boolean}
-     */
-    isValid: function () {
-      if (!this.get('showInputForValue')) {
-        return true;
-      }
-
-      var value = this.get('displayValue');
-
-      if (Em.isNone(value)) {
-        return false;
-      }
-
-      value = ('' + value).trim();
-
-      //only allow 1/10th of a second
-      if (numericUtils.getFloatDecimals(value) > 1) {
-        return false;
-      }
-
-      return validator.isValidFloat(value);
-    }.property('displayValue', 'showInputForValue')
-
-  }),
-
-  URI: App.AlertConfigProperty.extend({
-    name: 'uri',
-    label: 'URI',
-    displayType: 'textField',
-    classNames: 'alert-text-input',
-    apiProperty: 'source.uri'
-  }),
-
-  URIExtended: App.AlertConfigProperty.extend({
-    name: 'uri',
-    label: 'URI',
-    displayType: 'textArea',
-    classNames: 'alert-config-text-area',
-    apiProperty: 'source.uri',
-    apiFormattedValue: function () {
-      var result = {};
-      try {
-        result = JSON.parse(this.get('value'));
-      } catch (e) {
-        console.error('Wrong format of URI');
-      }
-      return result;
-    }.property('value')
-  }),
-
-  DefaultPort: App.AlertConfigProperty.extend({
-    name: 'default_port',
-    label: 'Default Port',
-    displayType: 'textField',
-    classNames: 'alert-port-input',
-    apiProperty: 'source.default_port'
-  }),
-
-  Path: App.AlertConfigProperty.extend({
-    name: 'path',
-    label: 'Path',
-    displayType: 'textField',
-    classNames: 'alert-text-input',
-    apiProperty: 'source.path'
-  }),
-
-  Metrics: App.AlertConfigProperty.extend({
-    name: 'metrics',
-    label: 'JMX/Ganglia Metrics',
-    displayType: 'textArea',
-    classNames: 'alert-config-text-area',
-    apiProperty: function () {
-      return this.get('isJMXMetric') ? 'source.jmx.property_list' : 'source.ganglia.property_list'
-    }.property('isJMXMetric'),
-    apiFormattedValue: function () {
-      return this.get('value').split(',\n');
-    }.property('value')
-  }),
-
-  FormatString: App.AlertConfigProperty.extend({
-    name: 'metrics_string',
-    label: 'Format String',
-    displayType: 'textArea',
-    classNames: 'alert-config-text-area',
-    apiProperty: function () {
-      return this.get('isJMXMetric') ? 'source.jmx.value' : 'source.ganglia.value'
-    }.property('isJMXMetric')
-  })
-
-};
-
-App.AlertConfigProperties.Thresholds = {
-
-  OkThreshold: App.AlertConfigProperties.Threshold.extend({
-
-    badge: 'OK',
-
-    name: 'ok_threshold',
-
-    apiProperty: function () {
-      var ret = [];
-      if (this.get('showInputForValue')) {
-        ret.push('source.reporting.ok.value');
-      }
-      if (this.get('showInputForText')) {
-        ret.push('source.reporting.ok.text');
-      }
-      return ret;
-    }.property('showInputForValue', 'showInputForText')
-
-  }),
-
-  WarningThreshold: App.AlertConfigProperties.Threshold.extend({
-
-    badge: 'WARNING',
-
-    name: 'warning_threshold',
-
-    apiProperty: function () {
-      var ret = [];
-      if (this.get('showInputForValue')) {
-        ret.push('source.reporting.warning.value');
-      }
-      if (this.get('showInputForText')) {
-        ret.push('source.reporting.warning.text');
-      }
-      return ret;
-    }.property('showInputForValue', 'showInputForText')
-
-  }),
-
-  CriticalThreshold: App.AlertConfigProperties.Threshold.extend({
-
-    badge: 'CRITICAL',
-
-    name: 'critical_threshold',
-
-    apiProperty: function () {
-      var ret = [];
-      if (this.get('showInputForValue')) {
-        ret.push('source.reporting.critical.value');
-      }
-      if (this.get('showInputForText')) {
-        ret.push('source.reporting.critical.text');
-      }
-      return ret;
-    }.property('showInputForValue', 'showInputForText')
-
-  }),
-
-  /**
-   * Mixin for <code>App.AlertConfigProperties.Threshold</code>
-   * Used to validate values in percentage range (0..1]
-   * @type {Em.Mixin}
-   */
-  PercentageMixin: Em.Mixin.create({
-
-    isValid: function () {
-      var value = this.get('displayValue');
-
-      if (!value) {
-        return false;
-      }
-
-      value = ('' + value).trim();
-      if (numericUtils.getFloatDecimals(value)) {
-        return false;
-      }
-
-      value = parseFloat(value);
-
-      //do not allow float values
-      if (parseInt(value, 10) !== value) {
-        return false;
-      }
-
-      return this.get('showInputForValue') ? !isNaN(value) && value > 0 && value <= 100 : true;
-    }.property('displayValue', 'showInputForValue'),
-
-    /**
-     * Return <code>value * 100</code>
-     * @returns {string}
-     */
-    getNewValue: function () {
-      var value = this.get('value');
-      return (value && !isNaN(value)) ? (Number(value) * 100) + '' : value;
-    },
-
-    /**
-     * Return <code>displayValue / 100</code>
-     * @returns {string}
-     */
-    getNewDisplayValue: function () {
-      var displayValue = this.get('displayValue');
-      return (displayValue && !isNaN(displayValue)) ? (Number(displayValue) / 100) + '' : displayValue;
-    }
-
-  }),
-
-  /**
-   * Mixin for <code>App.AlertConfigProperties.Threshold</code>
-   * Used to validate values that should be greater than 0
-   * @type {Em.Mixin}
-   */
-  PositiveMixin: Em.Mixin.create({
-
-    isValid: function () {
-      if (!this.get('showInputForValue')) {
-        return true;
-      }
-      var value = this.get('displayValue');
-
-      if (!value) {
-        return false;
-      }
-
-      //only allow 1/10th of a second
-      if (numericUtils.getFloatDecimals(value) > 1) {
-        return false;
-      }
-
-      value = ('' + value).trim();
-      value = parseFloat(value);
-
-      return !isNaN(value) && value > 0;
-    }.property('displayValue', 'showInputForValue')
-
-  })
-
-};

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alert_definition.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alert_definition.js b/ambari-web/app/models/alert_definition.js
deleted file mode 100644
index d1310ff..0000000
--- a/ambari-web/app/models/alert_definition.js
+++ /dev/null
@@ -1,344 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-var dateUtils = require('utils/date');
-
-App.AlertDefinition = DS.Model.extend({
-
-  name: DS.attr('string'),
-  label: DS.attr('string'),
-  description: DS.attr('string'),
-  service: DS.belongsTo('App.Service'),
-  serviceName: DS.attr('string'),
-  componentName: DS.attr('string'),
-  enabled: DS.attr('boolean'),
-  scope: DS.attr('string'),
-  interval: DS.attr('number'),
-  type: DS.attr('string'),
-  groups: DS.hasMany('App.AlertGroup'),
-  reporting: DS.hasMany('App.AlertReportDefinition'),
-  lastTriggered: DS.attr('number'),
-
-  //relates only to SCRIPT-type alert definition
-  location: DS.attr('string'),
-  //relates only to AGGREGATE-type alert definition
-  alertName: DS.attr('string'),
-  //relates only to WEB and METRIC types of alert definition
-  uri: DS.belongsTo('App.AlertMetricsUriDefinition'),
-  //relates only METRIC-type alert definition
-  jmx: DS.belongsTo('App.AlertMetricsSourceDefinition'),
-  ganglia: DS.belongsTo('App.AlertMetricsSourceDefinition'),
-  //relates only PORT-type alert definition
-  defaultPort: DS.attr('number'),
-  portUri: DS.attr('string'),
-
-  /**
-   * Raw data from AlertDefinition/source
-   * used to format request content for updating alert definition
-   * @type {Object}
-   */
-  rawSourceData: {},
-
-  /**
-   * Counts of alert grouped by their status
-   * Format:
-   * <code>
-   *   {
-   *    "CRITICAL": {
-   *      count: 1,
-   *      maintenanceCount: 0
-   *    },
-   *    "OK": {
-   *      count: 0,
-   *      maintenanceCount: 1
-   *    },
-   *    "UNKNOWN": {
-   *      count: 0,
-   *      maintenanceCount: 0
-   *    },
-   *    "WARNING": {
-   *      count: 1,
-   *      maintenanceCount: 1
-   *    }
-   *   }
-   * </code>
-   * @type {object}
-   */
-  summary: {},
-
-  /**
-   * Formatted timestamp for latest alert triggering for current alertDefinition
-   * @type {string}
-   */
-  lastTriggeredFormatted: function () {
-    return dateUtils.dateFormat(this.get('lastTriggered'));
-  }.property('lastTriggered'),
-
-  /**
-   * Formatted timestamp with <code>$.timeago</code>
-   * @type {string}
-   */
-  lastTriggeredAgoFormatted: function () {
-    var lastTriggered = this.get('lastTriggered');
-    return lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
-  }.property('lastTriggered'),
-
-  lastTriggeredVerboseDisplay: function () {
-    var lastTriggered = this.get('lastTriggered');
-    return Em.I18n.t('models.alert_definition.triggered.verbose').format(dateUtils.dateFormat(lastTriggered));
-  }.property('lastTriggered'),
-
-  /**
-   * Formatted timestamp in format: for 4 days
-   * @type {string}
-   */
-  lastTriggeredForFormatted: function () {
-    var lastTriggered = this.get('lastTriggered');
-    var previousSuffixAgo = $.timeago.settings.strings.suffixAgo;
-    var previousPrefixAgo = $.timeago.settings.strings.prefixAgo;
-    $.timeago.settings.strings.suffixAgo = null;
-    $.timeago.settings.strings.prefixAgo = 'for';
-    var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
-    $.timeago.settings.strings.suffixAgo = previousSuffixAgo;
-    $.timeago.settings.strings.prefixAgo = previousPrefixAgo;
-    return triggeredFor;
-  }.property('lastTriggered'),
-
-  /**
-   * Formatted displayName for <code>componentName</code>
-   * @type {String}
-   */
-  componentNameFormatted: function () {
-    return App.format.role(this.get('componentName'));
-  }.property('componentName'),
-
-  /**
-   * Status generates from child-alerts
-   * Format: OK(1)  WARN(2)  CRIT(1)  UNKN(1)
-   * If single host: show: OK/WARNING/CRITICAL/UNKNOWN
-   * If some there are no alerts with some state, this state isn't shown
-   * If no OK/WARN/CRIT/UNKN state, then show PENDING
-   * Order is equal to example
-   * @type {string}
-   */
-  status: function () {
-    var order = this.get('order'),
-        summary = this.get('summary'),
-        hostCnt = 0,
-        self = this;
-    order.forEach(function (state) {
-      hostCnt += summary[state] ? summary[state].count + summary[state].maintenanceCount : 0;
-    });
-    if (hostCnt > 1) {
-      // multiple hosts
-      return order.map(function (state) {
-        var shortState = self.get('shortState')[state];
-        var result = '';
-        result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + ' (' + summary[state].count + ')</span>' : '';
-        // add status with maintenance mode icon
-        result += summary[state].maintenanceCount ?
-        '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + ' (' + summary[state].maintenanceCount + ')</span>' : '';
-        return result;
-      }).without('').join(' ');
-    } else if (hostCnt == 1) {
-      // single host, single status
-      return order.map(function (state) {
-        var shortState = self.get('shortState')[state];
-        var result = '';
-        result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + '</span>' : '';
-        // add status with maintenance mode icon
-        result += summary[state].maintenanceCount ?
-        '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + '</span>' : '';
-        return result;
-      }).without('').join(' ');
-    } else if (hostCnt == 0) {
-      // none
-      return '<span class="alert-state-single-host label alert-state-PENDING">NONE</span>';
-    }
-    return '';
-  }.property('summary'),
-
-  latestText: function () {
-    var order = this.get('order'), summary = this.get('summary'), text = '';
-    order.forEach(function (state) {
-      var cnt = summary[state] ? summary[state].count + summary[state].maintenanceCount : 0;
-      if (cnt > 0) {
-        text = summary[state].latestText;
-      }
-    });
-    return text;
-  }.property('summary'),
-
-  isHostAlertDefinition: function () {
-    var serviceID = (this.get('service')._id === "AMBARI"),
-        component = (this.get('componentName') === "AMBARI_AGENT");
-    return serviceID && component;
-  }.property('service', 'componentName'),
-
-  typeIconClass: function () {
-    var typeIcons = this.get('typeIcons'),
-        type = this.get('type');
-    return typeIcons[type];
-  }.property('type'),
-
-  /**
-   * if this definition is in state: CRITICAL / WARNING, if true, will show up in alerts fast access popup
-   * instances with maintenance mode ON are ignored
-   * @type {boolean}
-   */
-  isCriticalOrWarning: function () {
-    return !!(this.get('summary.CRITICAL.count') || this.get('summary.WARNING.count'));
-  }.property('summary'),
-
-  /**
-   * if this definition is in state: CRIT
-   * @type {boolean}
-   */
-  isCritical: function () {
-    var summary = this.get('summary');
-    var state = 'CRITICAL';
-    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
-  }.property('summary'),
-
-  /**
-   * if this definition is in state: WARNING
-   * @type {boolean}
-   */
-  isWarning: function () {
-    var summary = this.get('summary');
-    var state = 'WARNING';
-    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
-  }.property('summary'),
-
-  /**
-   * if this definition is in state: OK
-   * @type {boolean}
-   */
-  isOK: function () {
-    var summary = this.get('summary');
-    var state = 'OK';
-    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
-  }.property('summary'),
-
-  /**
-   * if this definition is in state: OK
-   * @type {boolean}
-   */
-  isUnknown: function () {
-    var summary = this.get('summary');
-    var state = 'UNKNOWN';
-    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
-  }.property('summary'),
-
-  /**
-   * For alerts we will have processes which are not typical
-   * cluster services - like Ambari-Server. This method unifies
-   * cluster services and other services into a common display-name.
-   * @see App.AlertInstance#serviceDisplayName()
-   */
-  serviceDisplayName: function () {
-    var serviceName = this.get('service.displayName');
-    if (!serviceName) {
-      serviceName = this.get('serviceName');
-      if (serviceName) {
-        serviceName = serviceName.toCapital();
-      }
-    }
-    return serviceName;
-  }.property('serviceName', 'service.displayName'),
-
-  /**
-   * List of css-classes for alert types
-   * @type {object}
-   */
-  typeIcons: {
-    'METRIC': 'icon-bolt',
-    'SCRIPT': 'icon-file-text',
-    'WEB': 'icon-globe',
-    'PORT': 'icon-signin',
-    'AGGREGATE': 'icon-plus',
-    'SERVER': 'icon-desktop'
-  },
-
-  /**
-   * Sort on load definitions by this severity order
-   */
-  severityOrder: ['CRITICAL', 'WARNING', 'OK', 'UNKNOWN', 'PENDING'],
-  order: ['OK', 'WARNING', 'CRITICAL', 'UNKNOWN'],
-
-  shortState: {
-    'CRITICAL': 'CRIT',
-    'WARNING': 'WARN',
-    'OK': 'OK',
-    'UNKNOWN': 'UNKWN',
-    'PENDING': 'NONE'
-  }
-});
-
-App.AlertDefinition.reopenClass({
-
-  /**
-   * Return function to sort list of AlertDefinitions by their status
-   * It sorts according to <code>severityOrder</code>
-   * @param {boolean} order true - DESC, false - ASC
-   * @returns {Function}
-   * @method getSortDefinitionsByStatus
-   */
-  getSortDefinitionsByStatus: function (order) {
-    return function (a, b) {
-      var a_summary = a.get('summary'),
-        b_summary = b.get('summary'),
-        st_order = a.get('severityOrder'),
-        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]].count + a_summary[st_order[i]].maintenanceCount,
-          b_v = Em.isNone(b_summary[st_order[i]]) ? 0 : b_summary[st_order[i]].count + b_summary[st_order[i]].maintenanceCount;
-        ret = b_v - a_v;
-        if (ret !== 0) {
-          break;
-        }
-      }
-      return order ? ret : -ret;
-    };
-  }
-
-});
-
-App.AlertReportDefinition = DS.Model.extend({
-  type: DS.attr('string'),
-  text: DS.attr('string'),
-  value: DS.attr('number')
-});
-
-App.AlertMetricsSourceDefinition = DS.Model.extend({
-  propertyList: [],
-  value: DS.attr('string')
-});
-
-App.AlertMetricsUriDefinition = DS.Model.extend({
-  http: DS.attr('string'),
-  https: DS.attr('string'),
-  httpsProperty: DS.attr('string'),
-  httpsPropertyValue: DS.attr('string')
-});
-
-App.AlertDefinition.FIXTURES = [];
-App.AlertReportDefinition.FIXTURES = [];
-App.AlertMetricsSourceDefinition.FIXTURES = [];
-App.AlertMetricsUriDefinition.FIXTURES = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alert_group.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alert_group.js b/ambari-web/app/models/alert_group.js
deleted file mode 100644
index e914ed5..0000000
--- a/ambari-web/app/models/alert_group.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-/**
- * Represents an alert-group on the cluster.
- * A alert group is a collection of alert definitions
- *
- * Alert group hierarchy is at 2 levels. For
- * each service there is a 'Default' alert group
- * containing all definitions , this group is read-only
- *
- * User can create new alert group containing alert definitions from
- * any service.
- */
-App.AlertGroup = DS.Model.extend({
-
-  name: DS.attr('string'),
-
-  description: DS.attr('string'),
-
-  /**
-   * Is this group default for some service
-   * @type {boolean}
-   */
-  default: DS.attr('boolean'),
-
-  /**
-   * @type {App.AlertDefinition[]}
-   */
-  definitions: DS.hasMany('App.AlertDefinition'),
-
-  /**
-   * @type {App.AlertNotification[]}
-   */
-  targets: DS.hasMany('App.AlertNotification'),
-
-  /**
-   * @type {string}
-   */
-  displayName: function () {
-    var name = this.get('name');
-    if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) {
-      var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2);
-      name = name.substring(0, middle) + "..." + name.substring(name.length - middle);
-    }
-    return this.get('default') ? (name + ' Default') : name;
-  }.property('name', 'default'),
-
-  /**
-   * @type {string}
-   */
-  displayNameDefinitions: function () {
-    return this.get('displayName') + ' (' + this.get('definitions.length') + ')';
-  }.property('displayName', 'definitions.length'),
-
-  isAddDefinitionsDisabled: function () {
-    return this.get('default');
-  }.property('default')
-});
-App.AlertGroup.FIXTURES = [];
-
-

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/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
deleted file mode 100644
index 33df293..0000000
--- a/ambari-web/app/models/alert_instance.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-var dateUtils = require('utils/date');
-
-App.AlertInstance = DS.Model.extend({
-  id: DS.attr('number'),
-  label: DS.attr('string'),
-  definitionName: DS.attr('string'),
-  definitionId: DS.attr('number'),
-  service: DS.belongsTo('App.Service'),
-  serviceName: DS.attr('string'),
-  componentName: DS.attr('string'),
-  host: DS.belongsTo('App.Host'),
-  hostName: DS.attr('string'),
-  scope: DS.attr('string'),
-  originalTimestamp: DS.attr('number'),
-  latestTimestamp: DS.attr('number'),
-  maintenanceState: DS.attr('string'),
-  instance: DS.attr('string'),
-  state: DS.attr('string'),
-  text: DS.attr('string'),
-  notification: DS.hasMany('App.AlertNotification'),
-
-  /**
-   * Status icon markup
-   * @type {string}
-   */
-  status: function () {
-    var isMaintenanceStateOn = this.get('maintenanceState') === 'ON';
-    var state = this.get('state');
-    var stateClass = isMaintenanceStateOn ? 'PENDING' : state;
-    var shortState = this.get('shortState')[state];
-    var maintenanceIcon = isMaintenanceStateOn ? '<span class="icon-medkit"></span> ' : '';
-    return '<div class="label alert-state-single-host alert-state-' + stateClass + '">' + maintenanceIcon + shortState + '</div>';
-  }.property('state'),
-
-  /**
-   * For alerts we will have processes which are not typical
-   * cluster services - like Ambari-Server. This method unifies
-   * cluster services and other services into a common display-name.
-   * @see App.AlertDefinition#serviceDisplayName()
-   */
-  serviceDisplayName: function () {
-    var serviceName = this.get('service.displayName');
-    if (!serviceName) {
-      serviceName = this.get('serviceName');
-      if (serviceName) {
-        serviceName = serviceName.toCapital();
-      }
-    }
-    return serviceName;
-  }.property('serviceName', 'service.displayName'),
-
-  /**
-   * Formatted timestamp for latest instance triggering
-   * @type {string}
-   */
-  lastCheckedFormatted: function () {
-    return dateUtils.dateFormat(this.get('latestTimestamp'));
-  }.property('latestTimestamp'),
-
-  /**
-   * Formatted timestamp for latest instance triggering
-   * @type {string}
-   */
-  lastTriggeredFormatted: function () {
-    return dateUtils.dateFormat(this.get('originalTimestamp'));
-  }.property('originalTimestamp'),
-
-  /**
-   * Formatted timestamp with <code>$.timeago</code>
-   * @type {string}
-   */
-  lastTriggeredAgoFormatted: function () {
-    var lastTriggered = this.get('originalTimestamp');
-    return lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
-  }.property('originalTimestamp'),
-
-  lastTriggeredVerboseDisplay: function () {
-    var originalTimestamp = this.get('originalTimestamp');
-    var latestTimestamp = this.get('latestTimestamp');
-    return Em.I18n.t('models.alert_instance.tiggered.verbose').format(
-        dateUtils.dateFormat(originalTimestamp),
-        dateUtils.dateFormat(latestTimestamp));
-  }.property('originalTimestamp', 'latestTimestamp'),
-
-  /**
-   * Formatted timestamp with <code>$.timeago</code>
-   * @type {string}
-   */
-  lastTriggeredForFormatted: function () {
-    var lastTriggered = this.get('originalTimestamp');
-    var previousSuffixAgo = $.timeago.settings.strings.suffixAgo;
-    var previousPrefixAgo = $.timeago.settings.strings.prefixAgo;
-    $.timeago.settings.strings.suffixAgo = null;
-    $.timeago.settings.strings.prefixAgo = 'for';
-    var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
-    $.timeago.settings.strings.suffixAgo = previousSuffixAgo;
-    $.timeago.settings.strings.prefixAgo = previousPrefixAgo;
-    return triggeredFor;
-  }.property('originalTimestamp'),
-
-  /**
-  * escaped '<' and '>' special characters.
-  * @type {string}
-  */  
-  escapeSpecialCharactersFromTooltip: function () {
-    var displayedText = this.get('text');
-    return  displayedText.replace(/[<>]/g, '');
-  }.property('text'),
-
-  /**
-   * Formatted lastChecked and lastTriggered timestamp
-   * @returns {string}
-   */
-  statusChangedAndLastCheckedFormatted: function () {
-    var lastCheckedFormatted = this.get('lastCheckedFormatted');
-    var lastTriggeredFormatted = this.get('lastTriggeredFormatted');
-    return Em.I18n.t('models.alert_definition.triggered.checked').format(lastTriggeredFormatted, lastCheckedFormatted);
-  }.property('lastCheckedFormatted', 'lastTriggeredFormatted'),
-
-  /**
-   * List of css-classes for alert instance status
-   * @type {object}
-   */
-  typeIcons: {
-    'DISABLED': 'icon-off'
-  },
-
-  /**
-   * Define if definition serviceName is Ambari
-   * Used in some logic in templates to distinguish definitions with Ambari serviceName
-   * @returns {boolean}
-   */
-  isAmbariServiceName: function () {
-    return this.get('serviceName') === 'AMBARI';
-  }.property('serviceName'),
-
-  shortState: {
-    'CRITICAL': 'CRIT',
-    'WARNING': 'WARN',
-    'OK': 'OK',
-    'UNKNOWN': 'UNKWN',
-    'PENDING': 'NONE'
-  }
-});
-
-App.AlertInstance.FIXTURES = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alert_notification.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alert_notification.js b/ambari-web/app/models/alert_notification.js
deleted file mode 100644
index c2d7570..0000000
--- a/ambari-web/app/models/alert_notification.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.AlertNotification = DS.Model.extend({
-  id: DS.attr('number'),
-  name: DS.attr('string'),
-  type: DS.attr('string'),
-  description: DS.attr('string'),
-  groups: DS.hasMany('App.AlertGroup'),
-  global: DS.attr('boolean'),
-
-  properties: {},
-  alertStates: []
-});
-
-App.AlertNotification.FIXTURES = [];
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alerts/alert_config.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alerts/alert_config.js b/ambari-web/app/models/alerts/alert_config.js
new file mode 100644
index 0000000..df2e579
--- /dev/null
+++ b/ambari-web/app/models/alerts/alert_config.js
@@ -0,0 +1,619 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var validator = require('utils/validator');
+var numericUtils = require('utils/number_utils');
+
+App.AlertConfigProperty = Ember.Object.extend({
+
+  /**
+   * label to be shown for config property
+   * @type {String}
+   */
+  label: '',
+
+  /**
+   * PORT|METRIC|AGGREGATE
+   * @type {String}
+   */
+  type: '',
+
+  /**
+   * config property value
+   * @type {*}
+   */
+  value: null,
+
+  /**
+   * property value cache to realise undo function
+   * @type {*}
+   */
+  previousValue: null,
+
+  /**
+   * define either input is disabled or enabled
+   * @type {Boolean}
+   */
+  isDisabled: false,
+
+  /**
+   * options that Select list will have
+   * @type {Array}
+   */
+  options: [],
+
+  /**
+   * input displayType
+   * one of 'textFields', 'textArea', 'select' or 'threshold'
+   * @type {String}
+   */
+  displayType: '',
+
+  /**
+   * unit to be shown with value
+   * @type {String}
+   */
+  unit: null,
+
+  /**
+   * space separated list of css class names to use
+   * @type {String}
+   */
+  classNames: '',
+
+  /**
+   * define whether row with property should be shifted right
+   * @type {Boolean}
+   */
+  isShifted: false,
+
+  /**
+   * name or names of properties related to config
+   * may be either string for one property or array of strings for multiple properties
+   * if this property is array, then <code>apiFormattedValue</code> should also be an array
+   * example: <code>apiProperty[0]</code> relates to <code>apiFormattedValue[0]</code>
+   * @type {String|Array}
+   */
+  apiProperty: '',
+
+  /**
+   * for some metrics properties may be set true or false
+   * depending on what property is related to (JMX or Ganglia)
+   */
+  isJMXMetric: null,
+
+  /**
+   * define place to show label
+   * if true - label is shown before input
+   * if false - label is shown after input
+   * @type {Boolean}
+   */
+  isPreLabeled: function () {
+    var afterLabeledTypes = ['radioButton'];
+    return !afterLabeledTypes.contains(this.get('displayType'));
+  }.property('displayType'),
+
+  /**
+   * value converted to appropriate format for sending to server
+   * should be computed property
+   * should be defined in child class
+   * @type {*}
+   */
+  apiFormattedValue: function () {
+    return this.get('value');
+  }.property('value'),
+
+  /**
+   * define if property was changed by user
+   * @type {Boolean}
+   */
+  wasChanged: function () {
+    return this.get('previousValue') !== null && this.get('value') !== this.get('previousValue');
+  }.property('value', 'previousValue'),
+
+  /**
+   * view class according to <code>displayType</code>
+   * @type {Em.View}
+   */
+  viewClass: function () {
+    var displayType = this.get('displayType');
+    switch (displayType) {
+      case 'textField':
+        return App.AlertConfigTextFieldView;
+      case 'textArea':
+        return App.AlertConfigTextAreaView;
+      case 'select':
+        return App.AlertConfigSelectView;
+      case 'threshold':
+        return App.AlertConfigThresholdView;
+      case 'radioButton':
+        return App.AlertConfigRadioButtonView;
+      default:
+        console.error('Unable to find viewClass for displayType ', displayType);
+    }
+  }.property('displayType'),
+
+  /**
+   * Define whether property is valid
+   * Computed property
+   * Should be defined in child class
+   * @type {Boolean}
+   */
+  isValid: function () {
+    return true;
+  }.property()
+
+});
+
+App.AlertConfigProperties = {
+
+  AlertName: App.AlertConfigProperty.extend({
+    name: 'alert_name',
+    label: 'Alert Name',
+    displayType: 'textField',
+    classNames: 'alert-text-input',
+    apiProperty: 'name'
+  }),
+
+  AlertNameSelected: App.AlertConfigProperty.extend({
+    name: 'alert_name',
+    label: 'Alert Name',
+    displayType: 'select',
+    apiProperty: 'name'
+  }),
+
+  ServiceAlertType: App.AlertConfigProperty.extend({
+    name: 'alert_type_service',
+    label: 'Service Alert Definition',
+    displayType: 'radioButton',
+    group: 'alert_type'
+  }),
+
+  HostAlertType: App.AlertConfigProperty.extend({
+    name: 'alert_type_host',
+    label: 'Host Alert Definition',
+    displayType: 'radioButton',
+    group: 'alert_type'
+  }),
+
+  Service: App.AlertConfigProperty.extend({
+    name: 'service',
+    label: 'Service',
+    displayType: 'select',
+    apiProperty: 'service_name',
+    apiFormattedValue: function () {
+      return App.StackService.find().findProperty('displayName', this.get('value')).get('serviceName');
+    }.property('value')
+  }),
+
+  Component: App.AlertConfigProperty.extend({
+    name: 'component',
+    label: 'Component',
+    displayType: 'select',
+    apiProperty: 'component_name',
+    apiFormattedValue: function () {
+      return App.StackServiceComponent.find().findProperty('displayName', this.get('value')).get('componentName');
+    }.property('value')
+  }),
+
+  Scope: App.AlertConfigProperty.extend({
+    name: 'scope',
+    label: 'Scope',
+    displayType: 'select',
+    apiProperty: 'scope',
+    apiFormattedValue: function () {
+      return this.get('value').toUpperCase();
+    }.property('value')
+  }),
+
+  Description: App.AlertConfigProperty.extend({
+    name: 'description',
+    label: 'Description',
+    displayType: 'textArea',
+    classNames: 'alert-config-text-area',
+    // todo: check value after API will be provided
+    apiProperty: 'description'
+  }),
+
+  Interval: App.AlertConfigProperty.extend({
+    name: 'interval',
+    label: 'Check Interval',
+    displayType: 'textField',
+    unit: 'Minute',
+    classNames: 'alert-interval-input',
+    apiProperty: 'interval',
+    isValid: function () {
+      var value = this.get('value');
+      if (!value) return false;
+      return String(value) === String(parseInt(value, 10)) && value >= 1;
+    }.property('value')
+  }),
+
+  /**
+   * Implements threshold
+   * Main difference from other alertConfigProperties:
+   * it has two editable parts - <code>value</code> and <code>text</code>
+   * User may configure it to edit only one of them (use flags <code>showInputForValue</code> and <code>showInputForText</code>)
+   * This flags also determines update value and text in the API-request or not (see <code>App.AlertConfigProperties.Thresholds</code> for more examples)
+   *
+   * @type {App.AlertConfigProperty.Threshold}
+   */
+  Threshold: App.AlertConfigProperty.extend({
+
+    name: 'threshold',
+
+    /**
+     * Property text cache to realise undo function
+     * @type {*}
+     */
+    previousText: null,
+
+    label: '',
+
+    /**
+     * OK|WARNING|CRITICAL
+     * @type {string}
+     */
+    badge: '',
+
+    /**
+     * threshold-value
+     * @type {string}
+     */
+    value: '',
+
+    /**
+     * Type of value. This will be a fixed set of types (like %).
+     */
+    valueMetric: null,
+
+    /**
+     * Value actually displayed to the user. This value is transformed
+     * based on the limited types of 'valueMetric's. Mappings from
+     * 'value' to 'displayValue' is handled by observers.
+     */
+    displayValue: '',
+
+    /**
+     * threshold-text
+     * @type {string}
+     */
+    text: '',
+
+    displayType: 'threshold',
+
+    classNames: 'alert-thresholds-input',
+
+    apiProperty: [],
+
+    init: function () {
+      this.set('displayValue', this.getNewValue());
+      this._super();
+    },
+
+    /**
+     * @type {string[]}
+     */
+    apiFormattedValue: function () {
+      var ret = [];
+      if (this.get('showInputForValue')) {
+        ret.push(this.get('value'));
+      }
+      if (this.get('showInputForText')) {
+        ret.push(this.get('text'));
+      }
+      return ret;
+    }.property('value', 'text', 'showInputForValue', 'showInputForText'),
+
+    /**
+     * Determines if <code>value</code> should be visible and editable (if not - won't update API-value)
+     * @type {bool}
+     */
+    showInputForValue: true,
+
+    /**
+     * Determines if <code>text</code> should be visible and editable (if not - won't update API-text)
+     * @type {bool}
+     */
+    showInputForText: true,
+
+    /**
+     * Custom css-class for different badges
+     * type {string}
+     */
+    badgeCssClass: function () {
+      return 'alert-state-' + this.get('badge');
+    }.property('badge'),
+
+    /**
+     * Determines if <code>value</code> or <code>text</code> were changed
+     * @type {bool}
+     */
+    wasChanged: function () {
+      return (this.get('previousValue') !== null && this.get('value') !== this.get('previousValue')) ||
+      (this.get('previousText') !== null && this.get('text') !== this.get('previousText'));
+    }.property('value', 'text', 'previousValue', 'previousText'),
+
+    /**
+     * May be redefined in child-models, mixins etc
+     * @method getValue
+     * @returns {string}
+     */
+    getNewValue: function () {
+      return this.get('value');
+    },
+
+    valueWasChanged: function () {
+      var displayValue = this.get('displayValue');
+      var newDisplayValue = this.getNewValue();
+      if (newDisplayValue !== displayValue && !(isNaN(newDisplayValue) ||isNaN(displayValue))) {
+        this.set('displayValue', newDisplayValue);
+      }
+    }.observes('value'),
+
+    /**
+     * May be redefined in child-models, mixins etc
+     * @method getDisplayValue
+     * @returns {string}
+     */
+    getNewDisplayValue: function () {
+      return this.get('displayValue');
+    },
+
+    displayValueWasChanged: function () {
+      var value = this.get('value');
+      var newValue = this.getNewDisplayValue();
+      if (newValue !== value && !(isNaN(newValue) ||isNaN(value))) {
+        this.set('value', newValue);
+      }
+    }.observes('displayValue'),
+
+    /**
+     * Check if <code>displayValue</code> is valid float number
+     * If this value isn't shown (see <code>showInputForValue</code>), result is always true
+     * @return {boolean}
+     */
+    isValid: function () {
+      if (!this.get('showInputForValue')) {
+        return true;
+      }
+
+      var value = this.get('displayValue');
+
+      if (Em.isNone(value)) {
+        return false;
+      }
+
+      value = ('' + value).trim();
+
+      //only allow 1/10th of a second
+      if (numericUtils.getFloatDecimals(value) > 1) {
+        return false;
+      }
+
+      return validator.isValidFloat(value);
+    }.property('displayValue', 'showInputForValue')
+
+  }),
+
+  URI: App.AlertConfigProperty.extend({
+    name: 'uri',
+    label: 'URI',
+    displayType: 'textField',
+    classNames: 'alert-text-input',
+    apiProperty: 'source.uri'
+  }),
+
+  URIExtended: App.AlertConfigProperty.extend({
+    name: 'uri',
+    label: 'URI',
+    displayType: 'textArea',
+    classNames: 'alert-config-text-area',
+    apiProperty: 'source.uri',
+    apiFormattedValue: function () {
+      var result = {};
+      try {
+        result = JSON.parse(this.get('value'));
+      } catch (e) {
+        console.error('Wrong format of URI');
+      }
+      return result;
+    }.property('value')
+  }),
+
+  DefaultPort: App.AlertConfigProperty.extend({
+    name: 'default_port',
+    label: 'Default Port',
+    displayType: 'textField',
+    classNames: 'alert-port-input',
+    apiProperty: 'source.default_port'
+  }),
+
+  Path: App.AlertConfigProperty.extend({
+    name: 'path',
+    label: 'Path',
+    displayType: 'textField',
+    classNames: 'alert-text-input',
+    apiProperty: 'source.path'
+  }),
+
+  Metrics: App.AlertConfigProperty.extend({
+    name: 'metrics',
+    label: 'JMX/Ganglia Metrics',
+    displayType: 'textArea',
+    classNames: 'alert-config-text-area',
+    apiProperty: function () {
+      return this.get('isJMXMetric') ? 'source.jmx.property_list' : 'source.ganglia.property_list'
+    }.property('isJMXMetric'),
+    apiFormattedValue: function () {
+      return this.get('value').split(',\n');
+    }.property('value')
+  }),
+
+  FormatString: App.AlertConfigProperty.extend({
+    name: 'metrics_string',
+    label: 'Format String',
+    displayType: 'textArea',
+    classNames: 'alert-config-text-area',
+    apiProperty: function () {
+      return this.get('isJMXMetric') ? 'source.jmx.value' : 'source.ganglia.value'
+    }.property('isJMXMetric')
+  })
+
+};
+
+App.AlertConfigProperties.Thresholds = {
+
+  OkThreshold: App.AlertConfigProperties.Threshold.extend({
+
+    badge: 'OK',
+
+    name: 'ok_threshold',
+
+    apiProperty: function () {
+      var ret = [];
+      if (this.get('showInputForValue')) {
+        ret.push('source.reporting.ok.value');
+      }
+      if (this.get('showInputForText')) {
+        ret.push('source.reporting.ok.text');
+      }
+      return ret;
+    }.property('showInputForValue', 'showInputForText')
+
+  }),
+
+  WarningThreshold: App.AlertConfigProperties.Threshold.extend({
+
+    badge: 'WARNING',
+
+    name: 'warning_threshold',
+
+    apiProperty: function () {
+      var ret = [];
+      if (this.get('showInputForValue')) {
+        ret.push('source.reporting.warning.value');
+      }
+      if (this.get('showInputForText')) {
+        ret.push('source.reporting.warning.text');
+      }
+      return ret;
+    }.property('showInputForValue', 'showInputForText')
+
+  }),
+
+  CriticalThreshold: App.AlertConfigProperties.Threshold.extend({
+
+    badge: 'CRITICAL',
+
+    name: 'critical_threshold',
+
+    apiProperty: function () {
+      var ret = [];
+      if (this.get('showInputForValue')) {
+        ret.push('source.reporting.critical.value');
+      }
+      if (this.get('showInputForText')) {
+        ret.push('source.reporting.critical.text');
+      }
+      return ret;
+    }.property('showInputForValue', 'showInputForText')
+
+  }),
+
+  /**
+   * Mixin for <code>App.AlertConfigProperties.Threshold</code>
+   * Used to validate values in percentage range (0..1]
+   * @type {Em.Mixin}
+   */
+  PercentageMixin: Em.Mixin.create({
+
+    isValid: function () {
+      var value = this.get('displayValue');
+
+      if (!value) {
+        return false;
+      }
+
+      value = ('' + value).trim();
+      if (numericUtils.getFloatDecimals(value)) {
+        return false;
+      }
+
+      value = parseFloat(value);
+
+      //do not allow float values
+      if (parseInt(value, 10) !== value) {
+        return false;
+      }
+
+      return this.get('showInputForValue') ? !isNaN(value) && value > 0 && value <= 100 : true;
+    }.property('displayValue', 'showInputForValue'),
+
+    /**
+     * Return <code>value * 100</code>
+     * @returns {string}
+     */
+    getNewValue: function () {
+      var value = this.get('value');
+      return (value && !isNaN(value)) ? (Number(value) * 100) + '' : value;
+    },
+
+    /**
+     * Return <code>displayValue / 100</code>
+     * @returns {string}
+     */
+    getNewDisplayValue: function () {
+      var displayValue = this.get('displayValue');
+      return (displayValue && !isNaN(displayValue)) ? (Number(displayValue) / 100) + '' : displayValue;
+    }
+
+  }),
+
+  /**
+   * Mixin for <code>App.AlertConfigProperties.Threshold</code>
+   * Used to validate values that should be greater than 0
+   * @type {Em.Mixin}
+   */
+  PositiveMixin: Em.Mixin.create({
+
+    isValid: function () {
+      if (!this.get('showInputForValue')) {
+        return true;
+      }
+      var value = this.get('displayValue');
+
+      if (!value) {
+        return false;
+      }
+
+      //only allow 1/10th of a second
+      if (numericUtils.getFloatDecimals(value) > 1) {
+        return false;
+      }
+
+      value = ('' + value).trim();
+      value = parseFloat(value);
+
+      return !isNaN(value) && value > 0;
+    }.property('displayValue', 'showInputForValue')
+
+  })
+
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alerts/alert_definition.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alerts/alert_definition.js b/ambari-web/app/models/alerts/alert_definition.js
new file mode 100644
index 0000000..d1310ff
--- /dev/null
+++ b/ambari-web/app/models/alerts/alert_definition.js
@@ -0,0 +1,344 @@
+/**
+ * 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 dateUtils = require('utils/date');
+
+App.AlertDefinition = DS.Model.extend({
+
+  name: DS.attr('string'),
+  label: DS.attr('string'),
+  description: DS.attr('string'),
+  service: DS.belongsTo('App.Service'),
+  serviceName: DS.attr('string'),
+  componentName: DS.attr('string'),
+  enabled: DS.attr('boolean'),
+  scope: DS.attr('string'),
+  interval: DS.attr('number'),
+  type: DS.attr('string'),
+  groups: DS.hasMany('App.AlertGroup'),
+  reporting: DS.hasMany('App.AlertReportDefinition'),
+  lastTriggered: DS.attr('number'),
+
+  //relates only to SCRIPT-type alert definition
+  location: DS.attr('string'),
+  //relates only to AGGREGATE-type alert definition
+  alertName: DS.attr('string'),
+  //relates only to WEB and METRIC types of alert definition
+  uri: DS.belongsTo('App.AlertMetricsUriDefinition'),
+  //relates only METRIC-type alert definition
+  jmx: DS.belongsTo('App.AlertMetricsSourceDefinition'),
+  ganglia: DS.belongsTo('App.AlertMetricsSourceDefinition'),
+  //relates only PORT-type alert definition
+  defaultPort: DS.attr('number'),
+  portUri: DS.attr('string'),
+
+  /**
+   * Raw data from AlertDefinition/source
+   * used to format request content for updating alert definition
+   * @type {Object}
+   */
+  rawSourceData: {},
+
+  /**
+   * Counts of alert grouped by their status
+   * Format:
+   * <code>
+   *   {
+   *    "CRITICAL": {
+   *      count: 1,
+   *      maintenanceCount: 0
+   *    },
+   *    "OK": {
+   *      count: 0,
+   *      maintenanceCount: 1
+   *    },
+   *    "UNKNOWN": {
+   *      count: 0,
+   *      maintenanceCount: 0
+   *    },
+   *    "WARNING": {
+   *      count: 1,
+   *      maintenanceCount: 1
+   *    }
+   *   }
+   * </code>
+   * @type {object}
+   */
+  summary: {},
+
+  /**
+   * Formatted timestamp for latest alert triggering for current alertDefinition
+   * @type {string}
+   */
+  lastTriggeredFormatted: function () {
+    return dateUtils.dateFormat(this.get('lastTriggered'));
+  }.property('lastTriggered'),
+
+  /**
+   * Formatted timestamp with <code>$.timeago</code>
+   * @type {string}
+   */
+  lastTriggeredAgoFormatted: function () {
+    var lastTriggered = this.get('lastTriggered');
+    return lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
+  }.property('lastTriggered'),
+
+  lastTriggeredVerboseDisplay: function () {
+    var lastTriggered = this.get('lastTriggered');
+    return Em.I18n.t('models.alert_definition.triggered.verbose').format(dateUtils.dateFormat(lastTriggered));
+  }.property('lastTriggered'),
+
+  /**
+   * Formatted timestamp in format: for 4 days
+   * @type {string}
+   */
+  lastTriggeredForFormatted: function () {
+    var lastTriggered = this.get('lastTriggered');
+    var previousSuffixAgo = $.timeago.settings.strings.suffixAgo;
+    var previousPrefixAgo = $.timeago.settings.strings.prefixAgo;
+    $.timeago.settings.strings.suffixAgo = null;
+    $.timeago.settings.strings.prefixAgo = 'for';
+    var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
+    $.timeago.settings.strings.suffixAgo = previousSuffixAgo;
+    $.timeago.settings.strings.prefixAgo = previousPrefixAgo;
+    return triggeredFor;
+  }.property('lastTriggered'),
+
+  /**
+   * Formatted displayName for <code>componentName</code>
+   * @type {String}
+   */
+  componentNameFormatted: function () {
+    return App.format.role(this.get('componentName'));
+  }.property('componentName'),
+
+  /**
+   * Status generates from child-alerts
+   * Format: OK(1)  WARN(2)  CRIT(1)  UNKN(1)
+   * If single host: show: OK/WARNING/CRITICAL/UNKNOWN
+   * If some there are no alerts with some state, this state isn't shown
+   * If no OK/WARN/CRIT/UNKN state, then show PENDING
+   * Order is equal to example
+   * @type {string}
+   */
+  status: function () {
+    var order = this.get('order'),
+        summary = this.get('summary'),
+        hostCnt = 0,
+        self = this;
+    order.forEach(function (state) {
+      hostCnt += summary[state] ? summary[state].count + summary[state].maintenanceCount : 0;
+    });
+    if (hostCnt > 1) {
+      // multiple hosts
+      return order.map(function (state) {
+        var shortState = self.get('shortState')[state];
+        var result = '';
+        result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + ' (' + summary[state].count + ')</span>' : '';
+        // add status with maintenance mode icon
+        result += summary[state].maintenanceCount ?
+        '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + ' (' + summary[state].maintenanceCount + ')</span>' : '';
+        return result;
+      }).without('').join(' ');
+    } else if (hostCnt == 1) {
+      // single host, single status
+      return order.map(function (state) {
+        var shortState = self.get('shortState')[state];
+        var result = '';
+        result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + '</span>' : '';
+        // add status with maintenance mode icon
+        result += summary[state].maintenanceCount ?
+        '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + '</span>' : '';
+        return result;
+      }).without('').join(' ');
+    } else if (hostCnt == 0) {
+      // none
+      return '<span class="alert-state-single-host label alert-state-PENDING">NONE</span>';
+    }
+    return '';
+  }.property('summary'),
+
+  latestText: function () {
+    var order = this.get('order'), summary = this.get('summary'), text = '';
+    order.forEach(function (state) {
+      var cnt = summary[state] ? summary[state].count + summary[state].maintenanceCount : 0;
+      if (cnt > 0) {
+        text = summary[state].latestText;
+      }
+    });
+    return text;
+  }.property('summary'),
+
+  isHostAlertDefinition: function () {
+    var serviceID = (this.get('service')._id === "AMBARI"),
+        component = (this.get('componentName') === "AMBARI_AGENT");
+    return serviceID && component;
+  }.property('service', 'componentName'),
+
+  typeIconClass: function () {
+    var typeIcons = this.get('typeIcons'),
+        type = this.get('type');
+    return typeIcons[type];
+  }.property('type'),
+
+  /**
+   * if this definition is in state: CRITICAL / WARNING, if true, will show up in alerts fast access popup
+   * instances with maintenance mode ON are ignored
+   * @type {boolean}
+   */
+  isCriticalOrWarning: function () {
+    return !!(this.get('summary.CRITICAL.count') || this.get('summary.WARNING.count'));
+  }.property('summary'),
+
+  /**
+   * if this definition is in state: CRIT
+   * @type {boolean}
+   */
+  isCritical: function () {
+    var summary = this.get('summary');
+    var state = 'CRITICAL';
+    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
+  }.property('summary'),
+
+  /**
+   * if this definition is in state: WARNING
+   * @type {boolean}
+   */
+  isWarning: function () {
+    var summary = this.get('summary');
+    var state = 'WARNING';
+    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
+  }.property('summary'),
+
+  /**
+   * if this definition is in state: OK
+   * @type {boolean}
+   */
+  isOK: function () {
+    var summary = this.get('summary');
+    var state = 'OK';
+    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
+  }.property('summary'),
+
+  /**
+   * if this definition is in state: OK
+   * @type {boolean}
+   */
+  isUnknown: function () {
+    var summary = this.get('summary');
+    var state = 'UNKNOWN';
+    return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount);
+  }.property('summary'),
+
+  /**
+   * For alerts we will have processes which are not typical
+   * cluster services - like Ambari-Server. This method unifies
+   * cluster services and other services into a common display-name.
+   * @see App.AlertInstance#serviceDisplayName()
+   */
+  serviceDisplayName: function () {
+    var serviceName = this.get('service.displayName');
+    if (!serviceName) {
+      serviceName = this.get('serviceName');
+      if (serviceName) {
+        serviceName = serviceName.toCapital();
+      }
+    }
+    return serviceName;
+  }.property('serviceName', 'service.displayName'),
+
+  /**
+   * List of css-classes for alert types
+   * @type {object}
+   */
+  typeIcons: {
+    'METRIC': 'icon-bolt',
+    'SCRIPT': 'icon-file-text',
+    'WEB': 'icon-globe',
+    'PORT': 'icon-signin',
+    'AGGREGATE': 'icon-plus',
+    'SERVER': 'icon-desktop'
+  },
+
+  /**
+   * Sort on load definitions by this severity order
+   */
+  severityOrder: ['CRITICAL', 'WARNING', 'OK', 'UNKNOWN', 'PENDING'],
+  order: ['OK', 'WARNING', 'CRITICAL', 'UNKNOWN'],
+
+  shortState: {
+    'CRITICAL': 'CRIT',
+    'WARNING': 'WARN',
+    'OK': 'OK',
+    'UNKNOWN': 'UNKWN',
+    'PENDING': 'NONE'
+  }
+});
+
+App.AlertDefinition.reopenClass({
+
+  /**
+   * Return function to sort list of AlertDefinitions by their status
+   * It sorts according to <code>severityOrder</code>
+   * @param {boolean} order true - DESC, false - ASC
+   * @returns {Function}
+   * @method getSortDefinitionsByStatus
+   */
+  getSortDefinitionsByStatus: function (order) {
+    return function (a, b) {
+      var a_summary = a.get('summary'),
+        b_summary = b.get('summary'),
+        st_order = a.get('severityOrder'),
+        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]].count + a_summary[st_order[i]].maintenanceCount,
+          b_v = Em.isNone(b_summary[st_order[i]]) ? 0 : b_summary[st_order[i]].count + b_summary[st_order[i]].maintenanceCount;
+        ret = b_v - a_v;
+        if (ret !== 0) {
+          break;
+        }
+      }
+      return order ? ret : -ret;
+    };
+  }
+
+});
+
+App.AlertReportDefinition = DS.Model.extend({
+  type: DS.attr('string'),
+  text: DS.attr('string'),
+  value: DS.attr('number')
+});
+
+App.AlertMetricsSourceDefinition = DS.Model.extend({
+  propertyList: [],
+  value: DS.attr('string')
+});
+
+App.AlertMetricsUriDefinition = DS.Model.extend({
+  http: DS.attr('string'),
+  https: DS.attr('string'),
+  httpsProperty: DS.attr('string'),
+  httpsPropertyValue: DS.attr('string')
+});
+
+App.AlertDefinition.FIXTURES = [];
+App.AlertReportDefinition.FIXTURES = [];
+App.AlertMetricsSourceDefinition.FIXTURES = [];
+App.AlertMetricsUriDefinition.FIXTURES = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alerts/alert_group.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alerts/alert_group.js b/ambari-web/app/models/alerts/alert_group.js
new file mode 100644
index 0000000..e914ed5
--- /dev/null
+++ b/ambari-web/app/models/alerts/alert_group.js
@@ -0,0 +1,79 @@
+/**
+ * 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');
+
+/**
+ * Represents an alert-group on the cluster.
+ * A alert group is a collection of alert definitions
+ *
+ * Alert group hierarchy is at 2 levels. For
+ * each service there is a 'Default' alert group
+ * containing all definitions , this group is read-only
+ *
+ * User can create new alert group containing alert definitions from
+ * any service.
+ */
+App.AlertGroup = DS.Model.extend({
+
+  name: DS.attr('string'),
+
+  description: DS.attr('string'),
+
+  /**
+   * Is this group default for some service
+   * @type {boolean}
+   */
+  default: DS.attr('boolean'),
+
+  /**
+   * @type {App.AlertDefinition[]}
+   */
+  definitions: DS.hasMany('App.AlertDefinition'),
+
+  /**
+   * @type {App.AlertNotification[]}
+   */
+  targets: DS.hasMany('App.AlertNotification'),
+
+  /**
+   * @type {string}
+   */
+  displayName: function () {
+    var name = this.get('name');
+    if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) {
+      var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2);
+      name = name.substring(0, middle) + "..." + name.substring(name.length - middle);
+    }
+    return this.get('default') ? (name + ' Default') : name;
+  }.property('name', 'default'),
+
+  /**
+   * @type {string}
+   */
+  displayNameDefinitions: function () {
+    return this.get('displayName') + ' (' + this.get('definitions.length') + ')';
+  }.property('displayName', 'definitions.length'),
+
+  isAddDefinitionsDisabled: function () {
+    return this.get('default');
+  }.property('default')
+});
+App.AlertGroup.FIXTURES = [];
+
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alerts/alert_instance.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alerts/alert_instance.js b/ambari-web/app/models/alerts/alert_instance.js
new file mode 100644
index 0000000..33df293
--- /dev/null
+++ b/ambari-web/app/models/alerts/alert_instance.js
@@ -0,0 +1,165 @@
+/**
+ * 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 dateUtils = require('utils/date');
+
+App.AlertInstance = DS.Model.extend({
+  id: DS.attr('number'),
+  label: DS.attr('string'),
+  definitionName: DS.attr('string'),
+  definitionId: DS.attr('number'),
+  service: DS.belongsTo('App.Service'),
+  serviceName: DS.attr('string'),
+  componentName: DS.attr('string'),
+  host: DS.belongsTo('App.Host'),
+  hostName: DS.attr('string'),
+  scope: DS.attr('string'),
+  originalTimestamp: DS.attr('number'),
+  latestTimestamp: DS.attr('number'),
+  maintenanceState: DS.attr('string'),
+  instance: DS.attr('string'),
+  state: DS.attr('string'),
+  text: DS.attr('string'),
+  notification: DS.hasMany('App.AlertNotification'),
+
+  /**
+   * Status icon markup
+   * @type {string}
+   */
+  status: function () {
+    var isMaintenanceStateOn = this.get('maintenanceState') === 'ON';
+    var state = this.get('state');
+    var stateClass = isMaintenanceStateOn ? 'PENDING' : state;
+    var shortState = this.get('shortState')[state];
+    var maintenanceIcon = isMaintenanceStateOn ? '<span class="icon-medkit"></span> ' : '';
+    return '<div class="label alert-state-single-host alert-state-' + stateClass + '">' + maintenanceIcon + shortState + '</div>';
+  }.property('state'),
+
+  /**
+   * For alerts we will have processes which are not typical
+   * cluster services - like Ambari-Server. This method unifies
+   * cluster services and other services into a common display-name.
+   * @see App.AlertDefinition#serviceDisplayName()
+   */
+  serviceDisplayName: function () {
+    var serviceName = this.get('service.displayName');
+    if (!serviceName) {
+      serviceName = this.get('serviceName');
+      if (serviceName) {
+        serviceName = serviceName.toCapital();
+      }
+    }
+    return serviceName;
+  }.property('serviceName', 'service.displayName'),
+
+  /**
+   * Formatted timestamp for latest instance triggering
+   * @type {string}
+   */
+  lastCheckedFormatted: function () {
+    return dateUtils.dateFormat(this.get('latestTimestamp'));
+  }.property('latestTimestamp'),
+
+  /**
+   * Formatted timestamp for latest instance triggering
+   * @type {string}
+   */
+  lastTriggeredFormatted: function () {
+    return dateUtils.dateFormat(this.get('originalTimestamp'));
+  }.property('originalTimestamp'),
+
+  /**
+   * Formatted timestamp with <code>$.timeago</code>
+   * @type {string}
+   */
+  lastTriggeredAgoFormatted: function () {
+    var lastTriggered = this.get('originalTimestamp');
+    return lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
+  }.property('originalTimestamp'),
+
+  lastTriggeredVerboseDisplay: function () {
+    var originalTimestamp = this.get('originalTimestamp');
+    var latestTimestamp = this.get('latestTimestamp');
+    return Em.I18n.t('models.alert_instance.tiggered.verbose').format(
+        dateUtils.dateFormat(originalTimestamp),
+        dateUtils.dateFormat(latestTimestamp));
+  }.property('originalTimestamp', 'latestTimestamp'),
+
+  /**
+   * Formatted timestamp with <code>$.timeago</code>
+   * @type {string}
+   */
+  lastTriggeredForFormatted: function () {
+    var lastTriggered = this.get('originalTimestamp');
+    var previousSuffixAgo = $.timeago.settings.strings.suffixAgo;
+    var previousPrefixAgo = $.timeago.settings.strings.prefixAgo;
+    $.timeago.settings.strings.suffixAgo = null;
+    $.timeago.settings.strings.prefixAgo = 'for';
+    var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : '';
+    $.timeago.settings.strings.suffixAgo = previousSuffixAgo;
+    $.timeago.settings.strings.prefixAgo = previousPrefixAgo;
+    return triggeredFor;
+  }.property('originalTimestamp'),
+
+  /**
+  * escaped '<' and '>' special characters.
+  * @type {string}
+  */  
+  escapeSpecialCharactersFromTooltip: function () {
+    var displayedText = this.get('text');
+    return  displayedText.replace(/[<>]/g, '');
+  }.property('text'),
+
+  /**
+   * Formatted lastChecked and lastTriggered timestamp
+   * @returns {string}
+   */
+  statusChangedAndLastCheckedFormatted: function () {
+    var lastCheckedFormatted = this.get('lastCheckedFormatted');
+    var lastTriggeredFormatted = this.get('lastTriggeredFormatted');
+    return Em.I18n.t('models.alert_definition.triggered.checked').format(lastTriggeredFormatted, lastCheckedFormatted);
+  }.property('lastCheckedFormatted', 'lastTriggeredFormatted'),
+
+  /**
+   * List of css-classes for alert instance status
+   * @type {object}
+   */
+  typeIcons: {
+    'DISABLED': 'icon-off'
+  },
+
+  /**
+   * Define if definition serviceName is Ambari
+   * Used in some logic in templates to distinguish definitions with Ambari serviceName
+   * @returns {boolean}
+   */
+  isAmbariServiceName: function () {
+    return this.get('serviceName') === 'AMBARI';
+  }.property('serviceName'),
+
+  shortState: {
+    'CRITICAL': 'CRIT',
+    'WARNING': 'WARN',
+    'OK': 'OK',
+    'UNKNOWN': 'UNKWN',
+    'PENDING': 'NONE'
+  }
+});
+
+App.AlertInstance.FIXTURES = [];

http://git-wip-us.apache.org/repos/asf/ambari/blob/66d0b6e2/ambari-web/app/models/alerts/alert_instance_local.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/alerts/alert_instance_local.js b/ambari-web/app/models/alerts/alert_instance_local.js
new file mode 100644
index 0000000..be34565
--- /dev/null
+++ b/ambari-web/app/models/alerts/alert_instance_local.js
@@ -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.
+ */
+
+var App = require('app');
+
+/**
+ * Just a copy of <code>App.AlertInstance</code>, used to save separately alert instances for host and definition
+ * Sometimes alert instances can't be stored in the one model. Example: user is in the definition details page and open
+ * alerts popup. Different lists of instances should be shown and loaded. Also it's pretty hard to determine, which
+ * models should be removed from store, when user navigates in the popup or just leave the page
+ * @type {DS.Model}
+ */
+App.AlertInstanceLocal = App.AlertInstance.extend({});
+
+App.AlertInstanceLocal.FIXTURES = [];
\ No newline at end of file