http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less new file mode 100644 index 0000000..bd6d012 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.less @@ -0,0 +1,97 @@ +/** + * 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 '../mixins'; + +:host { + /deep/ filter-dropdown { + justify-content: flex-end; + } + + .panel-body { + overflow: hidden; + width: 100%; + } + + table { + width: 100%; + } + + tr.log-date-row, tr.log-date-row:hover { + background: @list-header-background-color; + border: none transparent; + th { + border: none transparent; + } + } + tr.log-item-row td { + background: none transparent; + } + + td { + &.log-action { + min-width: 3em; + /deep/ .btn, /deep/ .filter-label { + font-size: 1em; + height: auto; + line-height: 1em; + padding: 0; + } + } + &.log-time { + color: @grey-color; + min-width: 7em; + text-align: right; + } + &.log-level { + text-transform: uppercase; + min-width: 8em; + .log-colors; + } + &.log-type { + color: @link-color; + } + &.log-message, &.log-path { + width: 100%; + } + } + + tr:hover td.log-action { + /deep/ .btn { + display: inline-block; + } + } + + .table.table-hover > tbody > tr { + box-sizing: border-box; + border-width: 1px; + > td { + border-top: 0 none; + } + &:first-of-type { + border-top-color: transparent; + } + &:last-of-type { + border-bottom-color: transparent; + } + } + + .context-menu { + position: fixed; + } + +}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts new file mode 100644 index 0000000..0c323f7 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.spec.ts @@ -0,0 +1,126 @@ +/** + * 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 {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {StoreModule} from '@ngrx/store'; +import {MomentModule} from 'angular2-moment'; +import {MomentTimezoneModule} from 'angular-moment-timezone'; +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 {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage/service-logs-fields.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 {AppStateService, appState} from '@app/services/storage/app-state.service'; +import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service'; +import {TabsService, tabs} from '@app/services/storage/tabs.service'; +import {ClustersService, clusters} from '@app/services/storage/clusters.service'; +import {ComponentsService, components} from '@app/services/storage/components.service'; +import {HostsService, hosts} from '@app/services/storage/hosts.service'; +import {LogsContainerService} from '@app/services/logs-container.service'; +import {UtilsService} from '@app/services/utils.service'; +import {HttpClientService} from '@app/services/http-client.service'; +import {ComponentGeneratorService} from '@app/services/component-generator.service'; +import {ComponentActionsService} from '@app/services/component-actions.service'; +import {AuthService} from '@app/services/auth.service'; +import {PaginationComponent} from '@app/components/pagination/pagination.component'; +import {DropdownListComponent} from '@app/components/dropdown-list/dropdown-list.component'; + +import {ServiceLogsTableComponent} from './service-logs-table.component'; + +describe('ServiceLogsTableComponent', () => { + let component: ServiceLogsTableComponent; + let fixture: ComponentFixture<ServiceLogsTableComponent>; + const httpClient = { + get: () => { + return { + subscribe: () => { + } + }; + } + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + ServiceLogsTableComponent, + PaginationComponent, + DropdownListComponent + ], + imports: [ + FormsModule, + ReactiveFormsModule, + MomentModule, + MomentTimezoneModule, + ...TranslationModules, + StoreModule.provideStore({ + auditLogs, + serviceLogs, + auditLogsFields, + serviceLogsFields, + serviceLogsHistogramData, + serviceLogsTruncated, + appState, + appSettings, + tabs, + clusters, + components, + hosts + }) + ], + providers: [ + LogsContainerService, + UtilsService, + { + provide: HttpClientService, + useValue: httpClient + }, + AuditLogsService, + ServiceLogsService, + AuditLogsFieldsService, + ServiceLogsFieldsService, + ServiceLogsHistogramDataService, + ServiceLogsTruncatedService, + AppStateService, + AppSettingsService, + TabsService, + ClustersService, + ComponentsService, + HostsService, + ComponentGeneratorService, + ComponentActionsService, + AuthService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ServiceLogsTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 new file mode 100644 index 0000000..9f38371 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/service-logs-table/service-logs-table.component.ts @@ -0,0 +1,135 @@ +/** + * 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, AfterViewInit, ViewChild, ElementRef} from '@angular/core'; +import {ListItem} from '@app/classes/list-item'; +import {LogsTableComponent} from '@app/classes/components/logs-table-component'; +import {LogsContainerService} from '@app/services/logs-container.service'; +import {UtilsService} from '@app/services/utils.service'; + +@Component({ + selector: 'service-logs-table', + templateUrl: './service-logs-table.component.html', + styleUrls: ['./service-logs-table.component.less'] +}) +export class ServiceLogsTableComponent extends LogsTableComponent implements AfterViewInit { + + constructor(private logsContainer: LogsContainerService, private utils: UtilsService) { + super(); + } + + ngAfterViewInit() { + if (this.contextMenu) { + this.contextMenuElement = this.contextMenu.nativeElement; + } + } + + @ViewChild('contextmenu', { + read: ElementRef + }) + contextMenu: ElementRef; + + readonly dateFormat: string = 'dddd, MMMM Do'; + + readonly timeFormat: string = 'h:mm:ss A'; + + readonly logActions = [ + { + label: 'logs.copy', + iconClass: 'fa fa-files-o', + action: 'copyLog' + }, + { + label: 'logs.open', + iconClass: 'fa fa-external-link', + action: 'openLog' + }, + { + label: 'logs.context', + iconClass: 'fa fa-crosshairs', + action: 'openContext' + } + ]; + + readonly customStyledColumns: string[] = ['level', 'type', 'logtime', 'log_message']; + + readonly contextMenuItems: ListItem[] = [ + { + label: 'logs.addToQuery', + iconClass: 'fa fa-search-plus', + value: false // 'isExclude' is false + }, + { + label: 'logs.excludeFromQuery', + iconClass: 'fa fa-search-minus', + value: true // 'isExclude' is true + } + ]; + + private readonly messageFilterParameterName: string = 'log_message'; + + private contextMenuElement: HTMLElement; + + private selectedText: string = ''; + + get timeZone(): string { + return this.logsContainer.timeZone; + } + + get filters(): any { + return this.logsContainer.filters; + } + + get logsTypeMapObject(): object { + return this.logsContainer.logsTypeMap.serviceLogs; + } + + isDifferentDates(dateA, dateB): boolean { + return this.utils.isDifferentDates(dateA, dateB, this.timeZone); + } + + openMessageContextMenu(event: MouseEvent): void { + const selectedText = getSelection().toString(); + if (selectedText) { + let contextMenuStyle = this.contextMenuElement.style; + Object.assign(contextMenuStyle, { + left: `${event.clientX}px`, + top: `${event.clientY}px`, + display: 'block' + }); + this.selectedText = selectedText; + document.body.addEventListener('click', this.dismissContextMenu); + event.preventDefault(); + } + } + + updateQuery(event: ListItem): void { + this.logsContainer.queryParameterAdd.next({ + name: this.messageFilterParameterName, + value: this.selectedText, + isExclude: event.value + }); + } + + private dismissContextMenu = (): void => { + this.selectedText = ''; + this.contextMenuElement.style.display = 'none'; + document.body.removeEventListener('click', this.dismissContextMenu); + }; + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.html index 973db61..c3f0b6a 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.html @@ -16,13 +16,16 @@ --> <button class="btn btn-link dropdown-toggle" data-toggle="dropdown"> - {{selectedLabel | translate}} <span class="caret"></span> + <span *ngIf="selection">{{selection.label | translate}}</span> + <span class="caret"></span> </button> <div class="dropdown-menu row col-md-12"> <div class="col-md-4" (click)="$event.stopPropagation()"> <h4>{{'filter.timeRange' | translate}}</h4> - <date-picker (timeChange)="setStartTime($event)"></date-picker> - <date-picker (timeChange)="setEndTime($event)"></date-picker> + <div class="col-md-12 row text-uppercase">{{'filter.timeRange.from' | translate}}</div> + <date-picker class="col-md-12 row" [time]="startTime" (timeChange)="setStartTime($event)"></date-picker> + <div class="col-md-12 row text-uppercase">{{'filter.timeRange.to' | translate}}</div> + <date-picker class="col-md-12 row" [time]="endTime" (timeChange)="setEndTime($event)"></date-picker> <button class="btn btn-success pull-right" type="button" (click)="setCustomTimeRange()" [disabled]="!startTime || !endTime || startTime >= endTime"> {{'modal.apply' | translate}} http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 7612cc3..43e4bd5 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 @@ -25,8 +25,15 @@ import {AppStateService, appState} from '@app/services/storage/app-state.service import {ClustersService, clusters} from '@app/services/storage/clusters.service'; import {ComponentsService, components} from '@app/services/storage/components.service'; 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 {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 {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'; -import {FilteringService} from '@app/services/filtering.service'; +import {LogsContainerService} from '@app/services/logs-container.service'; import {TimeRangePickerComponent} from './time-range-picker.component'; @@ -51,7 +58,14 @@ describe('TimeRangePickerComponent', () => { appState, clusters, components, - hosts + hosts, + auditLogs, + auditLogsFields, + serviceLogs, + serviceLogsFields, + serviceLogsHistogramData, + serviceLogsTruncated, + tabs }), ...TranslationModules ], @@ -60,12 +74,19 @@ describe('TimeRangePickerComponent', () => { provide: HttpClientService, useValue: httpClient }, - FilteringService, + LogsContainerService, AppSettingsService, AppStateService, ClustersService, ComponentsService, - HostsService + HostsService, + AuditLogsService, + AuditLogsFieldsService, + ServiceLogsService, + ServiceLogsFieldsService, + ServiceLogsHistogramDataService, + ServiceLogsTruncatedService, + TabsService ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts index af4933a..74a2b2d 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts @@ -16,10 +16,10 @@ * limitations under the License. */ -import {Component, OnInit, Input, forwardRef} from '@angular/core'; +import {Component, forwardRef} from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; -import {Moment} from 'moment'; -import {FilteringService} from '@app/services/filtering.service'; +import {Moment} from 'moment-timezone'; +import {LogsContainerService} from '@app/services/logs-container.service'; import {ListItem} from '@app/classes/list-item'; import {TimeUnitListItem} from '@app/classes/filtering'; @@ -35,20 +35,11 @@ import {TimeUnitListItem} from '@app/classes/filtering'; } ] }) -export class TimeRangePickerComponent implements OnInit, ControlValueAccessor { +export class TimeRangePickerComponent implements ControlValueAccessor { - constructor(private filtering: FilteringService) { + constructor(private logsContainer: LogsContainerService) { } - ngOnInit() { - this.selectedLabel = this.defaultLabel; - } - - @Input() - defaultLabel?: string; - - selectedLabel: string; - startTime: Moment; endTime: Moment; @@ -56,18 +47,22 @@ export class TimeRangePickerComponent implements OnInit, ControlValueAccessor { private onChange: (fn: any) => void; get quickRanges(): (ListItem | TimeUnitListItem[])[] { - return this.filtering.filters.timeRange.options; + return this.logsContainer.filters.timeRange.options; } - private timeRange?: any; + private timeRange?: TimeUnitListItem; - get value(): any { + get selection(): TimeUnitListItem { return this.timeRange; } - set value(newValue: any) { + set selection(newValue: TimeUnitListItem) { this.timeRange = newValue; - this.onChange(newValue); + if (this.onChange) { + this.onChange(newValue); + } + this.setEndTime(this.logsContainer.getEndTimeMoment(newValue)); + this.setStartTime(this.logsContainer.getStartTimeMoment(newValue, this.endTime)); } setStartTime(timeObject: Moment): void { @@ -78,28 +73,30 @@ export class TimeRangePickerComponent implements OnInit, ControlValueAccessor { this.endTime = timeObject; } - setTimeRange(value: any, label: string) { - this.value = value; - this.selectedLabel = label; + setTimeRange(value: any, label: string): void { + this.selection = {label, value}; } - setCustomTimeRange() { - this.value = { - type: 'CUSTOM', - start: this.startTime, - end: this.endTime + setCustomTimeRange(): void { + this.selection = { + label: 'filter.timeRange.custom', + value: { + type: 'CUSTOM', + start: this.startTime, + end: this.endTime + } }; - this.selectedLabel = 'filter.timeRange.custom'; } - writeValue() { + writeValue(selection: TimeUnitListItem): void { + this.selection = selection; } registerOnChange(callback: any): void { this.onChange = callback; } - registerOnTouched() { + registerOnTouched(): void { } } http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 53afc47..ab56589 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 @@ -32,9 +32,9 @@ import {ServiceLogsHistogramDataService, serviceLogsHistogramData} from '@app/se 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'; -import {FilteringService} from '@app/services/filtering.service'; import {HttpClientService} from '@app/services/http-client.service'; import {LogsContainerService} from '@app/services/logs-container.service'; +import {AuthService} from '@app/services/auth.service'; import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe'; import {ModalComponent} from '@app/components/modal/modal.component'; @@ -90,12 +90,12 @@ describe('TimeZonePickerComponent', () => { ServiceLogsTruncatedService, TabsService, ComponentActionsService, - FilteringService, { provide: HttpClientService, useValue: httpClient }, - LogsContainerService + LogsContainerService, + AuthService ], }) .compileComponents(); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html index a7858a5..369ddd4 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.html @@ -15,7 +15,9 @@ limitations under the License. --> -<menu-button *ngFor="let item of items" label="{{item.label | translate}}" [action]="item.action" - [iconClass]="item.iconClass" [labelClass]="item.labelClass" - [subItems]="item.subItems" [hideCaret]="item.hideCaret" [badge]="item.badge"> -</menu-button> +<div class="pull-right"> + <menu-button *ngFor="let item of items" label="{{item.label | translate}}" [action]="item.action" + [iconClass]="item.iconClass" [labelClass]="item.labelClass" [subItems]="item.subItems" + [hideCaret]="item.hideCaret" [badge]="item.badge" [isRightAlign]="item.isRightAlign"> + </menu-button> +</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.less index 4fe899a..32d1beb 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/top-menu/top-menu.component.less @@ -19,4 +19,5 @@ :host { .default-flex; + margin-right: 0; } http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 73b6131..91f27e8 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 @@ -16,104 +16,28 @@ * limitations under the License. */ -import {Component, OnInit} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'top-menu', templateUrl: './top-menu.component.html', styleUrls: ['./top-menu.component.less'] }) -export class TopMenuComponent implements OnInit { - - constructor() { - } - - ngOnInit() { - } +export class TopMenuComponent { //TODO implement loading of real data into subItems readonly items = [ { - iconClass: 'fa fa-arrow-left', - label: 'topMenu.undo', - labelClass: 'unstyled-link', - action: 'undo', - subItems: [ - { - label: 'Apply \'Last week\' filter' - }, - { - label: 'Clear all filters' - }, - { - label: 'Apply \'HDFS\' filter' - }, - { - label: 'Apply \'Errors\' filter' - } - ] - }, - { - iconClass: 'fa fa-arrow-right', - label: 'topMenu.redo', - labelClass: 'unstyled-link', - action: 'redo', - subItems: [ - { - label: 'Apply \'Warnings\' filter' - }, - { - label: 'Switch to graph mode' - }, - { - label: 'Apply \'Custom Date\' filter' - } - ] - }, - { - iconClass: 'fa fa-refresh', - label: 'topMenu.refresh', - labelClass: 'unstyled-link', - action: 'refresh' - }, - { - iconClass: 'fa fa-history', - label: 'topMenu.history', - labelClass: 'unstyled-link', - action: 'openHistory', - subItems: [ - { - label: 'Apply \'Custom Date\' filter' - }, - { - label: 'Switch to graph mode' - }, - { - label: 'Apply \'Warnings\' filter' - }, - { - label: 'Apply \'Last week\' filter' - }, - { - label: 'Clear all filters' - }, - { - label: 'Apply \'HDFS\' filter' - }, - { - label: 'Apply \'Errors\' filter' - } - ] - }, - { iconClass: 'fa fa-user unstyled-link', hideCaret: true, + isRightAlign: true, subItems: [ { label: 'Options' }, { - label: 'Logout' + label: 'authorization.logout', + action: 'logout' } ] } http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 150ac56..7b7fcae 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less @@ -61,3 +61,6 @@ // Icon @icon-padding: 5px; + +// Table +@table-border-color: #EEEEEE; http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 4325f5b..7578867 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/mock-data.ts @@ -20,6 +20,7 @@ import * as moment from 'moment'; export const mockData = { login: {}, + logout: {}, api: { v1: { audit: { @@ -46,7 +47,7 @@ export const mockData = { proxyUsers: [ 'admin' ], - evtTime: '2017-05-29T11:30:22.531Z', + evtTime: 1496057422531, enforcer: 'ambari-acl', reqContext: 'ambari', cliType: 'GET', @@ -93,7 +94,7 @@ export const mockData = { proxyUsers: [ 'user' ], - evtTime: '2017-05-29T11:30:22.531Z', + evtTime: 1496057422531, enforcer: 'hdfs', reqContext: 'ambari_server', cliType: 'PUT', @@ -1064,4 +1065,4 @@ export const mockData = { } } } -}; \ No newline at end of file +}; http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts new file mode 100644 index 0000000..65f0936 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.spec.ts @@ -0,0 +1,133 @@ +/** + * 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 {TestBed, inject, async} from '@angular/core/testing'; +import {HttpModule} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/operator/first'; +import 'rxjs/add/operator/last'; +import 'rxjs/add/operator/take'; +import {StoreModule} from '@ngrx/store'; +import {AppStateService, appState} from '@app/services/storage/app-state.service'; +import {AuthService} from '@app/services/auth.service'; +import {HttpClientService} from '@app/services/http-client.service'; + +describe('AuthService', () => { + + const successResponse = { + type: 'default', + ok: true, + url: '/', + status: 200, + statusText: 'OK', + bytesLoaded: 100, + totalBytes: 100, + headers: null + }, + errorResponse = { + type: 'error', + ok: false, + url: '/', + status: 401, + statusText: 'ERROR', + bytesLoaded: 100, + totalBytes: 100, + headers: null + }; + + // Note: We add delay to help the isLoginInProgress test case. + let httpServiceStub = { + isError: false, + postFormData: function () { + const isError = this.isError; + return Observable.create(observer => observer.next(isError ? errorResponse : successResponse)).delay(1); + } + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpModule, + StoreModule.provideStore({ + appState + }) + ], + providers: [ + AuthService, + AppStateService, + {provide: HttpClientService, useValue: httpServiceStub} + ] + }); + }); + + it('should create service', inject([AuthService], (service: AuthService) => { + expect(service).toBeTruthy(); + })); + + it('should set the isAuthorized state to true in appState when the login is success', async(inject( + [AuthService, AppStateService, HttpClientService], + (authService: AuthService, appStateService: AppStateService, httpClientService) => { + httpClientService.isError = false; + authService.login('test', 'test') + .subscribe(() => { + appStateService.getParameter('isAuthorized').subscribe((value: Boolean): void => { + expect(value).toBe(true); + }); + }, value => { + throw value; + }); + } + ))); + + + it('should set the isAuthorized state to false in appState when the login is failed', async(inject( + [AuthService, AppStateService, HttpClientService], + (authService: AuthService, appStateService: AppStateService, httpClientService) => { + httpClientService.isError = true; + authService.login('test', 'test') + .subscribe(() => { + appStateService.getParameter('isAuthorized').subscribe((value: Boolean): void => { + expect(value).toBe(false); + }); + }); + } + ))); + + it('should set the isLoginInProgress state to true when the login started', async(inject( + [AuthService, AppStateService, HttpClientService], + (authService: AuthService, appStateService: AppStateService, httpClientService) => { + httpClientService.isError = false; + authService.login('test', 'test'); + appStateService.getParameter('isLoginInProgress').first().subscribe((value: Boolean): void => { + expect(value).toBe(true); + }); + } + ))); + + it('should set the isLoginInProgress state to true after the login is success', async(inject( + [AuthService, AppStateService, HttpClientService], + (authService: AuthService, appStateService: AppStateService, httpClientService) => { + httpClientService.isError = false; + authService.login('test', 'test'); + appStateService.getParameter('isLoginInProgress').take(2).last().subscribe((value: Boolean): void => { + expect(value).toBe(false); + }); + } + ))); + +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.ts new file mode 100644 index 0000000..8785ce2 --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/auth.service.ts @@ -0,0 +1,123 @@ +/** + * 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 {Response} from '@angular/http'; + +import {Observable} from 'rxjs/Observable'; + +import {HttpClientService} from '@app/services/http-client.service'; +import {AppStateService} from '@app/services/storage/app-state.service'; + +/** + * This service meant to be a single place where the authorization should happen. + */ +@Injectable() +export class AuthService { + + constructor(private httpClient: HttpClientService, private appState: AppStateService) {} + + /** + * The single entry point to request a login action. + * @param {string} username + * @param {string} password + * @returns {Observable<Response>} + */ + login(username: string, password: string): Observable<Response> { + this.setLoginInProgressAppState(true); + let obs = this.httpClient.postFormData('login', { + username: username, + password: password + }); + obs.subscribe( + (resp: Response) => this.onLoginResponse(resp), + (resp: Response) => this.onLoginError(resp) + ); + return obs; + } + + /** + * The single unique entry point to request a logout action + * @returns {Observable<boolean | Error>} + */ + logout(): Observable<Response> { + let obs = this.httpClient.get('logout'); + obs.subscribe( + (resp: Response) => this.onLogoutResponse(resp), + (resp: Response) => this.onLogoutError(resp) + ); + return obs; + } + + /** + * Set the isLoginInProgress state in AppState. The reason behind create a function for this is that we set this app + * state from two different places so let's do always the same way. + * @param {boolean} state the new value of the isLoginInProgress app state. + */ + private setLoginInProgressAppState(state: boolean) { + this.appState.setParameter('isLoginInProgress', state); + } + + /** + * Set the isAuthorized state in AppState. The reason behind create a function for this is that we set this app + * state from two different places so let's do always the same way. + * @param {boolean} state The new value of the isAuthorized app state. + */ + private setAuthorizedAppState(state: boolean) { + this.appState.setParameter('isAuthorized', state); + } + + /** + * Handling the login success response. The goal is to set the authorized property of the appState. + * @param resp + */ + private onLoginResponse(resp: Response): void { + if (resp && resp.ok) { + this.setLoginInProgressAppState(false); + this.setAuthorizedAppState(resp.ok); + } + } + + /** + * Handling the login error response. The goal is to set the authorized property correctly of the appState. + * @ToDo decide if we should have a loginError app state. + * @param {Reponse} resp + */ + private onLoginError(resp: Response): void { + this.setLoginInProgressAppState(false); + this.setAuthorizedAppState(false); + } + + /** + * Handling the logout success response. The goal is to set the authorized property of the appState. + * @param {Response} resp + */ + private onLogoutResponse(resp: Response): void { + if (resp && resp.ok) { + this.setAuthorizedAppState(false); + } + } + + /** + * Handling the logout error response. + * @ToDo decide if we should create a logoutError app state or not + * @param {Response} resp + */ + private onLogoutError(resp: Response): void {} + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 e6a02c0..6d43ff1 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 @@ -30,9 +30,9 @@ import {ServiceLogsFieldsService, serviceLogsFields} from '@app/services/storage 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 {FilteringService} from '@app/services/filtering.service'; import {HttpClientService} from '@app/services/http-client.service'; import {LogsContainerService} from '@app/services/logs-container.service'; +import {AuthService} from '@app/services/auth.service'; import {ComponentActionsService} from './component-actions.service'; @@ -78,12 +78,12 @@ describe('ComponentActionsService', () => { ServiceLogsHistogramDataService, ServiceLogsTruncatedService, TabsService, - FilteringService, { provide: HttpClientService, useValue: httpClient }, - LogsContainerService + LogsContainerService, + AuthService ] }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 c483cd8..e796183 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 @@ -20,15 +20,19 @@ import {Injectable} from '@angular/core'; import {AppSettingsService} from '@app/services/storage/app-settings.service'; import {TabsService} from '@app/services/storage/tabs.service'; import {CollectionModelService} from '@app/classes/models/store'; -import {FilteringService} from '@app/services/filtering.service'; import {LogsContainerService} from '@app/services/logs-container.service'; +import {AuthService} from '@app/services/auth.service'; import {ServiceLog} from '@app/classes/models/service-log'; -import {getFiltersForm} from '@app/classes/filtering'; +import {ListItem} from '@app/classes/list-item'; @Injectable() export class ComponentActionsService { - constructor(private appSettings: AppSettingsService, private tabsStorage: TabsService, private filtering: FilteringService, private logsContainer: LogsContainerService) { + constructor( + private appSettings: AppSettingsService, private tabsStorage: TabsService, + private logsContainer: LogsContainerService, + private authService: AuthService + ) { } //TODO implement actions @@ -81,7 +85,6 @@ export class ComponentActionsService { const tab = { id: log.id, type: 'serviceLogs', - isActive: true, isCloseable: true, label: `${log.host} >> ${log.type}`, appState: { @@ -92,7 +95,14 @@ export class ComponentActionsService { host_name: log.host, component_name: log.type }, - activeFiltersForm: getFiltersForm('serviceLogs') + activeFilters: Object.assign(this.logsContainer.getFiltersData('serviceLogs'), { + components: this.logsContainer.filters.components.options.find((option: ListItem): boolean => { + return option.value === log.type; + }), + hosts: this.logsContainer.filters.hosts.options.find((option: ListItem): boolean => { + return option.value === log.host; + }) + }) } }; this.tabsStorage.addInstance(tab); @@ -104,11 +114,11 @@ export class ComponentActionsService { } startCapture(): void { - this.filtering.startCaptureTimer(); + this.logsContainer.startCaptureTimer(); } stopCapture(): void { - this.filtering.stopCaptureTimer(); + this.logsContainer.stopCaptureTimer(); } setTimeZone(timeZone: string): void { @@ -121,9 +131,25 @@ export class ComponentActionsService { })); } - proceedWithExclude = (item: string): void => this.filtering.queryParameterNameChange.next({ + proceedWithExclude = (item: string): void => this.logsContainer.queryParameterNameChange.next({ item: item, isExclude: true }); + /** + * Request a login action from the AuthService + * @param {string} username + * @param {string} password + */ + login(username: string, password: string): void { + this.authService.login(username, password); + } + + /** + * Request a logout action from AuthService + */ + logout(): void { + this.authService.logout(); + } + } http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 5dc38b4..a161190 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 @@ -32,7 +32,6 @@ import {ServiceLogsTruncatedService, serviceLogsTruncated} from '@app/services/s import {TabsService, tabs} from '@app/services/storage/tabs.service'; import {LogsContainerService} from '@app/services/logs-container.service'; import {HttpClientService} from '@app/services/http-client.service'; -import {FilteringService} from '@app/services/filtering.service'; import {ComponentGeneratorService} from './component-generator.service'; @@ -70,7 +69,6 @@ describe('ComponentGeneratorService', () => { provide: HttpClientService, useValue: httpClient }, - FilteringService, HostsService, AuditLogsService, ServiceLogsService, http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.spec.ts deleted file mode 100644 index 2b3e326..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.spec.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * 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 {TestBed, inject} from '@angular/core/testing'; -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'; -import {ClustersService, clusters} from '@app/services/storage/clusters.service'; -import {ComponentsService, components} from '@app/services/storage/components.service'; -import {HostsService, hosts} from '@app/services/storage/hosts.service'; -import {UtilsService} from '@app/services/utils.service'; -import {HttpClientService} from '@app/services/http-client.service'; -import {ListItem} from '@app/classes/list-item'; -import {Node} from '@app/classes/models/node'; - -import {FilteringService} from './filtering.service'; - -describe('FilteringService', () => { - beforeEach(() => { - const httpClient = { - get: () => { - return { - subscribe: () => { - } - } - } - }; - TestBed.configureTestingModule({ - imports: [ - StoreModule.provideStore({ - appSettings, - appState, - clusters, - components, - hosts - }) - ], - providers: [ - FilteringService, - AppSettingsService, - AppStateService, - ClustersService, - ComponentsService, - HostsService, - UtilsService, - { - provide: HttpClientService, - useValue: httpClient - } - ] - }); - }); - - it('should create service', inject([FilteringService], (service: FilteringService) => { - expect(service).toBeTruthy(); - })); - - describe('#getListItemFromString()', () => { - it('should convert string to ListItem', inject([FilteringService], (service: FilteringService) => { - const getListItemFromString: (name: string) => ListItem = service['getListItemFromString']; - expect(getListItemFromString('customName')).toEqual({ - label: 'customName', - value: 'customName' - }); - })); - }); - - describe('#getListItemFromNode()', () => { - it('should convert Node to ListItem', inject([FilteringService], (service: FilteringService) => { - const getListItemFromNode: (node: Node) => ListItem = service['getListItemFromNode']; - expect(getListItemFromNode({ - name: 'customName', - value: '1', - isParent: true, - isRoot: true - })).toEqual({ - label: 'customName (1)', - value: 'customName' - }); - })); - }); -}); http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts deleted file mode 100644 index 85dc408..0000000 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts +++ /dev/null @@ -1,253 +0,0 @@ -/** - * 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, Input} from '@angular/core'; -import {FormGroup} from '@angular/forms'; -import {Response} from '@angular/http'; -import {Subject} from 'rxjs/Subject'; -import {Observable} from 'rxjs/Observable'; -import 'rxjs/add/observable/timer'; -import 'rxjs/add/operator/takeUntil'; -import * as moment from 'moment-timezone'; -import {ListItem} from '@app/classes/list-item'; -import {FilterCondition, filters} from '@app/classes/filtering'; -import {Node} from '@app/classes/models/node'; -import {AppSettingsService} from '@app/services/storage/app-settings.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 {AppStateService} from '@app/services/storage/app-state.service'; -import {HttpClientService} from '@app/services/http-client.service'; - -@Injectable() -export class FilteringService { - - constructor(private httpClient: HttpClientService, private appSettings: AppSettingsService, private clustersStorage: ClustersService, private componentsStorage: ComponentsService, private hostsStorage: HostsService, private appState: AppStateService) { - this.loadClusters(); - this.loadComponents(); - this.loadHosts(); - appSettings.getParameter('timeZone').subscribe(value => this.timeZone = value || this.defaultTimeZone); - clustersStorage.getAll().subscribe((clusters: string[]): void => { - this.filters.clusters.options = [...this.filters.clusters.options, ...clusters.map(this.getListItemFromString)]; - }); - componentsStorage.getAll().subscribe((components: Node[]): void => { - this.filters.components.options = [...this.filters.components.options, ...components.map(this.getListItemFromNode)]; - }); - hostsStorage.getAll().subscribe((hosts: Node[]): void => { - this.filters.hosts.options = [...this.filters.hosts.options, ...hosts.map(this.getListItemFromNode)]; - }); - appState.getParameter('activeFiltersForm').subscribe((form: FormGroup) => this.activeFiltersForm = form); - } - - /** - * Get instance for dropdown list from string - * @param name {string} - * @returns {ListItem} - */ - private getListItemFromString(name: string): ListItem { - return { - label: name, - value: name - }; - } - - /** - * Get instance for dropdown list from Node object - * @param node {Node} - * @returns {ListItem} - */ - private getListItemFromNode(node: Node): ListItem { - return { - label: `${node.name} (${node.value})`, - value: node.name - }; - } - - private readonly defaultTimeZone = moment.tz.guess(); - - timeZone: string = this.defaultTimeZone; - - /** - * A configurable property to indicate the maximum capture time in milliseconds. - * @type {number} - * @default 600000 (10 minutes) - */ - @Input() - maximumCaptureTimeLimit: number = 600000; - - filters: {[key: string]: FilterCondition} = Object.assign({}, filters); - - activeFiltersForm: FormGroup; - - queryParameterNameChange: Subject<any> = new Subject(); - - queryParameterAdd: Subject<any> = new Subject(); - - private stopTimer: Subject<any> = new Subject(); - - private stopAutoRefreshCountdown: Subject<any> = new Subject(); - - captureSeconds: number = 0; - - private readonly autoRefreshInterval: number = 30000; - - autoRefreshRemainingSeconds: number = 0; - - private startCaptureTime: number; - - private stopCaptureTime: number; - - startCaptureTimer(): void { - this.startCaptureTime = new Date().valueOf(); - const maxCaptureTimeInSeconds = this.maximumCaptureTimeLimit / 1000; - Observable.timer(0, 1000).takeUntil(this.stopTimer).subscribe((seconds: number): void => { - this.captureSeconds = seconds; - if (this.captureSeconds >= maxCaptureTimeInSeconds) { - this.stopCaptureTimer(); - } - }); - } - - stopCaptureTimer(): void { - const autoRefreshIntervalSeconds = this.autoRefreshInterval / 1000; - this.stopCaptureTime = new Date().valueOf(); - this.captureSeconds = 0; - this.stopTimer.next(); - this.setCustomTimeRange(this.startCaptureTime, this.stopCaptureTime); - Observable.timer(0, 1000).takeUntil(this.stopAutoRefreshCountdown).subscribe((seconds: number): void => { - this.autoRefreshRemainingSeconds = autoRefreshIntervalSeconds - seconds; - if (!this.autoRefreshRemainingSeconds) { - this.stopAutoRefreshCountdown.next(); - this.setCustomTimeRange(this.startCaptureTime, this.stopCaptureTime); - } - }); - } - - loadClusters(): void { - this.httpClient.get('clusters').subscribe((response: Response): void => { - const clusterNames = response.json(); - if (clusterNames) { - this.clustersStorage.addInstances(clusterNames); - } - }); - } - - loadComponents(): void { - this.httpClient.get('components').subscribe((response: Response): void => { - const jsonResponse = response.json(), - components = jsonResponse && jsonResponse.vNodeList.map((item): Node => Object.assign(item, { - value: item.logLevelCount.reduce((currentValue: number, currentItem): number => { - return currentValue + Number(currentItem.value); - }, 0) - })); - if (components) { - this.componentsStorage.addInstances(components); - } - }); - } - - loadHosts(): void { - this.httpClient.get('hosts').subscribe((response: Response): void => { - const jsonResponse = response.json(), - hosts = jsonResponse && jsonResponse.vNodeList; - if (hosts) { - this.hostsStorage.addInstances(hosts); - } - }); - } - - setCustomTimeRange(startTime: number, endTime: number): void { - this.activeFiltersForm.controls.timeRange.setValue({ - type: 'CUSTOM', - start: moment(startTime), - end: moment(endTime) - }); - } - - private getStartTime = (value: any, current: string): string => { - let time; - if (value) { - const endTime = moment(moment(current).valueOf()); - switch (value.type) { - case 'LAST': - time = endTime.subtract(value.interval, value.unit); - break; - case 'CURRENT': - time = moment().tz(this.timeZone).startOf(value.unit); - break; - case 'PAST': - time = endTime.startOf(value.unit); - break; - case 'CUSTOM': - time = value.start; - break; - default: - break; - } - } - return time ? time.toISOString() : ''; - }; - - private getEndTime = (value: any): string => { - let time; - if (value) { - switch (value.type) { - case 'LAST': - time = moment(); - break; - case 'CURRENT': - time = moment().tz(this.timeZone).endOf(value.unit); - break; - case 'PAST': - time = moment().tz(this.timeZone).startOf(value.unit).millisecond(-1); - break; - case 'CUSTOM': - time = value.end; - break; - default: - break; - } - } - return time ? time.toISOString() : ''; - }; - - private getQuery(isExclude: boolean): (value: any[]) => string { - return (value: any[]): string => { - let parameters; - if (value && value.length) { - parameters = value.filter(item => item.isExclude === isExclude).map(parameter => { - return { - [parameter.name]: parameter.value.replace(/\s/g, '+') - }; - }); - } - return parameters && parameters.length ? JSON.stringify(parameters) : ''; - } - } - - readonly valueGetters = { - to: this.getEndTime, - from: this.getStartTime, - sortType: value => value && value.type, - sortBy: value => value && value.key, - page: value => value == null ? value : value.toString(), - includeQuery: this.getQuery(false), - excludeQuery: this.getQuery(true) - }; - -} http://git-wip-us.apache.org/repos/asf/ambari/blob/e83bf1bd/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 ee0e1e7..47cb25d 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 @@ -31,7 +31,8 @@ import {HostsService, hosts} from '@app/services/storage/hosts.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'; -import {FilteringService} from '@app/services/filtering.service'; +import {ListItem} from '@app/classes/list-item'; +import {NodeItem} from '@app/classes/models/node-item'; import {LogsContainerService} from './logs-container.service'; @@ -79,8 +80,7 @@ describe('LogsContainerService', () => { { provide: HttpClientService, useValue: httpClient - }, - FilteringService + } ] }); }); @@ -88,4 +88,29 @@ describe('LogsContainerService', () => { it('should create service', inject([LogsContainerService], (service: LogsContainerService) => { expect(service).toBeTruthy(); })); + + describe('#getListItemFromString()', () => { + it('should convert string to ListItem', inject([LogsContainerService], (service: LogsContainerService) => { + const getListItemFromString: (name: string) => ListItem = service['getListItemFromString']; + expect(getListItemFromString('customName')).toEqual({ + label: 'customName', + value: 'customName' + }); + })); + }); + + describe('#getListItemFromNode()', () => { + it('should convert NodeItem to ListItem', inject([LogsContainerService], (service: LogsContainerService) => { + const getListItemFromNode: (node: NodeItem) => ListItem = service['getListItemFromNode']; + expect(getListItemFromNode({ + name: 'customName', + value: '1', + isParent: true, + isRoot: true + })).toEqual({ + label: 'customName (1)', + value: 'customName' + }); + })); + }); });
