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";
 

Reply via email to