You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2016/02/11 14:47:16 UTC

[16/17] ambari git commit: AMBARI-14993. Log Search: Add Host Log Metrics to Host Details -> Summary (alexantonenko)

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/branch-dev-patch-upgrade
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&param2=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&param2=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&param2=value2',
+          params: { query: '?param1=value1&param2=value2'},
+          e: {
+            result: {query: '?param1=value1&param2=value2'},
+            serializedQuery: {param1: 'value1', param2: 'value2'}
+          }
+        },
+        {
+          m: 'Query params with encodedComponent ?param1=value1%30&param2=value2',
+          params: { query: '?param1=value1%30&param2=value2'},
+          e: {
+            result: {query: '?param1=value1%30&param2=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);
+        });
+      });
+    });
+  });
 });