AMBARI-22531 Log Search UI: refine search box. (ababiichuk)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/2bf3c8ed Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/2bf3c8ed Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/2bf3c8ed Branch: refs/heads/branch-3.0-perf Commit: 2bf3c8edb26a6f0aac43699483e5ebee89dcc533 Parents: 33ee1a7 Author: ababiichuk <[email protected]> Authored: Tue Nov 28 16:13:51 2017 +0200 Committer: ababiichuk <[email protected]> Committed: Tue Nov 28 16:49:30 2017 +0200 ---------------------------------------------------------------------- .../src/app/classes/filtering.ts | 22 +++- .../src/app/classes/models/app-state.ts | 3 +- .../classes/queries/audit-logs-query-params.ts | 3 +- .../service-logs-truncated-query-params.ts | 3 +- .../src/app/classes/string.ts | 25 ++++ .../filters-panel/filters-panel.component.html | 8 +- .../filters-panel.component.spec.ts | 1 + .../filters-panel/filters-panel.component.ts | 99 ++++++++------- .../logs-container.component.html | 2 +- .../logs-container/logs-container.component.ts | 5 +- .../src/app/components/mixins.less | 2 +- .../search-box/search-box.component.html | 24 ++-- .../search-box/search-box.component.less | 23 ++-- .../search-box/search-box.component.ts | 122 ++++++++++++++----- .../src/app/components/variables.less | 6 +- .../services/component-actions.service.spec.ts | 4 +- .../app/services/component-actions.service.ts | 2 +- .../component-generator.service.spec.ts | 4 +- .../app/services/logs-container.service.spec.ts | 4 +- .../src/app/services/logs-container.service.ts | 69 ++++++++--- .../src/app/services/utils.service.ts | 4 + 21 files changed, 303 insertions(+), 132 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts index 2a7205f..d92dd41 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts @@ -18,9 +18,10 @@ import {Moment, unitOfTime} from 'moment'; import {ListItem} from '@app/classes/list-item'; +import {TimeRangeType, SortingType} from '@app/classes/string'; export interface TimeUnit { - type: 'CURRENT' | 'LAST' | 'PAST'; + type: TimeRangeType; unit: unitOfTime.DurationConstructor; interval?: number; } @@ -33,7 +34,7 @@ export interface CustomTimeRange { export interface SortingConditions { key: string; - type: 'asc' | 'desc'; + type: SortingType; } export interface TimeUnitListItem extends ListItem { @@ -49,4 +50,21 @@ export interface FilterCondition { options?: (ListItem | TimeUnitListItem[])[]; defaultSelection?: ListItem | ListItem[] | number; iconClass?: string; + fieldName?: string; +} + +export interface SearchBoxParameter { + name: string; + value: string; + isExclude: boolean; +} + +export interface SearchBoxParameterProcessed extends SearchBoxParameter { + id: number; + label: string; +} + +export interface SearchBoxParameterTriggered { + value: string; + isExclude: boolean; } http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts index afed497..c3279ce 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/models/app-state.ts @@ -17,12 +17,13 @@ */ import {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; +import {LogsType} from '@app/classes/string'; export interface AppState { isAuthorized: boolean; isInitialLoading: boolean; isLoginInProgress: boolean; - activeLogsType?: string; + activeLogsType?: LogsType; isServiceLogsFileView: boolean; isServiceLogContextView: boolean; activeLog: ActiveServiceLogEntry | null; http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/audit-logs-query-params.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/audit-logs-query-params.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/audit-logs-query-params.ts index 509fa04..3b38a03 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/audit-logs-query-params.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/audit-logs-query-params.ts @@ -17,6 +17,7 @@ */ import {QueryParams} from '@app/classes/queries/query-params'; +import {SortingType} from '@app/classes/string'; export const defaultParams = { page: '0', @@ -35,7 +36,7 @@ export class AuditLogsQueryParams extends QueryParams { pageSize: string; startIndex: string; sortBy?: string; - sortType?: 'asc' | 'desc'; + sortType?: SortingType; clusters?: string; mustBe?: string; mustNot?: string; http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/service-logs-truncated-query-params.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/service-logs-truncated-query-params.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/service-logs-truncated-query-params.ts index 6f9de16..3b08e11 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/service-logs-truncated-query-params.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/queries/service-logs-truncated-query-params.ts @@ -17,6 +17,7 @@ */ import {QueryParams} from '@app/classes/queries/query-params'; +import {ScrollType} from '@app/classes/string'; export const defaultParams = { numberRows: '10', @@ -32,5 +33,5 @@ export class ServiceLogsTruncatedQueryParams extends QueryParams { host_name: string; component_name: string; numberRows: string; - scrollType: 'before' | 'after' | ''; + scrollType: ScrollType; } http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/classes/string.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/string.ts b/ambari-logsearch/ambari-logsearch-web/src/app/classes/string.ts new file mode 100644 index 0000000..21ff4ca --- /dev/null +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/string.ts @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type LogsType = 'auditLogs' | 'serviceLogs'; + +export type TimeRangeType = 'CURRENT' | 'LAST' | 'PAST'; + +export type SortingType = 'asc' | 'desc'; + +export type ScrollType = 'before' | 'after' | ''; http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html index 2d327a6..4fe169d 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html @@ -20,9 +20,9 @@ <filter-dropdown *ngIf="isFilterConditionDisplayed('clusters')" [label]="filters.clusters.label" formControlName="clusters" [options]="filters.clusters.options" [isMultipleChoice]="true" class="filter-input"></filter-dropdown> - <search-box formControlName="query" [items]="searchBoxItemsTranslated" class="filter-input" - [parameterNameChangeSubject]="queryParameterNameChange" - [parameterAddSubject]="queryParameterAdd"></search-box> + <search-box [parameterAddSubject]="queryParameterAdd" [parameterNameChangeSubject]="queryParameterNameChange" + formControlName="query" [items]="searchBoxItemsTranslated" [itemsOptions]="options" + class="filter-input"></search-box> <time-range-picker *ngIf="isFilterConditionDisplayed('timeRange')" formControlName="timeRange" class="filter-input"></time-range-picker> <timezone-picker class="filter-input"></timezone-picker> @@ -31,7 +31,7 @@ </button--> </div> <div class="filter-buttons col-md-4"> - <dropdown-button [options]="searchBoxItems" iconClass="fa fa-search-minus" label="filter.excluded" + <dropdown-button [options]="searchBoxItems | async" iconClass="fa fa-search-minus" label="filter.excluded" [hideCaret]="true" [showSelectedValue]="false" action="proceedWithExclude"></dropdown-button> <filter-button *ngIf="isFilterConditionDisplayed('hosts')" formControlName="hosts" label="{{filters.hosts.label | translate}}" [iconClass]="filters.hosts.iconClass" http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts index 1f7e8db..c9f9b52 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.spec.ts @@ -106,6 +106,7 @@ describe('FiltersPanelComponent', () => { component.filtersForm = new FormGroup({ control: new FormControl() }); + component.logsType = 'auditLogs'; fixture.detectChanges(); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts index b41f7cd..01a8932 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts @@ -16,74 +16,83 @@ * limitations under the License. */ -import {Component, Input} from '@angular/core'; +import {Component, OnChanges, SimpleChanges, Input} from '@angular/core'; import {FormGroup} from '@angular/forms'; +import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; -import {TranslateService} from '@ngx-translate/core'; +import {FilterCondition} from '@app/classes/filtering'; import {ListItem} from '@app/classes/list-item'; +import {LogsType} from '@app/classes/string'; import {CommonEntry} from '@app/classes/models/common-entry'; -import {LogField} from '@app/classes/models/log-field'; import {LogsContainerService} from '@app/services/logs-container.service'; -import {AppStateService} from '@app/services/storage/app-state.service'; @Component({ selector: 'filters-panel', templateUrl: './filters-panel.component.html', styleUrls: ['./filters-panel.component.less'] }) -export class FiltersPanelComponent { +export class FiltersPanelComponent implements OnChanges { - constructor( - private translate: TranslateService, private logsContainer: LogsContainerService, - private appState: AppStateService - ) { - appState.getParameter('activeLogsType').subscribe(value => { - this.logsType = value; - logsContainer.logsTypeMap[value].fieldsModel.getAll().subscribe((fields: LogField[]): void => { - if (fields.length) { - const items = fields.filter((field: LogField): boolean => { - return this.excludedParameters.indexOf(field.name) === -1; - }).map((field: LogField): CommonEntry => { - return { - name: field.displayName || field.name, - value: field.name - }; - }), - labelKeys = items.map((item: CommonEntry): string => item.name); - this.searchBoxItems = items.map((item: CommonEntry): ListItem => { - return { - label: item.name, - value: item.value - }; - }); - translate.get(labelKeys).first().subscribe((translation: {[key: string]: string}): void => { - this.searchBoxItemsTranslated = items.map((item: CommonEntry): CommonEntry => { - return { - name: translation[item.name], - value: item.value - }; - }) - }); - } - }) - }); + constructor(private logsContainer: LogsContainerService) { + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.hasOwnProperty('logsType')) { + let result; + switch (changes.logsType.currentValue) { + case 'auditLogs': + result = this.logsContainer.auditLogsColumns; + break; + case 'serviceLogs': + result = this.logsContainer.serviceLogsColumns; + break; + } + this.searchBoxItems = result; + } } @Input() filtersForm: FormGroup; - private readonly excludedParameters = ['cluster', 'host', 'level', 'type', 'logtime']; - - private logsType: string; + @Input() + logsType: LogsType; - searchBoxItems: ListItem[] = []; + searchBoxItems: Observable<ListItem[]>; - searchBoxItemsTranslated: CommonEntry[] = []; + get searchBoxItemsTranslated(): CommonEntry[] { + switch (this.logsType) { + case 'auditLogs': + return this.logsContainer.auditLogsColumnsTranslated; + case 'serviceLogs': + return this.logsContainer.serviceLogsColumnsTranslated; + } + } - get filters(): any { + get filters(): {[key: string]: FilterCondition} { return this.logsContainer.filters; } + /** + * Object with options for search box parameter values + * @returns {[key: string]: CommonEntry[]} + */ + get options(): {[key: string]: CommonEntry[]} { + return Object.keys(this.filters).filter((key: string): boolean => { + const condition = this.filters[key]; + return Boolean(condition.fieldName && condition.options); + }).reduce((currentValue, currentKey) => { + const condition = this.filters[currentKey]; + return Object.assign(currentValue, { + [condition.fieldName]: condition.options.map((option: ListItem): CommonEntry => { + return { + name: option.value, + value: option.value + } + }) + }); + }, {}); + } + get queryParameterNameChange(): Subject<any> { return this.logsContainer.queryParameterNameChange; } http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 f34dd15..13911bd 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 @@ -25,7 +25,7 @@ </div> </div> <div class="container-fluid"> - <filters-panel class="row" [filtersForm]="filtersForm"></filters-panel> + <filters-panel class="row" [filtersForm]="filtersForm" [logsType]="logsType"></filters-panel> <div class="row"> <div *ngIf="autoRefreshRemainingSeconds" class="col-md-12"> <div class="auto-refresh-message pull-right"> http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 b06cfa4..86709fb 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 @@ -30,6 +30,7 @@ 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 {LogsType} from '@app/classes/string'; @Component({ selector: 'logs-container', @@ -43,7 +44,7 @@ export class LogsContainerComponent { private tabsStorage: TabsService, private logsContainer: LogsContainerService ) { this.logsContainer.loadColumnsNames(); - appState.getParameter('activeLogsType').subscribe((value: string) => this.logsType = value); + appState.getParameter('activeLogsType').subscribe((value: LogsType) => this.logsType = value); serviceLogsHistogramStorage.getAll().subscribe((data: BarGraph[]): void => { this.histogramData = this.logsContainer.getHistogramData(data); }); @@ -56,7 +57,7 @@ export class LogsContainerComponent { return this.logsContainer.filtersForm; }; - private logsType: string; + private logsType: LogsType; get totalCount(): number { return this.logsContainer.totalCount; http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less index 5fa265b..4460821 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less @@ -80,7 +80,7 @@ .dropdown-list-default { line-height: 1; - border-radius: 2px; + border-radius: @dropdown-border-radius; font-size: 14px; min-width: @dropdown-min-width; background: #FFF; http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.html ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.html b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.html index 92f9520..5bffdc5 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.html +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.html @@ -16,17 +16,25 @@ --> <label class="parameter-label" *ngFor="let parameter of parameters"> - <span *ngIf="parameter.isExclude" class="fa fa-search-minus exclude-icon"></span> + <span *ngIf="parameter.isExclude" class="fa fa-search-minus"></span> {{parameter.label | translate}}: <span class="parameter-value">{{parameter.value}}</span> <span class="fa fa-times remove-parameter" (click)="removeParameter($event, parameter.id)"></span> </label> <span class="active-parameter-label" *ngIf="isActive && activeItem">{{activeItem.name | translate}}:</span> <div [ngClass]="{'search-item-container': true, 'active': isActive, 'value': isValueInput}"> - <input #parameterInput auto-complete [(ngModel)]="currentValue" [source]="items" [list-formatter]="itemsListFormatter" - display-property-name="name" (valueChanged)="changeParameterName({item: $event, isExclude: false})" - class="search-item-input parameter-input form-control"> - <input #valueInput type="text" [(ngModel)]="currentValue" class="search-item-input value-input form-control" - (keyup)="onParameterValueChange($event)"> - <div class="search-item-text" [innerHTML]="currentValue"></div> -</div> \ No newline at end of file + <span class="parameter-input-wrapper"> + <input #parameterInput auto-complete class="search-item-input parameter-input form-control" + [(ngModel)]="currentValue" [source]="items" display-property-name="name" + [list-formatter]="itemsListFormatter" [value-formatter]="itemsValueFormatter" [match-formatted]="true" + (valueChanged)="changeParameterName({value: $event.value, isExclude: false})" + (keyup)="onParameterKeyUp($event)"> + </span> + <span [ngClass]="{'no-value-options': !activeItemValueOptions.length}"> + <input #valueInput auto-complete [(ngModel)]="currentValue" [source]="activeItemValueOptions" + [list-formatter]="itemsListFormatter" [value-formatter]="itemsValueFormatter" [match-formatted]="true" + (valueChanged)="onParameterValueChange($event.value)" (keydown)="onParameterValueKeyDown($event)" + (keyup)="onParameterValueKeyUp($event)" class="search-item-input value-input form-control"> + </span> + <div class="search-item-text">{{currentValue}}</div> +</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.less index f0a5ce0..eac3bd6 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.less @@ -35,16 +35,13 @@ cursor: text; .parameter-label { - // TODO implement actual styles margin: @label-margin; - padding: @label-margin; - background-color: @main-background-color; + border-radius: @dropdown-border-radius; + padding: @search-parameter-padding; + background-color: @search-parameter-background-color; + color: @base-font-color; font-size: 0.8em; - .exclude-icon { - color: @exclude-color; - } - .parameter-value { font-weight: normal; } @@ -94,8 +91,16 @@ } &.value { - /deep/ .ng2-auto-complete-wrapper, .parameter-input { - display: none; + .parameter-input-wrapper { + /deep/ .ng2-auto-complete-wrapper { + display: none; + } + } + + .no-value-options { + /deep/ .ng2-auto-complete { + display: none; + } } .value-input { http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 18ff715..14cc89b 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 @@ -19,6 +19,7 @@ import {Component, OnInit, OnDestroy, Input, ViewChild, ElementRef, forwardRef} from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {Subject} from 'rxjs/Subject'; +import {SearchBoxParameter, SearchBoxParameterProcessed, SearchBoxParameterTriggered} from '@app/classes/filtering'; import {CommonEntry} from '@app/classes/models/common-entry'; import {UtilsService} from '@app/services/utils.service'; @@ -42,7 +43,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.rootElement.addEventListener('keydown', this.onRootKeyDown); } - ngOnInit() { + ngOnInit(): void { this.parameterInput = this.parameterInputRef.nativeElement; this.valueInput = this.valueInputRef.nativeElement; this.parameterInput.addEventListener('focus', this.onParameterInputFocus); @@ -52,7 +53,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.parameterAddSubject.subscribe(this.onParameterAdd); } - ngOnDestroy() { + ngOnDestroy(): void { this.rootElement.removeEventListener('click', this.onRootClick); this.rootElement.removeEventListener('keydown', this.onRootKeyDown); this.parameterInput.removeEventListener('focus', this.onParameterInputFocus); @@ -62,6 +63,8 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.parameterAddSubject.unsubscribe(); } + private readonly messageParameterName: string = 'log_message'; + private currentId: number = 0; private isExclude: boolean = false; @@ -80,10 +83,13 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess items: CommonEntry[] = []; @Input() - parameterNameChangeSubject: Subject<any> = this.defaultSubject; + itemsOptions: {[key: string]: CommonEntry[]}; + + @Input() + parameterNameChangeSubject: Subject<SearchBoxParameterTriggered> = this.defaultSubject; @Input() - parameterAddSubject: Subject<any> = this.defaultSubject; + parameterAddSubject: Subject<SearchBoxParameter> = this.defaultSubject; @ViewChild('parameterInput') parameterInputRef: ElementRef; @@ -93,13 +99,18 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess private rootElement: HTMLElement; - private parameterInput: HTMLElement; + private parameterInput: HTMLInputElement; + + private valueInput: HTMLInputElement; - private valueInput: HTMLElement; + activeItem: CommonEntry | null = null; - activeItem?: any; + parameters: SearchBoxParameterProcessed[] = []; - parameters: any[] = []; + get activeItemValueOptions(): CommonEntry[] { + return this.itemsOptions && this.activeItem && this.itemsOptions[this.activeItem.value] ? + this.itemsOptions[this.activeItem.value] : []; + } private onChange: (fn: any) => void; @@ -133,52 +144,80 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess } }; - private getItem(name: string): CommonEntry { - return this.items.find(field => field.value === name); + private switchToParameterInput = (): void => { + this.activeItem = null; + this.isValueInput = false; + setTimeout(() => this.parameterInput.focus()); + }; + + private getItemByValue(name: string): CommonEntry { + return this.items.find((field: CommonEntry): boolean => field.value === name); + } + + private getItemByName(name: string): CommonEntry { + return this.items.find((field: CommonEntry): boolean => field.name === name); } clear(): void { this.isActive = false; this.activeItem = null; - this.currentValue = null; + this.currentValue = ''; + this.parameterInput.value = ''; + this.valueInput.value = ''; } itemsListFormatter(item: CommonEntry): string { return item.name; } - changeParameterName(item: any): void { - this.parameterNameChangeSubject.next(item); + itemsValueFormatter(item: CommonEntry): string { + return item.value; } - onParameterNameChange = (options: any): void => { - this.activeItem = typeof options.item === 'string' ? this.getItem(options.item) : options.item; - this.isExclude = options.isExclude; - this.isActive = true; - this.isParameterInput = false; - this.isValueInput = true; - this.currentValue = ''; - setTimeout(() => this.valueInput.focus(), 0); + changeParameterName(options: SearchBoxParameterTriggered): void { + this.parameterNameChangeSubject.next(options); + } + + onParameterNameChange = (options: SearchBoxParameterTriggered): void => { + if (options.value) { + this.activeItem = this.getItemByValue(options.value); + this.isExclude = options.isExclude; + this.isActive = true; + this.isParameterInput = false; + this.isValueInput = true; + this.currentValue = ''; + setTimeout(() => this.valueInput.focus(), 0); + } }; - onParameterValueChange(event: KeyboardEvent): void { + onParameterValueKeyDown(event: KeyboardEvent): void { + if (this.utils.isBackSpacePressed(event) && !this.currentValue) { + this.switchToParameterInput(); + } + } + + onParameterValueKeyUp(event: KeyboardEvent): void { if (this.utils.isEnterPressed(event) && this.currentValue) { + this.onParameterValueChange(this.currentValue); + } + } + + onParameterValueChange(value: string): void { + if (value) { this.parameters.push({ id: this.currentId++, name: this.activeItem.value, label: this.activeItem.name, - value: this.currentValue, + value: value, isExclude: this.isExclude }); - this.currentValue = ''; - this.activeItem = null; - this.isValueInput = false; this.updateValue(); } + this.switchToParameterInput(); } - onParameterAdd = (options: any): void => { - const item = this.getItem(options.name); + onParameterAdd = (options: SearchBoxParameter): void => { + const item = this.getItemByValue(options.name); this.parameters.push({ id: this.currentId++, name: options.name, @@ -189,19 +228,38 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.updateValue(); }; + onParameterKeyUp = (event: KeyboardEvent): void => { + if (this.utils.isEnterPressed(event) && this.currentValue) { + const existingItem = this.getItemByName(this.currentValue); + if (existingItem) { + this.changeParameterName({ + value: this.currentValue, + isExclude: false + }); + } else { + this.parameterAddSubject.next({ + name: this.messageParameterName, + value: this.currentValue, + isExclude: false + }); + } + } + }; + removeParameter(event: MouseEvent, id: number): void { - this.parameters = this.parameters.filter(parameter => parameter.id !== id); + this.parameters = this.parameters.filter((parameter: SearchBoxParameterProcessed): boolean => parameter.id !== id); this.updateValue(); event.stopPropagation(); } - updateValue() { + updateValue(): void { + this.currentValue = ''; if (this.onChange) { this.onChange(this.parameters); } } - writeValue(parameters: any [] = []) { + writeValue(parameters: SearchBoxParameterProcessed[] = []): void { this.parameters = parameters; this.updateValue(); } @@ -210,7 +268,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.onChange = callback; } - registerOnTouched() { + registerOnTouched(): void { } } http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 7b7fcae..18268ad 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less @@ -23,7 +23,6 @@ @button-border-radius: 4px; @input-border-width: 1px; @input-border: @input-border-width solid #CFD3D7; -@button-border-radius: 4px; @input-group-addon-padding: 6px 12px 6px 0; @block-margin-top: 20px; @link-color: #1491C1; @@ -37,9 +36,11 @@ @checkbox-top: 4px; @dropdown-min-width: 160px; @dropdown-max-height: 500px; // TODO get rid of magic number, base on actual design +@dropdown-border-radius: 2px; @input-height: 34px; @input-padding: 10px; @col-padding: 15px; +@search-parameter-padding: 5px 2px; @fatal-color: #830A0A; @error-color: #E81D1D; @@ -51,6 +52,7 @@ @submit-color: #5CB85C; @submit-hover-color: #449D44; @exclude-color: #EF6162; +@search-parameter-background-color: #DDD; // Panels @panel-heading: rgba(255, 255, 255, 1); @@ -63,4 +65,4 @@ @icon-padding: 5px; // Table -@table-border-color: #EEEEEE; +@table-border-color: #EEE; http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 6d43ff1..c2cee8d 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 @@ -17,6 +17,7 @@ */ import {TestBed, inject} from '@angular/core/testing'; +import {TranslationModules} from '@app/test-config.spec'; 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'; @@ -62,7 +63,8 @@ describe('ComponentActionsService', () => { serviceLogsHistogramData, serviceLogsTruncated, tabs - }) + }), + ...TranslationModules ], providers: [ ComponentActionsService, http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 e796183..0fc9fde 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 @@ -132,7 +132,7 @@ export class ComponentActionsService { } proceedWithExclude = (item: string): void => this.logsContainer.queryParameterNameChange.next({ - item: item, + value: item, isExclude: true }); http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 a161190..3f65cd1 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 @@ -17,6 +17,7 @@ */ import {TestBed, inject} from '@angular/core/testing'; +import {TranslationModules} from '@app/test-config.spec'; import {StoreModule} from '@ngrx/store'; import {HostsService, hosts} from '@app/services/storage/hosts.service'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; @@ -60,7 +61,8 @@ describe('ComponentGeneratorService', () => { components, serviceLogsTruncated, tabs - }) + }), + ...TranslationModules ], providers: [ ComponentGeneratorService, http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 47cb25d..870058b 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 @@ -17,6 +17,7 @@ */ import {TestBed, inject} from '@angular/core/testing'; +import {TranslationModules} from '@app/test-config.spec'; import {StoreModule} from '@ngrx/store'; import {AuditLogsService, auditLogs} from '@app/services/storage/audit-logs.service'; import {ServiceLogsService, serviceLogs} from '@app/services/storage/service-logs.service'; @@ -61,7 +62,8 @@ describe('LogsContainerService', () => { hosts, serviceLogsTruncated, tabs - }) + }), + ...TranslationModules ], providers: [ AuditLogsService, http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/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 a715adc..64b14b8 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,6 +27,7 @@ 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'; @@ -41,8 +42,11 @@ 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 {ActiveServiceLogEntry} from '@app/classes/active-service-log-entry'; -import {FilterCondition, TimeUnitListItem, SortingListItem} from '@app/classes/filtering'; +import { + FilterCondition, TimeUnitListItem, SortingListItem, SearchBoxParameter, SearchBoxParameterTriggered +} from '@app/classes/filtering'; import {ListItem} from '@app/classes/list-item'; +import {LogsType, ScrollType, SortingType} from '@app/classes/string'; import {Tab} from '@app/classes/models/tab'; import {LogField} from '@app/classes/models/log-field'; import {AuditLog} from '@app/classes/models/audit-log'; @@ -51,14 +55,15 @@ import {ServiceLog} from '@app/classes/models/service-log'; import {ServiceLogField} from '@app/classes/models/service-log-field'; import {BarGraph} from '@app/classes/models/bar-graph'; import {NodeItem} from '@app/classes/models/node-item'; +import {CommonEntry} from '@app/classes/models/common-entry'; @Injectable() export class LogsContainerService { constructor( - private httpClient: HttpClientService, private auditLogsStorage: AuditLogsService, - private auditLogsFieldsStorage: AuditLogsFieldsService, private serviceLogsStorage: ServiceLogsService, - private serviceLogsFieldsStorage: ServiceLogsFieldsService, + 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, @@ -78,7 +83,7 @@ export class LogsContainerService { this.loadHosts(); appState.getParameter('activeLog').subscribe((value: ActiveServiceLogEntry | null) => this.activeLog = value); appState.getParameter('isServiceLogsFileView').subscribe((value: boolean) => this.isServiceLogsFileView = value); - appState.getParameter('activeLogsType').subscribe((value: string) => this.activeLogsType = value); + appState.getParameter('activeLogsType').subscribe((value: LogsType) => this.activeLogsType = value); appSettings.getParameter('timeZone').subscribe((value: string) => this.timeZone = value || this.defaultTimeZone); tabsStorage.mapCollection((tab: Tab): Tab => { let currentAppState = tab.appState || {}; @@ -111,6 +116,8 @@ export class LogsContainerService { this.loadLogs(); }); }); + this.auditLogsColumns.subscribe(this.getTranslationKeysSubscriber('auditLogsColumnsTranslated')); + this.serviceLogsColumns.subscribe(this.getTranslationKeysSubscriber('serviceLogsColumnsTranslated')); } private readonly paginationOptions: string[] = ['10', '25', '50', '100']; @@ -119,7 +126,8 @@ export class LogsContainerService { clusters: { label: 'filter.clusters', options: [], - defaultSelection: [] + defaultSelection: [], + fieldName: 'cluster' }, timeRange: { options: [ @@ -346,7 +354,8 @@ export class LogsContainerService { label: 'filter.components', iconClass: 'fa fa-cubes', options: [], - defaultSelection: [] + defaultSelection: [], + fieldName: 'type' }, levels: { label: 'filter.levels', @@ -381,13 +390,15 @@ export class LogsContainerService { value: 'UNKNOWN' } ], - defaultSelection: [] + defaultSelection: [], + fieldName: 'level' }, hosts: { label: 'filter.hosts', iconClass: 'fa fa-server', options: [], - defaultSelection: [] + defaultSelection: [], + fieldName: 'host' }, auditLogsSorting: { label: 'sorting.title', @@ -533,7 +544,7 @@ export class LogsContainerService { activeLog: ActiveServiceLogEntry | null = null; - activeLogsType: string; + activeLogsType: LogsType; private filtersFormChange: Subject<any> = new Subject(); @@ -561,10 +572,30 @@ export class LogsContainerService { } } + private getTranslationKeysSubscriber = (propertyName: string): (items: ListItem[]) => void => { + return (items: ListItem[]): void => { + const keys = items.map((item: ListItem): string => item.label); + if (keys.length) { + this.translate.get(keys).first().subscribe((translation: {[key: string]: string}): void => { + this[propertyName] = items.map((item: ListItem): CommonEntry => { + return { + name: translation[item.label], + value: item.value + }; + }); + }); + } + }; + }; + auditLogsColumns: Observable<ListItem[]> = this.auditLogsFieldsStorage.getAll().map(this.columnsMapper); + auditLogsColumnsTranslated: CommonEntry[] = []; + serviceLogsColumns: Observable<ListItem[]> = this.serviceLogsFieldsStorage.getAll().map(this.columnsMapper); + serviceLogsColumnsTranslated: CommonEntry[] = []; + serviceLogs: Observable<ServiceLog[]> = Observable.combineLatest(this.serviceLogsStorage.getAll(), this.serviceLogsColumns).map(this.logsMapper); auditLogs: Observable<AuditLog[]> = Observable.combineLatest(this.auditLogsStorage.getAll(), this.auditLogsColumns).map(this.logsMapper); @@ -593,9 +624,9 @@ export class LogsContainerService { }; } - queryParameterNameChange: Subject<any> = new Subject(); + queryParameterNameChange: Subject<SearchBoxParameterTriggered> = new Subject(); - queryParameterAdd: Subject<any> = new Subject(); + queryParameterAdd: Subject<SearchBoxParameter> = new Subject(); private stopTimer: Subject<any> = new Subject(); @@ -611,7 +642,7 @@ export class LogsContainerService { private stopCaptureTime: number; - loadLogs = (logsType: string = this.activeLogsType): void => { + loadLogs = (logsType: LogsType = this.activeLogsType): void => { this.httpClient.get(logsType, this.getParams('listFilters')).subscribe((response: Response): void => { const jsonResponse = response.json(), model = this.logsTypeMap[logsType].logsModel; @@ -640,7 +671,7 @@ export class LogsContainerService { } }; - loadLogContext(id: string, hostName: string, componentName: string, scrollType: 'before' | 'after' | '' = ''): void { + loadLogContext(id: string, hostName: string, componentName: string, scrollType: ScrollType = ''): void { const params = { id: id, host_name: hostName, @@ -671,7 +702,7 @@ export class LogsContainerService { }); } - private getParams(filtersMapName: string, logsType: string = this.activeLogsType): {[key: string]: string} { + private getParams(filtersMapName: string, logsType: LogsType = this.activeLogsType): {[key: string]: string} { let params = {}; this.logsTypeMap[logsType][filtersMapName].forEach((key: string): void => { const inputValue = this.filtersForm.getRawValue()[key], @@ -787,11 +818,11 @@ export class LogsContainerService { return endMoment ? endMoment.toISOString() : ''; }; - private getQuery(isExclude: boolean): (value: any[]) => string { - return (value: any[]): string => { + private getQuery(isExclude: boolean): (value: SearchBoxParameter[]) => string { + return (value: SearchBoxParameter[]): string => { let parameters; if (value && value.length) { - parameters = value.filter(item => item.isExclude === isExclude).map(parameter => { + parameters = value.filter((item: SearchBoxParameter): boolean => item.isExclude === isExclude).map((parameter: SearchBoxParameter): {[key: string]: string} => { return { [parameter.name]: parameter.value.replace(/\s/g, '+') }; @@ -801,7 +832,7 @@ export class LogsContainerService { } } - private getSortType(selection: SortingListItem[] = []): 'asc' | 'desc' { + private getSortType(selection: SortingListItem[] = []): SortingType { return selection[0] && selection[0].value ? selection[0].value.type : 'desc'; } http://git-wip-us.apache.org/repos/asf/ambari/blob/2bf3c8ed/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts b/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts index 175b585..dd9075c 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/services/utils.service.ts @@ -77,6 +77,10 @@ export class UtilsService { return event.keyCode === 13; } + isBackSpacePressed(event: KeyboardEvent): boolean { + return event.keyCode === 8; + } + isDifferentDates(dateA, dateB, timeZone): boolean { const momentA = moment(dateA).tz(timeZone), momentB = moment(dateB).tz(timeZone);
