Repository: ambari Updated Branches: refs/heads/trunk 0af0d9b3c -> 6fc532bc4
AMBARI-8072. Alerts UI: Summary Page. Create basic table with mock data. (onechiporenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/6fc532bc Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/6fc532bc Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/6fc532bc Branch: refs/heads/trunk Commit: 6fc532bc4a7cab0a667229be68491c9dc83e9dc8 Parents: 0af0d9b Author: Oleg Nechiporenko <onechipore...@apache.org> Authored: Fri Oct 31 19:28:29 2014 +0200 Committer: Oleg Nechiporenko <onechipore...@apache.org> Committed: Fri Oct 31 19:28:29 2014 +0200 ---------------------------------------------------------------------- ambari-web/app/controllers.js | 1 + .../main/alert_definitions_controller.js | 108 ++++++++ .../app/mappers/alert_definitions_mapper.js | 3 + ambari-web/app/messages.js | 4 + ambari-web/app/models/alertDefinition.js | 13 +- ambari-web/app/models/alert_instance.js | 92 ++++++- ambari-web/app/routes/main.js | 6 +- ambari-web/app/styles/alerts.less | 87 +++++++ ambari-web/app/styles/application.less | 11 +- ambari-web/app/styles/common.less | 29 +++ ambari-web/app/templates/main/alerts.hbs | 61 ++++- ambari-web/app/utils/data_manipulation.js | 97 ++++++++ ambari-web/app/views.js | 2 +- ambari-web/app/views/common/filter_view.js | 3 +- .../app/views/main/alert_definitions_view.js | 244 +++++++++++++++++++ ambari-web/test/utils/data_manipulation_test.js | 89 +++++++ 16 files changed, 823 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/controllers.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers.js b/ambari-web/app/controllers.js index aeec3ee..1690f94 100644 --- a/ambari-web/app/controllers.js +++ b/ambari-web/app/controllers.js @@ -66,6 +66,7 @@ require('controllers/main/admin/security/add/step3'); require('controllers/main/admin/security/add/step4'); require('controllers/main/admin/authentication'); require('controllers/main/alerts_controller'); +require('controllers/main/alert_definitions_controller'); require('controllers/main/service'); require('controllers/main/service/item'); require('controllers/main/service/info/summary'); http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/controllers/main/alert_definitions_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/alert_definitions_controller.js b/ambari-web/app/controllers/main/alert_definitions_controller.js new file mode 100644 index 0000000..d41ff91 --- /dev/null +++ b/ambari-web/app/controllers/main/alert_definitions_controller.js @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var App = require('app'); +var customDatePopup = require('/views/common/custom_date_popup'); + +App.MainAlertDefinitionsController = Em.ArrayController.extend({ + + name: 'mainAlertDefinitionsController', + + /** + * Timestamp when <code>App.alertDefinitionsMapper</code> run last time + * Current <code>content</code> is updated on when it changed + * @type {number|null} + */ + mapperTimestamp: null, + + /** + * List of all <code>App.AlertDefinition</code> + * Consists of: + * <ul> + * <li>App.PortAlertDefinition</li> + * <li>App.MetricsAlertDefinition</li> + * <li>App.WebAlertDefinition</li> + * <li>App.AggregateAlertDefinition</li> + * <li>App.ScriptAlertDefinition</li> + * </ul> + * @type {App.AlertDefinition[]} + */ + content: function() { + return Array.prototype.concat.call(Array.prototype, App.PortAlertDefinition.find().toArray(), + App.MetricsAlertDefinition.find().toArray(), + App.WebAlertDefinition.find().toArray(), + App.AggregateAlertDefinition.find().toArray(), + App.ScriptAlertDefinition.find().toArray()); + }.property('mapperTimestamp'), + + /** + * Object for lastTriggered-filter + * @type {Em.Object} + */ + modifiedFilter: Em.Object.create({ + optionValue: 'Any', + filterModified: function () { + var time = ""; + var curTime = new Date().getTime(); + + switch (this.get('optionValue')) { + case 'Past 1 hour': + time = curTime - 3600000; + break; + case 'Past 1 Day': + time = curTime - 86400000; + break; + case 'Past 2 Days': + time = curTime - 172800000; + break; + case 'Past 7 Days': + time = curTime - 604800000; + break; + case 'Past 14 Days': + time = curTime - 1209600000; + break; + case 'Past 30 Days': + time = curTime - 2592000000; + break; + case 'Custom': + case 'Custom2': + customDatePopup.showCustomDatePopup(this, this.get('actualValues')); + break; + case 'Any': + time = ""; + break; + } + if (this.get('modified') !== "Custom") { + this.set("actualValues.startTime", time); + this.set("actualValues.endTime", ''); + } + }.observes('optionValue'), + cancel: function () { + this.set('optionValue', 'Any'); + }, + actualValues: Em.Object.create({ + startTime: "", + endTime: "" + }) + }), + + toggleState: Em.K, + + gotoAlertDetails: Em.K + +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/mappers/alert_definitions_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/alert_definitions_mapper.js b/ambari-web/app/mappers/alert_definitions_mapper.js index b75c195..196c649 100644 --- a/ambari-web/app/mappers/alert_definitions_mapper.js +++ b/ambari-web/app/mappers/alert_definitions_mapper.js @@ -152,6 +152,9 @@ App.alertDefinitionsMapper = App.QuickDataMapper.create({ App.store.loadMany(App.WebAlertDefinition, webAlertDefinitions); App.store.loadMany(App.AggregateAlertDefinition, aggregateAlertDefinitions); App.store.loadMany(App.ScriptAlertDefinition, scriptAlertDefinitions); + if (App.router.get('mainAlertDefinitionsController')) { + App.router.set('mainAlertDefinitionsController.mapperTimestamp', (new Date()).getTime()); + } } }, http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index 65a0260..9e854be 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -819,6 +819,10 @@ Em.I18n.translations = { 'form.validator.configGroupName':'Invalid Group Name. Only alphanumerics, hyphens, spaces and underscores are allowed.', 'form.validator.configKey.specific':'"{0}" is invalid Key. Only alphanumerics, hyphens, underscores, asterisks and periods are allowed.', + 'alerts.table.noAlerts': 'No Alerts to display', + 'alerts.table.header.lastTriggered': 'Last Triggered', + 'alerts.filters.filteredAlertsInfo': '{0} of {1} alerts showing', + 'admin.advanced.caution':'This section is for advanced user only.<br/>Proceed with caution.', 'admin.advanced.button.uninstallIncludingData':'Uninstall cluster including all data.', 'admin.advanced.button.uninstallKeepData':'Uninstall cluster but keep data.', http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/models/alertDefinition.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alertDefinition.js b/ambari-web/app/models/alertDefinition.js index e5c18ab..e5441ea 100644 --- a/ambari-web/app/models/alertDefinition.js +++ b/ambari-web/app/models/alertDefinition.js @@ -17,6 +17,7 @@ */ var App = require('app'); +var dateUtils = require('utils/date'); App.AlertDefinition = DS.Model.extend({ @@ -28,7 +29,17 @@ App.AlertDefinition = DS.Model.extend({ scope: DS.attr('string'), interval: DS.attr('number'), type: DS.attr('string'), - reporting: DS.hasMany('App.AlertReportDefinition') + reporting: DS.hasMany('App.AlertReportDefinition'), + alerts: DS.hasMany('App.AlertInstance'), + + /** + * Formatted timestamp for latest alert triggering for current alertDefinition + * @type {string} + */ + lastTriggered: function() { + return dateUtils.dateFormat(Math.max.apply(Math, this.get('alerts').mapProperty('latestTimestamp'))); + }.property('alerts.@each.latestTimestamp') + }); App.AlertReportDefinition = DS.Model.extend({ http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/models/alert_instance.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alert_instance.js b/ambari-web/app/models/alert_instance.js index 1d6c99c..b13b952 100644 --- a/ambari-web/app/models/alert_instance.js +++ b/ambari-web/app/models/alert_instance.js @@ -21,7 +21,7 @@ var App = require('app'); App.AlertInstance = DS.Model.extend({ id: DS.attr('number'), label: DS.attr('string'), - alertDefinition: DS.attr('string'), + alertDefinition: DS.belongsTo('App.AlertDefinition'), serviceName: DS.attr('string'), componentName: DS.attr('string'), hostName: DS.attr('string'), @@ -32,4 +32,92 @@ App.AlertInstance = DS.Model.extend({ instance: DS.attr('string'), state: DS.attr('string'), text: DS.attr('string') -}); \ No newline at end of file +}); + +App.AlertInstance.FIXTURES = [ + { + "id": 1, + "cluster_name": "tdk", + "component_name": "SECONDARY_NAMENODE", + "host_name": "tr-2.c.pramod-thangali.internal", + "instance": null, + "label": "Secondary NameNode Process", + "latest_timestamp": 1414664775337, + "maintenance_state": "OFF", + "name": "secondary_namenode_process", + "original_timestamp": 1414585335334, + "scope": "ANY", + "service_name": "HDFS", + "state": "CRITICAL", + "text": "Connection failed: [Errno 111] Connection refused on host tr-2.c.pramod-thangali.internal:50090", + "alert_definition": 1 + }, + { + "cluster_name" : "tdk", + "component_name" : "DATANODE", + "host_name" : "tr-3.c.pramod-thangali.internal", + "id" : 2, + "instance" : null, + "label" : "DataNode Web UI", + "latest_timestamp" : 1414666905645, + "maintenance_state" : "OFF", + "name" : "datanode_webui", + "original_timestamp" : 1414585365674, + "scope" : "HOST", + "service_name" : "HDFS", + "state" : "CRITICAL", + "text" : "Connection failed to 0.0.0.0:50075", + "alert_definition": 2 + }, + { + "cluster_name": "tdk", + "component_name": "ZOOKEEPER_SERVER", + "host_name": "tr-1.c.pramod-thangali.internal", + "id": 3, + "instance": null, + "label": "ZooKeeper Server Process", + "latest_timestamp": 1414665174611, + "maintenance_state": "OFF", + "name": "zookeeper_server_process", + "original_timestamp": 1414585014606, + "scope": "ANY", + "service_name": "ZOOKEEPER", + "state": "CRITICAL", + "text": "TCP OK - 0.0000 response on port 2181", + "alert_definition": 3 + }, + { + "cluster_name": "tdk", + "component_name": "ZOOKEEPER_SERVER", + "host_name": "tr-2.c.pramod-thangali.internal", + "id": 4, + "instance": null, + "label": "ZooKeeper Server Process", + "latest_timestamp": 1414665135341, + "maintenance_state": "OFF", + "name": "zookeeper_server_process", + "original_timestamp": 1414585035316, + "scope": "ANY", + "service_name": "ZOOKEEPER", + "state": "OK", + "text": "TCP OK - 0.0000 response on port 2181", + "alert_definition": 3 + }, + { + "cluster_name": "tdk", + "component_name": "ZOOKEEPER_SERVER", + "host_name": "tr-3.c.pramod-thangali.internal", + "id": 5, + "instance": null, + "label": "ZooKeeper Server Process", + "latest_timestamp": 1414665165640, + "maintenance_state": "OFF", + "name": "zookeeper_server_process", + "original_timestamp": 1414585065616, + "scope": "ANY", + "service_name": "ZOOKEEPER", + "state": "OK", + "text": "TCP OK - 0.0000 response on port 2181", + "alert_definition": 3 + } +]; http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/routes/main.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/routes/main.js b/ambari-web/app/routes/main.js index 41536cc..787e1db 100644 --- a/ambari-web/app/routes/main.js +++ b/ambari-web/app/routes/main.js @@ -315,10 +315,10 @@ module.exports = Em.Route.extend({ alerts: Em.Route.extend({ route: '/alerts', - index: Ember.Route.extend({ + index: Em.Route.extend({ route: '/', connectOutlets: function (router, context) { - router.get('mainController').connectOutlet('mainAlerts'); + router.get('mainController').connectOutlet('mainAlertDefinitions'); } }), @@ -327,7 +327,7 @@ module.exports = Em.Route.extend({ connectOutlets: function (router, host) { }, - index: Ember.Route.extend({ + index: Em.Route.extend({ route: '/' }) }) http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/styles/alerts.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/alerts.less b/ambari-web/app/styles/alerts.less new file mode 100644 index 0000000..d263f54 --- /dev/null +++ b/ambari-web/app/styles/alerts.less @@ -0,0 +1,87 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import 'common.less'; + +.alert-state-OK {color: @health-status-green;} +.alert-state-WARNING {color: @health-status-red;} +.alert-state-CRITICAL {color: @health-status-red;} +.alert-state-DISABLED {color: #000;} +.alert-state-UNKNOWN {color: @health-status-yellow;} + +#alerts-table { + + margin-top: 10px; + margin-bottom: 10px; + font-size: 13px\9; + + .filter-row { + th { + padding: 0px; + padding-left: 4px; + } + } + thead { + background: none repeat scroll 0 0 #F8F8F8; + th { + padding: 8px; + } + } + + a { + &.disabled { + color: #000; + } + } + + .col0, + td:first-child, + th:first-child { + width: 30%; + } + + .col1, + td:first-child + td, + th:first-child + th{ + width: 18%; + .filter-input-width{ + width:90%; + } + } + + .col2, + td:first-child + td + td, + th:first-child + th + th{ + width: 17%; + .filter-input-width{ + width:90%; + } + } + .col3, + td:first-child + td + td + td, + th:first-child + th + th + th { + width: 25% + } + + .col4, + td:first-child + td + td + td + td, + th:first-child + th + th + th + th{ + width: 10%; + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/styles/application.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less index 8d2bd6f..d1ecd1f 100644 --- a/ambari-web/app/styles/application.less +++ b/ambari-web/app/styles/application.less @@ -16,6 +16,7 @@ * limitations under the License. */ +@import 'common.less'; @space-s: 5px; @space-m: 10px; @@ -493,16 +494,6 @@ h1 { .show { visibility: visible; } -/************************************************************************ -* Health status(service/host/host component health)icon colors -***********************************************************************/ -@health-status-red: red; -@health-status-green: #5AB400; -@health-status-yellow: #FFD13D; -@health-status-orange: #FF8E00; -/************************************************************************ -* Health status(service/host/host component health)icon colors ends -***********************************************************************/ /*************** * Ambari wide icon colors http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/styles/common.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/common.less b/ambari-web/app/styles/common.less new file mode 100644 index 0000000..e003989 --- /dev/null +++ b/ambari-web/app/styles/common.less @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/************************************************************************ +* Health status(service/host/host component health)icon colors +***********************************************************************/ +@health-status-red: red; +@health-status-green: #5AB400; +@health-status-yellow: #FFD13D; +@health-status-orange: #FF8E00; +/************************************************************************ +* Health status(service/host/host component health)icon colors ends +***********************************************************************/ + http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/templates/main/alerts.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/alerts.hbs b/ambari-web/app/templates/main/alerts.hbs index d4120ed..5e71eb6 100644 --- a/ambari-web/app/templates/main/alerts.hbs +++ b/ambari-web/app/templates/main/alerts.hbs @@ -19,20 +19,65 @@ <div id="alerts"> <div class="box-header row"> </div> - <table class="table advanced-header-table table-bordered table-striped" id=""> + <table class="table advanced-header-table table-bordered table-striped" id="alerts-table"> <thead> - <tr class="filter-row"> - </tr> + {{#view view.sortView classNames="label-row" contentBinding="view.filteredContent"}} + {{view view.parentView.nameSort class="first"}} + {{view view.parentView.statusSort}} + {{view view.parentView.serviceSort}} + {{view view.parentView.lastTriggeredSort}} + <th><span class="icon-off"></span></th> + {{/view}} + <tr class="filter-row"> + <th class="first">{{view view.nameFilterView}}</th> + <th>{{view view.stateFilterView}}</th> + <th>{{view view.serviceFilterView}}</th> + <th>{{view view.triggeredFilterView}}</th> + <th></th> + </tr> </thead> <tbody> + {{#if view.pageContent}} + {{#each alertDefinition in view.pageContent}} + {{#view view.AlertDefinitionView contentBinding="alertDefinition"}} + <td class="first"> + <a href="#" {{action "gotoAlertDetails" alertDefinition target="controller"}}>{{alertDefinition.label}}</a> + </td> + <td>{{{view.status}}}</td> + <td>{{alertDefinition.service.serviceName}}</td> + <td>{{alertDefinition.lastTriggered}}</td> + <td class="last"> + <a href="#" {{action "toggleState" alertDefinition target="controller"}} {{bindAttr class="alertDefinition.enabled:enabled:disabled"}}> + <span class="icon-off"></span> + </a> + </td> + {{/view}} + {{/each}} + {{else}} + <tr> + <td class="first"></td> + <td colspan="4"> + {{t alerts.table.noAlerts}} + </td> + </tr> + {{/if}} </tbody> </table> - <div> - <div class="spinner"></div> - </div> - <div class="page-bar"> + <div class="filtered-info span4"> + <label>{{view.filteredContentInfo}} - <a {{action clearFilters target="view"}} + href="#">{{t tableView.filters.clearAllFilters}}</a></label> + </div> + <div class="selected-hosts-info span4"> + </div> + <div class="items-on-page"> + <label>{{t common.show}}: {{view view.rowsPerPageSelectView selectionBinding="view.displayLength"}}</label> + </div> + <div class="info">{{view.paginationInfo}}</div> + <div class="paging_two_button"> + <a {{bindAttr class="view.paginationLeftClass"}}{{action previousPage target="view"}}><i class="icon-arrow-left"></i></a> + <a {{bindAttr class="view.paginationRightClass"}}{{action nextPage target="view"}}><i class="icon-arrow-right"></i></a> + </div> </div> </div> - http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/utils/data_manipulation.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/data_manipulation.js b/ambari-web/app/utils/data_manipulation.js new file mode 100644 index 0000000..7aab73b --- /dev/null +++ b/ambari-web/app/utils/data_manipulation.js @@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + + /** + * Exclude objects from <code>collection</code> if its <code>key</code> exist in <code>valuesToReject</code> + * Example: + * <code> + * var collection = [{n: 'v1'}, {n: 'v2'}, {n: 'v3'}, {n: 'v4'}], + * key = 'n', + * valuesToReject = ['v2', 'v3']; + * var result = rejectPropertyValues(collection, key, valuesToReject); // [{n: 'v1'}, {n: 'v4'}] + * </code> + * @param {Object[]} collection array of objects + * @param {String} key property name of each object to be checked + * @param {String[]} valuesToReject list of <code>key</code> values. Objects with <code>key</code>, + * equal to one of them, will be excluded + * @returns {Object[]} Rejected array + */ + rejectPropertyValues: function(collection, key, valuesToReject) { + return collection.filter(function (item) { + var propertyValue = Em.get(item, key); + return valuesToReject.contains(propertyValue); + }); + }, + + /** + * Wrapper to <code>filterProperty</code>-method that allows using list of values to filter + * Example: + * <code> + * var collection = [{n: 'v1'}, {n: 'v2'}, {n: 'v3'}, {n: 'v4'}], + * key = 'n', + * valuesToFilter = ['v2', 'v3']; + * var result = filterPropertyValues(collection, key, valuesToFilter); // [{n: 'v2'}, {n: 'v3'}] + * </code> + * @param {Object[]} collection array of objects + * @param {String} key property name of each object to be filtered + * @param {String|String[]} valuesToFilter array of values (or one value-string) to filter + * @returns {Object[]} Filtered array + */ + filterPropertyValues: function(collection, key, valuesToFilter) { + var type = Em.typeOf(valuesToFilter); + if ('string' === type) + return collection.filterProperty(key, valuesToFilter); + + var ret = []; + if ('array' === type) { + valuesToFilter.forEach(function (value) { + ret = ret.concat(collection.filterProperty(key, value)); + }); + } + return ret; + }, + + /** + * Group <code>collection</code> items by <code>key</code> value + * Example: + * <code> + * var collection = [{n: 'v1'}, {n: 'v2'}, {n: 'v2'}, {n: 'v4'}], + * key = 'n'; + * var result = groupPropertyValues(collection, key); // {v1: [{n: 'v1'}], v2: [{n: 'v2'}, {n: 'v2'}], v4: [{n: 'v4'}]} + * </code> + * @param {Object[]} collection array of objects + * @param {String} key property name of each object to be grouped + * @returns {*} + */ + groupPropertyValues: function(collection, key) { + var group = {}; + collection.forEach(function(item) { + var value = Em.get(item, key); + if (Em.isNone(group[value])) { + group[value] = [item]; + } + else { + group[value].pushObject(item); + } + }); + return group; + } + +}; http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/views.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js index 736b4ca..7990f5e 100644 --- a/ambari-web/app/views.js +++ b/ambari-web/app/views.js @@ -41,7 +41,7 @@ require('views/common/table_view'); require('views/login'); require('views/main'); require('views/main/menu'); -require('views/main/alerts'); +require('views/main/alert_definitions_view'); require('views/main/charts'); require('views/main/views/details'); require('views/main/host'); http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/views/common/filter_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/filter_view.js b/ambari-web/app/views/common/filter_view.js index a730a05..6e28209 100644 --- a/ambari-web/app/views/common/filter_view.js +++ b/ambari-web/app/views/common/filter_view.js @@ -569,8 +569,7 @@ module.exports = { break; case 'select': return function (origin, compareValue){ - //TODO add filter by select value - return true; + return origin == compareValue; }; break; case 'range': http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/app/views/main/alert_definitions_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/alert_definitions_view.js b/ambari-web/app/views/main/alert_definitions_view.js new file mode 100644 index 0000000..22272ea --- /dev/null +++ b/ambari-web/app/views/main/alert_definitions_view.js @@ -0,0 +1,244 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var App = require('app'); +var filters = require('views/common/filter_view'), + sort = require('views/common/sort_view'), + date = require('utils/date'), + dataUtils = require('utils/data_manipulation'); + +App.MainAlertDefinitionsView = App.TableView.extend({ + + templateName: require('templates/main/alerts'), + + content: function() { + return this.get('controller.content'); + }.property('controller.content.@each'), + + /** + * @type {number} + */ + totalCount: function () { + return this.get('content.length'); + }.property('content.length'), + + colPropAssoc: ['', 'label', 'state', 'service.serviceName', 'lastTriggered'], + + /** + * List of css-classes for alert types + * @type {object} + */ + typeIcons: { + 'OK': 'icon-ok-sign', + 'WARNING': 'icon-warning-sign', + 'CRITICAL': 'icon-remove', + 'DISABLED': 'icon-off', + 'UNKNOWN': 'icon-question-sign' + }, + + sortView: sort.wrapperView, + + /** + * Sorting header for <label>alertDefinition.label</label> + * @type {Em.View} + */ + nameSort: sort.fieldView.extend({ + column: 1, + name: 'label', + displayName: Em.I18n.t('common.name') + }), + + /** + * Sorting header for <label>alertDefinition.status</label> + * @type {Em.View} + */ + statusSort: sort.fieldView.extend({ + column: 2, + name: 'status', + displayName: Em.I18n.t('common.status'), + type: 'string' + }), + + /** + * Sorting header for <label>alertDefinition.service.serviceName</label> + * @type {Em.View} + */ + serviceSort: sort.fieldView.extend({ + column: 3, + name: 'service.serviceName', + displayName: Em.I18n.t('common.service'), + type: 'string' + }), + + /** + * Sorting header for <label>alertDefinition.lastTriggeredSort</label> + * @type {Em.View} + */ + lastTriggeredSort: sort.fieldView.extend({ + column: 4, + name: 'memory', + displayName: Em.I18n.t('alerts.table.header.lastTriggered'), + type: 'date' + }), + + /** + * Filtering header for <label>alertDefinition.label</label> + * @type {Em.View} + */ + nameFilterView: filters.createTextView({ + column: 1, + fieldType: 'filter-input-width', + onChangeValue: function(){ + this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'string'); + } + }), + + /** + * Filtering header for <label>alertDefinition.status</label> + * @type {Em.View} + */ + stateFilterView: filters.createSelectView({ + column: 2, + fieldType: 'filter-input-width', + content: ['All', 'OK', 'WARNING', 'CRITICAL', 'DISABLED', 'UNKNOWN'], + onChangeValue: function () { + this.get('parentView').updateFilter(this.get('column'), this.get('actualValue'), 'select'); + }, + emptyValue: Em.I18n.t('common.all') + }), + + /** + * Filtering header for <label>alertDefinition.service.serviceName</label> + * @type {Em.View} + */ + serviceFilterView: filters.createSelectView({ + column: 3, + fieldType: 'filter-input-width', + content: function () { + return ['All'].concat(App.Service.find().mapProperty('serviceName')); + }.property('App.router.clusterController.isLoaded'), + onChangeValue: function () { + this.get('parentView').updateFilter(this.get('column'), this.get('actualValue'), 'select'); + }, + emptyValue: Em.I18n.t('common.all') + }), + + /** + * Filtering header for <label>alertDefinition.lastTriggered</label> + * @type {Em.View} + */ + triggeredFilterView: filters.createSelectView({ + column: 4, + triggeredOnSameValue: [ + { + values: ['Custom', 'Custom2'], + displayAs: 'Custom' + } + ], + appliedEmptyValue: ["", ""], + fieldType: 'filter-input-width,modified-filter', + content: ['Any', 'Past 1 hour', 'Past 1 Day', 'Past 2 Days', 'Past 7 Days', 'Past 14 Days', 'Past 30 Days', 'Custom', 'Custom2'], + valueBinding: "controller.modifiedFilter.optionValue", + startTimeBinding: "controller.modifiedFilter.actualValues.startTime", + endTimeBinding: "controller.modifiedFilter.actualValues.endTime", + onTimeChange: function () { + this.get('parentView').updateFilter(this.get('column'), [this.get('controller.modifiedFilter.actualValues.startTime'), this.get('controller.modifiedFilter.actualValues.endTime')], 'range'); + }.observes('controller.modifiedFilter.actualValues.startTime', 'controller.modifiedFilter.actualValues.endTime') + }), + + /** + * Filtered number of all content number information displayed on the page footer bar + * @returns {String} + */ + filteredContentInfo: function () { + return this.t('alerts.filters.filteredAlertsInfo').format(this.get('filteredCount'), this.get('totalCount')); + }.property('filteredCount', 'totalCount'), + + /** + * Determines how display "back"-link - as link or text + * @type {string} + */ + paginationLeftClass: function () { + if (this.get("startIndex") > 1) { + return "paginate_previous"; + } + return "paginate_disabled_previous"; + }.property("startIndex", 'filteredCount'), + + /** + * Determines how display "next"-link - as link or text + * @type {string} + */ + paginationRightClass: function () { + if ((this.get("endIndex")) < this.get("filteredCount")) { + return "paginate_next"; + } + return "paginate_disabled_next"; + }.property("endIndex", 'filteredCount'), + + /** + * Show previous-page if user not in the first page + * @method previousPage + */ + previousPage: function () { + if (this.get('paginationLeftClass') === 'paginate_previous') { + this._super(); + } + }, + + /** + * Show next-page if user not in the last page + * @method nextPage + */ + nextPage: function () { + if (this.get('paginationRightClass') === 'paginate_next') { + this._super(); + } + }, + + /** + * View for each table row with <code>alertDefinition</code> + * @type {Em.View} + */ + AlertDefinitionView: Em.View.extend({ + + tagName: 'tr', + + /** + * Status generates from child-alerts + * Format: 1 OK / 2 WARN / 1 CRIT / 1 DISABLED / 1 UNKNOWN + * If some there are no alerts with some state, this state isn't shown + * Order is equal to example + * @type {string} + */ + status: function () { + var typeIcons = this.get('parentView.typeIcons'), + ordered = ['OK', 'WARNING', 'CRITICAL', 'DISABLED', 'UNKNOWN'], + grouped = dataUtils.groupPropertyValues(this.get('content.alerts'), 'state'); + return ordered.map(function (state) { + if (grouped[state]) { + return grouped[state].length + ' <span class="' + typeIcons[state] + ' alert-state-' + state + '"></span>'; + } + return null; + }).compact().join(' / '); + }.property('content.alerts.@each.status') + + }) + + +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/6fc532bc/ambari-web/test/utils/data_manipulation_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/utils/data_manipulation_test.js b/ambari-web/test/utils/data_manipulation_test.js new file mode 100644 index 0000000..16f834f --- /dev/null +++ b/ambari-web/test/utils/data_manipulation_test.js @@ -0,0 +1,89 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var data_manipulation = require('utils/data_manipulation'); + +describe('data_manipulation', function () { + + describe('#rejectPropertyValues', function () { + + it('Basic test', function () { + var collection = [ + {n: 'v1'}, + {n: 'v2'}, + {n: 'v3'}, + {n: 'v4'} + ], + key = 'n', + valuesToReject = ['v2', 'v3']; + var result = data_manipulation.rejectPropertyValues(collection, key, valuesToReject); + expect(result).to.eql([ + {n: 'v1'}, + {n: 'v4'} + ]); + }); + + }); + + describe('#filterPropertyValues', function () { + + it('Basic test', function () { + var collection = [ + {n: 'v1'}, + {n: 'v2'}, + {n: 'v3'}, + {n: 'v4'} + ], + key = 'n', + valuesToFilter = ['v2', 'v3']; + var result = data_manipulation.filterPropertyValues(collection, key, valuesToFilter); + expect(result).to.eql([ + {n: 'v2'}, + {n: 'v3'} + ]); + }); + + }); + + describe('#groupPropertyValues', function () { + + it('Basic test', function () { + var collection = [ + {n: 'v1'}, + {n: 'v2'}, + {n: 'v2'}, + {n: 'v4'} + ], + key = 'n'; + var result = data_manipulation.groupPropertyValues(collection, key); + expect(result).to.eql({ + v1: [ + {n: 'v1'} + ], + v2: [ + {n: 'v2'}, + {n: 'v2'} + ], + v4: [ + {n: 'v4'} + ]}); + }); + + }); + +}); \ No newline at end of file