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.