AMBARI-22610 Log Search UI: fixes for search box autocomplete. (ababiichuk)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/862b7d7b Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/862b7d7b Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/862b7d7b Branch: refs/heads/branch-3.0-perf Commit: 862b7d7b17e8e2ad165f548c4f9c328dd9151607 Parents: a9c3bf5 Author: ababiichuk <ababiic...@hortonworks.com> Authored: Thu Dec 7 15:23:30 2017 +0200 Committer: ababiichuk <ababiic...@hortonworks.com> Committed: Thu Dec 7 19:21:59 2017 +0200 ---------------------------------------------------------------------- .../ambari-logsearch-web/package.json | 3 +- .../ambari-logsearch-web/src/app/app.module.ts | 8 +- .../src/app/classes/filtering.ts | 2 +- .../dropdown-list/dropdown-list.component.less | 24 ++- .../filters-panel/filters-panel.component.html | 5 +- .../filters-panel/filters-panel.component.ts | 23 +- .../src/app/components/mixins.less | 17 +- .../search-box/search-box.component.html | 28 ++- .../search-box/search-box.component.less | 30 +-- .../search-box/search-box.component.spec.ts | 79 +++++++ .../search-box/search-box.component.ts | 214 ++++++++++--------- .../app/services/component-actions.service.ts | 9 +- .../src/app/services/logs-container.service.ts | 22 -- ambari-logsearch/ambari-logsearch-web/yarn.lock | 6 +- 14 files changed, 259 insertions(+), 211 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/ambari-logsearch/ambari-logsearch-web/package.json ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/package.json b/ambari-logsearch/ambari-logsearch-web/package.json index 2c6aa8d..b9ee179 100644 --- a/ambari-logsearch/ambari-logsearch-web/package.json +++ b/ambari-logsearch/ambari-logsearch-web/package.json @@ -34,8 +34,7 @@ "jquery": "^1.12.4", "moment": "^2.18.1", "moment-timezone": "^0.5.13", - "ng2-auto-complete": "^0.12.0", - "ngx-bootstrap": "^1.6.6", + "ngx-bootstrap": "^1.9.3", "rxjs": "^5.4.3", "zone.js": "^0.8.4" }, http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts index 5e43582..b76de20 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/app.module.ts @@ -21,13 +21,12 @@ import {NgModule, CUSTOM_ELEMENTS_SCHEMA, Injector} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {HttpModule, Http, XHRBackend, BrowserXhr, ResponseOptions, XSRFStrategy} from '@angular/http'; import {InMemoryBackendService} from 'angular-in-memory-web-api'; -import {AlertModule} from 'ngx-bootstrap'; +import {TypeaheadModule} from 'ngx-bootstrap'; import {TranslateModule, TranslateLoader} from '@ngx-translate/core'; import {TranslateHttpLoader} from '@ngx-translate/http-loader'; import {StoreModule} from '@ngrx/store'; import {MomentModule} from 'angular2-moment'; import {MomentTimezoneModule} from 'angular-moment-timezone'; -import {Ng2AutoCompleteModule} from 'ng2-auto-complete'; import {environment} from '@envs/environment'; @@ -150,7 +149,7 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR FormsModule, ReactiveFormsModule, HttpModule, - AlertModule.forRoot(), + TypeaheadModule.forRoot(), TranslateModule.forRoot({ loader: { provide: TranslateLoader, @@ -160,8 +159,7 @@ export function getXHRBackend(injector: Injector, browser: BrowserXhr, xsrf: XSR }), StoreModule.provideStore(reducer), MomentModule, - MomentTimezoneModule, - Ng2AutoCompleteModule + MomentTimezoneModule ], providers: [ HttpClientService, http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 d92dd41..3348969 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/filtering.ts @@ -65,6 +65,6 @@ export interface SearchBoxParameterProcessed extends SearchBoxParameter { } export interface SearchBoxParameterTriggered { - value: string; + item: ListItem; isExclude: boolean; } http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.less ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.less b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.less index 674b195..d20bf75 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-list/dropdown-list.component.less @@ -22,20 +22,24 @@ max-height: @dropdown-max-height; overflow-y: auto; - .list-item-label { + > li { .dropdown-item-default; - label { - margin-bottom: 0; - cursor: pointer; - } + .list-item-label { + .dropdown-item-child-default; - input[type=checkbox]:checked + label:after { - top: @checkbox-top; - } + label { + margin-bottom: 0; + cursor: pointer; + } + + input[type=checkbox]:checked + label:after { + top: @checkbox-top; + } - .label-container { - width: 100%; + .label-container { + width: 100%; + } } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 440efde..f0cf3f4 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 @@ -18,8 +18,9 @@ <form [formGroup]="filtersForm"> <div class="form-inline filter-input-container col-md-8"> <search-box [parameterAddSubject]="queryParameterAdd" [parameterNameChangeSubject]="queryParameterNameChange" - formControlName="query" [items]="searchBoxItemsTranslated" [itemsOptions]="options" - [updateValueImmediately]="false" [updateValueSubject]="searchBoxValueUpdate" class="filter-input"></search-box> + class="filter-input" formControlName="query" [items]="searchBoxItems | async" [itemsOptions]="options" + [updateValueImmediately]="false" [updateValueSubject]="searchBoxValueUpdate" + defaultParameterName="log_message"></search-box> <time-range-picker *ngIf="isFilterConditionDisplayed('timeRange')" formControlName="timeRange" class="filter-input"></time-range-picker> <timezone-picker class="filter-input"></timezone-picker> http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 1717bd7..f9fe94b 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 @@ -24,7 +24,6 @@ import 'rxjs/add/observable/from'; import {FilterCondition, SearchBoxParameter, SearchBoxParameterTriggered} 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 {LogsContainerService} from '@app/services/logs-container.service'; @Component({ @@ -63,38 +62,22 @@ export class FiltersPanelComponent implements OnChanges { searchBoxItems: Observable<ListItem[]>; - get searchBoxItemsTranslated(): CommonEntry[] { - switch (this.logsType) { - case 'auditLogs': - return this.logsContainer.auditLogsColumnsTranslated; - case 'serviceLogs': - return this.logsContainer.serviceLogsColumnsTranslated; - default: - return []; - } - } - get filters(): {[key: string]: FilterCondition} { return this.logsContainer.filters; } /** * Object with options for search box parameter values - * @returns {[key: string]: CommonEntry[]} + * @returns {[key: string]: ListItem[]} */ - get options(): {[key: string]: CommonEntry[]} { + get options(): {[key: string]: ListItem[]} { 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 - } - }) + [condition.fieldName]: condition.options }); }, {}); } http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 0bf169d..a6e5616 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/mixins.less @@ -95,21 +95,26 @@ .dropdown-item-default { display: block; - padding: 3px 20px; - clear: both; - font-weight: 400; - line-height: 1.42857143; color: #333; - white-space: nowrap; cursor: pointer; - &:hover { + &.active > a, &:hover { color: #262626; text-decoration: none; background-color: #F5F5F5; } } +.dropdown-item-child-default { + display: block; + min-height: 24px; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + white-space: nowrap; +} + .log-colors { &.fatal { color: @fatal-color; http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 5ab9a69..786c130 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 @@ -15,26 +15,24 @@ limitations under the License. --> +<ng-template #listItemTemplate let-item="item"> + {{item.label | translate}} +</ng-template> <label class="parameter-label" *ngFor="let parameter of parameters"> <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> +<span class="active-parameter-label" *ngIf="isActive && activeItem">{{activeItem.label | translate}}:</span> <div [ngClass]="{'search-item-container': true, 'active': isActive, 'value': isValueInput}"> - <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-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> + <input #parameterInput [(ngModel)]="currentValue" [typeahead]="items" typeaheadOptionField="value" + [typeaheadItemTemplate]="listItemTemplate" (typeaheadNoResults)="setParameterNameMatchFlag($event)" + (typeaheadOnSelect)="changeParameterName({item: $event.item, isExclude: false})" + (focus)="onParameterInputFocus()" (keyup)="onParameterKeyUp($event)" + class="search-item-input parameter-input form-control"> + <input #valueInput [(ngModel)]="currentValue" [typeahead]="activeItemValueOptions" typeaheadOptionField="value" + [typeaheadItemTemplate]="listItemTemplate" (typeaheadNoResults)="setParameterValueMatchFlag($event)" + (typeaheadOnSelect)="onParameterValueChange($event.value)" (keydown)="onParameterValueKeyDown($event)" + (keyup)="onParameterValueKeyUp($event)" class="search-item-input value-input form-control"> </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 9deea92..80c0e5d 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 @@ -70,11 +70,6 @@ .collapsed-form-control; } - .search-item-text { - visibility: hidden; - padding: 0 @input-padding; - } - &.active { min-width: @dropdown-min-width; @@ -87,35 +82,24 @@ } &.value { - .parameter-input-wrapper { - /deep/ .ng2-auto-complete-wrapper { - display: none; - } + .parameter-input { + display: none; } .value-input { width: 100%; } } - - .no-options { - /deep/ .ng2-auto-complete { - display: none; - } - } } - /deep/ .ng2-auto-complete { - cursor: pointer; + /deep/ typeahead-container .dropdown-menu { .dropdown-list-default; - > ul { - border: none; + > li { + .dropdown-item-default; - li { - border: none; - background-color: initial; - .dropdown-item-default; + > a{ + .dropdown-item-child-default; } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.spec.ts ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.spec.ts index 72795a4..8d42c84 100644 --- a/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.spec.ts +++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.spec.ts @@ -48,4 +48,83 @@ describe('SearchBoxComponent', () => { it('should create component', () => { expect(component).toBeTruthy(); }); + + describe('#activeItemValueOptions()', () => { + const cases = [ + { + itemsOptions: null, + activeItem: { + value: 'v0' + }, + result: [], + title: 'no options available' + }, + { + itemsOptions: { + v1: [ + { + value: 'v2' + } + ] + }, + activeItem: null, + result: [], + title: 'no active item' + }, + { + itemsOptions: {}, + activeItem: { + value: 'v3' + }, + result: [], + title: 'empty itemsOptions object' + }, + { + itemsOptions: { + v4: [ + { + value: 'v5' + } + ] + }, + activeItem: { + value: 'v6' + }, + result: [], + title: 'no options available for active item' + }, + { + itemsOptions: { + v7: [ + { + value: 'v8' + }, + { + value: 'v9' + } + ] + }, + activeItem: { + value: 'v7' + }, + result: [ + { + value: 'v8' + }, + { + value: 'v9' + } + ], + title: 'options are available for active item' + } + ]; + + cases.forEach(test => { + it(test.title, () => { + component.itemsOptions = test.itemsOptions; + component.activeItem = test.activeItem; + expect(component.activeItemValueOptions).toEqual(test.result); + }); + }); + }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 64b8c36..b2136f4 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 @@ -16,11 +16,11 @@ * limitations under the License. */ -import {Component, OnInit, OnDestroy, Input, ViewChild, ElementRef, forwardRef} from '@angular/core'; +import {Component, OnInit, OnDestroy, HostListener, 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 {ListItem} from '@app/classes/list-item'; import {UtilsService} from '@app/services/utils.service'; @Component({ @@ -37,53 +37,65 @@ import {UtilsService} from '@app/services/utils.service'; }) export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccessor { - constructor(private element: ElementRef, private utils: UtilsService) { - this.rootElement = element.nativeElement; - this.rootElement.addEventListener('click', this.onRootClick); - this.rootElement.addEventListener('keydown', this.onRootKeyDown); + constructor(private utils: UtilsService) { } ngOnInit(): void { this.parameterInput = this.parameterInputRef.nativeElement; this.valueInput = this.valueInputRef.nativeElement; - this.parameterInput.addEventListener('focus', this.onParameterInputFocus); - this.parameterInput.addEventListener('blur', this.onParameterInputBlur); - this.valueInput.addEventListener('blur', this.onValueInputBlur); this.parameterNameChangeSubject.subscribe(this.onParameterNameChange); this.parameterAddSubject.subscribe(this.onParameterAdd); this.updateValueSubject.subscribe(this.updateValue); } ngOnDestroy(): void { - this.rootElement.removeEventListener('click', this.onRootClick); - this.rootElement.removeEventListener('keydown', this.onRootKeyDown); - this.parameterInput.removeEventListener('focus', this.onParameterInputFocus); - this.parameterInput.removeEventListener('blur', this.onParameterInputBlur); - this.valueInput.removeEventListener('blur', this.onValueInputBlur); this.parameterNameChangeSubject.unsubscribe(); this.parameterAddSubject.unsubscribe(); this.updateValueSubject.unsubscribe(); } - private readonly messageParameterName: string = 'log_message'; - private currentId: number = 0; private isExclude: boolean = false; + /** + * Indicates whether search box is currently active + * @type {boolean} + */ isActive: boolean = false; - isParameterInput: boolean = false; - + /** + * Indicates whether search query parameter value is currently typed + * @type {boolean} + */ isValueInput: boolean = false; currentValue: string; + /** + * Indicates whether there's no autocomplete matches in preset options for search query parameter name + * @type {boolean} + */ + private noMatchingParameterName: boolean = true; + + /** + * Indicates whether there's no autocomplete matches in preset options for search query parameter value + * @type {boolean} + */ + private noMatchingParameterValue: boolean = true; + + @Input() + items: ListItem[] = []; + @Input() - items: CommonEntry[] = []; + itemsOptions: {[key: string]: ListItem[]} = {}; + /** + * Name of parameter to be used if there are no matching values + * @type {string} + */ @Input() - itemsOptions: {[key: string]: CommonEntry[]}; + defaultParameterName?: string; @Input() parameterNameChangeSubject: Subject<SearchBoxParameterTriggered> = new Subject(); @@ -95,7 +107,8 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess updateValueSubject: Subject<void> = new Subject(); /** - * Indicates whether form should receive updated value immediately after user adds new search parameter + * Indicates whether form should receive updated value immediately after user adds new search parameter, without + * explicit actions like pressing Submit button or Enter key * @type {boolean} */ @Input() @@ -107,65 +120,65 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess @ViewChild('valueInput') valueInputRef: ElementRef; - private rootElement: HTMLElement; - private parameterInput: HTMLInputElement; private valueInput: HTMLInputElement; - activeItem: CommonEntry | null = null; + /** + * Currently active search query parameter + * @type {ListItem | null} + */ + activeItem: ListItem | null = null; + /** + * Search query parameters that are already specified by user + * @type {SearchBoxParameterProcessed[]} + */ parameters: SearchBoxParameterProcessed[] = []; - get activeItemValueOptions(): CommonEntry[] { + /** + * Available options for value of currently active search query parameter + * @returns {ListItem[]} + */ + get activeItemValueOptions(): ListItem[] { return this.itemsOptions && this.activeItem && this.itemsOptions[this.activeItem.value] ? this.itemsOptions[this.activeItem.value] : []; } private onChange: (fn: any) => void; - private onRootClick = (): void => { + @HostListener('click') + private onRootClick(): void { if (!this.isActive) { this.parameterInput.focus(); } - }; + } - private onRootKeyDown = (event: KeyboardEvent): void => { + @HostListener('keydown', ['$event']) + private onRootKeyDown(event: KeyboardEvent): void { if (this.utils.isEnterPressed(event)) { event.preventDefault(); } }; - private onParameterInputFocus = (): void => { - this.isActive = true; - this.isValueInput = false; - this.isParameterInput = true; - }; - - private onParameterInputBlur = (): void => { - if (!this.isValueInput) { - this.clear(); - } + @HostListener('blur') + private onRootBlur(): void { + this.clear(); }; - private onValueInputBlur = (): void => { - if (!this.isParameterInput) { - this.clear(); - } - }; + onParameterInputFocus(): void { + this.isActive = true; + } private switchToParameterInput = (): void => { - this.activeItem = null; + this.clear(); + this.isActive = true; this.isValueInput = false; setTimeout(() => this.parameterInput.focus(), 0); }; - 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); + private getItemByValue(name: string): ListItem { + return this.items.find((field: ListItem): boolean => field.value === name); } clear(): void { @@ -176,28 +189,17 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.valueInput.value = ''; } - itemsListFormatter(item: CommonEntry): string { - return item.name; - } - - itemsValueFormatter(item: CommonEntry): string { - return item.value; - } - 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); - } + this.activeItem = options.item.label ? options.item : this.getItemByValue(options.item.value); + this.isExclude = options.isExclude; + this.isActive = true; + this.isValueInput = true; + this.currentValue = ''; + this.valueInput.focus(); }; onParameterValueKeyDown(event: KeyboardEvent): void { @@ -207,59 +209,63 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess } onParameterValueKeyUp(event: KeyboardEvent): void { - if (this.utils.isEnterPressed(event) && this.currentValue) { + if (this.utils.isEnterPressed(event) && this.currentValue && this.noMatchingParameterValue) { 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: value, - isExclude: this.isExclude - }); - if (this.updateValueImmediately) { - this.updateValueSubject.next(); - } + this.parameters.push({ + id: this.currentId++, + name: this.activeItem.value, + label: this.activeItem.label, + value: value, + isExclude: this.isExclude + }); + if (this.updateValueImmediately) { + this.updateValueSubject.next(); } this.switchToParameterInput(); } - onParameterAdd = (options: SearchBoxParameter): void => { - const item = this.getItemByValue(options.name); + /** + * Adding the new parameter to search query + * @param parameter {SearchBoxParameter} + */ + onParameterAdd = (parameter: SearchBoxParameter): void => { + const item = this.getItemByValue(parameter.name); this.parameters.push({ id: this.currentId++, - name: options.name, - label: item.name, - value: options.value, - isExclude: options.isExclude + name: parameter.name, + label: item.label, + value: parameter.value, + isExclude: parameter.isExclude }); if (this.updateValueImmediately) { this.updateValueSubject.next(); } + this.switchToParameterInput(); }; - 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 { + onParameterKeyUp(event: KeyboardEvent): void { + if (this.utils.isEnterPressed(event)) { + if (!this.currentValue && !this.updateValueImmediately) { + this.updateValueSubject.next(); + } else if (this.currentValue && this.noMatchingParameterName && this.defaultParameterName) { this.parameterAddSubject.next({ - name: this.messageParameterName, + name: this.defaultParameterName, value: this.currentValue, isExclude: false }); } } - }; + } + /** + * Removing parameter from search query + * @param event {MouseEvent} - event that triggered this action + * @param id {number} - id of parameter + */ removeParameter(event: MouseEvent, id: number): void { this.parameters = this.parameters.filter((parameter: SearchBoxParameterProcessed): boolean => parameter.id !== id); if (this.updateValueImmediately) { @@ -275,6 +281,22 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess } }; + /** + * Update flag that indicates presence of autocomplete matches in preset options for search query parameter name + * @param hasNoMatches {boolean} + */ + setParameterNameMatchFlag(hasNoMatches: boolean): void { + this.noMatchingParameterName = hasNoMatches; + } + + /** + * Update flag that indicates presence of autocomplete matches in preset options for search query parameter value + * @param hasNoMatches {boolean} + */ + setParameterValueMatchFlag(hasNoMatches: boolean): void { + this.noMatchingParameterValue = hasNoMatches; + } + writeValue(parameters: SearchBoxParameterProcessed[] = []): void { this.parameters = parameters; this.updateValueSubject.next(); http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 0fc9fde..51b0c0b 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 @@ -29,9 +29,8 @@ import {ListItem} from '@app/classes/list-item'; export class ComponentActionsService { constructor( - private appSettings: AppSettingsService, private tabsStorage: TabsService, - private logsContainer: LogsContainerService, - private authService: AuthService + private appSettings: AppSettingsService, private tabsStorage: TabsService, private authService: AuthService, + private logsContainer: LogsContainerService ) { } @@ -132,7 +131,9 @@ export class ComponentActionsService { } proceedWithExclude = (item: string): void => this.logsContainer.queryParameterNameChange.next({ - value: item, + item: { + value: item + }, isExclude: true }); http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/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 4adf577..e754aa4 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 @@ -116,8 +116,6 @@ 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']; @@ -572,30 +570,10 @@ 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); http://git-wip-us.apache.org/repos/asf/ambari/blob/862b7d7b/ambari-logsearch/ambari-logsearch-web/yarn.lock ---------------------------------------------------------------------- diff --git a/ambari-logsearch/ambari-logsearch-web/yarn.lock b/ambari-logsearch/ambari-logsearch-web/yarn.lock index c005503..8eb2bbd 100644 --- a/ambari-logsearch/ambari-logsearch-web/yarn.lock +++ b/ambari-logsearch/ambari-logsearch-web/yarn.lock @@ -4152,11 +4152,7 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -ng2-auto-complete@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/ng2-auto-complete/-/ng2-auto-complete-0.12.0.tgz#9a78c39c5012404e7bc8365c03815ab7f68cea3d" - -ngx-bootstrap@^1.6.6: +ngx-bootstrap@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-1.9.3.tgz#28e75d14fb1beaee609383d7694de4eb3ba03b26"