You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2016/02/11 10:48:24 UTC
[1/2] ambari git commit: AMBARI-14993. Log Search: Add Host Log
Metrics to Host Details -> Summary (alexantonenko)
Repository: ambari
Updated Branches:
refs/heads/trunk b9a35f1ea -> 96bdecf84
AMBARI-14993. Log Search: Add Host Log Metrics to Host Details -> Summary (alexantonenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/96bdecf8
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/96bdecf8
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/96bdecf8
Branch: refs/heads/trunk
Commit: 96bdecf8474064465b2ecc1261152d9ba68730e2
Parents: 209ec33
Author: Alex Antonenko <hi...@gmail.com>
Authored: Wed Feb 10 16:31:52 2016 +0200
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Thu Feb 11 11:48:18 2016 +0200
----------------------------------------------------------------------
ambari-web/app/messages.js | 1 +
ambari-web/app/routes/main.js | 5 +-
.../app/templates/main/host/log_metrics.hbs | 26 ++++
ambari-web/app/templates/main/host/summary.hbs | 24 +++-
ambari-web/app/utils/ember_reopen.js | 46 ++++++
ambari-web/app/views.js | 1 +
ambari-web/app/views/common/chart/pie.js | 11 +-
ambari-web/app/views/main/host/log_metrics.js | 141 +++++++++++++++++++
ambari-web/app/views/main/host/logs_view.js | 12 ++
ambari-web/test/utils/ember_reopen_test.js | 57 ++++++++
10 files changed, 311 insertions(+), 13 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 00fb4b9..59877a5 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -2307,6 +2307,7 @@ Em.I18n.translations = {
'hosts.host.summary.hostname':'Hostname',
'hosts.host.summary.agentHeartbeat':'Heartbeat',
'hosts.host.summary.hostMetrics':'Host Metrics',
+ 'hosts.host.summary.hostLogMetrics':'Host Log Metrics',
'hosts.host.summary.addComponent':'Add Component',
'hosts.host.summary.currentVersion':'Current Version',
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/routes/main.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/routes/main.js b/ambari-web/app/routes/main.js
index 891bf34..419f845 100644
--- a/ambari-web/app/routes/main.js
+++ b/ambari-web/app/routes/main.js
@@ -265,13 +265,16 @@ module.exports = Em.Route.extend(App.RouterRedirections, {
}),
logs: Em.Route.extend({
- route: '/logs',
+ route: '/logs:query',
connectOutlets: function (router, context) {
if (App.get('supports.logSearch')) {
router.get('mainHostDetailsController').connectOutlet('mainHostLogs')
} else {
router.transitionTo('summary');
}
+ },
+ serialize: function(router, params) {
+ return this.serializeQueryParams(router, params, 'mainHostDetailsController');
}
}),
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/templates/main/host/log_metrics.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host/log_metrics.hbs b/ambari-web/app/templates/main/host/log_metrics.hbs
new file mode 100644
index 0000000..22a39be
--- /dev/null
+++ b/ambari-web/app/templates/main/host/log_metrics.hbs
@@ -0,0 +1,26 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div class="row-fluid log-metrics-charts mtl">
+ {{#each item in view.logsData}}
+ <div class="span6 text-center mtl">
+ {{view view.chartView contentBinding="item"}}
+ <a href="#" {{action transitionByService item target="view"}}>{{item.service.displayName}}</a>
+ </div>
+ {{/each}}
+</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/templates/main/host/summary.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host/summary.hbs b/ambari-web/app/templates/main/host/summary.hbs
index 6b4c7a5..17a0b69 100644
--- a/ambari-web/app/templates/main/host/summary.hbs
+++ b/ambari-web/app/templates/main/host/summary.hbs
@@ -168,19 +168,31 @@
</div>
</div>
</div>
- {{!metrics}}
- {{#unless view.isNoHostMetricsService}}
- <div class="span6">
+ <div class="span6">
+ {{!metrics}}
+ {{#unless view.isNoHostMetricsService}}
<div class="box">
<div class="box-header">
<h4>{{t hosts.host.summary.hostMetrics}}</h4>
{{view view.timeRangeListView}}
</div>
<div>
- {{view App.MainHostMetricsView contentBinding="view.content"}}
+ {{view App.MainHostMetricsView contentBinding="view.content"}}
</div>
</div>
- </div>
+ {{/unless}}
+
+ {{!logs metrics}}
+ {{#if App.supports.logSearch}}
+ <div class="box">
+ <div class="box-header">
+ <h4>{{t hosts.host.summary.hostLogMetrics}}</h4>
+ </div>
+ <div>
+ {{view App.MainHostLogMetrics contentBinding="view.content"}}
+ </div>
+ </div>
+ {{/if}}
</div>
- {{/unless}}
+ </div>
</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/utils/ember_reopen.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/ember_reopen.js b/ambari-web/app/utils/ember_reopen.js
index 512b3da..bf091da 100644
--- a/ambari-web/app/utils/ember_reopen.js
+++ b/ambari-web/app/utils/ember_reopen.js
@@ -242,6 +242,23 @@ Ember.TextArea.reopen({
attributeBindings: ['readonly']
});
+/**
+ * Simply converts query string to object.
+ *
+ * @param {string} queryString query string e.g. '?param1=value1¶m2=value2'
+ * @return {object} converted object
+ */
+function parseQueryParams(queryString) {
+ if (!queryString) {
+ return {};
+ }
+ return queryString.replace(/^\?/, '').split('&').map(decodeURIComponent)
+ .reduce(function(p, c) {
+ var keyVal = c.split('=');
+ p[keyVal[0]] = keyVal[1];
+ return p;
+ }, {});
+};
Ember.Route.reopen({
/**
@@ -257,6 +274,35 @@ Ember.Route.reopen({
*/
exitRoute: function (router, context, callback) {
callback();
+ },
+
+ /**
+ * Query Params serializer. This method should be used inside <code>serialize</code> method.
+ * You need to specify `:query` dynamic sygment in your route's <code>route</code> attribute
+ * e.g. Em.Route.extend({ route: '/login:query'}) and return result of this method.
+ * This method will set <code>serializedQuery</code> property to specified controller by name.
+ * For concrete example see `app/routes/main.js`.
+ *
+ * @example
+ * queryParams: Em.Route.extend({
+ * route: '/queryDemo:query',
+ * serialize: function(route, params) {
+ * return this.serializeQueryParams(route, params, 'controllerNameToSetQueryObject');
+ * }
+ * });
+ * // now when navigated to http://example.com/#/queryDemo?param1=value1¶m2=value2
+ * // App.router.get('controllerNameToSetQueryObject').get('serializedQuery')
+ * // will return { param1: 'value1', param2: 'value2' }
+ *
+ * @param {Em.Router} router router instance passed to <code>serialize</code> method
+ * @param {object} params dynamic segment passed to <code>seriazlie</code>
+ * @param {string} controllerName name of the controller to set `serializedQuery` as result
+ * @return {object}
+ */
+ serializeQueryParams: function(router, params, controllerName) {
+ var controller = router.get(controllerName);
+ controller.set('serializedQuery', parseQueryParams(params ? params.query : ''));
+ return params || { query: ''};
}
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index a4008f7..2440086 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -128,6 +128,7 @@ require('views/main/host/summary');
require('views/main/host/configs');
require('views/main/host/configs_service');
require('views/main/host/configs_service_menu');
+require('views/main/host/log_metrics');
require('views/main/host/metrics');
require('views/main/host/stack_versions_view');
require('views/main/host/add_view');
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/views/common/chart/pie.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/chart/pie.js b/ambari-web/app/views/common/chart/pie.js
index 0280f87..ce9bda4 100644
--- a/ambari-web/app/views/common/chart/pie.js
+++ b/ambari-web/app/views/common/chart/pie.js
@@ -82,16 +82,15 @@ App.ChartPieView = Em.View.extend({
.append("svg:g")
.attr("transform", "translate(" + thisChart.get('w') / 2 + "," + thisChart.get('h') / 2 + ")"));
- this.set('arcs', thisChart.get('svg').selectAll("path")
+ this.set('arcs', thisChart.get('svg').selectAll(".arc")
.data(thisChart.donut(thisChart.get('data')))
- .enter().append("svg:path")
+ .enter()
+ .append("svg:g").attr('class', 'arc')
+ .append('svg:path')
.attr("fill", function (d, i) {
return thisChart.palette.color(i);
})
.attr("d", thisChart.get('arc'))
-
);
-
}
-
-});
\ No newline at end of file
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/views/main/host/log_metrics.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/log_metrics.js b/ambari-web/app/views/main/host/log_metrics.js
new file mode 100644
index 0000000..20f5ec6
--- /dev/null
+++ b/ambari-web/app/views/main/host/log_metrics.js
@@ -0,0 +1,141 @@
+/**
+ * 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');
+
+/**
+ * @typedef {Ember.Object} LogLevelItemObject
+ * @property {string} level level name
+ * @property {number} counter
+ */
+/**
+ * @typedef {Object} ServiceLogMetricsObject
+ * @property {App.Service} service model instance
+ * @property {LogLevelItemObject[]} logs
+ */
+App.MainHostLogMetrics = Em.View.extend({
+ templateName: require('templates/main/host/log_metrics'),
+ classNames: ['host-log-metrics'],
+
+ /**
+ * @type {ServiceLogMetricsObject[]}
+ */
+ logsData: function() {
+ var services = this.get('content').get('hostComponents').mapProperty('service').uniq();
+ var logLevels = ['fatal', 'critical', 'error', 'warning', 'info', 'debug'];
+ return services.map(function(service) {
+ var levels = logLevels.map(function(level) {
+ return Em.Object.create({
+ level: level,
+ counter: Math.ceil(Math.random()*10)
+ });
+ });
+ return Em.Object.create({
+ service: service,
+ logs: levels
+ });
+ });
+ }.property('content'),
+
+ /**
+ * @type {Ember.View} Pie Chart view
+ * @extends App.PieChartView
+ */
+ chartView: App.ChartPieView.extend({
+ classNames: ['log-metrics-chart'],
+ w: 150,
+ h: 150,
+ stroke: '#fff',
+ strokeWidth: 1,
+ levelColors: {
+ FATAL: '#B10202',
+ CRITICAL: '#E00505',
+ ERROR: App.healthStatusRed,
+ INFO: App.healthStatusGreen,
+ WARNING: App.healthStatusOrange,
+ DEBUG: '#1e61f7'
+ },
+ innerR: 36,
+ donut: d3.layout.pie().sort(null).value(function(d) { return d.get('counter'); }),
+
+ prepareChartData: function(content) {
+ this.set('data', content.get('logs'));
+ },
+
+ didInsertElement: function() {
+ this.prepareChartData(this.get('content'));
+ this._super();
+ this.appendLabels();
+ this.formatCenterText();
+ this.attachArcEvents();
+ this.colorizeArcs();
+ },
+
+ attachArcEvents: function() {
+ var self = this;
+ this.get('svg').selectAll('.arc')
+ .on('mouseover', function(d) {
+ self.get('svg').select('g.center-text').select('text')
+ .text(d.data.get('level').capitalize() + ": " + d.data.get('counter'));
+ })
+ .on('mouseout', function() {
+ self.get('svg').select('g.center-text').select('text').text('');
+ });
+ },
+
+ formatCenterText: function() {
+ this.get('svg')
+ .append('svg:g')
+ .attr('class', 'center-text')
+ .attr('render-order', 1)
+ .append('svg:text')
+ .attr('transform', "translate(0,0)")
+ .attr('text-anchor', 'middle')
+ .attr('stroke', '#000')
+ .attr('stroke-width', 0)
+ },
+
+ appendLabels: function() {
+ var labelArc = d3.svg.arc()
+ .outerRadius(this.get('outerR') - 15)
+ .innerRadius(this.get('outerR') - 15);
+ this.get('svg').selectAll('.arc')
+ .append('text')
+ .attr('transform', function(d) { return "translate(" + labelArc.centroid(d) + ")"; })
+ .attr('stroke', '#000')
+ .attr('stroke-width', 0)
+ .attr('font-size', '12px')
+ .attr('dy', '.50em')
+ .text(function(d) { return d.data.get('counter'); });
+ },
+
+ colorizeArcs: function() {
+ var self = this;
+ this.get('svg').selectAll('.arc path')
+ .attr('fill', function(d) {
+ return self.get('levelColors')[d.data.get('level').toUpperCase()];
+ });
+ }
+ }),
+
+
+ transitionByService: function(e) {
+ var service = e.context;
+ App.router.transitionTo('logs', {query: '?service_name=' + service.get('service.serviceName')});
+ }
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/app/views/main/host/logs_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/logs_view.js b/ambari-web/app/views/main/host/logs_view.js
index dfe00e4..b51f955 100644
--- a/ambari-web/app/views/main/host/logs_view.js
+++ b/ambari-web/app/views/main/host/logs_view.js
@@ -67,6 +67,10 @@ App.MainHostLogsView = App.TableView.extend({
serviceNameFilterView: filters.createSelectView({
column: 1,
fieldType: 'filter-input-width',
+ didInsertElement: function() {
+ this.setValue(Em.getWithDefault(this, 'controller.serializedQuery.service_name', ''));
+ this._super();
+ },
content: function() {
return [{
value: '',
@@ -86,6 +90,10 @@ App.MainHostLogsView = App.TableView.extend({
componentNameFilterView: filters.createSelectView({
column: 2,
fieldType: 'filter-input-width',
+ didInsertElement: function() {
+ this.setValue(Em.getWithDefault(this, 'controller.serializedQuery.component_name', ''));
+ this._super();
+ },
content: function() {
var hostName = this.get('parentView').get('host.hostName'),
hostComponents = App.HostComponent.find().filterProperty('hostName', hostName),
@@ -108,6 +116,10 @@ App.MainHostLogsView = App.TableView.extend({
fileExtensionsFilter: filters.createSelectView({
column: 3,
fieldType: 'filter-input-width',
+ didInsertElement: function() {
+ this.setValue(Em.getWithDefault(this, 'controller.serializedQuery.file_extension', ''));
+ this._super();
+ },
content: function() {
return [{
value: '',
http://git-wip-us.apache.org/repos/asf/ambari/blob/96bdecf8/ambari-web/test/utils/ember_reopen_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/utils/ember_reopen_test.js b/ambari-web/test/utils/ember_reopen_test.js
index eda5e81..aa50a50 100644
--- a/ambari-web/test/utils/ember_reopen_test.js
+++ b/ambari-web/test/utils/ember_reopen_test.js
@@ -78,4 +78,61 @@ describe('Ember functionality extension', function () {
});
+ describe('#Em.Route', function() {
+ describe('#serializeQueryParams', function() {
+ var route,
+ cases = [
+ {
+ m: 'No query params',
+ params: undefined,
+ e: {
+ result: {query: ''},
+ serializedQuery: {}
+ }
+ },
+ {
+ m: 'Query params ?param1=value1¶m2=value2',
+ params: { query: '?param1=value1¶m2=value2'},
+ e: {
+ result: {query: '?param1=value1¶m2=value2'},
+ serializedQuery: {param1: 'value1', param2: 'value2'}
+ }
+ },
+ {
+ m: 'Query params with encodedComponent ?param1=value1%30¶m2=value2',
+ params: { query: '?param1=value1%30¶m2=value2'},
+ e: {
+ result: {query: '?param1=value1%30¶m2=value2'},
+ serializedQuery: {param1: 'value10', param2: 'value2'}
+ }
+ }
+ ];
+
+ beforeEach(function() {
+ route = Ember.Route.create({
+ route: 'demo:query',
+ serialize: function(router, params) {
+ return this.serializeQueryParams(router, params, 'testController');
+ }
+ });
+ });
+
+ afterEach(function() {
+ route.destroy();
+ route = null;
+ });
+
+ cases.forEach(function(test) {
+ it(test.m, function() {
+ var ctrl = Em.Object.create({});
+ var router = Em.Object.create({
+ testController: ctrl
+ });
+ var ret = route.serialize(router, test.params);
+ expect(ret).to.be.eql(test.e.result);
+ expect(ctrl.get('serializedQuery')).to.be.eql(test.e.serializedQuery);
+ });
+ });
+ });
+ });
});
[2/2] ambari git commit: AMBARI-14992. Upon changing the time range
for displaying graphs,
UI does not show feedback (doesn't update and keeps showing the same graph)
when data is not available (alexantonenko)
Posted by al...@apache.org.
AMBARI-14992. Upon changing the time range for displaying graphs, UI does not show feedback (doesn't update and keeps showing the same graph) when data is not available (alexantonenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/209ec330
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/209ec330
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/209ec330
Branch: refs/heads/trunk
Commit: 209ec330def90559aa767211f3b869d21c13b673
Parents: b9a35f1
Author: Alex Antonenko <hi...@gmail.com>
Authored: Wed Feb 10 15:47:16 2016 +0200
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Thu Feb 11 11:48:18 2016 +0200
----------------------------------------------------------------------
.../app/mixins/common/widgets/widget_mixin.js | 49 +++++++-
ambari-web/app/styles/application.less | 5 +
.../views/common/widget/graph_widget_view.js | 4 +-
.../test/mixins/common/widget_mixin_test.js | 124 +++++++++++++++++++
4 files changed, 176 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/209ec330/ambari-web/app/mixins/common/widgets/widget_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/widgets/widget_mixin.js b/ambari-web/app/mixins/common/widgets/widget_mixin.js
index df7ff76..b6fe353 100644
--- a/ambari-web/app/mixins/common/widgets/widget_mixin.js
+++ b/ambari-web/app/mixins/common/widgets/widget_mixin.js
@@ -119,6 +119,7 @@ App.WidgetMixin = Ember.Mixin.create({
context: this,
startCallName: 'getHostComponentMetrics',
successCallback: this.getHostComponentMetricsSuccessCallback,
+ errorCallback: this.getMetricsErrorCallback,
completeCallback: function () {
requestCounter--;
if (requestCounter === 0) this.onMetricsLoaded();
@@ -130,6 +131,7 @@ App.WidgetMixin = Ember.Mixin.create({
context: this,
startCallName: 'getServiceComponentMetrics',
successCallback: this.getMetricsSuccessCallback,
+ errorCallback: this.getMetricsErrorCallback,
completeCallback: function () {
requestCounter--;
if (requestCounter === 0) this.onMetricsLoaded();
@@ -271,12 +273,41 @@ App.WidgetMixin = Ember.Mixin.create({
if (!Em.isNone(metric_data)) {
_metric.data = metric_data;
this.get('metrics').pushObject(_metric);
+ } else if (this.get('graphView')) {
+ var graph = this.get('childViews') && this.get('childViews').findProperty('_showMessage');
+ if (graph) {
+ graph.set('hasData', false);
+ this.set('isExportButtonHidden', true);
+ graph._showMessage('info', this.t('graphs.noData.title'), this.t('graphs.noDataAtTime.message'));
+ this.get('metrics').clear();
+ }
}
}, this);
}
},
/**
+ * error callback on getting aggregated metrics and host component metrics
+ * @param {object} xhr
+ * @param {string} textStatus
+ * @param {string} errorThrown
+ */
+ getMetricsErrorCallback: function (xhr, textStatus, errorThrown) {
+ if (this.get('graphView')) {
+ var graph = this.get('childViews') && this.get('childViews').findProperty('_showMessage');
+ if (graph) {
+ if (xhr.readyState == 4 && xhr.status) {
+ textStatus = xhr.status + " " + textStatus;
+ }
+ graph.set('hasData', false);
+ this.set('isExportButtonHidden', true);
+ graph._showMessage('warn', this.t('graphs.error.title'), this.t('graphs.error.message').format(textStatus, errorThrown));
+ this.get('metrics').clear();
+ }
+ }
+ },
+
+ /**
* make GET call to get metrics value for all host components
* @param {object} request
* @return {$.ajax}
@@ -704,6 +735,7 @@ App.WidgetLoadAggregator = Em.Object.create({
bulks[id].subRequests = [{
context: request.context,
successCallback: request.successCallback,
+ errorCallback: request.errorCallback,
completeCallback: request.completeCallback
}];
} else {
@@ -711,6 +743,7 @@ App.WidgetLoadAggregator = Em.Object.create({
bulks[id].subRequests.push({
context: request.context,
successCallback: request.successCallback,
+ errorCallback: request.errorCallback,
completeCallback: request.completeCallback
});
}
@@ -732,11 +765,17 @@ App.WidgetLoadAggregator = Em.Object.create({
_request.subRequests.forEach(function (subRequest) {
subRequest.successCallback.call(subRequest.context, response);
}, this);
- }).complete(function () {
- _request.subRequests.forEach(function (subRequest) {
- subRequest.completeCallback.call(subRequest.context);
- }, this);
- });
+ }).fail(function (xhr, textStatus, errorThrown) {
+ _request.subRequests.forEach(function (subRequest) {
+ if (subRequest.errorCallback) {
+ subRequest.errorCallback.call(subRequest.context, xhr, textStatus, errorThrown);
+ }
+ }, this);
+ }).complete(function () {
+ _request.subRequests.forEach(function (subRequest) {
+ subRequest.completeCallback.call(subRequest.context);
+ }, this);
+ });
})(bulks[id]);
}
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/209ec330/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index 6c5d4d0..57b7e76 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -2245,6 +2245,7 @@ a:focus {
left: 60px;
overflow: visible;
position: relative;
+ text-align: center;
}
.chart-y-axis {
position: absolute;
@@ -2260,6 +2261,10 @@ a:focus {
margin-top: 35px !important;
}
}
+ .alert {
+ display: inline-block;
+ padding-right: 14px;
+ }
}
position: relative;
margin: 20px 15px 0 15px;
http://git-wip-us.apache.org/repos/asf/ambari/blob/209ec330/ambari-web/app/views/common/widget/graph_widget_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/widget/graph_widget_view.js b/ambari-web/app/views/common/widget/graph_widget_view.js
index da30dca..6feaa28 100644
--- a/ambari-web/app/views/common/widget/graph_widget_view.js
+++ b/ambari-web/app/views/common/widget/graph_widget_view.js
@@ -311,7 +311,9 @@ App.GraphWidgetView = Em.View.extend(App.WidgetMixin, App.ExportMetricsMixin, {
self.set('parentView.isExportMenuHidden', true);
});
this.setYAxisFormatter();
- this.loadData();
+ if (!arguments.length || this.get('parentView.data.length')) {
+ this.loadData();
+ }
var self = this;
Em.run.next(function () {
if (self.get('isPreview')) {
http://git-wip-us.apache.org/repos/asf/ambari/blob/209ec330/ambari-web/test/mixins/common/widget_mixin_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/widget_mixin_test.js b/ambari-web/test/mixins/common/widget_mixin_test.js
index 00606f4..91f628f 100644
--- a/ambari-web/test/mixins/common/widget_mixin_test.js
+++ b/ambari-web/test/mixins/common/widget_mixin_test.js
@@ -384,6 +384,129 @@ describe('App.WidgetMixin', function () {
});
});
});
+
+ describe('#getMetricsErrorCallback()', function () {
+
+ var obj,
+ view = Em.Object.create({
+ _showMessage: Em.K
+ }),
+ cases = [
+ {
+ graphView: null,
+ metricsLength: 1,
+ showMessageCallCount: 0,
+ isExportButtonHidden: false,
+ title: 'no graph view'
+ },
+ {
+ graphView: {},
+ metricsLength: 1,
+ showMessageCallCount: 0,
+ isExportButtonHidden: false,
+ title: 'no childViews property'
+ },
+ {
+ graphView: {},
+ childViews: [],
+ metricsLength: 1,
+ showMessageCallCount: 0,
+ isExportButtonHidden: false,
+ title: 'no child views'
+ },
+ {
+ graphView: {},
+ childViews: [Em.Object.create({})],
+ metricsLength: 1,
+ showMessageCallCount: 0,
+ isExportButtonHidden: false,
+ title: 'no view with _showMessage method'
+ },
+ {
+ graphView: {},
+ childViews: [Em.Object.create({}), view],
+ metricsLength: 0,
+ showMessageCallCount: 1,
+ isExportButtonHidden: true,
+ title: 'graph view is available'
+ }
+ ],
+ messageCases = [
+ {
+ readyState: 2,
+ status: 0,
+ textStatus: 'error',
+ title: 'incomplete request'
+ },
+ {
+ readyState: 4,
+ status: 0,
+ textStatus: 'error',
+ title: 'no status code'
+ },
+ {
+ readyState: 4,
+ status: 404,
+ textStatus: '404 error',
+ title: 'status code available'
+ }
+ ];
+
+ beforeEach(function () {
+ sinon.spy(view, '_showMessage');
+ });
+
+ afterEach(function () {
+ view._showMessage.restore();
+ });
+
+ cases.forEach(function (item) {
+
+ describe(item.title, function () {
+
+ beforeEach(function () {
+ obj = Em.Object.create(App.WidgetMixin, {
+ metrics: [{}],
+ isExportButtonHidden: false,
+ graphView: item.graphView,
+ childViews: item.childViews
+ });
+ obj.getMetricsErrorCallback({});
+ });
+
+ it('metrics array', function () {
+ expect(obj.get('metrics')).to.have.length(item.metricsLength);
+ });
+
+ it('error message', function () {
+ expect(view._showMessage.callCount).to.equal(item.showMessageCallCount);
+ });
+
+ it('export button display', function () {
+ expect(obj.get('isExportButtonHidden')).to.equal(item.isExportButtonHidden);
+ });
+
+ });
+
+ });
+
+ messageCases.forEach(function (item) {
+
+ it(item.title, function () {
+ obj = Em.Object.create(App.WidgetMixin, {
+ graphView: Em.Object.create({}),
+ childViews: [view]
+ });
+ obj.getMetricsErrorCallback({
+ readyState: item.readyState,
+ status: item.status
+ }, 'error', 'Not Found');
+ expect(view._showMessage.firstCall.args).to.eql(['warn', Em.I18n.t('graphs.error.title'), Em.I18n.t('graphs.error.message').format(item.textStatus, 'Not Found')]);
+ });
+
+ });
+
+ });
});
@@ -502,6 +625,7 @@ describe('App.WidgetLoadAggregator', function () {
f1: function () {
return {
done: Em.K,
+ fail: Em.K,
complete: Em.K
}
},