http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts new file mode 100644 index 0000000..14fa60e --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.spec.ts @@ -0,0 +1,50 @@ +/** + * 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 {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {TranslationModules} from '@app/test-config.spec'; +import {GraphLegendItemComponent} from '@app/components/graph-legend-item/graph-legend-item.component'; + +import {GraphTooltipComponent} from './graph-tooltip.component'; + +describe('GraphTooltipComponent', () => { + let component: GraphTooltipComponent; + let fixture: ComponentFixture<GraphTooltipComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + GraphTooltipComponent, + GraphLegendItemComponent + ], + imports: TranslationModules, + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GraphTooltipComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); +});
http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.ts new file mode 100644 index 0000000..9d26a2e --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/graph-tooltip/graph-tooltip.component.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. + */ + +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'graph-tooltip', + templateUrl: './graph-tooltip.component.html', + styleUrls: ['./graph-tooltip.component.less'] +}) +export class GraphTooltipComponent { + + @Input() + title: string | number = ''; + + @Input() + data = []; + + @Input() + labelClass: string = 'initial-color'; + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html new file mode 100644 index 0000000..015013f --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.html @@ -0,0 +1,22 @@ +<!-- + 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> +<graph-legend class="col-md-12" [items]="legendItems"></graph-legend> +<graph-tooltip #tooltip [data]="tooltipInfo.data" [title]="tooltipInfo.title" [ngClass]="{'hide': !tooltipInfo.data}" + [style.top]="tooltipInfo.data ? tooltipPosition.top + 'px' : ''" + [style.left]="tooltipInfo.data ? tooltipPosition.left + 'px' : ''"></graph-tooltip> http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.less new file mode 100644 index 0000000..395737a --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.less @@ -0,0 +1,22 @@ +/** + * 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 { + padding-top: @graph-padding; +} http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts new file mode 100644 index 0000000..2c59916 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.spec.ts @@ -0,0 +1,61 @@ +/** + * 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} from '@angular/core'; +import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; +import {TranslationModules} from '@app/test-config.spec'; +import {ServiceInjector} from '@app/classes/service-injector'; +import {GraphLegendComponent} from '@app/components/graph-legend/graph-legend.component'; +import {GraphLegendItemComponent} from '@app/components/graph-legend-item/graph-legend-item.component'; +import {GraphTooltipComponent} from '@app/components/graph-tooltip/graph-tooltip.component'; +import {UtilsService} from '@app/services/utils.service'; + +import {HorizontalHistogramComponent} from './horizontal-histogram.component'; + +describe('HorizontalHistogramComponent', () => { + let component: HorizontalHistogramComponent; + let fixture: ComponentFixture<HorizontalHistogramComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + HorizontalHistogramComponent, + GraphLegendComponent, + GraphLegendItemComponent, + GraphTooltipComponent + ], + imports: [ + ...TranslationModules + ], + providers: [ + UtilsService + ] + }) + .compileComponents(); + })); + + beforeEach(inject([Injector], (injector: Injector) => { + ServiceInjector.injector = injector; + fixture = TestBed.createComponent(HorizontalHistogramComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts new file mode 100644 index 0000000..9553e2e --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/horizontal-histogram/horizontal-histogram.component.ts @@ -0,0 +1,114 @@ +/** + * 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 {GraphComponent} from '@app/classes/components/graph/graph.component'; +import {HomogeneousObject} from '@app/classes/object'; + +@Component({ + selector: 'horizontal-histogram', + templateUrl: './horizontal-histogram.component.html', + styleUrls: ['../../classes/components/graph/graph.component.less', './horizontal-histogram.component.less'] +}) +export class HorizontalHistogramComponent extends GraphComponent { + + /** + * Thickness of horizontal bar o the graph + * @type {number} + */ + @Input() + barSize: number = 5; + + rowsCount: number; + + readonly reverseYRange: boolean = true; + + protected populate(): void { + const barSize = this.barSize, + data = this.data, + yValues = Object.keys(data), + keys = Object.keys(this.labels), + rowsCount = yValues.reduce((currentCount: number, currentKey: string): number => { + return currentCount + Object.keys(this.data[currentKey]).length; + }, 0), + formattedData = yValues.reduce((currentData, currentKey: string) => { + const currentValues = data[currentKey], + currentObjects = keys.map((key: string): HomogeneousObject<number> => { + return { + [key]: currentValues[key] || 0 + }; + }); + return [...currentData, Object.assign({ + tick: currentKey + }, ...currentObjects)]; + }, []), + layers = d3.stack().keys(keys)(formattedData), + formattedLayers = d3.transpose<any>(layers); + + this.rowsCount = rowsCount; + + this.setXScaleDomain(); + this.setYScaleDomain(); + + // drawing the axis + this.drawXAxis(); + this.drawYAxis(rowsCount); + + let i = 0; + + // populate the data and drawing the bars + this.svg.selectAll().data(formattedLayers).enter().append('g').attr('class', 'value') + .selectAll().data(item => item).enter().append('rect') + .attr('x', item => this.xScale(0) + 1).attr('y', item => { + if (item [0] !== item[1]) { + return this.yScale(i++) - this.barSize / 2; + } + }).attr('height', item => item[0] === item[1] ? '0' : barSize.toString()) + .attr('width', item => this.xScale(item[1]) - this.xScale(item[0])) + .style('fill', (item, index) => this.orderedColors[index]) + .on('mouseover', this.handleRectMouseOver) + .on('mousemove', this.handleRectMouseMove) + .on('mouseout', this.handleRectMouseOut); + } + + protected setXScaleDomain(): 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.xScale.domain([0, maximum]); + } + + protected setYScaleDomain(): void { + this.yScale.domain([0, this.rowsCount]); + } + + protected yAxisTickFormatter = (tick: any, index: number): string | undefined => { + const data = this.data, + keys = Object.keys(data); + let currentIndex = 0; + for (let i = 0; i < keys.length && i <= index; i++) { + const currentKey = keys[i]; + if (currentIndex === index) { + return currentKey; + } else { + currentIndex += Object.keys(data[currentKey]).length; + } + } + }; + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 d1b11e6..8e507ae 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 @@ -41,14 +41,14 @@ }}</div> </div> <collapsible-panel openTitle="logs.hideGraph" collapsedTitle="logs.showGraph"> - <time-histogram [data]="histogramData" [customOptions]="histogramOptions" svgId="service-logs-histogram" + <time-histogram [data]="histogramData" [colors]="serviceLogsHistogramColors" svgId="service-logs-histogram" (selectArea)="setCustomTimeRange($event[0], $event[1])"></time-histogram> </collapsible-panel> <ng-container [ngSwitch]="logsType"> <service-logs-table *ngSwitchCase="'serviceLogs'" [totalCount]="totalCount" [logs]="serviceLogs | async" [columns]="serviceLogsColumns | async" [filtersForm]="filtersForm"></service-logs-table> - <audit-logs-table *ngSwitchCase="'auditLogs'" [totalCount]="totalCount" [logs]="auditLogs | async" - [columns]="auditLogsColumns | async" [filtersForm]="filtersForm"></audit-logs-table> + <audit-logs-entries *ngSwitchCase="'auditLogs'" [totalCount]="totalCount" [logs]="auditLogs | async" + [columns]="auditLogsColumns | async" [filtersForm]="filtersForm"></audit-logs-entries> </ng-container> <log-context *ngIf="isServiceLogContextView" [id]="activeLog.id" [hostName]="activeLog.host_name" [componentName]="activeLog.component_name"></log-context> http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 cf28a8b..6d50a17 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,8 +28,8 @@ import {ServiceLog} from '@app/classes/models/service-log'; import {Tab} from '@app/classes/models/tab'; import {BarGraph} from '@app/classes/models/bar-graph'; import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; -import {HistogramOptions} from '@app/classes/histogram-options'; import {ListItem} from '@app/classes/list-item'; +import {HomogeneousObject} from '@app/classes/object'; import {LogsType} from '@app/classes/string'; import {FiltersPanelComponent} from "@app/components/filters-panel/filters-panel.component"; @@ -74,11 +74,11 @@ export class LogsContainerComponent { return this.logsContainer.totalCount; } - histogramData: {[key: string]: number}; + histogramData: HomogeneousObject<HomogeneousObject<number>>; - readonly histogramOptions: HistogramOptions = { - keysWithColors: this.logsContainer.colors - }; + get serviceLogsHistogramColors(): HomogeneousObject<string> { + return this.logsContainer.colors; + } get autoRefreshRemainingSeconds(): number { return this.logsContainer.autoRefreshRemainingSeconds; http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts index 3836e7a..67d9423 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts @@ -16,9 +16,10 @@ * limitations under the License. */ -import {NO_ERRORS_SCHEMA} from '@angular/core'; -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {NO_ERRORS_SCHEMA, Injector} from '@angular/core'; +import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; import {TranslationModules} from '@app/test-config.spec'; +import {ServiceInjector} from '@app/classes/service-injector'; import {StoreModule} from '@ngrx/store'; import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; import {AppStateService, appState} from '@app/services/storage/app-state.service'; @@ -97,11 +98,12 @@ describe('MenuButtonComponent', () => { .compileComponents(); })); - beforeEach(() => { + beforeEach(inject([Injector], (injector: Injector) => { + ServiceInjector.injector = injector; fixture = TestBed.createComponent(MenuButtonComponent); component = fixture.componentInstance; fixture.detectChanges(); - }); + })); it('should create component', () => { expect(component).toBeTruthy(); http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts index ca89935..12da4ac 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts @@ -18,6 +18,7 @@ import {Component, Input, ViewChild, ElementRef} from '@angular/core'; import {ListItem} from '@app/classes/list-item'; +import {ServiceInjector} from '@app/classes/service-injector'; import {ComponentActionsService} from '@app/services/component-actions.service'; @Component({ @@ -27,7 +28,8 @@ import {ComponentActionsService} from '@app/services/component-actions.service'; }) export class MenuButtonComponent { - constructor(protected actions: ComponentActionsService) { + constructor() { + this.actions = ServiceInjector.injector.get(ComponentActionsService); } @ViewChild('dropdown') @@ -82,6 +84,8 @@ export class MenuButtonComponent { @Input() maxLongClickDelay: number = 0; + private actions: ComponentActionsService; + /** * This is a private property to indicate the mousedown timestamp, so that we can check it when teh click event * has been triggered. http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts index b2136f4..9dd8e26 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts @@ -21,6 +21,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {Subject} from 'rxjs/Subject'; import {SearchBoxParameter, SearchBoxParameterProcessed, SearchBoxParameterTriggered} from '@app/classes/filtering'; import {ListItem} from '@app/classes/list-item'; +import {HomogeneousObject} from '@app/classes/object'; import {UtilsService} from '@app/services/utils.service'; @Component({ @@ -88,7 +89,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess items: ListItem[] = []; @Input() - itemsOptions: {[key: string]: ListItem[]} = {}; + itemsOptions: HomogeneousObject<ListItem[]> = {}; /** * Name of parameter to be used if there are no matching values http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts index 9f38371..84a59b9 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts @@ -18,7 +18,7 @@ import {Component, AfterViewInit, ViewChild, ElementRef} from '@angular/core'; import {ListItem} from '@app/classes/list-item'; -import {LogsTableComponent} from '@app/classes/components/logs-table-component'; +import {LogsTableComponent} from '@app/classes/components/logs-table/logs-table-component'; import {LogsContainerService} from '@app/services/logs-container.service'; import {UtilsService} from '@app/services/utils.service'; http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts index 2df5090..7eef0e1 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.spec.ts @@ -47,7 +47,6 @@ describe('TabsComponent', () => { let activeTab; const tab = { id: 'tab0', - type: '', isActive: true, label: '', appState: null @@ -64,21 +63,18 @@ describe('TabsComponent', () => { const items = [ { id: 'serviceLogs', - type: '', isActive: false, label: '', appState: null }, { id: 'auditLogs', - type: '', isActive: false, label: '', appState: null }, { id: 'newTab', - type: '', isActive: false, label: '', appState: null http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts index ef941e6..e202c2d 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/tabs/tabs.component.ts @@ -35,6 +35,7 @@ export class TabsComponent { tabClosed: EventEmitter<Tab[]> = new EventEmitter(); switchTab(tab: Tab): void { + this.items.forEach((item: Tab) => item.isActive = item.id === tab.id); this.tabSwitched.emit(tab); } http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html index 1193b2e..720f55e 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.html @@ -18,30 +18,20 @@ <header> <div class="container-fluid"> <div class="row"> - <div *ngIf="chartTimeGap" class="time-gap col-lg-2 col-md-offset-5"> + <div *ngIf="isTimeGraph && chartTimeGap" class="time-gap col-md-2 col-md-offset-5"> {{chartTimeGap.value}} {{chartTimeGap.label | translate}} {{'histogram.gap' | translate}} </div> - <div class="legends col-md-5" [class.md-offset-7]="!chartTimeGap"> - <div *ngFor="let legend of legends" class="legend {{legend.level | lowercase}}"> - {{ legend.label | translate }} - </div> - </div> + <graph-legend [ngClass]="{'col-md-5 text-right': true, 'md-offset-7': !chartTimeGap}" + [items]="legendItems"></graph-legend> </div> </div> </header> -<div #container></div> -<footer *ngIf="firstDateTick || lastDateTick"> - <div *ngIf="firstDateTick" class="first-date-tick-label">{{firstDateTick | amTz:timeZone | amDateFormat:historyStartEndTimeFormat}}</div> - <div *ngIf="lastDateTick" class="last-date-tick-label">{{lastDateTick | amTz:timeZone | amDateFormat:historyStartEndTimeFormat}}</div> +<div #graphContainer></div> +<footer *ngIf="isTimeGraph && (firstDateTick || lastDateTick)"> + <div *ngIf="firstDateTick">{{firstDateTick | amTz: timeZone | amDateFormat: historyStartEndTimeFormat}}</div> + <div *ngIf="lastDateTick">{{lastDateTick | amTz: timeZone | amDateFormat: historyStartEndTimeFormat}}</div> </footer> -<div [ngClass]="{hide: !tooltipInfo, 'tooltip-left': tooltipOnTheLeft, 'tooltip-chart': true}" #tooltipEl - [style.top]="tooltipInfo ? (tooltipPosition.top + 'px') : ''" [style.left]="tooltipInfo ? (tooltipPosition.left + 'px') : ''"> - <ng-container *ngIf="tooltipInfo"> - <div class="tooltip-chart-date">{{tooltipInfo.timeStamp | amTz:timeZone | amDateFormat:tickTimeFormat}}</div> - <div *ngFor="let data of tooltipInfo.data" class="level {{data.level | lowercase}}"> - <span class="level-label">{{data.levelLabel | translate }}</span> - <span class="level-value">{{data.value}}</span> - </div> - </ng-container> -</div> - +<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/aa5b0fe7/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 1d3766d..364517c 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 @@ -20,28 +20,11 @@ :host { position: relative; - .level-mixin(@level, @size: .8em) { - @name: "@{level}-color"; - border-radius: 100%; - content: ""; - display: inline-block; - height: .8em; - width: .8em; - background-color: @@name; - } - background: #ECECEC; // TODO add style according to actual design - display: block; /deep/ .axis { - .domain { - display: none; - } .tick { cursor: default; - line { - display: none; - } } } @@ -49,137 +32,21 @@ cursor: crosshair; } - /deep/ .value { - cursor: pointer; - rect { - transition: opacity 250ms; - opacity: .8; - &:hover { - opacity: 1; - } - } - } - - /deep/ .tooltip-chart { - background: #fff; - border-radius: 4px; - border: @input-border; - display: block; - font-size: .8em; - margin: 0 1.5em; - min-height: 2em; - min-width: 5em; - padding: .5em; - position: absolute; - &:empty { - display: none; - } - &::before { - .caret-mixin(6px, left, #fff); - left: -6px; - position: absolute; - top: calc(50% - 2px); - } - &.tooltip-left::before { - display: none; - } - &.tooltip-left::after { - .caret-mixin(6px, right, #fff); - right: -6px; - position: absolute; - top: calc(50% - 2px); - } - .tooltip-chart-date { - padding: 0 0 .1em 0; - text-align: center; - } - .level { - display: flex; - &::before { - margin: auto .2em auto 0; - } - .level-label { - flex-grow: 3; - padding: 0 2em 0 0; - } - .level-value { - text-align: right; - } - } - - .fatal::before { - .level-mixin('fatal'); - } - .error::before { - .level-mixin('error'); - } - .warn::before { - .level-mixin('warning'); - } - .info::before { - .level-mixin('info'); - } - .trace::before { - .level-mixin('trace'); - } - .debug::before { - .level-mixin('debug'); - } - .unknown::before { - .level-mixin('unknown'); - } - } header { - padding: .5rem; - } - .legends { - text-align: right; - .legend { - display: inline-block; - font-size: 1rem; - text-transform: uppercase; - padding-right: 1em; - } - .fatal::before { - .level-mixin('fatal'); - } - .error::before { - .level-mixin('error'); - } - .warn::before { - .level-mixin('warning'); - } - .info::before { - .level-mixin('info'); - } - .trace::before { - .level-mixin('trace'); - } - .debug::before { - .level-mixin('debug'); - } - .unknown::before { - .level-mixin('unknown'); - } + padding: @graph-padding; } .time-gap { - color: #666; + color: @base-font-color; font-size: 1.2rem; text-align: center; } footer { - display: flex; - div { - color: #666; - flex-grow: 1; - font-size: 1.2rem; - padding: 0 1em .5em 1em; - } - .last-date-tick-label { - text-align: right; - } + .default-flex; + font-size: 1.2rem; + color: @base-font-color; + padding: 0 1em .5em; } /deep/ rect.drag-area { http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts index ee14780..09cd5d8 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-histogram/time-histogram.component.spec.ts @@ -16,34 +16,41 @@ * limitations under the License. */ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {Injector} from '@angular/core'; +import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing'; import {StoreModule} from '@ngrx/store'; import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; import {TranslationModules} from '@app/test-config.spec'; import {MomentModule} from 'angular2-moment'; import {MomentTimezoneModule} from 'angular-moment-timezone'; +import {ServiceInjector} from '@app/classes/service-injector'; import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe'; +import {GraphLegendComponent} from '@app/components/graph-legend/graph-legend.component'; +import {GraphLegendItemComponent} from '@app/components/graph-legend-item/graph-legend-item.component'; +import {GraphTooltipComponent} from '@app/components/graph-tooltip/graph-tooltip.component'; import {ServiceLogsHistogramDataService} from '@app/services/storage/service-logs-histogram-data.service'; import {TimeHistogramComponent} from './time-histogram.component'; import {LogsContainerService} from '@app/services/logs-container.service'; -import {HttpClientService} from "@app/services/http-client.service"; -import {AppStateService} from "@app/services/storage/app-state.service"; -import {AuditLogsService} from "@app/services/storage/audit-logs.service"; -import {AuditLogsFieldsService} from "@app/services/storage/audit-logs-fields.service"; -import {ServiceLogsService} from "@app/services/storage/service-logs.service"; -import {ServiceLogsFieldsService} from "@app/services/storage/service-logs-fields.service"; -import {ServiceLogsTruncatedService} from "@app/services/storage/service-logs-truncated.service"; -import {TabsService} from "@app/services/storage/tabs.service"; -import {ClustersService} from "@app/services/storage/clusters.service"; -import {ComponentsService} from "@app/services/storage/components.service"; -import {HostsService} from "@app/services/storage/hosts.service"; +import {HttpClientService} from '@app/services/http-client.service'; +import {UtilsService} from '@app/services/utils.service'; +import {AppStateService} from '@app/services/storage/app-state.service'; +import {AuditLogsService} from '@app/services/storage/audit-logs.service'; +import {AuditLogsFieldsService} from '@app/services/storage/audit-logs-fields.service'; +import {ServiceLogsService} from '@app/services/storage/service-logs.service'; +import {ServiceLogsFieldsService} from '@app/services/storage/service-logs-fields.service'; +import {ServiceLogsTruncatedService} from '@app/services/storage/service-logs-truncated.service'; +import {TabsService} from '@app/services/storage/tabs.service'; +import {ClustersService} from '@app/services/storage/clusters.service'; +import {ComponentsService} from '@app/services/storage/components.service'; +import {HostsService} from '@app/services/storage/hosts.service'; +import {HomogeneousObject} from '@app/classes/object'; describe('TimeHistogramComponent', () => { let component: TimeHistogramComponent; let fixture: ComponentFixture<TimeHistogramComponent>; let histogramData: any; - let customOptions: any; + let colors: HomogeneousObject<string>; beforeEach(async(() => { const httpClient = { @@ -54,29 +61,42 @@ describe('TimeHistogramComponent', () => { } }; histogramData = { - "1512476481940": { - "FATAL": 0, - "ERROR": 1000, - "WARN": 700, - "INFO": 0, - "DEBUG": 0, - "TRACE": 0, - "UNKNOWN": 0 - }, "1512472881940": {"FATAL": 0, "ERROR": 2000, "WARN": 900, "INFO": 0, "DEBUG": 0, "TRACE": 0, "UNKNOWN": 0} - }; - customOptions = { - keysWithColors: { - FATAL: '#830A0A', - ERROR: '#E81D1D', - WARN: '#FF8916', - INFO: '#2577B5', - DEBUG: '#65E8FF', - TRACE: '#888', - UNKNOWN: '#BDBDBD' + 1512476481940: { + FATAL: 0, + ERROR: 1000, + WARN: 700, + INFO: 0, + DEBUG: 0, + TRACE: 0, + UNKNOWN: 0 + }, + 1512472881940: { + FATAL: 0, + ERROR: 2000, + WARN: 900, + INFO: 0, + DEBUG: 0, + TRACE: 0, + UNKNOWN: 0 } }; + colors = { + FATAL: '#830A0A', + ERROR: '#E81D1D', + WARN: '#FF8916', + INFO: '#2577B5', + DEBUG: '#65E8FF', + TRACE: '#888', + UNKNOWN: '#BDBDBD' + }; TestBed.configureTestingModule({ - declarations: [TimeHistogramComponent, TimeZoneAbbrPipe], + declarations: [ + TimeHistogramComponent, + GraphLegendComponent, + GraphLegendItemComponent, + GraphTooltipComponent, + TimeZoneAbbrPipe + ], imports: [ StoreModule.provideStore({ appSettings @@ -93,6 +113,7 @@ describe('TimeHistogramComponent', () => { provide: HttpClientService, useValue: httpClient }, + UtilsService, AppStateService, AuditLogsService, AuditLogsFieldsService, @@ -109,14 +130,15 @@ describe('TimeHistogramComponent', () => { .compileComponents(); })); - beforeEach(() => { - fixture = TestBed.createComponent(TimeHistogramComponent); - component = fixture.componentInstance; - component.customOptions = customOptions; - component.svgId = "HistogramSvg"; - component.data = histogramData; - fixture.detectChanges(); - }); + beforeEach(inject([Injector], (injector: Injector) => { + ServiceInjector.injector = injector; + fixture = TestBed.createComponent(TimeHistogramComponent); + component = fixture.componentInstance; + component.colors = colors; + component.svgId = 'HistogramSvg'; + component.data = histogramData; + fixture.detectChanges(); + })); it('should create component', () => { expect(component).toBeTruthy(); http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 fb3092f..8544677 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,100 +16,48 @@ * limitations under the License. */ -import {Component, OnInit, AfterViewInit, OnChanges, Input, Output, ViewChild, ElementRef, EventEmitter} from '@angular/core'; -import {ContainerElement, Selection} from 'd3'; +import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; 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'; +import {GraphComponent} from '@app/classes/components/graph/graph.component'; +import {GraphScaleItem} from '@app/classes/graph'; @Component({ selector: 'time-histogram', templateUrl: './time-histogram.component.html', - styleUrls: ['./time-histogram.component.less'] + styleUrls: ['../../classes/components/graph/graph.component.less', './time-histogram.component.less'] }) -export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges { +export class TimeHistogramComponent extends GraphComponent implements OnInit { - constructor(private appSettings: AppSettingsService) {} + constructor(private appSettings: AppSettingsService) { + super(); + } ngOnInit() { this.appSettings.getParameter('timeZone').subscribe((value: string): void => { this.timeZone = value; - this.createHistogram(); + this.createGraph(); }); - this.options = Object.assign({}, this.defaultOptions, this.customOptions); - } - - ngAfterViewInit() { - this.htmlElement = this.element.nativeElement; - this.tooltipElement = this.tooltipEl.nativeElement; - this.host = d3.select(this.htmlElement); - } - - ngOnChanges() { - this.createHistogram(); } - @ViewChild('container') - element: ElementRef; - - @ViewChild('tooltipEl') - tooltipEl: ElementRef; - - @Input() - svgId: string; - @Input() - customOptions: HistogramOptions; - - @Input() - data: {[key: string]: number}; + columnWidth = { + second: 40, + minute: 30, + hour: 25, + day: 20, + base: 20 + }; @Output() selectArea: EventEmitter<number[]> = new EventEmitter(); - private readonly defaultOptions: HistogramStyleOptions = { - margin: { - top: 5, - right: 50, - bottom: 30, - left: 50 - }, - height: 150, - tickPadding: 10, - columnWidth: { - second: 40, - minute: 30, - hour: 25, - day: 20, - base: 20 - } - }; - - private options: HistogramOptions; + readonly isTimeGraph: boolean = true; private timeZone: string; - private host; - - private svg; - - private width: number; - - private xScale; - - private yScale; - - private colorScale; - - private xAxis; - - private yAxis; - - private htmlElement: HTMLElement; - private tooltipElement: HTMLElement; - - private dragArea: Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>; + private dragArea: d3.Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>; private dragStartX: number; @@ -118,26 +66,12 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges private maxDragX: number; private readonly tickTimeFormat: string = 'MM/DD HH:mm'; - private readonly historyStartEndTimeFormat = 'dddd, MMMM DD, YYYY'; + + private readonly historyStartEndTimeFormat: string = 'dddd, MMMM DD, YYYY'; histogram: any; /** - * This property is to hold the data of the bar where the mouse is over. - */ - private tooltipInfo: {data: object, timeStamp: number}; - /** - * This is the computed position of the tooltip relative to the @htmlElement which is the container of the histogram. - * It is set when the mousemoving over the bars in the @handleRectMouseMove method. - */ - private tooltipPosition: {top: number, left: number}; - /** - * This property indicates if the tooltip should be positioned on the left side of the cursor or not. - * It should be true when the tooltip is out from the window. - * @type {boolean} - */ - private tooltipOnTheLeft: boolean = false; - /** * 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. @@ -146,11 +80,11 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges /** * This is the rectangle element to represent the unselected time range on the left side of the selected time range */ - private leftDragArea: Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>; + 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: Selection<SVGGraphicsElement, undefined, SVGGraphicsElement, undefined>; + 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. */ @@ -166,134 +100,12 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges return (ticks && ticks.length && ticks[ticks.length-1]) || undefined; } - /** - * 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. - * @returns {Array<{level: string; label: string; color: string}>} - */ - private get legends(): Array<{level: string, label: string, color: string}> { - return Object.keys(this.options.keysWithColors).map(level => Object.assign({},{ - level, - label: `levels.${level.toLowerCase()}`, - color: this.options.keysWithColors[level] - })); - } - - private createHistogram(): void { - if (this.host) { - this.setup(); - this.buildSVG(); - this.populate(); - } - } - - private setup(): void { - const margin = this.options.margin, - keysWithColors = this.options.keysWithColors, - keys = Object.keys(keysWithColors), - 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]); - this.colorScale = d3.scaleOrdinal(colors); - } - - private buildSVG(): void { - const margin = this.options.margin; - this.host.html(''); - 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})`); - } - - /** - * 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. - */ - private drawXAxis(): void { - this.xAxis = d3.axisBottom(this.xScale) - .tickFormat(tick => moment(tick).tz(this.timeZone).format(this.tickTimeFormat)) - .tickPadding(this.options.tickPadding); - this.svg.append('g').attr('class', 'axis axis-x').attr('transform', `translate(0,${this.options.height})`).call(this.xAxis); - } - - /** - * It draws the svg representation of the y axis. The goal is to set the ticks here, add the axis to the svg element - * and set the position of the axis. - */ - private drawYAxis(): void { - this.yAxis = d3.axisLeft(this.yScale).tickFormat((tick: number): string | undefined => { - if (Number.isInteger(tick)) { - return tick.toFixed(0); - } else { - return; - } - }).tickPadding(this.options.tickPadding); - this.svg.append('g').attr('class', 'axis axis-y').call(this.yAxis).append('text'); - }; - - /** - * The goal is to handle the mouse over event on the rect svg elements so that we can populate the tooltip info object - * and set the initial position of the tooltip. So we call the corresponding methods. - * @param d The data for the currently "selected" bar - * @param {number} index The index of the current element in the selection - * @param elements The selection of the elements - */ - private handleRectMouseOver = (d: any, index: number, elements: any):void => { - this.setTooltipDataFromChartData(d); - this.setTooltipPosition(); - }; - - /** - * The goal is to handle the movement of the mouse over the rect svg elements, so that we can set the position of - * the tooltip by calling the @setTooltipPosition method. - */ - private handleRectMouseMove = ():void => { - this.setTooltipPosition(); + protected xAxisTickFormatter = (tick: Date): string => { + return moment(tick).tz(this.timeZone).format(this.tickTimeFormat); }; - /** - * The goal is to reset the tooltipInfo object so that the tooltip will be hidden. - */ - private handleRectMouseOut = ():void => { - this.tooltipInfo = null; - }; - - /** - * The goal is set the tooltip - * @param d - */ - private setTooltipDataFromChartData(d: {data: any, [key: string]: any}): void { - let {timeStamp, ...data} = d.data; - let levelColors = this.options.keysWithColors; - this.tooltipInfo = { - data: Object.keys(levelColors).map(key => Object.assign({}, { - level: key, - levelLabel: `levels.${key.toLowerCase()}`, - value: data[key] - })), - timeStamp - }; - } - - /** - * The goal of this function is to set the tooltip position regarding the d3.mouse event relative to the @htmlElement. - * Onlty if we have @tooltipInfo - */ - private setTooltipPosition():void { - if (this.tooltipInfo) { - let tEl = this.tooltipElement; - let pos = d3.mouse(this.htmlElement); - let left = pos[0]; - let top = pos[1] - (tEl.offsetHeight / 2); - let tooltipWidth = tEl.offsetWidth; - let windowSize = window.innerWidth; - if (left + tooltipWidth > windowSize) { - left = pos[0] - (tooltipWidth + 25); - } - this.tooltipOnTheLeft = left < pos[0]; - this.tooltipPosition = {left, top}; - } + protected yAxisTickFormatter = (tick: number): string | undefined => { + return Number.isInteger(tick) ? tick.toFixed(0) : undefined; }; /** @@ -361,33 +173,33 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges /** * 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: [{timeStamp: 1233455677, WARN: 12, ERROR: 123}] - * @param {Array<{timeStamp: number; [p: string]: number}>} data + * An example data: [{tick: 1233455677, WARN: 12, ERROR: 123}] + * @param {GraphScaleItem[]} data */ - private setYScaleDomain(data: Array<{timeStamp: number, [key: string]: number}>): void { - const keys = Object.keys(this.options.keysWithColors); + 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: [{timeStamp: 1233455677, WARN: 12, ERROR: 123}] - * @param {Array<{timeStamp: number; [p: string]: any}>} data + * An example data: [{tick: 1233455677, WARN: 12, ERROR: 123}] + * @param {GraphScaleItem[]} data */ - private setXScaleDomain(data: Array<{timeStamp: number, [key: string]: any}>): void { - this.xScale.domain(d3.extent(data, item => item.timeStamp)).nice(); + protected setXScaleDomain(data: GraphScaleItem[]): void { + this.xScale.domain(d3.extent(data, item => item.tick)).nice(); } - private populate(): void { - const keys = Object.keys(this.options.keysWithColors); + 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): {timeStamp: number, [key: string]: number} => Object.assign({ - timeStamp: Number(timeStamp) + const formattedData = timeStamps.map((timeStamp: string): {tick: number, [key: string]: number} => Object.assign({ + tick: Number(timeStamp) }, data[timeStamp])); - const layers = (d3.stack().keys(keys)(formattedData)); + const layers = d3.stack().keys(keys)(formattedData); // after we have the data we set the domain values both scales this.setXScaleDomain(formattedData); @@ -396,39 +208,32 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges // Setting the timegap label above the chart this.setChartTimeGapByXScale(); - let unitD3TimeProp = this.chartTimeGap.unit.charAt(0).toUpperCase() + this.chartTimeGap.unit.slice(1); + const unitD3TimeProp = this.chartTimeGap.unit.charAt(0).toUpperCase() + this.chartTimeGap.unit.slice(1); this.xScale.nice(d3[`time${unitD3TimeProp}`], 2); - let columnWidth = this.options.columnWidth[this.chartTimeGap.unit] || this.options.columnWidth.base; + const columnWidth = this.columnWidth[this.chartTimeGap.unit] || this.columnWidth.base; // drawing the axis this.drawXAxis(); this.drawYAxis(); // populate the data and drawing the bars - const layer = this.svg.selectAll('.value').data(d3.transpose<any>(layers)) - .attr('class', 'value') - .enter().append('g') - .attr('class', 'value'); - layer.selectAll('.value rect').data(item => item) - .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)) - .enter().append('rect') - .attr('x', item => this.xScale(item.data.timeStamp) - columnWidth / 2) + 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.tick) - 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)) + .style('fill', (item, index) => this.orderedColors[index]) .on('mouseover', this.handleRectMouseOver) .on('mousemove', this.handleRectMouseMove) .on('mouseout', this.handleRectMouseOut); this.setDragBehavior(); } - private getTimeRangeByXRanges(startX: number, endX:number): [number, number] { + 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; @@ -442,7 +247,7 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges * @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.options.height + this.options.margin.top + this.options.margin.bottom; + 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); @@ -456,9 +261,8 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges private setInvertDragArea(startX: number, currentX: number): void { const left: number = Math.min(startX, currentX); const right: number = Math.max(startX, currentX); - let rightAreaWidth: number = this.width - right; - rightAreaWidth = rightAreaWidth > 0 ? rightAreaWidth : 0; - let leftAreaWidth: number = left > 0 ? left : 0; + 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); } @@ -472,20 +276,20 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges } private setDragBehavior(): void { - this.minDragX = this.options.margin.left; - this.maxDragX = this.htmlElement.clientWidth; + 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: ContainerElement[]): void => { + .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.options.margin.left); + 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.options.height).attr('class', 'drag-area'); + .attr('height', this.height).attr('class', 'drag-area'); }) - .on('drag', (datum: undefined, index: number, containers: ContainerElement[]): void => { + .on('drag', (datum: undefined, index: number, containers: d3.ContainerElement[]): void => { const mousePos = this.getDragX(containers[0]); - const currentX = Math.max(mousePos, this.minDragX) - this.options.margin.left; + 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); @@ -507,7 +311,7 @@ export class TimeHistogramComponent implements OnInit, AfterViewInit, OnChanges })); } - private getDragX(element: ContainerElement): number { + private getDragX(element: d3.ContainerElement): number { return d3.mouse(element)[0]; } http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts index 6df7ab3..8d739ec 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.ts @@ -20,6 +20,7 @@ import {Component} from '@angular/core'; import {FormGroup} from '@angular/forms'; import {FilterCondition, TimeUnitListItem} from '@app/classes/filtering'; import {ListItem} from '@app/classes/list-item'; +import {HomogeneousObject} from '@app/classes/object'; import {LogsContainerService} from '@app/services/logs-container.service'; @Component({ @@ -36,7 +37,7 @@ export class TopMenuComponent { return this.logsContainer.filtersForm; }; - get filters(): {[key: string]: FilterCondition} { + get filters(): HomogeneousObject<FilterCondition> { return this.logsContainer.filters; }; http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less index a9ca3b4..9b9bbfd 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less @@ -65,3 +65,6 @@ // Table @table-border-color: #EEE; + +// Graph +@graph-padding: .5rem; http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 7578867..aa86f4f 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts @@ -18,6 +18,8 @@ import * as moment from 'moment'; +const currentTime = moment(); + export const mockData = { login: {}, logout: {}, @@ -153,34 +155,82 @@ export const mockData = { }, components: {}, resources: { - graphData: [ - { - dataCount: [ - { - name: 'n16', - value: 800 - }, - { - name: 'n17', - value: 400 - } - ], - name: 'graph8' - }, - { - dataCount: [ - { - name: 'n18', - value: 600 - }, - { - name: 'n19', - value: 300 - } - ], - name: 'graph9' - } - ] + 6: { + graphData: [ + { + dataCount: [ + { + name: 'hdfs', + value: 800 + }, + { + name: 'zookeeper', + value: 400 + }, + { + name: 'ambari_metrics', + value: 200 + } + ], + name: 'admin' + }, + { + dataCount: [ + { + name: 'ambari_agent', + value: 400 + }, + { + name: 'hdfs', + value: 600 + }, + { + name: 'ambari_metrics', + value: 300 + } + ], + name: 'user' + } + ] + }, + 10: { + graphData: [ + { + dataCount: [ + { + name: 'ambari', + value: 800 + }, + { + name: 'hdfs', + value: 400 + }, + { + name: 'hbase', + value: 200 + }, + ], + name: '/user' + }, + { + dataCount: [ + { + name: 'hdfs', + value: 400 + }, + { + name: 'hbase', + value: 600 + }, + { + name: 'kafka', + value: 300 + } + ], + name: '/root' + } + ] + } }, schema: { fields: { @@ -299,7 +349,7 @@ export const mockData = { path: '/var/log/ambari-metrics-collector/ambari-metrics-collector.log', host: 'h0', level: 'WARN', - logtime: moment().valueOf(), + logtime: currentTime.valueOf(), ip: '192.168.0.1', logfile_line_number: 8, type: 'ams_collector', @@ -316,14 +366,14 @@ export const mockData = { event_md5: '1908755391', event_dur_ms: 200, _ttl_: '+5DAYS', - _expire_at_: moment().add(5, 'd').valueOf(), + _expire_at_: currentTime.clone().add(5, 'd').valueOf(), _router_field_: 20 }, { path: '/var/log/ambari-metrics-collector/ambari-metrics-collector.log', host: 'h1', level: 'ERROR', - logtime: moment().subtract(2, 'd').valueOf(), + logtime: currentTime.clone().subtract(2, 'd').valueOf(), ip: '192.168.0.2', type: 'ams_collector', _version_: 14, @@ -340,14 +390,14 @@ export const mockData = { event_md5: '1029384756', event_dur_ms: 700, _ttl_: '+5DAYS', - _expire_at_: moment().add(3, 'd').valueOf(), + _expire_at_: currentTime.clone().add(3, 'd').valueOf(), _router_field_: 5 }, { path: '/var/log/ambari-metrics-collector/ambari-metrics-collector.log', host: 'h1', level: 'FATAL', - logtime: moment().subtract(10, 'd').valueOf(), + logtime: currentTime.clone().subtract(10, 'd').valueOf(), ip: '192.168.0.3', type: 'ambari_agent', _version_: 14, @@ -364,14 +414,14 @@ export const mockData = { event_md5: '67589403', event_dur_ms: 100, _ttl_: '+5DAYS', - _expire_at_: moment().subtract(5, 'd').valueOf(), + _expire_at_: currentTime.clone().subtract(5, 'd').valueOf(), _router_field_: 45 }, { path: '/var/log/ambari-metrics-collector/zookeeper-server.log', host: 'h1', level: 'INFO', - logtime: moment().subtract(25, 'h').valueOf(), + logtime: currentTime.clone().subtract(25, 'h').valueOf(), ip: '192.168.0.4', type: 'zookeeper_server', _version_: 14, @@ -388,14 +438,14 @@ export const mockData = { event_md5: '67589403', event_dur_ms: 1000, _ttl_: '+5DAYS', - _expire_at_: moment().subtract(25, 'h').add(5, 'd').valueOf(), + _expire_at_: currentTime.clone().subtract(25, 'h').add(5, 'd').valueOf(), _router_field_: 55 }, { path: '/var/log/ambari-metrics-collector/zookeeper-server.log', host: 'h1', level: 'DEBUG', - logtime: moment().subtract(25, 'd').valueOf(), + logtime: currentTime.clone().subtract(25, 'd').valueOf(), ip: '192.168.0.4', type: 'zookeeper_server', _version_: 14, @@ -412,14 +462,14 @@ export const mockData = { event_md5: '67589403', event_dur_ms: 1000, _ttl_: '+5DAYS', - _expire_at_: moment().subtract(20, 'd').valueOf(), + _expire_at_: currentTime.clone().subtract(20, 'd').valueOf(), _router_field_: 55 }, { path: '/var/log/ambari-metrics-collector/zookeeper-client.log', host: 'h1', level: 'TRACE', - logtime: moment().subtract(2, 'h').valueOf(), + logtime: currentTime.clone().subtract(2, 'h').valueOf(), ip: '192.168.0.4', type: 'zookeeper_client', _version_: 14, @@ -436,14 +486,14 @@ export const mockData = { event_md5: '67589403', event_dur_ms: 1000, _ttl_: '+5DAYS', - _expire_at_: moment().subtract(2, 'h').add(5, 'd').valueOf(), + _expire_at_: currentTime.clone().subtract(2, 'h').add(5, 'd').valueOf(), _router_field_: 55 }, { path: '/var/log/ambari-metrics-collector/zookeeper-client.log', host: 'h1', level: 'UNKNOWN', - logtime: moment().subtract(31, 'd').valueOf(), + logtime: currentTime.clone().subtract(31, 'd').valueOf(), ip: '192.168.0.4', type: 'zookeeper_client', _version_: 14, @@ -460,7 +510,7 @@ export const mockData = { event_md5: '67589403', event_dur_ms: 1000, _ttl_: '+5DAYS', - _expire_at_: moment().subtract(26, 'd').valueOf(), + _expire_at_: currentTime.clone().subtract(26, 'd').valueOf(), _router_field_: 55 } ], @@ -637,11 +687,11 @@ export const mockData = { { dataCount: [ { - name: moment().toISOString(), + name: currentTime.toISOString(), value: '1000' }, { - name: moment().subtract(1, 'h').toISOString(), + name: currentTime.clone().subtract(1, 'h').toISOString(), value: '2000' } ], @@ -650,11 +700,11 @@ export const mockData = { { dataCount: [ { - name: moment().toISOString(), + name: currentTime.toISOString(), value: '700' }, { - name: moment().subtract(1, 'h').toISOString(), + name: currentTime.clone().subtract(1, 'h').toISOString(), value: '900' } ], http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts index 51b0c0b..36c4d8d 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts @@ -83,7 +83,6 @@ export class ComponentActionsService { openLog(log: ServiceLog): void { const tab = { id: log.id, - type: 'serviceLogs', isCloseable: true, label: `${log.host} >> ${log.type}`, appState: { http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 9b61bf6..6f98bf5 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 @@ -19,8 +19,12 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/first'; -import {Http, XHRBackend, Request, RequestOptions, RequestOptionsArgs, Response, Headers, URLSearchParams} from '@angular/http'; -import {AuditLogsQueryParams} from '@app/classes/queries/audit-logs-query-params'; +import { + Http, XHRBackend, Request, RequestOptions, RequestOptionsArgs, Response, Headers, URLSearchParams +} from '@angular/http'; +import {HomogeneousObject} from '@app/classes/object'; +import {AuditLogsListQueryParams} from '@app/classes/queries/audit-logs-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'; import {ServiceLogsTruncatedQueryParams} from '@app/classes/queries/service-logs-truncated-query-params'; @@ -41,7 +45,7 @@ export class HttpClientService extends Http { }, auditLogs: { url: 'audit/logs', - params: opts => new AuditLogsQueryParams(opts) + params: opts => new AuditLogsListQueryParams(opts) }, auditLogsFields: { url: 'audit/logs/schema/fields' @@ -69,14 +73,31 @@ export class HttpClientService extends Http { }, hosts: { url: 'service/logs/tree' + }, + topAuditLogsResources: { + url: variables => `audit/logs/resources/${variables.number}`, + params: opts => new AuditLogsTopResourcesQueryParams(opts) } }; private readonly unauthorizedStatuses = [401, 403, 419]; - private generateUrlString(url: string): string { + private generateUrlString(url: string, urlVariables?: HomogeneousObject<string>): string { const preset = this.endPoints[url]; - return preset ? `${this.apiPrefix}${preset.url}` : url; + let generatedUrl: string; + if (preset) { + const urlExpression = preset.url; + let path: string; + if (typeof urlExpression === 'function') { + path = preset.url(urlVariables); + } else if (typeof urlExpression === 'string') { + path = preset.url; + } + generatedUrl = `${this.apiPrefix}${path}`; + } else { + generatedUrl = url; + } + return generatedUrl; } private generateUrl(request: string | Request): string | Request { @@ -89,7 +110,7 @@ export class HttpClientService extends Http { } } - private generateOptions(url: string, params: {[key: string]: string}): RequestOptionsArgs { + private generateOptions(url: string, params: HomogeneousObject<string>): RequestOptionsArgs { const preset = this.endPoints[url], rawParams = preset && preset.params ? preset.params(params) : params; if (rawParams) { @@ -122,11 +143,11 @@ export class HttpClientService extends Http { return req; } - get(url, params?: {[key: string]: string}): Observable<Response> { - return super.get(this.generateUrlString(url), this.generateOptions(url, params)); + get(url, params?: HomogeneousObject<string>, urlVariables?: HomogeneousObject<string>): Observable<Response> { + return super.get(this.generateUrlString(url, urlVariables), this.generateOptions(url, params)); } - postFormData(url: string, params: {[key: string]: string}, options?: RequestOptionsArgs): Observable<Response> { + postFormData(url: string, params: HomogeneousObject<string>, options?: RequestOptionsArgs): Observable<Response> { const encodedParams = this.generateOptions(url, params).params; let body; if (encodedParams && encodedParams instanceof URLSearchParams) { http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 870058b..acdc0c2 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 @@ -23,7 +23,9 @@ import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.serv import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; import {AuditLogsFieldsService, auditLogsFields} from '@app/services/storage/audit-logs-fields.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'; http://git-wip-us.apache.org/repos/asf/ambari/blob/aa5b0fe7/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 d719893..78bcdb1 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 @@ -27,7 +27,6 @@ import 'rxjs/add/operator/first'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/takeUntil'; import * as moment from 'moment-timezone'; -import {TranslateService} from '@ngx-translate/core'; 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'; @@ -46,6 +45,7 @@ import { FilterCondition, TimeUnitListItem, SortingListItem, SearchBoxParameter, SearchBoxParameterTriggered } from '@app/classes/filtering'; import {ListItem} from '@app/classes/list-item'; +import {HomogeneousObject} from '@app/classes/object'; import {LogsType, ScrollType, SortingType} from '@app/classes/string'; import {Tab} from '@app/classes/models/tab'; import {LogField} from '@app/classes/models/log-field'; @@ -61,15 +61,15 @@ import {CommonEntry} from '@app/classes/models/common-entry'; export class LogsContainerService { constructor( - private translate: TranslateService, private httpClient: HttpClientService, - private auditLogsStorage: AuditLogsService, private auditLogsFieldsStorage: AuditLogsFieldsService, - private serviceLogsStorage: ServiceLogsService, private serviceLogsFieldsStorage: ServiceLogsFieldsService, - private serviceLogsHistogramStorage: ServiceLogsHistogramDataService, - private serviceLogsTruncatedStorage: ServiceLogsTruncatedService, private appState: AppStateService, - private appSettings: AppSettingsService, private tabsStorage: TabsService, private clustersStorage: ClustersService, - private componentsStorage: ComponentsService, private hostsStorage: HostsService + 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 serviceLogsHistogramStorage: ServiceLogsHistogramDataService, private clustersStorage: ClustersService, + private componentsStorage: ComponentsService, private hostsStorage: HostsService, + private serviceLogsTruncatedStorage: ServiceLogsTruncatedService ) { - const formItems = Object.keys(this.filters).reduce((currentObject: any, key: string): {[key: string]: FormControl} => { + const formItems = Object.keys(this.filters).reduce((currentObject: any, key: string): HomogeneousObject<FormControl> => { let formControl = new FormControl(), item = { [key]: formControl @@ -88,7 +88,7 @@ export class LogsContainerService { tabsStorage.mapCollection((tab: Tab): Tab => { let currentAppState = tab.appState || {}; const appState = Object.assign({}, currentAppState, { - activeFilters: this.getFiltersData(tab.type) + activeFilters: this.getFiltersData(tab.appState.activeLogsType) }); return Object.assign({}, tab, { appState @@ -120,7 +120,7 @@ export class LogsContainerService { private readonly paginationOptions: string[] = ['10', '25', '50', '100']; - filters: {[key: string]: FilterCondition} = { + filters: HomogeneousObject<FilterCondition> = { clusters: { label: 'filter.clusters', options: [], @@ -507,18 +507,25 @@ export class LogsContainerService { query: ['includeQuery', 'excludeQuery'] }; + readonly topResourcesCount: string = '10'; + + readonly topUsersCount: string = '6'; + readonly logsTypeMap = { auditLogs: { logsModel: this.auditLogsStorage, fieldsModel: this.auditLogsFieldsStorage, // TODO add all the required fields listFilters: ['clusters', 'timeRange', 'auditLogsSorting', 'pageSize', 'page', 'query'], + topResourcesFilters: ['clusters', 'timeRange', 'query'], histogramFilters: ['clusters', 'timeRange', 'query'] }, serviceLogs: { logsModel: this.serviceLogsStorage, fieldsModel: this.serviceLogsFieldsStorage, - listFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'serviceLogsSorting', 'pageSize', 'page', 'query'], + listFilters: [ + 'clusters', 'timeRange', 'components', 'levels', 'hosts', 'serviceLogsSorting', 'pageSize', 'page', 'query' + ], histogramFilters: ['clusters', 'timeRange', 'components', 'levels', 'hosts', 'query'] } }; @@ -620,6 +627,10 @@ export class LogsContainerService { private stopCaptureTime: number; + topUsersGraphData: HomogeneousObject<HomogeneousObject<number>> = {}; + + topResourcesGraphData: HomogeneousObject<HomogeneousObject<number>> = {}; + loadLogs = (logsType: LogsType = this.activeLogsType): void => { this.httpClient.get(logsType, this.getParams('listFilters')).subscribe((response: Response): void => { const jsonResponse = response.json(), @@ -647,6 +658,34 @@ export class LogsContainerService { } }); } + if (logsType === 'auditLogs') { + this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', { + field: 'resource' + }), { + number: this.topResourcesCount + }).subscribe((response: Response): void => { + const jsonResponse = response.json(); + if (jsonResponse) { + const data = jsonResponse.graphData; + if (data) { + this.topResourcesGraphData = this.parseAuditLogsTopData(data); + } + } + }); + this.httpClient.get('topAuditLogsResources', this.getParams('topResourcesFilters', { + field: 'reqUser' + }), { + number: this.topUsersCount + }).subscribe((response: Response): void => { + const jsonResponse = response.json(); + if (jsonResponse) { + const data = jsonResponse.graphData; + if (data) { + this.topUsersGraphData = this.parseAuditLogsTopData(data); + } + } + }); + } }; loadLogContext(id: string, hostName: string, componentName: string, scrollType: ScrollType = ''): void { @@ -680,7 +719,19 @@ export class LogsContainerService { }); } - private getParams(filtersMapName: string, logsType: LogsType = this.activeLogsType): {[key: string]: string} { + private parseAuditLogsTopData(data: BarGraph[]): HomogeneousObject<HomogeneousObject<number>> { + return data.reduce((currentObject: HomogeneousObject<HomogeneousObject<number>>, currentItem: BarGraph): HomogeneousObject<HomogeneousObject<number>> => Object.assign(currentObject, { + [currentItem.name]: currentItem.dataCount.reduce((currentDataObject: HomogeneousObject<number>, currentDataItem: CommonEntry): HomogeneousObject<number> => { + return Object.assign(currentDataObject, { + [currentDataItem.name]: currentDataItem.value + }); + }, {}) + }), {}); + } + + private getParams( + filtersMapName: string, additionalParams: HomogeneousObject<string> = {}, logsType: LogsType = this.activeLogsType + ): HomogeneousObject<string> { let params = {}; this.logsTypeMap[logsType][filtersMapName].forEach((key: string): void => { const inputValue = this.filtersForm.getRawValue()[key], @@ -698,10 +749,10 @@ export class LogsContainerService { } }); }, this); - return params; + return Object.assign({}, params, additionalParams); } - getHistogramData(data: BarGraph[]): {[key: string]: number} { + getHistogramData(data: BarGraph[]): HomogeneousObject<HomogeneousObject<number>> { let histogramData = {}; data.forEach(type => { const name = type.name; @@ -800,7 +851,9 @@ export class LogsContainerService { return (value: SearchBoxParameter[]): string => { let parameters; if (value && value.length) { - parameters = value.filter((item: SearchBoxParameter): boolean => item.isExclude === isExclude).map((parameter: SearchBoxParameter): {[key: string]: string} => { + parameters = value.filter((item: SearchBoxParameter): boolean => { + return item.isExclude === isExclude; + }).map((parameter: SearchBoxParameter): HomogeneousObject<string> => { return { [parameter.name]: parameter.value.replace(/\s/g, '+') };