Repository: ambari Updated Branches: refs/heads/branch-3.0-perf d4cd91436 -> f26436798
AMBARI-21404 Integrate Hosts page with websocket events. (atkach) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/f2643679 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/f2643679 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/f2643679 Branch: refs/heads/branch-3.0-perf Commit: f2643679847f9123b3b522651987ef12f3e96aad Parents: d4cd914 Author: Andrii Tkach <[email protected]> Authored: Wed Jul 5 14:55:04 2017 +0300 Committer: Andrii Tkach <[email protected]> Committed: Wed Jul 5 14:55:04 2017 +0300 ---------------------------------------------------------------------- ambari-web/app/assets/test/tests.js | 1 + .../app/controllers/global/update_controller.js | 25 +++++++++-- ambari-web/app/mappers.js | 1 + ambari-web/app/mappers/hosts_mapper.js | 5 +-- .../socket/host_component_status_mapper.js | 10 +++-- .../app/mappers/socket/host_state_mapper.js | 37 ++++++++++++++++ ambari-web/app/messages.js | 1 + ambari-web/app/models/host.js | 12 ++++-- ambari-web/app/utils/stomp_client.js | 2 +- ambari-web/app/views/main/host/summary.js | 10 +++-- .../global/update_controller_test.js | 4 +- .../socket/host_component_status_mapper_test.js | 10 +++-- .../mappers/socket/host_state_mapper_test.js | 44 ++++++++++++++++++++ ambari-web/test/models/host_test.js | 14 +++++++ ambari-web/test/views/main/host/summary_test.js | 17 ++++++-- 15 files changed, 166 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/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 11ab702..5575627 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -170,6 +170,7 @@ var files = [ 'test/mappers/socket/alert_summary_mapper_test', 'test/mappers/socket/host_component_status_mapper_test', 'test/mappers/socket/service_state_mapper_test', + 'test/mappers/socket/host_state_mapper_test', 'test/mixins/common/configs/enhanced_configs_test', 'test/mixins/common/configs/config_recommendations_test', 'test/mixins/common/configs/config_recommendation_parser_test', http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/controllers/global/update_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/global/update_controller.js b/ambari-web/app/controllers/global/update_controller.js index 109b266..d556831 100644 --- a/ambari-web/app/controllers/global/update_controller.js +++ b/ambari-web/app/controllers/global/update_controller.js @@ -192,8 +192,9 @@ App.UpdateController = Em.Controller.extend({ App.StompClient.subscribe('/events/topologies', App.topologyMapper.map.bind(App.topologyMapper)); App.StompClient.subscribe('/events/configs', this.makeCallForClusterEnv.bind(this)); App.StompClient.subscribe('/events/services', App.serviceStateMapper.map.bind(App.serviceStateMapper)); + App.StompClient.subscribe('/events/hosts', App.hostStateMapper.map.bind(App.hostStateMapper)); - App.updater.run(this, 'updateHost', 'isWorking'); + App.updater.run(this, 'updateHostsMetrics', 'isWorking', App.contentUpdateInterval, '\/main\/(hosts).*'); App.updater.run(this, 'updateServiceMetric', 'isWorking', App.componentsUpdateInterval, '\/main\/(dashboard|services).*'); App.updater.run(this, 'updateComponentsState', 'isWorking', App.componentsUpdateInterval, '\/main\/(dashboard|services|hosts).*'); App.updater.run(this, 'graphsUpdate', 'isWorking'); @@ -212,6 +213,7 @@ App.UpdateController = Em.Controller.extend({ App.StompClient.unsubscribe('/events/topologies'); App.StompClient.unsubscribe('/events/configs'); App.StompClient.unsubscribe('/events/services'); + App.StompClient.unsubscribe('/events/hosts'); } }.observes('isWorking', 'App.router.mainAlertInstancesController.isUpdating'), @@ -226,7 +228,7 @@ App.UpdateController = Em.Controller.extend({ self = this, hostDetailsFilter = '', realUrl = '/hosts?fields=Hosts/rack_info,Hosts/host_name,Hosts/maintenance_state,Hosts/public_host_name,Hosts/cpu_count,Hosts/ph_cpu_count,Hosts/last_agent_env,' + - 'alerts_summary,Hosts/host_status,Hosts/last_heartbeat_time,Hosts/ip,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,' + + 'alerts_summary,Hosts/host_status,Hosts/host_state,Hosts/last_heartbeat_time,Hosts/ip,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,' + 'host_components/HostRoles/stale_configs,host_components/HostRoles/service_name,host_components/HostRoles/display_name,host_components/HostRoles/desired_admin_state,' + '<metrics>Hosts/total_mem<hostDetailsParams><stackVersions>&minimal_response=true', hostDetailsParams = ',Hosts/os_arch,Hosts/os_type,metrics/cpu/cpu_system,metrics/cpu/cpu_user,metrics/memory/mem_total,metrics/memory/mem_free', @@ -316,6 +318,23 @@ App.UpdateController = Em.Controller.extend({ } }, + updateHostsMetrics: function(callback) { + let queryParams = App.router.get('mainHostController').getQueryParameters(true); + if (App.router.get('currentState.parentState.name') === 'hostDetails') { + const currentHostname = App.router.get('location.lastSetURL') + .match(/\/hosts\/(.*)\/(summary|configs|alerts|stackVersions|logs)/)[1]; + queryParams = [ + { + key: 'Hosts/host_name', + value: [currentHostname], + type: 'MULTIPLE', + isHostDetails: true + } + ] + } + this.loadHostsMetric(queryParams).always(callback); + }, + /** * * @param {Array} queryParams @@ -353,7 +372,7 @@ App.UpdateController = Em.Controller.extend({ success: 'loadHostsMetricSuccessCallback' }); } - return null; + return $.Deferred().resolve().promise(); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/mappers.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers.js b/ambari-web/app/mappers.js index aca5291..f4c7e5c 100644 --- a/ambari-web/app/mappers.js +++ b/ambari-web/app/mappers.js @@ -49,3 +49,4 @@ require('mappers/socket/topology_mapper'); require('mappers/socket/service_state_mapper'); require('mappers/socket/host_component_status_mapper'); require('mappers/socket/alert_summary_mapper'); +require('mappers/socket/host_state_mapper'); http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/mappers/hosts_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/hosts_mapper.js b/ambari-web/app/mappers/hosts_mapper.js index 203cd67..545f312 100644 --- a/ambari-web/app/mappers/hosts_mapper.js +++ b/ambari-web/app/mappers/hosts_mapper.js @@ -43,6 +43,7 @@ App.hostsMapper = App.QuickDataMapper.create({ disk_total: 'metrics.disk.disk_total', disk_free: 'metrics.disk.disk_free', health_status: 'Hosts.host_status', + state: 'Hosts.host_state', load_one: 'metrics.load.load_one', load_five: 'metrics.load.load_five', load_fifteen: 'metrics.load.load_fifteen', @@ -51,7 +52,6 @@ App.hostsMapper = App.QuickDataMapper.create({ mem_total: 'metrics.memory.mem_total', mem_free: 'metrics.memory.mem_free', last_heart_beat_time: "Hosts.last_heartbeat_time", - raw_last_heart_beat_time: "Hosts.last_heartbeat_time", os_arch: 'Hosts.os_arch', os_type: 'Hosts.os_type', ip: 'Hosts.ip', @@ -177,8 +177,6 @@ App.hostsMapper = App.QuickDataMapper.create({ stackVersions.push(this.parseIt(stackVersion, this.stackVersionConfig)); } - var alertsSummary = item.alerts_summary; - item.critical_warning_alerts_count = alertsSummary ? (alertsSummary.CRITICAL || 0) + (alertsSummary.WARNING || 0) : 0; item.cluster_id = clusterName; var existingHost = hostsMap[item.Hosts.host_name]; // There is no need to override existing index in host detail view since old model(already have indexes) will not be cleared. @@ -193,7 +191,6 @@ App.hostsMapper = App.QuickDataMapper.create({ }); var parsedItem = this.parseIt(item, this.config); - parsedItem.last_heart_beat_time = App.dateTimeWithTimeZone(parsedItem.last_heart_beat_time); parsedItem.selected = selectedHosts.contains(parsedItem.host_name); parsedItem.not_started_components = notStartedComponents; parsedItem.components_in_passive_state = componentsInPassiveState; http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/mappers/socket/host_component_status_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/socket/host_component_status_mapper.js b/ambari-web/app/mappers/socket/host_component_status_mapper.js index 6069cbc..0d253d4 100644 --- a/ambari-web/app/mappers/socket/host_component_status_mapper.js +++ b/ambari-web/app/mappers/socket/host_component_status_mapper.js @@ -23,9 +23,11 @@ App.hostComponentStatusMapper = App.QuickDataMapper.create({ * @param {object} event */ map: function (event) { - const hostComponent = App.HostComponent.find(event.componentName + '_' + event.hostName); - if (hostComponent.get('isLoaded')) { - hostComponent.set('workStatus', event.currentState); - } + event.hostComponents.forEach((componentState) => { + const hostComponent = App.HostComponent.find(componentState.componentName + '_' + componentState.hostName); + if (hostComponent.get('isLoaded')) { + hostComponent.set('workStatus', componentState.currentState); + } + }); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/mappers/socket/host_state_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/socket/host_state_mapper.js b/ambari-web/app/mappers/socket/host_state_mapper.js new file mode 100644 index 0000000..7d469d7 --- /dev/null +++ b/ambari-web/app/mappers/socket/host_state_mapper.js @@ -0,0 +1,37 @@ +/** + * 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.hostStateMapper = App.QuickDataMapper.create({ + + config: { + alertsSummary: 'alerts_summary', + healthStatus: 'host_status', + state: 'host_state', + lastHeartBeatTime: 'last_heartbeat_time', + passiveState: 'maintenance_state' + }, + + /** + * @param {object} event + */ + map: function(event) { + //TODO event should have properties named in CamelCase format + this.updatePropertiesByConfig(App.Host.find(event.host_name), event, this.config); + } +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index 8f8d981..f76821d 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -350,6 +350,7 @@ Em.I18n.translations = { 'common.hostOrdered': 'Host Ordered', 'common.rolling.downgrade': 'Rolling Downgrade', 'common.express.downgrade': 'Express Downgrade', + 'common.minute.ago': 'less than a minute ago', 'models.alert_instance.tiggered.verbose': "Occurred on {0} <br> Checked on {1}", 'models.alert_definition.triggered.verbose': "Occurred on {0}", http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/models/host.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/host.js b/ambari-web/app/models/host.js index 58ffd41..4116cc3 100644 --- a/ambari-web/app/models/host.js +++ b/ambari-web/app/models/host.js @@ -37,8 +37,8 @@ App.Host = DS.Model.extend({ ip: DS.attr('string'), rack: DS.attr('string'), healthStatus: DS.attr('string'), + state: DS.attr('string'), lastHeartBeatTime: DS.attr('number'), - rawLastHeartBeatTime: DS.attr('number'), hasJcePolicy: DS.attr('string'), osType: DS.attr("string"), diskInfo: DS.attr('object'), @@ -49,7 +49,6 @@ App.Host = DS.Model.extend({ memFree:DS.attr('number'), cpuSystem:DS.attr('number'), cpuUser:DS.attr('number'), - criticalWarningAlertsCount: DS.attr('number'), alertsSummary: DS.attr('object'), passiveState: DS.attr('string'), index: DS.attr('number'), @@ -60,6 +59,11 @@ App.Host = DS.Model.extend({ */ selected:DS.attr('boolean'), + criticalWarningAlertsCount: function() { + const alertsSummary = this.get('alertsSummary'); + return alertsSummary ? (alertsSummary.CRITICAL || 0) + (alertsSummary.WARNING || 0) : 0; + }.property('alertsSummary.CRITICAL', 'alertsSummary.WARNING'), + currentVersion: function () { var current = this.get('stackVersions').findProperty('isCurrent'); return current ? current.get('repoVersion') : null; @@ -178,8 +182,8 @@ App.Host = DS.Model.extend({ * @returns {bool} */ isNotHeartBeating : function() { - return (App.get('testMode')) ? false : (this.get('healthStatus') === "UNKNOWN"); - }.property('lastHeartBeatTime'), + return this.get('state') === "HEARTBEAT_LOST"; + }.property('state'), /** * Average load http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/utils/stomp_client.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/stomp_client.js b/ambari-web/app/utils/stomp_client.js index 11a62b7..18167f6 100644 --- a/ambari-web/app/utils/stomp_client.js +++ b/ambari-web/app/utils/stomp_client.js @@ -156,7 +156,7 @@ module.exports = Em.Object.extend({ const handlers = { default: handler }; - if (!this.get('client.connected') || this.get('subscriptions')[destination]) { + if (!this.get('client.connected')) { return null; } const subscription = this.get('client').subscribe(destination, (message) => { http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/app/views/main/host/summary.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/host/summary.js b/ambari-web/app/views/main/host/summary.js index 89332de..e1e4313 100644 --- a/ambari-web/app/views/main/host/summary.js +++ b/ambari-web/app/views/main/host/summary.js @@ -297,9 +297,13 @@ App.MainHostSummaryView = Em.View.extend(App.TimeRangeMixin, { * @type {String} */ timeSinceHeartBeat: function () { - var d = this.get('content.rawLastHeartBeatTime'); - return d ? $.timeago(d) : ''; - }.property('content.rawLastHeartBeatTime'), + if (this.get('content.isNotHeartBeating')) { + const d = this.get('content.lastHeartBeatTime'); + return d ? $.timeago(d) : ''; + } + //when host hasn't lost heartbeat we assume that last heartbeat was a minute ago + return Em.I18n.t('common.minute.ago'); + }.property('content.lastHeartBeatTime', 'content.isNotHeartBeating'), /** * Get clients with custom commands http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/test/controllers/global/update_controller_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/global/update_controller_test.js b/ambari-web/test/controllers/global/update_controller_test.js index c793750..44b6c36 100644 --- a/ambari-web/test/controllers/global/update_controller_test.js +++ b/ambari-web/test/controllers/global/update_controller_test.js @@ -74,6 +74,7 @@ describe('App.UpdateController', function () { expect(App.StompClient.unsubscribe.calledWith('/events/topologies')).to.be.true; expect(App.StompClient.unsubscribe.calledWith('/events/configs')).to.be.true; expect(App.StompClient.unsubscribe.calledWith('/events/services')).to.be.true; + expect(App.StompClient.unsubscribe.calledWith('/events/hosts')).to.be.true; }); it('isWorking = true', function () { @@ -84,6 +85,7 @@ describe('App.UpdateController', function () { expect(App.StompClient.subscribe.calledWith('/events/topologies')).to.be.true; expect(App.StompClient.subscribe.calledWith('/events/configs')).to.be.true; expect(App.StompClient.subscribe.calledWith('/events/services')).to.be.true; + expect(App.StompClient.subscribe.calledWith('/events/hosts')).to.be.true; }); }); @@ -290,7 +292,7 @@ describe('App.UpdateController', function () { }); it("AMBARI_METRICS is not started", function () { this.mock.returns(Em.Object.create({isStarted: false})); - expect(controller.loadHostsMetric([])).to.be.null; + expect(controller.loadHostsMetric([])).to.be.object; var args = testHelpers.findAjaxRequest('name', 'hosts.metrics.lazy_load'); expect(args).to.not.exists; }); http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/test/mappers/socket/host_component_status_mapper_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mappers/socket/host_component_status_mapper_test.js b/ambari-web/test/mappers/socket/host_component_status_mapper_test.js index 0de9846..eab309e 100644 --- a/ambari-web/test/mappers/socket/host_component_status_mapper_test.js +++ b/ambari-web/test/mappers/socket/host_component_status_mapper_test.js @@ -36,9 +36,13 @@ describe('App.hostComponentStatusMapper', function () { it('host-component should have STARTED status', function() { const event = { - componentName: 'C1', - hostName: 'host1', - currentState: 'STARTED' + hostComponents: [ + { + componentName: 'C1', + hostName: 'host1', + currentState: 'STARTED' + } + ] }; App.hostComponentStatusMapper.map(event); expect(hc.get('workStatus')).to.be.equal('STARTED'); http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/test/mappers/socket/host_state_mapper_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mappers/socket/host_state_mapper_test.js b/ambari-web/test/mappers/socket/host_state_mapper_test.js new file mode 100644 index 0000000..614015c --- /dev/null +++ b/ambari-web/test/mappers/socket/host_state_mapper_test.js @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var App = require('app'); + +require('mappers/socket/host_state_mapper'); + +describe('App.hostStateMapper', function () { + + describe('#map', function() { + beforeEach(function() { + sinon.stub(App.Host, 'find'); + sinon.stub(App.hostStateMapper, 'updatePropertiesByConfig'); + }); + afterEach(function() { + App.Host.find.restore(); + App.hostStateMapper.updatePropertiesByConfig.restore(); + }); + + it('updatePropertiesByConfig should be called', function() { + const event = { + host_name: 'S1', + state: 'HEARTBEAT_LOST' + }; + App.hostStateMapper.map(event); + expect(App.hostStateMapper.updatePropertiesByConfig.calledOnce).to.be.true; + }); + }); +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/test/models/host_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/models/host_test.js b/ambari-web/test/models/host_test.js index 5b63b10..fc3e391 100644 --- a/ambari-web/test/models/host_test.js +++ b/ambari-web/test/models/host_test.js @@ -31,6 +31,7 @@ describe('App.Host', function () { disk_total: 100.555, disk_free: 90.555, health_status: 'HEALTHY', + state: 'HEALTHY', last_heart_beat_time: (new Date()).getTime() - 18100000 }, { @@ -40,6 +41,7 @@ describe('App.Host', function () { disk_total: 90, disk_free: 90, health_status: 'HEALTHY', + state: 'HEALTHY', last_heart_beat_time: (new Date()).getTime() - 170000 }, { @@ -49,6 +51,7 @@ describe('App.Host', function () { disk_total: 99.999, disk_free: 0, health_status: 'UNKNOWN', + state: 'HEARTBEAT_LOST', last_heart_beat_time: (new Date()).getTime() } ]; @@ -425,6 +428,17 @@ describe('App.Host', function () { }); }); + describe('#criticalWarningAlertsCount', function () { + it('should return sum of critical and warning alerts', function () { + host1.set('alertsSummary', { + CRITICAL: 1, + WARNING: 2, + OK: 0 + }); + expect(host1.get('criticalWarningAlertsCount')).to.equal(3); + }); + }); + App.TestAliases.testAsComputedGetByKey(host1, 'healthIconClass', 'healthIconClassMap', 'healthClass', {defaultValue: '', map: { 'health-status-LIVE': App.healthIconClassGreen, 'health-status-DEAD-RED': App.healthIconClassRed, http://git-wip-us.apache.org/repos/asf/ambari/blob/f2643679/ambari-web/test/views/main/host/summary_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/views/main/host/summary_test.js b/ambari-web/test/views/main/host/summary_test.js index f1c9d06..0c4cf7b 100644 --- a/ambari-web/test/views/main/host/summary_test.js +++ b/ambari-web/test/views/main/host/summary_test.js @@ -705,17 +705,26 @@ describe('App.MainHostSummaryView', function() { $.timeago.restore(); }); - it("rawLastHeartBeatTime = null", function() { - mainHostSummaryView.set('content.rawLastHeartBeatTime', null); + it("lastHeartBeatTime = null", function() { + mainHostSummaryView.set('content.isNotHeartBeating', true); + mainHostSummaryView.set('content.lastHeartBeatTime', null); mainHostSummaryView.propertyDidChange('timeSinceHeartBeat'); expect(mainHostSummaryView.get('timeSinceHeartBeat')).to.be.empty; }); - it("rawLastHeartBeatTime = 1", function() { - mainHostSummaryView.set('content.rawLastHeartBeatTime', '1'); + it("lastHeartBeatTime = 1", function() { + mainHostSummaryView.set('content.isNotHeartBeating', true); + mainHostSummaryView.set('content.lastHeartBeatTime', '1'); mainHostSummaryView.propertyDidChange('timeSinceHeartBeat'); expect(mainHostSummaryView.get('timeSinceHeartBeat')).to.be.equal('1'); }); + + it("host has heartbeat", function() { + mainHostSummaryView.set('content.isNotHeartBeating', false); + mainHostSummaryView.set('content.lastHeartBeatTime', '1'); + mainHostSummaryView.propertyDidChange('timeSinceHeartBeat'); + expect(mainHostSummaryView.get('timeSinceHeartBeat')).to.be.equal(Em.I18n.t('common.minute.ago')); + }); }); describe("#clientsWithCustomCommands", function () {
