You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by rl...@apache.org on 2017/10/03 20:16:05 UTC
[18/22] ambari git commit: AMBARI-22118 Log Search UI: implement time
range selection from graph. (ababiichuk)
AMBARI-22118 Log Search UI: implement time range selection from graph. (ababiichuk)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/1f00c19d
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/1f00c19d
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/1f00c19d
Branch: refs/heads/branch-feature-AMBARI-20859
Commit: 1f00c19d09ffe9889a6fe59df28c2905a09c1333
Parents: c28b797
Author: ababiichuk <ab...@hortonworks.com>
Authored: Tue Oct 3 16:02:56 2017 +0300
Committer: ababiichuk <ab...@hortonworks.com>
Committed: Tue Oct 3 16:35:56 2017 +0300
----------------------------------------------------------------------
.../src/app/classes/histogram-options.class.ts | 36 ++++++++
.../logs-container.component.html | 4 +-
.../logs-container/logs-container.component.ts | 13 ++-
.../time-histogram.component.less | 22 +++--
.../time-histogram/time-histogram.component.ts | 94 ++++++++++++++++----
.../src/app/services/filtering.service.ts | 23 ++---
6 files changed, 154 insertions(+), 38 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/1f00c19d/ambari-logsearch/ambari-logsearch-web/src/app/classes/histogram-options.class.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/histogram-options.class.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/histogram-options.class.ts
new file mode 100644
index 0000000..dee5d98
--- /dev/null
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/histogram-options.class.ts
@@ -0,0 +1,36 @@
+/**
+ * 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.
+ */
+
+export interface HistogramMarginOptions {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+}
+
+export interface HistogramStyleOptions {
+ margin?: HistogramMarginOptions;
+ height?: number;
+ tickPadding?: number;
+ columnWidth?: number;
+ dragAreaColor?: string;
+}
+
+export interface HistogramOptions extends HistogramStyleOptions {
+ keysWithColors: {[key: string]: string};
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1f00c19d/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
index 9c6c336..776bb9a 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.html
@@ -20,7 +20,9 @@
{{'filter.capture.triggeringRefresh' | translate: autoRefreshMessageParams}}
</div>
</div>
-<time-histogram class="col-md-12" [data]="histogramData" [customOptions]="histogramOptions"></time-histogram>
+<time-histogram class="col-md-12" [data]="histogramData" [customOptions]="histogramOptions"
+ svgId="service-logs-histogram"
+ (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram>
<dropdown-button *ngIf="!isServiceLogsFileView" class="pull-right" label="logs.columns"
[options]="availableColumns | async" [isRightAlign]="true" [isMultipleChoice]="true"
action="updateSelectedColumns" [additionalArgs]="logsTypeMapObject.fieldsModel"></dropdown-button>
http://git-wip-us.apache.org/repos/asf/ambari/blob/1f00c19d/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
index fd3a58b..7345288 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/logs-container/logs-container.component.ts
@@ -28,6 +28,7 @@ import {AuditLog} from '@app/models/audit-log.model';
import {ServiceLog} from '@app/models/service-log.model';
import {LogField} from '@app/models/log-field.model';
import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry.class';
+import {HistogramOptions} from '@app/classes/histogram-options.class';
@Component({
selector: 'logs-container',
@@ -92,9 +93,9 @@ export class LogsContainerComponent implements OnInit {
displayedColumns: any[] = [];
- histogramData: any;
+ histogramData: {[key: string]: number};
- readonly histogramOptions = {
+ readonly histogramOptions: HistogramOptions = {
keysWithColors: this.logsContainer.colors
};
@@ -116,9 +117,13 @@ export class LogsContainerComponent implements OnInit {
get isServiceLogsFileView(): boolean {
return this.logsContainer.isServiceLogsFileView;
- };
+ }
get activeLog(): ActiveServiceLogEntry | null {
return this.logsContainer.activeLog;
- };
+ }
+
+ setCustomTimeRange(startTime: number, endTime: number): void {
+ this.filtering.setCustomTimeRange(startTime, endTime);
+ }
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1f00c19d/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
index d891862..1d29c55 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.less
@@ -16,14 +16,24 @@
* limitations under the License.
*/
-/deep/ .axis {
- .domain {
- display: none;
- }
+:host {
+ cursor: crosshair;
- .tick {
- line {
+ /deep/ .axis {
+ .domain {
display: none;
}
+
+ .tick {
+ cursor: default;
+
+ line {
+ display: none;
+ }
+ }
+ }
+
+ /deep/ .value {
+ cursor: pointer;
}
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1f00c19d/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.ts
----------------------------------------------------------------------
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 7856ecc..c3ec388 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
@@ -16,10 +16,12 @@
* limitations under the License.
*/
-import {Component, OnInit, AfterViewInit, OnChanges, Input, ViewChild, ElementRef} from '@angular/core';
+import {Component, OnInit, AfterViewInit, OnChanges, Input, Output, ViewChild, ElementRef, EventEmitter} from '@angular/core';
+import {ContainerElement, Selection} from 'd3';
import * as d3 from 'd3';
import * as moment from 'moment-timezone';
import {AppSettingsService} from '@app/services/storage/app-settings.service';
+import {HistogramStyleOptions, HistogramOptions} from '@app/classes/histogram-options.class';
@Component({
selector: 'time-histogram',
@@ -29,14 +31,14 @@ import {AppSettingsService} from '@app/services/storage/app-settings.service';
export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges {
constructor(private appSettings: AppSettingsService) {
- appSettings.getParameter('timeZone').subscribe(value => {
+ appSettings.getParameter('timeZone').subscribe((value: string): void => {
this.timeZone = value;
this.createHistogram();
});
}
ngOnInit() {
- Object.assign(this.options, this.defaultOptions, this.customOptions);
+ this.options = Object.assign({}, this.defaultOptions, this.customOptions);
}
ngAfterViewInit() {
@@ -52,12 +54,18 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
element: ElementRef;
@Input()
- customOptions: any;
+ svgId: string;
@Input()
- data: any;
+ customOptions: HistogramOptions;
- private readonly defaultOptions = {
+ @Input()
+ data: {[key: string]: number};
+
+ @Output()
+ selectArea: EventEmitter<number[]> = new EventEmitter();
+
+ private readonly defaultOptions: HistogramStyleOptions = {
margin: {
top: 20,
right: 20,
@@ -66,10 +74,11 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
},
height: 200,
tickPadding: 10,
- columnWidth: 20
+ columnWidth: 20,
+ dragAreaColor: '#FFF'
};
- private options: any = {};
+ private options: HistogramOptions;
private timeZone: string;
@@ -77,7 +86,7 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
private svg;
- private width;
+ private width: number;
private xScale;
@@ -91,6 +100,16 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
private htmlElement: HTMLElement;
+ private dragArea: Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>;
+
+ private dragStartX: number;
+
+ private minDragX: number;
+
+ private maxDragX: number;
+
+ private readonly timeFormat: string = 'MM/DD HH:mm';
+
histogram: any;
private createHistogram(): void {
@@ -105,7 +124,7 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
const margin = this.options.margin,
keysWithColors = this.options.keysWithColors,
keys = Object.keys(keysWithColors),
- colors = keys.reduce((array, key) => [...array, keysWithColors[key]], []);
+ colors = keys.reduce((array: string[], key: string): string[] => [...array, keysWithColors[key]], []);
this.width = this.htmlElement.clientWidth - margin.left - margin.right;
this.xScale = d3.scaleTime().range([0, this.width]);
this.yScale = d3.scaleLinear().range([this.options.height, 0]);
@@ -115,20 +134,20 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
private buildSVG(): void {
const margin = this.options.margin;
this.host.html('');
- this.svg = this.host.append('svg').attr('width', this.width + margin.left + margin.right)
+ this.svg = this.host.append('svg').attr('id', this.svgId).attr('width', this.htmlElement.clientWidth)
.attr('height', this.options.height + margin.top + margin.bottom).append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
}
private drawXAxis(): void {
this.xAxis = d3.axisBottom(this.xScale)
- .tickFormat(tick => moment(tick).tz(this.timeZone).format('MM/DD HH:mm'))
+ .tickFormat(tick => moment(tick).tz(this.timeZone).format(this.timeFormat))
.tickPadding(this.options.tickPadding);
this.svg.append('g').attr('class', 'axis').attr('transform', `translate(0,${this.options.height})`).call(this.xAxis);
}
private drawYAxis(): void {
- this.yAxis = d3.axisLeft(this.yScale).tickFormat((tick: number) => {
+ this.yAxis = d3.axisLeft(this.yScale).tickFormat((tick: number): string | undefined => {
if (Number.isInteger(tick)) {
return tick.toFixed(0);
} else {
@@ -142,20 +161,61 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges
const keys = Object.keys(this.options.keysWithColors),
data = this.data,
timeStamps = Object.keys(data),
- formattedData = timeStamps.map(timeStamp => Object.assign({
- timeStamp: timeStamp
+ formattedData = timeStamps.map((timeStamp: string): {[key: string]: number} => Object.assign({
+ timeStamp: Number(timeStamp)
}, data[timeStamp])),
layers = (d3.stack().keys(keys)(formattedData)),
columnWidth = this.options.columnWidth;
this.xScale.domain(d3.extent(formattedData, item => item.timeStamp));
- this.yScale.domain([0, d3.max(formattedData, item => keys.reduce((sum, key) => sum + item[key], 0))]);
+ this.yScale.domain([0, d3.max(formattedData, item => keys.reduce((sum: number, key: string): number => sum + item[key], 0))]);
this.drawXAxis();
this.drawYAxis();
- const layer = this.svg.selectAll().data(d3.transpose<any>(layers)).enter().append('g');
+ const layer = this.svg.selectAll().data(d3.transpose<any>(layers)).enter().append('g').attr('class', 'value');
layer.selectAll().data(item => item).enter().append('rect')
.attr('x', item => this.xScale(item.data.timeStamp) - columnWidth / 2).attr('y', item => this.yScale(item[1]))
.attr('height', item => this.yScale(item[0]) - this.yScale(item[1])).attr('width', columnWidth.toString())
.style('fill', (item, index) => this.colorScale(index));
+ this.setDragBehavior();
+ }
+
+ private setDragBehavior(): void {
+ this.minDragX = this.options.margin.left;
+ this.maxDragX = this.htmlElement.clientWidth;
+ d3.selectAll(`svg#${this.svgId}`).call(d3.drag()
+ .on('start', (datum: undefined, index: number, containers: ContainerElement[]): void => {
+ if (this.dragArea) {
+ this.dragArea.remove();
+ }
+ this.dragStartX = Math.max(0, this.getDragX(containers[0]) - this.options.margin.left);
+ this.dragArea = this.svg.insert('rect', ':first-child').attr('x', this.dragStartX).attr('y', 0).attr('width', 0)
+ .attr('height', this.options.height).style('fill', this.options.dragAreaColor);
+ })
+ .on('drag', (datum: undefined, index: number, containers: ContainerElement[]): void => {
+ const currentX = Math.max(this.getDragX(containers[0]), this.minDragX) - this.options.margin.left,
+ startX = Math.min(currentX, this.dragStartX),
+ currentWidth = Math.abs(currentX - this.dragStartX);
+ this.dragArea.attr('x', startX).attr('width', currentWidth);
+ })
+ .on('end', (): void => {
+ const dragAreaDetails = this.dragArea.node().getBBox(),
+ startX = Math.max(0, dragAreaDetails.x),
+ endX = Math.min(this.width, dragAreaDetails.x + dragAreaDetails.width),
+ xScaleInterval = this.xScale.domain().map((point: Date): number => point.valueOf()),
+ xScaleLength = xScaleInterval[1] - xScaleInterval[0],
+ ratio = xScaleLength / this.width,
+ startTimeStamp = Math.round(xScaleInterval[0] + ratio * startX),
+ endTimeStamp = Math.round(xScaleInterval[0] + ratio * endX);
+ this.selectArea.emit([startTimeStamp, endTimeStamp]);
+ this.dragArea.remove();
+ })
+ );
+ d3.selectAll(`svg#${this.svgId} .value, svg#${this.svgId} .axis`).call(d3.drag().on('start', (): void => {
+ d3.event.sourceEvent.stopPropagation();
+ }));
+ }
+
+ private getDragX(element: ContainerElement): number {
+ return d3.mouse(element)[0];
}
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1f00c19d/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts
----------------------------------------------------------------------
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts
index 6697c54..0fff75d 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts
@@ -22,7 +22,6 @@ import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/takeUntil';
-import {Moment} from 'moment';
import * as moment from 'moment-timezone';
import {ListItem} from '@app/classes/list-item.class';
import {AppSettingsService} from '@app/services/storage/app-settings.service';
@@ -400,29 +399,25 @@ export class FilteringService {
autoRefreshRemainingSeconds: number = 0;
- private startCaptureMoment: Moment;
+ private startCaptureTime: number;
- private stopCaptureMoment: Moment;
+ private stopCaptureTime: number;
startCaptureTimer(): void {
- this.startCaptureMoment = moment();
+ this.startCaptureTime = new Date().valueOf();
Observable.timer(0, 1000).takeUntil(this.stopTimer).subscribe(seconds => this.captureSeconds = seconds);
}
stopCaptureTimer(): void {
const autoRefreshIntervalSeconds = this.autoRefreshInterval / 1000;
- this.stopCaptureMoment = moment();
+ this.stopCaptureTime = new Date().valueOf();
this.captureSeconds = 0;
this.stopTimer.next();
Observable.timer(0, 1000).takeUntil(this.stopAutoRefreshCountdown).subscribe(seconds => {
this.autoRefreshRemainingSeconds = autoRefreshIntervalSeconds - seconds;
if (!this.autoRefreshRemainingSeconds) {
this.stopAutoRefreshCountdown.next();
- this.filtersForm.controls.timeRange.setValue({
- type: 'CUSTOM',
- start: this.startCaptureMoment,
- end: this.stopCaptureMoment
- });
+ this.setCustomTimeRange(this.startCaptureTime, this.stopCaptureTime);
}
});
}
@@ -457,6 +452,14 @@ export class FilteringService {
});
}
+ setCustomTimeRange(startTime: number, endTime: number): void {
+ this.filtersForm.controls.timeRange.setValue({
+ type: 'CUSTOM',
+ start: moment(startTime),
+ end: moment(endTime)
+ });
+ }
+
private getStartTime = (value: any, current: string): string => {
let time;
if (value) {