Repository: ambari Updated Branches: refs/heads/trunk 2b9a34b0f -> 011e448e2
http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/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 8544677..e021075 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,31 +16,25 @@ * limitations under the License. */ -import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; +import {Component, Input} from '@angular/core'; import * as d3 from 'd3'; -import * as moment from 'moment-timezone'; -import {AppSettingsService} from '@app/services/storage/app-settings.service'; -import {GraphComponent} from '@app/classes/components/graph/graph.component'; +import {TimeGraphComponent} from '@app/classes/components/graph/time-graph.component'; import {GraphScaleItem} from '@app/classes/graph'; @Component({ selector: 'time-histogram', templateUrl: './time-histogram.component.html', - styleUrls: ['../../classes/components/graph/graph.component.less', './time-histogram.component.less'] + styleUrls: [ + '../../classes/components/graph/graph.component.less', '../../classes/components/graph/time-graph.component.less', + './time-histogram.component.less' + ] }) -export class TimeHistogramComponent extends GraphComponent implements OnInit { +export class TimeHistogramComponent extends TimeGraphComponent { - constructor(private appSettings: AppSettingsService) { + constructor() { super(); } - ngOnInit() { - this.appSettings.getParameter('timeZone').subscribe((value: string): void => { - this.timeZone = value; - this.createGraph(); - }); - } - @Input() columnWidth = { second: 40, @@ -50,153 +44,18 @@ export class TimeHistogramComponent extends GraphComponent implements OnInit { base: 20 }; - @Output() - selectArea: EventEmitter<number[]> = new EventEmitter(); - - readonly isTimeGraph: boolean = true; - - private timeZone: string; - - private dragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>; - - private dragStartX: number; - - private minDragX: number; - - private maxDragX: number; - - private readonly tickTimeFormat: string = 'MM/DD HH:mm'; - - private readonly historyStartEndTimeFormat: string = 'dddd, MMMM DD, YYYY'; - - histogram: any; - - /** - * This property holds the data structure describing the gaps between the xAxis ticks. - * The unit property can be: second, minute, hour, day - * The value is the number of the given unit. - */ - private chartTimeGap: {value: number, unit: string, label: string} | null; - /** - * This is the rectangle element to represent the unselected time range on the left side of the selected time range - */ - private leftDragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>; - /** - * This is the rectangle element to represent the unselected time range on the right side of the selected time range - */ - private rightDragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>; - /** - * This is a Date object holding the value of the first tick of the xAxis. It is a helper getter for the template. - */ - private get firstDateTick(): Date | undefined { - const ticks = this.xScale && this.xScale.ticks(); - return (ticks && ticks.length && ticks[0]) || undefined; - } - /** - * This is a Date object holding the value of the last tick of the xAxis. It is a helper getter for the template. - */ - private get lastDateTick(): Date | undefined { - const ticks = this.xScale && this.xScale.ticks(); - return (ticks && ticks.length && ticks[ticks.length-1]) || undefined; - } - - protected xAxisTickFormatter = (tick: Date): string => { - return moment(tick).tz(this.timeZone).format(this.tickTimeFormat); - }; - - protected yAxisTickFormatter = (tick: number): string | undefined => { - return Number.isInteger(tick) ? tick.toFixed(0) : undefined; - }; - - /** - * The goal is to calculate the time gap between the given dates. It will return an object representing the unit and - * the value in the given unit. Eg.: {unit: 'minute', value: 5} - * @param {Date} startDate - * @param {Date} endDate - * @returns {{value: number; unit: string, label: string}} - */ - private getTimeGap(startDate: Date, endDate: Date): {value: number, unit: string, label: string} { - const startDateMoment = moment(startDate); - const endDateMoment = moment(endDate); - const diffInWeek: number = endDateMoment.diff(startDateMoment, 'weeks'); - const diffInDay: number = endDateMoment.diff(startDateMoment, 'days'); - const diffInHour: number = endDateMoment.diff(startDateMoment, 'hours'); - const diffInMin: number = endDateMoment.diff(startDateMoment, 'minutes'); - const diffInSec: number = endDateMoment.diff(startDateMoment, 'seconds'); - const value = diffInWeek >= 1 ? diffInWeek : ( - diffInDay >= 1 ? diffInDay : ( - diffInHour >= 1 ? diffInHour : (diffInMin >= 1 ? diffInMin : diffInSec) - ) - ); - const unit: string = diffInWeek >= 1 ? 'week' : ( - diffInDay >= 1 ? `day` : ( - diffInHour >= 1 ? `hour` : (diffInMin >= 1 ? `minute` : `second`) - ) - ); - const label = `histogram.gap.${unit}${value>1 ? 's' : ''}`; - return { - value, - unit, - label - }; - } - - /** - * The goal is to have a simple function to set the time gap corresponding to the xScale ticks. - * It will reset the time gap if the xScale is not set or there are no ticks. - */ - private setChartTimeGapByXScale() { - let ticks = this.xScale && this.xScale.ticks(); - if (ticks && ticks.length) { - this.setChartTimeGap(ticks[0], ticks[1] || ticks[0]); - } else { - this.resetChartTimeGap(); - } - } - - /** - * Simply reset the time gap property to null. - */ - private resetChartTimeGap(): void { - this.chartTimeGap = null; - } - - /** - * The goal is to have a single point where we set the chartTimeGap property corresponding the given timerange. - * @param {Date} startDate - * @param {Date} endDate - */ - private setChartTimeGap(startDate: Date, endDate: Date): void { - this.chartTimeGap = this.getTimeGap(startDate, endDate); - } - - /** - * Set the domain for the y scale regarding the given data. The maximum value of the data is the sum of the log level - * values. - * An example data: [{tick: 1233455677, WARN: 12, ERROR: 123}] - * @param {GraphScaleItem[]} data - */ 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)); this.yScale.domain([0, maxYValue]); } - /** - * Set the domain values for the x scale regarding the given data. - * An example data: [{tick: 1233455677, WARN: 12, ERROR: 123}] - * @param {GraphScaleItem[]} data - */ - protected setXScaleDomain(data: GraphScaleItem[]): void { - this.xScale.domain(d3.extent(data, item => item.tick)).nice(); - } - protected populate(): void { const keys = Object.keys(this.colors); const data = this.data; const timeStamps = Object.keys(data); // we create a more consumable data structure for d3 - const formattedData = timeStamps.map((timeStamp: string): {tick: number, [key: string]: number} => Object.assign({ + const formattedData = timeStamps.map((timeStamp: string): GraphScaleItem => Object.assign({ tick: Number(timeStamp) }, data[timeStamp])); const layers = d3.stack().keys(keys)(formattedData); @@ -227,92 +86,10 @@ export class TimeHistogramComponent extends GraphComponent implements OnInit { .attr('height', item => this.yScale(item[0]) - this.yScale(item[1])) .attr('width', columnWidth.toString()) .style('fill', (item, index) => this.orderedColors[index]) - .on('mouseover', this.handleRectMouseOver) - .on('mousemove', this.handleRectMouseMove) - .on('mouseout', this.handleRectMouseOut); + .on('mouseover', this.handleMouseOver) + .on('mousemove', this.handleMouseMove) + .on('mouseout', this.handleMouseOut); this.setDragBehavior(); } - private getTimeRangeByXRanges(startX: number, endX: number): [number, number] { - const xScaleInterval = this.xScale.domain().map((point: Date): number => point.valueOf()); - const xScaleLength = xScaleInterval[1] - xScaleInterval[0]; - const ratio = xScaleLength / this.width; - return [Math.round(xScaleInterval[0] + ratio * startX), Math.round(xScaleInterval[0] + ratio * endX)]; - } - - /** - * The goal is to create the two shadow rectangle beside the selected area. Actually we blurout the not selected - * timeranges - * @param {number} startX This is the starting position of the drag event withing the container - * @param {number} currentX This is the ending point of the drag within the container - */ - private createInvertDragArea(startX: number, currentX: number): void { - const height: number = this.height + this.margin.top + this.margin.bottom; - this.leftDragArea = this.svg.insert('rect').attr('height', height).attr('class', 'unselected-drag-area'); - this.rightDragArea = this.svg.insert('rect').attr('height', height).attr('class', 'unselected-drag-area'); - this.setInvertDragArea(startX, currentX); - } - - /** - * Set the position and the width of the blur/shadow rectangles of the unselected area(s). - * @param {number} startX The start point of the selected area. - * @param {number} currentX The end point of the selected area. - */ - private setInvertDragArea(startX: number, currentX: number): void { - const left: number = Math.min(startX, currentX); - const right: number = Math.max(startX, currentX); - const rightAreaWidth: number = Math.max(0, this.width - right); - const leftAreaWidth: number = Math.max(0, left); - this.leftDragArea.attr('x', 0).attr('width', leftAreaWidth); - this.rightDragArea.attr('x', right).attr('width', rightAreaWidth); - } - - /** - * The goal is to have a single point where we remove the rectangles of the blur/shadow, unselected time range(s) - */ - private clearInvertDragArea(): void { - this.leftDragArea.remove(); - this.rightDragArea.remove(); - } - - private setDragBehavior(): void { - this.minDragX = this.margin.left; - this.maxDragX = this.graphContainer.clientWidth; - d3.selectAll(`svg#${this.svgId}`).call(d3.drag() - .on('start', (datum: undefined, index: number, containers: d3.ContainerElement[]): void => { - if (this.dragArea) { - this.dragArea.remove(); - } - this.dragStartX = Math.max(0, this.getDragX(containers[0]) - this.margin.left); - this.dragArea = this.svg.insert('rect', ':first-child').attr('x', this.dragStartX).attr('y', 0).attr('width', 0) - .attr('height', this.height).attr('class', 'drag-area'); - }) - .on('drag', (datum: undefined, index: number, containers: d3.ContainerElement[]): void => { - const mousePos = this.getDragX(containers[0]); - const currentX = Math.max(mousePos, this.minDragX) - this.margin.left; - 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); - this.setChartTimeGap(new Date(timeRange[0]), new Date(timeRange[1])); - }) - .on('end', (): void => { - const dragAreaDetails = this.dragArea.node().getBBox(); - const startX = Math.max(0, dragAreaDetails.x); - const endX = Math.min(this.width, dragAreaDetails.x + dragAreaDetails.width); - const dateRange: [number, number] = this.getTimeRangeByXRanges(startX, endX); - this.selectArea.emit(dateRange); - this.dragArea.remove(); - this.setChartTimeGap(new Date(dateRange[0]), new Date(dateRange[1])); - }) - ); - d3.selectAll(`svg#${this.svgId} .value, svg#${this.svgId} .axis`).call(d3.drag().on('start', (): void => { - d3.event.sourceEvent.stopPropagation(); - })); - } - - private getDragX(element: d3.ContainerElement): number { - return d3.mouse(element)[0]; - } - } http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.html new file mode 100644 index 0000000..e2ca744 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.html @@ -0,0 +1,27 @@ +<!-- + 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 #graphContainer></div> +<footer *ngIf="firstDateTick || lastDateTick"> + <div *ngIf="firstDateTick">{{firstDateTick | amTz: timeZone | amDateFormat: historyStartEndTimeFormat}}</div> + <div *ngIf="lastDateTick">{{lastDateTick | amTz: timeZone | amDateFormat: historyStartEndTimeFormat}}</div> +</footer> +<graph-legend class="col-md-12" [items]="legendItems"></graph-legend> +<graph-tooltip #tooltip [data]="tooltipInfo.data" [ngClass]="{'hide': !tooltipInfo.data}" + [style.top]="tooltipInfo.data ? tooltipPosition.top + 'px' : ''" + [style.left]="tooltipInfo.data ? tooltipPosition.left + 'px' : ''" + [title]="tooltipInfo.title | amTz: timeZone | amDateFormat: tickTimeFormat"></graph-tooltip> http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.less new file mode 100644 index 0000000..5f14eee --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.less @@ -0,0 +1,42 @@ +/** + * 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. + */ + +@import '../variables'; + +:host { + /deep/ .line { + fill: none; + stroke-width: 1.5px; + } + + /deep/ .grid-line-area { + fill: transparent; + + + .grid-line { + stroke: transparent; + } + + &.visible-grid-line-area { + cursor: pointer; + + + .grid-line { + stroke: @base-font-color; + } + } + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.spec.ts new file mode 100644 index 0000000..b58717b --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.spec.ts @@ -0,0 +1,69 @@ +/** + * 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. + */ + +import {Injector, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; +import {MomentModule} from 'angular2-moment'; +import {MomentTimezoneModule} from 'angular-moment-timezone'; +import {TranslationModules} from '@app/test-config.spec'; +import {StoreModule} from '@ngrx/store'; +import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; +import {UtilsService} from '@app/services/utils.service'; +import {ServiceInjector} from '@app/classes/service-injector'; +import {GraphLegendComponent} from '@app/components/graph-legend/graph-legend.component'; + +import {TimeLineGraphComponent} from './time-line-graph.component'; + +describe('TimeLineGraphComponent', () => { + let component: TimeLineGraphComponent; + let fixture: ComponentFixture<TimeLineGraphComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + TimeLineGraphComponent, + GraphLegendComponent + ], + imports: [ + MomentModule, + MomentTimezoneModule, + ...TranslationModules, + StoreModule.provideStore({ + appSettings + }) + ], + providers: [ + UtilsService, + AppSettingsService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(inject([Injector], (injector: Injector) => { + ServiceInjector.injector = injector; + fixture = TestBed.createComponent(TimeLineGraphComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.ts new file mode 100644 index 0000000..2f0b450 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-line-graph/time-line-graph.component.ts @@ -0,0 +1,111 @@ +/** + * 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. + */ + +import {Component, Input} from '@angular/core'; +import * as d3 from 'd3'; +import {GraphScaleItem, GraphLinePoint, GraphLineData} from '@app/classes/graph'; +import {TimeGraphComponent} from '@app/classes/components/graph/time-graph.component'; + +@Component({ + selector: 'time-line-graph', + templateUrl: './time-line-graph.component.html', + styleUrls: [ + '../../classes/components/graph/graph.component.less', '../../classes/components/graph/time-graph.component.less', + './time-line-graph.component.less' + ] +}) +export class TimeLineGraphComponent extends TimeGraphComponent { + + @Input() + pointRadius: number = 3.5; + + protected populate(): void { + const keys = Object.keys(this.colors), + data = this.data, + timeStamps = Object.keys(data), + dataForDomain = timeStamps.map((timeStamp: string): GraphScaleItem => Object.assign({ + tick: Number(timeStamp) + }, data[timeStamp])), + dataForSvg = keys.map((key: string): GraphLineData => { + return { + points: timeStamps.map((timeStamp: string): GraphScaleItem => { + return { + tick: Number(timeStamp), + y: data[timeStamp][key] + }; + }), + key: key + }; + }), + line = d3.line<GraphScaleItem>().x(item => this.xScale(item.tick)).y(item => this.yScale(item.y)); + + // after we have the data we set the domain values both scales + this.setXScaleDomain(dataForDomain); + this.setYScaleDomain(); + + // drawing the axis + this.drawXAxis(); + this.drawYAxis(); + + // populate the data and drawing the lines and points + const layer = this.svg.selectAll().data(dataForSvg); + layer.enter().append('path') + .attr('class', 'line').attr('d', (item: GraphLineData) => line(item.points)) + .style('stroke', (item: GraphLineData): string => this.colors[item.key]); + layer.enter().append('g').selectAll('circle') + .data((item: GraphLineData): GraphLinePoint[] => item.points.map((point: GraphScaleItem): GraphLinePoint => { + return Object.assign({}, point, { + color: this.colors[item.key] + }); + })) + .enter().append('circle') + .attr('cx', (item: GraphLinePoint): number => this.xScale(item.tick)) + .attr('cy', (item: GraphLinePoint): number => this.yScale(item.y)) + .attr('r', this.pointRadius) + .style('fill', (item: GraphLinePoint): string => item.color); + const gridLinesParent = this.svg.selectAll().data(dataForDomain).enter().append('g').selectAll() + .data((item: GraphScaleItem): GraphScaleItem[] => [item]).enter(); + gridLinesParent.append('rect').attr('class', 'grid-line-area') + .attr('x', (item: GraphScaleItem): number => this.xScale(item.tick) - this.pointRadius).attr('y', 0) + .style('width', `${this.pointRadius * 2}px`).style('height', `${this.height}px`) + .on('mouseover', (d: GraphScaleItem, index: number, elements: HTMLElement[]): void => { + elements.forEach((element: HTMLElement) => element.classList.add('visible-grid-line-area')); + this.handleMouseOver(Object.assign([], d, { + data: d + }), index, elements); + }) + .on('mousemove', this.handleMouseMove) + .on('mouseout', (d: GraphScaleItem, index: number, elements: HTMLElement[]): void => { + elements.forEach((element: HTMLElement) => element.classList.remove('visible-grid-line-area')); + this.handleMouseOut(); + }); + gridLinesParent.append('line').attr('class', 'grid-line') + .attr('x1', (item: GraphScaleItem): number => this.xScale(item.tick)) + .attr('x2', (item: GraphScaleItem): number => this.xScale(item.tick)) + .attr('y1', 0).attr('y2', this.height); + this.setDragBehavior(); + } + + protected setYScaleDomain(): void { + const keys = Object.keys(this.data), + maxValues = keys.map((currentKey: string): number => this.utils.getMaxNumberInObject(this.data[currentKey]), 0), + maximum = Math.max(...maxValues); + this.yScale.domain([0, maximum]); + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts index 43e4bd5..e3034b0 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.spec.ts @@ -27,9 +27,12 @@ import {ComponentsService, components} from '@app/services/storage/components.se import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {AuditLogsGraphDataService, auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; -import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; +import { + ServiceLogsHistogramDataService, serviceLogsHistogramData +} from '@app/services/storage/service-logs-histogram-data.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {HttpClientService} from '@app/services/http-client.service'; @@ -61,6 +64,7 @@ describe('TimeRangePickerComponent', () => { hosts, auditLogs, auditLogsFields, + auditLogsGraphData, serviceLogs, serviceLogsFields, serviceLogsHistogramData, @@ -82,6 +86,7 @@ describe('TimeRangePickerComponent', () => { HostsService, AuditLogsService, AuditLogsFieldsService, + AuditLogsGraphDataService, ServiceLogsService, ServiceLogsFieldsService, ServiceLogsHistogramDataService, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts index ab56589..1772ec0 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts @@ -26,9 +26,12 @@ import {ComponentsService, components} from '@app/services/storage/components.se import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {AuditLogsGraphDataService, auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; -import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; +import { + ServiceLogsHistogramDataService, serviceLogsHistogramData +} from '@app/services/storage/service-logs-histogram-data.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {ComponentActionsService} from '@app/services/component-actions.service'; @@ -68,6 +71,7 @@ describe('TimeZonePickerComponent', () => { hosts, auditLogs, auditLogsFields, + auditLogsGraphData, serviceLogs, serviceLogsFields, serviceLogsHistogramData, @@ -84,6 +88,7 @@ describe('TimeZonePickerComponent', () => { HostsService, AuditLogsService, AuditLogsFieldsService, + AuditLogsGraphDataService, ServiceLogsService, ServiceLogsFieldsService, ServiceLogsHistogramDataService, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts index ba5b613..ce4fa1c 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.spec.ts @@ -24,6 +24,7 @@ import {TranslationModules} from '@app/test-config.spec'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {AuditLogsGraphDataService, auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; import { ServiceLogsHistogramDataService, serviceLogsHistogramData @@ -60,6 +61,7 @@ describe('TopMenuComponent', () => { auditLogs, serviceLogs, auditLogsFields, + auditLogsGraphData, serviceLogsFields, serviceLogsHistogramData, serviceLogsTruncated, @@ -82,6 +84,7 @@ describe('TopMenuComponent', () => { AuditLogsService, ServiceLogsService, AuditLogsFieldsService, + AuditLogsGraphDataService, ServiceLogsFieldsService, ServiceLogsHistogramDataService, ServiceLogsTruncatedService, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts index aa86f4f..1e1246b 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts @@ -128,28 +128,44 @@ export const mockData = { { dataCount: [ { - name: 'n0', - value: 1 + name: currentTime.toISOString(), + value: '75' }, { - name: 'n1', - value: 2 + name: currentTime.clone().subtract(20, 'm').toISOString(), + value: '100' + }, + { + name: currentTime.clone().subtract(40, 'm').toISOString(), + value: '75' + }, + { + name: currentTime.clone().subtract(1, 'h').toISOString(), + value: '50' } ], - name: 'graph0' + name: 'AMBARI' }, { dataCount: [ { - name: 'n2', - value: 10 + name: currentTime.toISOString(), + value: '150' }, { - name: 'n3', - value: 20 + name: currentTime.clone().subtract(20, 'm').toISOString(), + value: '50' + }, + { + name: currentTime.clone().subtract(40, 'm').toISOString(), + value: '75' + }, + { + name: currentTime.clone().subtract(1, 'h').toISOString(), + value: '100' } ], - name: 'graph1' + name: 'HDFS' } ] }, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts index c2cee8d..952c542 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts @@ -27,8 +27,11 @@ import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {AuditLogsGraphDataService, auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; -import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; +import { + ServiceLogsHistogramDataService, serviceLogsHistogramData +} from '@app/services/storage/service-logs-histogram-data.service'; import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {HttpClientService} from '@app/services/http-client.service'; @@ -59,6 +62,7 @@ describe('ComponentActionsService', () => { auditLogs, serviceLogs, auditLogsFields, + auditLogsGraphData, serviceLogsFields, serviceLogsHistogramData, serviceLogsTruncated, @@ -76,6 +80,7 @@ describe('ComponentActionsService', () => { AuditLogsService, ServiceLogsService, AuditLogsFieldsService, + AuditLogsGraphDataService, ServiceLogsFieldsService, ServiceLogsHistogramDataService, ServiceLogsTruncatedService, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts index 3f65cd1..a8ab5e8 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-generator.service.spec.ts @@ -23,8 +23,11 @@ import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {AuditLogsGraphDataService, auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; -import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/services/storage/service-logs-histogram-data.service'; +import { + ServiceLogsHistogramDataService, serviceLogsHistogramData +} from '@app/services/storage/service-logs-histogram-data.service'; import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; import {AppStateService, appState} from '@app/services/storage/app-state.service'; import {ClustersService, clusters} from '@app/services/storage/clusters.service'; @@ -53,6 +56,7 @@ describe('ComponentGeneratorService', () => { auditLogs, serviceLogs, auditLogsFields, + auditLogsGraphData, serviceLogsFields, serviceLogsHistogramData, appSettings, @@ -75,6 +79,7 @@ describe('ComponentGeneratorService', () => { AuditLogsService, ServiceLogsService, AuditLogsFieldsService, + AuditLogsGraphDataService, ServiceLogsFieldsService, ServiceLogsHistogramDataService, AppSettingsService, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts index 6f98bf5..1650d90 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/http-client.service.ts @@ -24,6 +24,7 @@ import { } from '@angular/http'; import {HomogeneousObject} from '@app/classes/object'; import {AuditLogsListQueryParams} from '@app/classes/queries/audit-logs-query-params'; +import {AuditLogsGraphQueryParams} from '@app/classes/queries/audit-logs-graph-query-params'; import {AuditLogsTopResourcesQueryParams} from '@app/classes/queries/audit-logs-top-resources-query-params'; import {ServiceLogsQueryParams} from '@app/classes/queries/service-logs-query-params'; import {ServiceLogsHistogramQueryParams} from '@app/classes/queries/service-logs-histogram-query-params'; @@ -47,6 +48,10 @@ export class HttpClientService extends Http { url: 'audit/logs', params: opts => new AuditLogsListQueryParams(opts) }, + auditLogsGraph: { + url: 'audit/logs/bargraph', + params: opts => new AuditLogsGraphQueryParams(opts) + }, auditLogsFields: { url: 'audit/logs/schema/fields' }, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts index acdc0c2..5961309 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.spec.ts @@ -22,6 +22,7 @@ import {StoreModule} from '@ngrx/store'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {AuditLogsGraphDataService, auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; import { ServiceLogsHistogramDataService, serviceLogsHistogramData @@ -55,6 +56,7 @@ describe('LogsContainerService', () => { auditLogs, serviceLogs, auditLogsFields, + auditLogsGraphData, serviceLogsFields, serviceLogsHistogramData, appSettings, @@ -71,6 +73,7 @@ describe('LogsContainerService', () => { AuditLogsService, ServiceLogsService, AuditLogsFieldsService, + AuditLogsGraphDataService, ServiceLogsFieldsService, ServiceLogsHistogramDataService, AppSettingsService, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts index 78bcdb1..4c8f2a3 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/logs-container.service.ts @@ -30,6 +30,7 @@ import * as moment from 'moment-timezone'; import {HttpClientService} from '@app/services/http-client.service'; import {AuditLogsService} from '@app/services/storage/audit-logs.service'; import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service'; +import {AuditLogsGraphDataService} from '@app/services/storage/audit-logs-graph-data.service'; import {ServiceLogsService} from '@app/services/storage/service-logs.service'; import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service'; import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service'; @@ -61,13 +62,13 @@ import {CommonEntry} from '@app/classes/models/common-entry'; export class LogsContainerService { constructor( - private httpClient: HttpClientService, private appState: AppStateService, - private appSettings: AppSettingsService, private auditLogsStorage: AuditLogsService, - private auditLogsFieldsStorage: AuditLogsFieldsService, private serviceLogsStorage: ServiceLogsService, - private serviceLogsFieldsStorage: ServiceLogsFieldsService, private tabsStorage: TabsService, + private httpClient: HttpClientService, private tabsStorage: TabsService, private componentsStorage: ComponentsService, + private hostsStorage: HostsService, private appState: AppStateService, private auditLogsStorage: AuditLogsService, + private auditLogsGraphStorage: AuditLogsGraphDataService, private auditLogsFieldsStorage: AuditLogsFieldsService, + private serviceLogsStorage: ServiceLogsService, private serviceLogsFieldsStorage: ServiceLogsFieldsService, private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private clustersStorage: ClustersService, - private componentsStorage: ComponentsService, private hostsStorage: HostsService, - private serviceLogsTruncatedStorage: ServiceLogsTruncatedService + private serviceLogsTruncatedStorage: ServiceLogsTruncatedService, private appSettings: AppSettingsService + ) { const formItems = Object.keys(this.filters).reduce((currentObject: any, key: string): HomogeneousObject<FormControl> => { let formControl = new FormControl(), @@ -498,7 +499,7 @@ export class LogsContainerService { query: ['includeQuery', 'excludeQuery'] }; - private readonly histogramFilters = { + private readonly graphFilters = { clusters: ['clusters'], timeRange: ['to', 'from'], components: ['mustBe'], @@ -518,7 +519,9 @@ export class LogsContainerService { // TODO add all the required fields listFilters: ['clusters', 'timeRange', 'auditLogsSorting', 'pageSize', 'page', 'query'], topResourcesFilters: ['clusters', 'timeRange', 'query'], - histogramFilters: ['clusters', 'timeRange', 'query'] + graphFilters: ['clusters', 'timeRange', 'query'], + graphRequestName: 'auditLogsGraph', + graphModel: this.auditLogsGraphStorage }, serviceLogs: { logsModel: this.serviceLogsStorage, @@ -526,7 +529,9 @@ export class LogsContainerService { listFilters: [ 'clusters', 'timeRange', 'components', 'levels', 'hosts', 'serviceLogsSorting', 'pageSize', 'page', 'query' ], - histogramFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'query'] + graphFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'query'], + graphRequestName: 'serviceLogsHistogram', + graphModel: this.serviceLogsHistogramStorage } }; @@ -645,19 +650,17 @@ export class LogsContainerService { this.totalCount = count; } }); - if (logsType === 'serviceLogs') { - // TODO rewrite to implement conditional data loading for service logs histogram or audit logs graph - this.httpClient.get('serviceLogsHistogram', this.getParams('histogramFilters')).subscribe((response: Response): void => { - const jsonResponse = response.json(); - this.serviceLogsHistogramStorage.clear(); - if (jsonResponse) { - const histogramData = jsonResponse.graphData; - if (histogramData) { - this.serviceLogsHistogramStorage.addInstances(histogramData); - } + this.httpClient.get(this.logsTypeMap[logsType].graphRequestName, this.getParams('graphFilters')).subscribe((response: Response): void => { + const jsonResponse = response.json(), + model = this.logsTypeMap[logsType].graphModel; + model.clear(); + if (jsonResponse) { + const graphData = jsonResponse.graphData; + if (graphData) { + model.addInstances(graphData); } - }); - } + } + }); if (logsType === 'auditLogs') { this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', { field: 'resource' @@ -752,21 +755,23 @@ export class LogsContainerService { return Object.assign({}, params, additionalParams); } - getHistogramData(data: BarGraph[]): HomogeneousObject<HomogeneousObject<number>> { - let histogramData = {}; + getGraphData(data: BarGraph[], keys?: string[]): HomogeneousObject<HomogeneousObject<number>> { + let graphData = {}; data.forEach(type => { const name = type.name; type.dataCount.forEach(entry => { const timeStamp = new Date(entry.name).valueOf(); - if (!histogramData[timeStamp]) { + if (!graphData[timeStamp]) { let initialValue = {}; - Object.keys(this.colors).forEach(key => initialValue[key] = 0); - histogramData[timeStamp] = initialValue; + if (keys) { + keys.forEach((key: string) => initialValue[key] = 0); + } + graphData[timeStamp] = initialValue; } - histogramData[timeStamp][name] = Number(entry.value); + graphData[timeStamp][name] = Number(entry.value); }); }); - return histogramData; + return graphData; } loadColumnsNames(): void { http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/audit-logs-graph-data.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/audit-logs-graph-data.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/audit-logs-graph-data.service.ts new file mode 100644 index 0000000..eeb2780 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/audit-logs-graph-data.service.ts @@ -0,0 +1,32 @@ +/** + * 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. + */ + +import {Injectable} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppStore, CollectionModelService, getCollectionReducer} from '@app/classes/models/store'; + +export const modelName = 'auditLogsGraphData'; + +@Injectable() +export class AuditLogsGraphDataService extends CollectionModelService { + constructor(store: Store<AppStore>) { + super(modelName, store); + } +} + +export const auditLogsGraphData = getCollectionReducer(modelName); http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/reducers.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/reducers.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/reducers.service.ts index d700b16..3f54625 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/reducers.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/storage/reducers.service.ts @@ -30,6 +30,7 @@ import {serviceLogsHistogramData} from '@app/services/storage/service-logs-histo import {serviceLogsTruncated} from '@app/services/storage/service-logs-truncated.service'; import {serviceLogsFields} from '@app/services/storage/service-logs-fields.service'; import {auditLogsFields} from '@app/services/storage/audit-logs-fields.service'; +import {auditLogsGraphData} from '@app/services/storage/audit-logs-graph-data.service'; import {userConfigs} from '@app/services/storage/user-configs.service'; import {tabs} from '@app/services/storage/tabs.service'; @@ -37,6 +38,7 @@ export const reducers = { appSettings, appState, auditLogs, + auditLogsGraphData, serviceLogs, serviceLogsHistogramData, serviceLogsTruncated, http://git-wip-us.apache.org/repos/asf/ambari/blob/011e448e/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json index 48c1d88..923160a 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json +++ b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json @@ -161,6 +161,7 @@ "logs.showGraph": "Show Graph", "logs.topUsers": "Top {{number}} Users", "logs.topResources": "Top {{number}} Resources", + "logs.duration": "Duration", "histogram.gap": "gap", "histogram.gaps": "gaps",
