You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2018/04/12 09:19:07 UTC

[ambari] branch trunk updated: [AMBARI-23534] Log Search UI: service logs chart fixes

This is an automated email from the ASF dual-hosted git repository.

ababiichuk pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new cf86bfd  [AMBARI-23534] Log Search UI: service logs chart fixes
cf86bfd is described below

commit cf86bfd5f9a0225d55fbb3b4e20d363b34ac7e8f
Author: Istvan Tobias <to...@gmail.com>
AuthorDate: Wed Apr 11 03:33:04 2018 +0200

    [AMBARI-23534] Log Search UI: service logs chart fixes
---
 .../classes/components/graph/graph.component.ts    | 102 ++++++++++++---------
 .../components/graph/time-graph.component.ts       |  33 ++++---
 .../time-histogram/time-histogram.component.ts     |  11 ++-
 .../src/mockdata/mock-data-get.ts                  |  61 ++++++------
 4 files changed, 117 insertions(+), 90 deletions(-)

diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts
index b9140cd..baf3ce4 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/graph.component.ts
@@ -17,38 +17,22 @@
  */
 
 import {
-  AfterViewInit, OnChanges, SimpleChanges, ViewChild, ElementRef, Input, Output, EventEmitter
+  AfterViewInit, OnChanges, SimpleChanges, ViewChild, ElementRef, Input, Output, EventEmitter, OnInit, OnDestroy
 } from '@angular/core';
 import * as d3 from 'd3';
 import * as d3sc from 'd3-scale-chromatic';
+import {Observable} from 'rxjs/Observable';
+import 'rxjs/add/observable/fromEvent';
+import 'rxjs/add/operator/throttleTime';
 import {
-  GraphPositionOptions, GraphMarginOptions, GraphTooltipInfo, LegendItem, GraphEventData, GraphEmittedEvent
+GraphPositionOptions, GraphMarginOptions, GraphTooltipInfo, LegendItem, GraphEventData, GraphEmittedEvent
 } from '@app/classes/graph';
 import {HomogeneousObject} from '@app/classes/object';
 import {ServiceInjector} from '@app/classes/service-injector';
 import {UtilsService} from '@app/services/utils.service';
+import {Subscription} from 'rxjs/Subscription';
 
-export class GraphComponent implements AfterViewInit, OnChanges {
-
-  constructor() {
-    this.utils = ServiceInjector.injector.get(UtilsService);
-  }
-
-  ngAfterViewInit() {
-    this.graphContainer = this.graphContainerRef.nativeElement;
-    this.tooltip = this.tooltipRef.nativeElement;
-    this.host = d3.select(this.graphContainer);
-  }
-
-  ngOnChanges(changes: SimpleChanges) {
-    const dataChange = changes.data;
-    if (dataChange && dataChange.currentValue && !this.utils.isEmptyObject(dataChange.currentValue)
-      && (!dataChange.previousValue || this.utils.isEmptyObject(dataChange.previousValue))
-      && this.utils.isEmptyObject(this.labels)) {
-      this.setDefaultLabels();
-    }
-    this.createGraph();
-  }
+export class GraphComponent implements AfterViewInit, OnChanges, OnInit, OnDestroy {
 
   @Input()
   data: HomogeneousObject<HomogeneousObject<number>> = {};
@@ -197,6 +181,38 @@ export class GraphComponent implements AfterViewInit, OnChanges {
    */
   private tooltipOnTheLeft: boolean = false;
 
+  protected subscriptions: Subscription[] = [];
+
+  constructor() {
+    this.utils = ServiceInjector.injector.get(UtilsService);
+  }
+
+  ngOnInit() {
+    this.subscriptions.push(
+      Observable.fromEvent(window, 'resize').throttleTime(100).subscribe(this.onWindowResize)
+    );
+  }
+
+  ngOnDestroy() {
+    this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
+  }
+
+  ngAfterViewInit() {
+    this.graphContainer = this.graphContainerRef.nativeElement;
+    this.tooltip = this.tooltipRef.nativeElement;
+    this.host = d3.select(this.graphContainer);
+  }
+
+  ngOnChanges(changes: SimpleChanges) {
+    const dataChange = changes.data;
+    if (dataChange && dataChange.currentValue && !this.utils.isEmptyObject(dataChange.currentValue)
+      && (!dataChange.previousValue || this.utils.isEmptyObject(dataChange.previousValue))
+      && this.utils.isEmptyObject(this.labels)) {
+      this.setDefaultLabels();
+    }
+    this.createGraph();
+  }
+
   /**
    * This will return the information about the used levels and the connected colors and labels.
    * The goal is to provide an easy property to the template to display the legend of the levels.
@@ -209,6 +225,10 @@ export class GraphComponent implements AfterViewInit, OnChanges {
     }));
   }
 
+  onWindowResize = () => {
+    this.createGraph();
+  }
+
   protected createGraph(): void {
     if (this.host && !this.utils.isEmptyObject(this.labels)) {
       this.setup();
@@ -221,16 +241,16 @@ export class GraphComponent implements AfterViewInit, OnChanges {
    * Method that sets default labels map object based on data if no custom one is specified
    */
   protected setDefaultLabels() {
-    const data = this.data,
-      keys = Object.keys(data),
-      labels = keys.reduce((keys: HomogeneousObject<string>, dataKey: string): HomogeneousObject<string> => {
+    const data = this.data;
+    const keys = Object.keys(data);
+    const labels = keys.reduce((keysReduced: HomogeneousObject<string>, dataKey: string): HomogeneousObject<string> => {
         const newKeys = Object.keys(data[dataKey]),
           newKeysObj = newKeys.reduce((subKeys: HomogeneousObject<string>, key: string): HomogeneousObject<string> => {
             return Object.assign(subKeys, {
               [key]: key
             });
         }, {});
-        return Object.assign(keys, newKeysObj);
+        return Object.assign(keysReduced, newKeysObj);
       }, {});
     this.labels = labels;
   }
@@ -239,9 +259,9 @@ export class GraphComponent implements AfterViewInit, OnChanges {
     const margin = this.margin;
     if (this.utils.isEmptyObject(this.colors)) {
       // set default color scheme for different values if no custom colors specified
-      const keys = Object.keys(this.labels),
-        keysCount = keys.length,
-        specterLength = keysCount > 2 ? keysCount : 3; // length of minimal available spectral scheme is 3
+      const keys = Object.keys(this.labels);
+      const keysCount = keys.length;
+      const specterLength = keysCount > 2 ? keysCount : 3; // length of minimal available spectral scheme is 3
       let colorsArray;
       if (keysCount > 2) {
         colorsArray = Array.from(d3sc.schemeSpectral[keysCount]);
@@ -260,9 +280,7 @@ export class GraphComponent implements AfterViewInit, OnChanges {
         keys = Object.keys(keysWithColors);
       this.orderedColors = keys.reduce((array: string[], key: string): string[] => [...array, keysWithColors[key]], []);
     }
-    if (!this.width) {
-      this.width = this.graphContainer.clientWidth - margin.left - margin.right;
-    }
+    this.width = this.graphContainer.clientWidth - margin.left - margin.right;
     const xScale = this.isTimeGraph ? d3.scaleTime() : d3.scaleLinear();
     const yScale = d3.scaleLinear();
     const xScaleWithRange = this.reverseXRange ? xScale.range([this.width, 0]) : xScale.range([0, this.width]);
@@ -297,14 +315,16 @@ export class GraphComponent implements AfterViewInit, OnChanges {
    * It draws the svg representation of the x axis. The goal is to set the ticks here, add the axis to the svg element
    * and set the position of the axis.
    * @param {number} ticksCount - optional parameter which sets number of ticks explicitly
+   * @param {number} leftOffset
    */
-  protected drawXAxis(ticksCount?: number): void {
+  protected drawXAxis(ticksCount?: number, leftOffset?: number): void {
     const axis = d3.axisBottom(this.xScale).tickFormat(this.xAxisTickFormatter).tickPadding(this.tickPadding);
     if (ticksCount) {
       axis.ticks(ticksCount);
     }
     this.xAxis = axis;
-    this.svg.append('g').attr('class', `axis ${this.xAxisClassName}`).attr('transform', `translate(0,${this.height})`)
+    this.svg.append('g').attr('class', `axis ${this.xAxisClassName}`)
+      .attr('transform', `translate(${leftOffset || 0}, ${this.height})`)
       .call(this.xAxis);
     if (this.xTickContextMenu.observers.length) {
       this.svg.selectAll(`.${this.xAxisClassName} .tick`).on('contextmenu', (tickValue: any, index: number): void => {
@@ -330,7 +350,7 @@ export class GraphComponent implements AfterViewInit, OnChanges {
     this.svg.append('g').attr('class', `axis ${this.yAxisClassName}`).call(this.yAxis);
     if (this.yTickContextMenu.observers.length) {
       this.svg.selectAll(`.${this.yAxisClassName} .tick`).on('contextmenu', (tickValue: any, index: number): void => {
-        const tick = this.emitFormattedYTick ? this.yAxisTickFormatter(tickValue, index): tickValue,
+        const tick = this.emitFormattedYTick ? this.yAxisTickFormatter(tickValue, index) : tickValue,
           nativeEvent = d3.event;
         this.yTickContextMenu.emit({tick, nativeEvent});
         event.preventDefault();
@@ -352,7 +372,7 @@ export class GraphComponent implements AfterViewInit, OnChanges {
     } else {
       return Number.isInteger(tick) ? tick.toFixed(0) : undefined;
     }
-  };
+  }
 
   /**
    * Function that formats the labels for Y axis ticks.
@@ -368,7 +388,7 @@ export class GraphComponent implements AfterViewInit, OnChanges {
     } else {
       return Number.isInteger(tick) ? tick.toFixed(0) : undefined;
     }
-  };
+  }
 
   /**
    * The goal is to handle the mouse over event on the svg elements so that we can populate the tooltip info object
@@ -380,7 +400,7 @@ export class GraphComponent implements AfterViewInit, OnChanges {
   protected handleMouseOver = (d: GraphEventData, index: number, elements: HTMLElement[]): void => {
     this.setTooltipDataFromChartData(d);
     this.setTooltipPosition();
-  };
+  }
 
   /**
    * The goal is to handle the movement of the mouse over the svg elements, so that we can set the position of
@@ -388,14 +408,14 @@ export class GraphComponent implements AfterViewInit, OnChanges {
    */
   protected handleMouseMove = (): void => {
     this.setTooltipPosition();
-  };
+  }
 
   /**
    * The goal is to reset the tooltipInfo object so that the tooltip will be hidden.
    */
   protected handleMouseOut = (): void => {
     this.tooltipInfo = {};
-  };
+  }
 
   /**
    * The goal is set the tooltip
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/time-graph.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/time-graph.component.ts
index 4e0aa10..381c9cf 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/time-graph.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/components/graph/time-graph.component.ts
@@ -26,18 +26,6 @@ import {GraphComponent} from '@app/classes/components/graph/graph.component';
 
 export class TimeGraphComponent extends GraphComponent implements OnInit {
 
-  constructor() {
-    super();
-    this.appSettings = ServiceInjector.injector.get(AppSettingsService);
-  }
-
-  ngOnInit() {
-    this.appSettings.getParameter('timeZone').subscribe((value: string): void => {
-      this.timeZone = value;
-      this.createGraph();
-    });
-  }
-
   @Input()
   tickTimeFormat: string = 'MM/DD HH:mm';
 
@@ -78,6 +66,21 @@ export class TimeGraphComponent extends GraphComponent implements OnInit {
    */
   protected rightDragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
 
+  constructor() {
+    super();
+    this.appSettings = ServiceInjector.injector.get(AppSettingsService);
+  }
+
+  ngOnInit() {
+    this.subscriptions.push(
+      this.appSettings.getParameter('timeZone').subscribe((value: string): void => {
+        this.timeZone = value;
+        this.createGraph();
+      })
+    );
+    super.ngOnInit();
+  }
+
   /**
    * This is a Date object holding the value of the first tick of the xAxis. It is a helper getter for the template.
    */
@@ -96,7 +99,7 @@ export class TimeGraphComponent extends GraphComponent implements OnInit {
 
   protected xAxisTickFormatter = (tick: Date): string => {
     return moment(tick).tz(this.timeZone).format(this.tickTimeFormat);
-  };
+  }
 
   protected setXScaleDomain(data: GraphScaleItem[]): void {
     this.xScale.domain(d3.extent(data, item => item.tick)).nice();
@@ -140,7 +143,7 @@ export class TimeGraphComponent extends GraphComponent implements OnInit {
    * It will reset the time gap if the xScale is not set or there are no ticks.
    */
   protected setChartTimeGapByXScale(): void {
-    let ticks = this.xScale && this.xScale.ticks();
+    const ticks = this.xScale && this.xScale.ticks();
     if (ticks && ticks.length) {
       this.setChartTimeGap(ticks[0], ticks[1] || ticks[0]);
     } else {
@@ -224,7 +227,7 @@ export class TimeGraphComponent extends GraphComponent implements OnInit {
         const startX = Math.min(currentX, this.dragStartX);
         const currentWidth = Math.abs(currentX - this.dragStartX);
         this.dragArea.attr('x', startX).attr('width', currentWidth);
-        let timeRange = this.getTimeRangeByXRanges(startX, startX + currentWidth);
+        const timeRange = this.getTimeRangeByXRanges(startX, startX + currentWidth);
         this.setChartTimeGap(new Date(timeRange[0]), new Date(timeRange[1]));
       })
       .on('end', (): void => {
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
index e021075..697de1e 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
@@ -31,10 +31,6 @@ import {GraphScaleItem} from '@app/classes/graph';
 })
 export class TimeHistogramComponent extends TimeGraphComponent {
 
-  constructor() {
-    super();
-  }
-
   @Input()
   columnWidth = {
     second: 40,
@@ -44,6 +40,10 @@ export class TimeHistogramComponent extends TimeGraphComponent {
     base: 20
   };
 
+  constructor() {
+    super();
+  }
+
   protected setYScaleDomain(data: GraphScaleItem[]): void {
     const keys = Object.keys(this.labels);
     const maxYValue = d3.max(data, item => keys.reduce((sum: number, key: string): number => sum + item[key], 0));
@@ -73,7 +73,7 @@ export class TimeHistogramComponent extends TimeGraphComponent {
     const columnWidth = this.columnWidth[this.chartTimeGap.unit] || this.columnWidth.base;
 
     // drawing the axis
-    this.drawXAxis();
+    this.drawXAxis(null, (columnWidth / 2) + 2);
     this.drawYAxis();
 
     // populate the data and drawing the bars
@@ -81,6 +81,7 @@ export class TimeHistogramComponent extends TimeGraphComponent {
       .enter().append('g')
       .attr('class', 'value');
     layer.selectAll().data(item => item).enter().append('rect')
+        .attr('transform', `translate(${(columnWidth / 2) + 2}, 0)`)
         .attr('x', item => this.xScale(item.data.tick) - columnWidth / 2)
         .attr('y', item => this.yScale(item[1]))
         .attr('height', item => this.yScale(item[0]) - this.yScale(item[1]))
diff --git a/ambari-logsearch/ambari-logsearch-web/src/mockdata/mock-data-get.ts b/ambari-logsearch/ambari-logsearch-web/src/mockdata/mock-data-get.ts
index 37590e1..72e6b7d 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/mockdata/mock-data-get.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/mockdata/mock-data-get.ts
@@ -33,9 +33,32 @@ import {
   generateServiceLog,
   generateAuditLog
 } from './mock-data-common';
+import Base = moment.unitOfTime.Base;
 
 const currentTime: Moment = moment();
 
+function generateDataCount(from, to, unit, gap) {
+  let current = moment(from);
+  const end = moment(to);
+  const data = [];
+  while (current.isBefore(end)) {
+    data.push({
+      name: current.toISOString(),
+      value: getRandomInt(9000)
+    });
+    current = current.add(gap, unit);
+  }
+  return data;
+}
+
+function generateGraphData(from, to, unit, gap) {
+  return levels.map((level) => {
+    return {
+      dataCount: generateDataCount(from, to, unit, gap),
+      name: level
+    };
+  });
+}
 
 export const mockDataGet = {
   'login': {},
@@ -1447,35 +1470,15 @@ export const mockDataGet = {
       services: services
     }
   },
-  'api/v1/service/logs/histogram': {
-    graphData: [
-      {
-        dataCount: [
-          {
-            name: currentTime.toISOString(),
-            value: '1000'
-          },
-          {
-            name: currentTime.clone().subtract(1, 'h').toISOString(),
-            value: '2000'
-          }
-        ],
-        name: 'ERROR'
-      },
-      {
-        dataCount: [
-          {
-            name: currentTime.toISOString(),
-            value: '700'
-          },
-          {
-            name: currentTime.clone().subtract(1, 'h').toISOString(),
-            value: '900'
-          }
-        ],
-        name: 'WARN'
-      }
-    ]
+  'api/v1/service/logs/histogram': (query: URLSearchParams) => {
+    const unitParam: string[] = decodeURIComponent(query.get('unit')).match(/(\d{1,})([a-zA-Z]{1,})/);
+    const unit: Base = <Base>unitParam[2];
+    const amount: number = parseInt(unitParam[1], 0);
+    const from = moment(decodeURIComponent(query.get('from')));
+    const to = moment(decodeURIComponent(query.get('to')));
+    return {
+      graphData: generateGraphData(from, to, unit, amount)
+    };
   },
   'api/v1/service/logs/hosts': {
     groupList: hosts.map(host => Object.assign({}, {host}))

-- 
To stop receiving notification emails like this one, please contact
ababiichuk@apache.org.