AMBARI-22004 Log Search UI: implement 'Excluded' filter. (ababiichuk)
authorababiichuk <ababiichuk@hortonworks.com>
Wed, 20 Sep 2017 10:11:48 +0000 (13:11 +0300)
committerababiichuk <ababiichuk@hortonworks.com>
Wed, 20 Sep 2017 10:11:48 +0000 (13:11 +0300)
16 files changed:
ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.html
ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.less
ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.spec.ts
ambari-logsearch/ambari-logsearch-web/src/app/components/dropdown-button/dropdown-button.component.ts
ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.spec.ts
ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.html
ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.ts
ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.html
ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.less
ambari-logsearch/ambari-logsearch-web/src/app/components/search-box/search-box.component.ts
ambari-logsearch/ambari-logsearch-web/src/app/components/timezone-picker/timezone-picker.component.spec.ts
ambari-logsearch/ambari-logsearch-web/src/app/components/variables.less
ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.spec.ts
ambari-logsearch/ambari-logsearch-web/src/app/services/component-actions.service.ts
ambari-logsearch/ambari-logsearch-web/src/app/services/filtering.service.ts

index a16b205..9536573 100644 (file)
   limitations under the License.
 -->
 
-<div class="filter-label" *ngIf="label">{{label | translate}}</div>
 <div [ngClass]="{'dropup': isDropup}">
   <button class="btn btn-link dropdown-toggle" data-toggle="dropdown">
-    <span *ngIf="!isMultipleChoice">{{selectedLabel | translate}}</span> <span class="caret"></span>
+    <span *ngIf="iconClass || label"
+          [ngClass]="{'filter-label': true, 'plain': !isMultipleChoice && !hideCaret && showSelectedValue}">
+      <span *ngIf="iconClass" [ngClass]="iconClass"></span>
+      <span *ngIf="label">{{label | translate}}</span>
+    </span>
+    <span *ngIf="showSelectedValue && !isMultipleChoice">{{selectedLabel | translate}}</span>
+    <span *ngIf="!hideCaret" class="caret"></span>
   </button>
   <ul data-component="dropdown-list" [ngClass]="{'dropdown-menu': true, 'dropdown-menu-right': isRightAlign}"
       [items]="options" [isMultipleChoice]="isMultipleChoice" (selectedItemChange)="updateValue($event)"></ul>
index c5845b3..04730ef 100644 (file)
@@ -23,9 +23,12 @@ import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {ClustersService, clusters} from '@app/services/storage/clusters.service';
 import {ComponentsService, components} from '@app/services/storage/components.service';
+import {AppStateService, appState} from '@app/services/storage/app-state.service';
+import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {UtilsService} from '@app/services/utils.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
+import {HttpClientService} from '@app/services/http-client.service';
 
 import {DropdownButtonComponent} from './dropdown-button.component';
 
@@ -40,7 +43,9 @@ describe('DropdownButtonComponent', () => {
         StoreModule.provideStore({
           appSettings,
           clusters,
-          components
+          components,
+          appState,
+          hosts
         }),
         ...TranslationModules
       ],
@@ -48,9 +53,12 @@ describe('DropdownButtonComponent', () => {
         AppSettingsService,
         ClustersService,
         ComponentsService,
+        AppStateService,
+        HostsService,
         FilteringService,
         UtilsService,
-        ComponentActionsService
+        ComponentActionsService,
+        HttpClientService
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })
index 5800190..42b9451 100644 (file)
@@ -38,6 +38,15 @@ export class DropdownButtonComponent implements OnInit {
   label?: string;
 
   @Input()
+  iconClass?: string;
+
+  @Input()
+  hideCaret: boolean = false;
+
+  @Input()
+  showSelectedValue: boolean = true;
+
+  @Input()
   options?: any[];
 
   @Input()
index 22e4fca..fddf645 100644 (file)
@@ -23,9 +23,12 @@ import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
 import {ClustersService, clusters} from '@app/services/storage/clusters.service';
 import {ComponentsService, components} from '@app/services/storage/components.service';
+import {AppStateService, appState} from '@app/services/storage/app-state.service';
+import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import {FilteringService} from '@app/services/filtering.service';
 import {UtilsService} from '@app/services/utils.service';
+import {HttpClientService} from '@app/services/http-client.service';
 
 import {FilterButtonComponent} from './filter-button.component';
 
@@ -40,7 +43,9 @@ describe('FilterButtonComponent', () => {
         StoreModule.provideStore({
           appSettings,
           clusters,
-          components
+          components,
+          appState,
+          hosts
         }),
         ...TranslationModules
       ],
@@ -48,9 +53,12 @@ describe('FilterButtonComponent', () => {
         AppSettingsService,
         ClustersService,
         ComponentsService,
+        AppStateService,
+        HostsService,
         ComponentActionsService,
         FilteringService,
-        UtilsService
+        UtilsService,
+        HttpClientService
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })
index fb40a1c..dc76e79 100644 (file)
@@ -20,7 +20,8 @@
     <filter-dropdown [label]="filters.clusters.label" formControlName="clusters" [options]="filters.clusters.options"
                      [defaultLabel]="filters.clusters.defaultLabel" [isMultipleChoice]="true"
                      class="filter-input"></filter-dropdown>
-    <search-box formControlName="query" [items]="searchBoxItems" class="filter-input"></search-box>
+    <search-box formControlName="query" [items]="searchBoxItemsTranslated" class="filter-input"
+                [parameterNameChangeSubject]="queryParameterNameChange"></search-box>
     <time-range-picker formControlName="timeRange" [defaultLabel]="filters.timeRange.defaultLabel"
                        class="filter-input"></time-range-picker>
     <timezone-picker class="filter-input"></timezone-picker>
@@ -29,9 +30,8 @@
     </button-->
   </div>
   <div class="default-flex col-md-4">
-    <a href="#">
-      <span class="fa fa-search-minus"></span> {{'filter.excluded' | translate}}
-    </a>
+    <dropdown-button [options]="searchBoxItems" iconClass="fa fa-search-minus" label="filter.excluded"
+                     [hideCaret]="true" [showSelectedValue]="false" action="proceedWithExclude"></dropdown-button>
     <filter-button formControlName="hosts" [label]="filters.hosts.label"
                    [iconClass]="filters.hosts.iconClass" [subItems]="filters.hosts.options"
                    [isMultipleChoice]="true" [isRightAlign]="true"
index 644048f..72ab222 100644 (file)
@@ -18,6 +18,7 @@
 
 import {Component} from '@angular/core';
 import {FormGroup} from '@angular/forms';
+import {Subject} from 'rxjs/Subject';
 import {TranslateService} from '@ngx-translate/core';
 import {FilteringService} from '@app/services/filtering.service';
 import {LogsContainerService} from '@app/services/logs-container.service';
@@ -42,7 +43,13 @@ export class FiltersPanelComponent {
               };
             }),
             labelKeys = items.map(item => item.name);
-          translate.get(labelKeys).first().subscribe(translation => this.searchBoxItems = items.map(item => {
+          this.searchBoxItems = items.map(item => {
+            return {
+              label: item.name,
+              value: item.value
+            };
+          });
+          translate.get(labelKeys).first().subscribe(translation => this.searchBoxItemsTranslated = items.map(item => {
             return {
               name: translation[item.name],
               value: item.value
@@ -62,6 +69,8 @@ export class FiltersPanelComponent {
 
   searchBoxItems: any[] = [];
 
+  searchBoxItemsTranslated: any[] = [];
+
   get filters(): any {
     return this.filtering.filters;
   }
@@ -70,4 +79,8 @@ export class FiltersPanelComponent {
     return this.filtering.filtersForm;
   }
 
+  get queryParameterNameChange(): Subject<any> {
+    return this.filtering.queryParameterNameChange;
+  }
+
 }
index c57c11d..65f0ee6 100644 (file)
@@ -21,8 +21,13 @@ import {async, ComponentFixture, TestBed} 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';
+import {ClustersService, clusters} from '@app/services/storage/clusters.service';
+import {ComponentsService, components} from '@app/services/storage/components.service';
+import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
 import {FilteringService} from '@app/services/filtering.service';
+import {HttpClientService} from '@app/services/http-client.service';
 
 import {MenuButtonComponent} from './menu-button.component';
 
@@ -35,14 +40,23 @@ describe('MenuButtonComponent', () => {
       declarations: [MenuButtonComponent],
       imports: [
         StoreModule.provideStore({
-          appSettings
+          appSettings,
+          appState,
+          clusters,
+          components,
+          hosts
         }),
         ...TranslationModules
       ],
       providers: [
         AppSettingsService,
+        AppStateService,
+        ClustersService,
+        ComponentsService,
+        HostsService,
         ComponentActionsService,
-        FilteringService
+        FilteringService,
+        HttpClientService
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })
index 64e15dc..e88159c 100644 (file)
@@ -16,6 +16,7 @@
 -->
 
 <label class="parameter-label" *ngFor="let parameter of parameters">
+  <span *ngIf="parameter.isExclude" class="fa fa-search-minus exclude-icon"></span>
   {{parameter.label | translate}}:
   <span class="parameter-value">{{parameter.value}}</span>
   <span class="remove-parameter" (click)="removeParameter($event, parameter.id)">&times;</span>
@@ -23,7 +24,7 @@
 <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)="onParameterNameChange($event)"
+         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)">
index cccf5d5..6d4378b 100644 (file)
     background-color: @main-background-color;
     font-size: 0.8em;
 
+    .exclude-icon {
+      color: @exclude-color;
+    }
+
     .parameter-value {
       font-weight: normal;
     }
index 82c455e..4730190 100644 (file)
@@ -18,6 +18,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 {UtilsService} from '@app/services/utils.service';
 
 @Component({
@@ -46,6 +47,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
     this.parameterInput.addEventListener('focus', this.onParameterInputFocus);
     this.parameterInput.addEventListener('blur', this.onParameterInputBlur);
     this.valueInput.addEventListener('blur', this.onValueInputBlur);
+    this.parameterNameChangeSubject.subscribe(this.onParameterNameChange);
   }
 
   ngOnDestroy() {
@@ -54,10 +56,15 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
     this.parameterInput.removeEventListener('focus', this.onParameterInputFocus);
     this.parameterInput.removeEventListener('blur', this.onParameterInputBlur);
     this.valueInput.removeEventListener('blur', this.onValueInputBlur);
+    this.parameterNameChangeSubject.unsubscribe();
   }
 
   private currentId: number = 0;
 
+  private isExclude: boolean = false;
+
+  private defaultSubject: Subject<any> = new Subject();
+
   isActive: boolean = false;
 
   isParameterInput: boolean = false;
@@ -69,6 +76,9 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
   @Input()
   items: any[] = [];
 
+  @Input()
+  parameterNameChangeSubject: Subject<any> = this.defaultSubject;
+
   @ViewChild('parameterInput')
   parameterInputRef: ElementRef;
 
@@ -127,14 +137,19 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
     return item.name;
   }
 
-  onParameterNameChange(item: any): void {
-    if (item) {
-      this.isParameterInput = false;
-      this.isValueInput = true;
-      this.activeItem = item;
-      this.currentValue = '';
-      setTimeout(() => this.valueInput.focus(), 0);
-    }
+  changeParameterName(item: any): void {
+    this.parameterNameChangeSubject.next(item);
+  }
+
+  onParameterNameChange = (options: any): void => {
+    this.activeItem = typeof options.item === 'string' ?
+      this.items.find(field => field.value === options.item) : options.item;
+    this.isExclude = options.isExclude;
+    this.isActive = true;
+    this.isParameterInput = false;
+    this.isValueInput = true;
+    this.currentValue = '';
+    setTimeout(() => this.valueInput.focus(), 0);
   }
 
   onParameterValueChange(event: KeyboardEvent): void {
@@ -144,7 +159,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess
         name: this.activeItem.value,
         label: this.activeItem.name,
         value: this.currentValue,
-        isExclude: false
+        isExclude: this.isExclude
       });
       this.currentValue = '';
       this.activeItem = null;
index 380f030..abbc9ce 100644 (file)
@@ -20,7 +20,13 @@ import {async, ComponentFixture, TestBed} 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';
+import {ClustersService, clusters} from '@app/services/storage/clusters.service';
+import {ComponentsService, components} from '@app/services/storage/components.service';
+import {HostsService, hosts} from '@app/services/storage/hosts.service';
 import {ComponentActionsService} from '@app/services/component-actions.service';
+import {FilteringService} from '@app/services/filtering.service';
+import {HttpClientService} from '@app/services/http-client.service';
 import {TimeZoneAbbrPipe} from '@app/pipes/timezone-abbr.pipe';
 import {ModalComponent} from '@app/components/modal/modal.component';
 
@@ -39,13 +45,23 @@ describe('TimeZonePickerComponent', () => {
       ],
       imports: [
         StoreModule.provideStore({
-          appSettings
+          appSettings,
+          appState,
+          clusters,
+          components,
+          hosts
         }),
         ...TranslationModules
       ],
       providers: [
         AppSettingsService,
-        ComponentActionsService
+        AppStateService,
+        ClustersService,
+        ComponentsService,
+        HostsService,
+        ComponentActionsService,
+        FilteringService,
+        HttpClientService
       ],
     })
     .compileComponents();
index 8dba5b3..ab95030 100644 (file)
@@ -23,7 +23,7 @@
 @input-border-width: 1px;
 @input-border: @input-border-width solid #CFD3D7;
 @button-border-radius: 4px;
-@input-group-addon-padding: 6px 0 6px 12px;
+@input-group-addon-padding: 6px 12px 6px 0;
 @block-margin-top: 20px;
 @link-color: #1491C1;
 @link-hover-color: #23527C;
@@ -49,6 +49,7 @@
 @unknown-color: #BDBDBD;
 @submit-color: #5CB85C;
 @submit-hover-color: #449D44;
+@exclude-color: #EF6162;
 
 // Mixins
 .flex-vertical-align {
index ff0ee37..f961907 100644 (file)
 import {TestBed, inject} from '@angular/core/testing';
 import {StoreModule} from '@ngrx/store';
 import {AppSettingsService, appSettings} from '@app/services/storage/app-settings.service';
+import {ClustersService, clusters} from '@app/services/storage/clusters.service';
+import {ComponentsService, components} from '@app/services/storage/components.service';
+import {HostsService, hosts} from '@app/services/storage/hosts.service';
+import {FilteringService} from '@app/services/filtering.service';
+import {HttpClientService} from '@app/services/http-client.service';
 
 import {ComponentActionsService} from './component-actions.service';
 
 describe('ComponentActionsService', () => {
+  const httpClient = {
+    get: () => {
+      return {
+        subscribe: () => {
+        }
+      };
+    }
+  };
+
   beforeEach(() => {
     TestBed.configureTestingModule({
       imports: [
         StoreModule.provideStore({
-          appSettings
+          appSettings,
+          clusters,
+          components,
+          hosts
         })
       ],
       providers: [
+        ComponentActionsService,
         AppSettingsService,
-        ComponentActionsService
+        ClustersService,
+        ComponentsService,
+        HostsService,
+        FilteringService,
+        {
+          provide: HttpClientService,
+          useValue: httpClient
+        }
       ]
     });
   });
index a8235fa..f29705c 100644 (file)
 import {Injectable} from '@angular/core';
 import {AppSettingsService} from '@app/services/storage/app-settings.service';
 import {CollectionModelService} from '@app/models/store.model';
+import {FilteringService} from '@app/services/filtering.service';
 
 @Injectable()
 export class ComponentActionsService {
 
-  constructor(private appSettings: AppSettingsService) {
+  constructor(private appSettings: AppSettingsService, private filtering: FilteringService) {
   }
 
   //TODO implement actions
@@ -50,4 +51,9 @@ export class ComponentActionsService {
     }));
   }
 
+  proceedWithExclude = (item: string): void => this.filtering.queryParameterNameChange.next({
+    item: item,
+    isExclude: true
+  });
+
 }
index cf82ff6..2d3f640 100644 (file)
@@ -18,6 +18,7 @@
 
 import {Injectable} from '@angular/core';
 import {FormControl, FormGroup} from '@angular/forms';
+import {Subject} from 'rxjs/Subject';
 import * as moment from 'moment-timezone';
 import {AppSettingsService} from '@app/services/storage/app-settings.service';
 import {ClustersService} from '@app/services/storage/clusters.service';
@@ -432,6 +433,8 @@ export class FilteringService {
 
   filtersForm = new FormGroup(this.filtersFormItems);
 
+  queryParameterNameChange: Subject<any> = new Subject();
+
   loadClusters(): void {
     this.httpClient.get('clusters').subscribe(response => {
       const clusterNames = response.json();