This is an automated email from the ASF dual-hosted git repository.

ababiichuk pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 0f569fb  AMBARI-23362 Log Search UI: filter dropdown lists fixes
0f569fb is described below

commit 0f569fb1f2f2eff7691a785bded7c904a979eaf8
Author: Istvan Tobias <[email protected]>
AuthorDate: Thu Mar 29 12:09:04 2018 +0200

    AMBARI-23362 Log Search UI: filter dropdown lists fixes
---
 .../src/app/classes/list-item.ts                   |   1 +
 .../filters-panel/filters-panel.component.html     |  11 +-
 .../filters-panel/filters-panel.component.less     |   6 ++
 .../components/log-level/log-level.component.ts    |  18 ++--
 .../menu-button/menu-button.component.html         |   1 +
 .../menu-button/menu-button.component.ts           |   3 +
 .../dropdown-list/dropdown-list.component.html     |  61 ++++++++---
 .../dropdown-list/dropdown-list.component.less     |  42 +++++++-
 .../dropdown-list/dropdown-list.component.ts       | 118 ++++++++++++++++++---
 .../shipper-configuration.component.ts             |   4 +-
 .../src/app/services/logs-container.service.ts     |   6 +-
 .../ambari-logsearch-web/src/assets/i18n/en.json   |   4 +
 12 files changed, 228 insertions(+), 47 deletions(-)

diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts 
b/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
index 8505373..3a9b72c 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/classes/list-item.ts
@@ -21,6 +21,7 @@ export interface ListItem {
   label?: string;
   value: any;
   iconClass?: string;
+  cssClass?: string;
   isChecked?: boolean;
   onSelect?: Function;
   isDivider?: boolean;
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 0383230..397603b 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
@@ -35,16 +35,19 @@
     <filter-button *ngIf="isFilterConditionDisplayed('hosts')" 
formControlName="hosts"
                    label="{{filters.hosts.label | translate}}" 
[iconClass]="filters.hosts.iconClass"
                    [subItems]="filters.hosts.options" 
[isMultipleChoice]="true" [isRightAlign]="true"
-                   
additionalLabelComponentSetter="getDataForHostsNodeBar"></filter-button>
+                   additionalLabelComponentSetter="getDataForHostsNodeBar"
+                   [useDropDownLocalFilter]="true"></filter-button>
     <filter-button *ngIf="isFilterConditionDisplayed('users')" 
formControlName="users"
                    label="{{filters.users.label | translate}}" 
[iconClass]="filters.users.iconClass"
-                   [subItems]="filters.users.options" 
[isMultipleChoice]="true" [isRightAlign]="true"></filter-button>
-    <filter-button *ngIf="isFilterConditionDisplayed('components')" 
formControlName="components"
+                   [subItems]="filters.users.options" 
[isMultipleChoice]="true" [isRightAlign]="true"
+                   [useDropDownLocalFilter]="true"></filter-button>
+    <filter-button *ngIf="isFilterConditionDisplayed('components')" 
formControlName="components"  [useDropDownLocalFilter]="true"
                    label="{{filters.components.label | translate}}" 
[iconClass]="filters.components.iconClass"
                    [subItems]="filters.components.options" 
[isMultipleChoice]="true" [isRightAlign]="true"
                    
additionalLabelComponentSetter="getDataForComponentsNodeBar"></filter-button>
     <filter-button *ngIf="isFilterConditionDisplayed('levels')" 
formControlName="levels"
                    label="{{filters.levels.label | translate}}" 
[iconClass]="filters.levels.iconClass"
-                   [subItems]="filters.levels.options" 
[isMultipleChoice]="true" [isRightAlign]="true"></filter-button>
+                   [subItems]="filters.levels.options" 
[isMultipleChoice]="true" [isRightAlign]="true"
+                   [useDropDownLocalFilter]="true"></filter-button>
   </div>
 </form>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.less
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.less
index a1ecc02..c748c37 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.less
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/filters-panel/filters-panel.component.less
@@ -71,4 +71,10 @@
   .filter-buttons {
     .default-flex;
   }
+
+  /deep/ .dropdown-menu {
+    .log-level-item {
+      .log-colors;
+    }
+  }
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts
index 8542770..61ca495 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-level/log-level.component.ts
@@ -28,14 +28,7 @@ import {Component, Input} from '@angular/core';
 })
 export class LogLevelComponent {
 
-  /**
-   * This is the log entry object
-   * @type {object}
-   */
-  @Input()
-  logEntry: any;
-
-  private classMap: object = {
+  static classMap: object = {
     warn: 'fa-exclamation-triangle',
     fatal: 'fa-exclamation-circle',
     error: 'fa-exclamation-circle',
@@ -45,8 +38,15 @@ export class LogLevelComponent {
     unknown: 'fa-question-circle'
   };
 
+  /**
+   * This is the log entry object
+   * @type {object}
+   */
+  @Input()
+  logEntry: any;
+
   get cssClass() {
-    return this.classMap[((this.logEntry && this.logEntry.level) || 
'unknown').toLowerCase()];
+    return LogLevelComponent.classMap[((this.logEntry && this.logEntry.level) 
|| 'unknown').toLowerCase()];
   }
 
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
index 8e67b5d..12dd59c 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.html
@@ -27,5 +27,6 @@
       (selectedItemChange)="onDropdownItemChange($event)" 
[isMultipleChoice]="isMultipleChoice"
       [additionalLabelComponentSetter]="additionalLabelComponentSetter"
       [ngClass]="'dropdown-menu' + (isRightAlign ? ' dropdown-menu-right' : 
'') + (listClass ? ' ' + listClass : '')"
+      [useLocalFilter]="useDropDownLocalFilter"
   ></ul>
 </div>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
index a966985..f94bc0e 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.ts
@@ -59,6 +59,9 @@ export class MenuButtonComponent {
   @Input()
   caretClass: string = 'fa-caret-down';
 
+  @Input()
+  useDropDownLocalFilter: boolean = false;
+
   /**
    * The minimum time to handle a mousedown as a longclick. Default is 500 ms 
(0.5sec)
    * @default 500
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
index 9861c24..e2c6daa 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.html
@@ -14,22 +14,55 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-
-<li *ngFor="let item of items" [class.divider]="item.isDivider" 
[attr.role]="item.isDivider ? 'separator' : null">
-  <ng-container *ngIf="!item.isDivider">
-    <label class="list-item-label" *ngIf="isMultipleChoice">
-      <input type="checkbox" [attr.id]="item.id || item.value" 
[(ngModel)]="item.isChecked"
-             (change)="changeSelectedItem({value: item.value, isChecked: 
$event.currentTarget.checked})">
-      <label [attr.for]="item.id || item.value" class="label-container">
+<ng-template #listItem let-item let-isMultipleChoice="isMultipleChoice">
+  <li [class.divider]="item.isDivider" [class.filtered]="isFiltered(item)"
+      [attr.role]="item.isDivider ? 'separator' : null" 
[class]="(item.cssClass || '')">
+    <ng-container *ngIf="!item.isDivider">
+      <label class="list-item-label" *ngIf="isMultipleChoice">
+        <input type="checkbox" [attr.id]="item.id || item.value" 
[(ngModel)]="item.isChecked"
+               (change)="changeSelectedItem({value: item.value, isChecked: 
$event.currentTarget.checked}, $event)">
+        <label [attr.for]="item.id || item.value" class="label-container">
+          <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
+          {{item.label | translate}}
+          <span #additionalComponent></span>
+        </label>
+      </label>
+      <span class="list-item-label label-container" *ngIf="!isMultipleChoice" 
(click)="changeSelectedItem(item, $event)">
         <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
         {{item.label | translate}}
         <span #additionalComponent></span>
-      </label>
+      </span>
+    </ng-container>
+  </li>
+</ng-template>
+
+<li *ngIf="useLocalFilter" class="filter">
+  <input #filter [value]="filterStr" (keyup)="onFilterInputKeyUp($event)" 
class="form-control">
+  <i class="fa fa-search"></i>
+  <i class="fa fa-times-circle-o clear-filter" [class.hide]="!filterRegExp" 
(click)="clearFilter($event)"></i>
+</li>
+<li class="selection-all">
+  <label *ngIf="isMultipleChoice && items && items.length" 
class="list-item-label" (click)="changeAllSelection($event)">
+    <input type="checkbox" #selectAll [value]="items && items.length"
+           [checked]="items && (items.length === itemsSelected.length)">
+    <label class="label-container">
+      <span>{{'dropdown.selection.all' | translate:({total: items && 
items.length, listName: ''})}}</span>
     </label>
-    <span class="list-item-label label-container" *ngIf="!isMultipleChoice" 
(click)="changeSelectedItem(item)">
-      <span *ngIf="item.iconClass" [ngClass]="item.iconClass"></span>
-      {{item.label | translate}}
-      <span #additionalComponent></span>
-    </span>
-  </ng-container>
+  </label>
+</li>
+<li *ngIf="isMultipleChoice && items && items.length" class="divider"></li>
+<li class="selections" *ngIf="itemsSelected.length>0">
+  <span>{{'dropdown.selection' | translate:({total: 
itemsSelected.length})}}</span>
+  <a href="#" 
(click)="onClearSelectionClick($event)">{{'dropdown.selection.clear' | 
translate}}</a>
 </li>
+
+<ng-container *ngFor="let item of itemsSelected">
+  <ng-container
+    *ngTemplateOutlet="listItem; context:{$implicit: item, isMultipleChoice: 
isMultipleChoice}"></ng-container>
+</ng-container>
+
+<li *ngIf="itemsSelected.length && itemsUnSelected.length" 
class="divider"></li>
+<ng-container *ngFor="let item of itemsUnSelected">
+  <ng-container
+    *ngTemplateOutlet="listItem; context:{$implicit: item, isMultipleChoice: 
isMultipleChoice}"></ng-container>
+</ng-container>
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
index 58c97d1..6777224 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.less
@@ -24,7 +24,12 @@
 
   > li {
     .dropdown-item-default;
-
+    transition: opacity 300ms ease-in, height 100ms 400ms ease-in;
+    &.filtered {
+      overflow: hidden;
+      opacity: 0;
+      height: 0;
+    }
     .list-item-label {
       .dropdown-item-child-default;
 
@@ -41,5 +46,40 @@
         width: 100%;
       }
     }
+    &.filter {
+      padding: 0 .25em;
+      position: relative;
+      &:hover {
+        background-color: transparent;
+      }
+      .fa {
+        margin: 0 .6em 0 0;
+        opacity: .8;
+        position: absolute;
+        right: 0;
+        top: .5em
+      }
+      .fa-search {
+        cursor: auto;
+      }
+      .clear-filter {
+        right: 1.3em;
+      }
+      input {
+        height: 2em;
+        width: 100%;
+      }
+      .clear-filter {
+        cursor: pointer;
+        float: right;
+      }
+    }
+    &.selections {
+      font-size: .75em;
+      padding: .1em 20px;
+      a {
+        display: inline;
+      }
+    }
   }
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
index 78cd39c..8ceb51c 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-list/dropdown-list.component.ts
@@ -18,10 +18,12 @@
 
 import {
   Component, OnChanges, AfterViewChecked, SimpleChanges, Input, Output, 
EventEmitter, ViewChildren, ViewContainerRef,
-  QueryList, ChangeDetectorRef
+  QueryList, ChangeDetectorRef, ElementRef, ViewChild
 } from '@angular/core';
 import {ListItem} from '@app/classes/list-item';
 import {ComponentGeneratorService} from 
'@app/services/component-generator.service';
+import {TranslateService} from '@ngx-translate/core';
+import {Observable} from 'rxjs/Observable';
 
 @Component({
   selector: 'ul[data-component="dropdown-list"]',
@@ -30,23 +32,14 @@ import {ComponentGeneratorService} from 
'@app/services/component-generator.servi
 })
 export class DropdownListComponent implements OnChanges, AfterViewChecked {
 
-  constructor(private componentGenerator: ComponentGeneratorService, private 
changeDetector: ChangeDetectorRef) {
-  }
-
   private shouldRenderAdditionalComponents: boolean = false;
 
-  ngOnChanges(changes: SimpleChanges): void {
-    if (changes.hasOwnProperty('items')) {
-      this.shouldRenderAdditionalComponents = true;
-    }
-  }
+  @Input()
+  items: ListItem[] = [];
 
-  ngAfterViewChecked() {
-    this.renderAdditionalComponents();
-  }
+  private itemsSelected: ListItem[] = [];
 
-  @Input()
-  items: ListItem[];
+  private itemsUnSelected: ListItem[] = [];
 
   @Input()
   isMultipleChoice?: boolean = false;
@@ -65,9 +58,95 @@ export class DropdownListComponent implements OnChanges, 
AfterViewChecked {
   })
   containers: QueryList<ViewContainerRef>;
 
+  @Input()
+  useLocalFilter = false;
+
+  @ViewChild('filter')
+  filterRef: ElementRef;
+
+  @Input()
+  filterStr = '';
+
+
+  @ViewChild('selectAll')
+  selectAllRef: ElementRef;
+
+  private filterRegExp: RegExp;
+
+  constructor(
+    private componentGenerator: ComponentGeneratorService,
+    private changeDetector: ChangeDetectorRef
+  ) {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.hasOwnProperty('items')) {
+      this.separateSelections();
+      this.shouldRenderAdditionalComponents = true;
+    }
+  }
+
+  ngAfterViewChecked() {
+    this.renderAdditionalComponents();
+  }
+
+  private separateSelections() {
+    this.itemsSelected = this.items ? this.items.filter((item: ListItem) => 
item.isChecked) : [];
+    this.itemsUnSelected = this.items ? this.items.filter((item: ListItem) => 
!item.isChecked) : [];
+    this.shouldRenderAdditionalComponents = true;
+  }
+
+  private clearSelection() {
+    this.items.forEach((item: ListItem) => item.isChecked = false);
+    this.separateSelections();
+  }
+
+  private onClearSelectionClick = (event): void => {
+    event.preventDefault();
+    event.stopPropagation();
+    this.clearSelection();
+  }
+
+  private changeAllSelection(event) {
+    event.stopPropagation();
+    if (!this.selectAllRef.nativeElement.checked) {
+      this.selectAll();
+    } else {
+      this.unSelectAll();
+    }
+    this.separateSelections();
+  }
+
+  selectAll() {
+    this.items.forEach((item: ListItem) => item.isChecked = true);
+  }
+
+  unSelectAll() {
+    this.items.forEach((item: ListItem) => item.isChecked = false);
+  }
+
+  private onFilterInputKeyUp(event) {
+    if (this.useLocalFilter) {
+      this.filterRegExp = event.target.value ? new 
RegExp(`${event.target.value}`, 'gi') : null;
+      this.filterStr = event.target.value;
+    }
+  }
+
+  private isFiltered = (item: ListItem): boolean => {
+    return this.useLocalFilter && this.filterRegExp && (
+      !this.filterRegExp.test(item.value)
+      &&
+      !this.filterRegExp.test(item.label)
+    );
+  }
+
+  private clearFilter = (event: MouseEvent): void => {
+    this.filterRegExp = null;
+    this.filterStr = '';
+  }
+
   private renderAdditionalComponents(): void {
-    const setter = this.additionalLabelComponentSetter,
-      containers = this.containers;
+    const setter = this.additionalLabelComponentSetter;
+    const containers = this.containers;
     if (this.shouldRenderAdditionalComponents && setter && containers) {
       containers.forEach((container, index) => 
this.componentGenerator[setter](this.items[index].value, container));
       this.shouldRenderAdditionalComponents = false;
@@ -75,11 +154,16 @@ export class DropdownListComponent implements OnChanges, 
AfterViewChecked {
     }
   }
 
-  changeSelectedItem(options: ListItem): void {
+  changeSelectedItem(options: ListItem, event?: MouseEvent): void {
     if (options.onSelect) {
       options.onSelect(...this.actionArguments);
     }
+    this.separateSelections();
     this.selectedItemChange.emit(options);
+    if (event) {
+      event.preventDefault();
+      event.stopPropagation();
+    }
   }
 
 }
diff --git 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
index 78d4ee3..0031a70 100644
--- 
a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
+++ 
b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shipper/components/shipper-configuration/shipper-configuration.component.ts
@@ -27,7 +27,9 @@ import {ShipperCluster} from 
'../../models/shipper-cluster.type';
 import {ShipperClusterService} from 
'../../models/shipper-cluster-service.type';
 import {ShipperConfigurationService} from 
'../../services/shipper-configuration.service';
 import {ShipperClusterServiceListService} from 
'../../services/shipper-cluster-service-list.service';
-import {ShipperServiceConfigurationFormComponent} from 
'@modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component';
+import {
+  ShipperServiceConfigurationFormComponent
+} from 
'@modules/shipper/components/shipper-service-configuration-form/shipper-service-configuration-form.component';
 import {TranslateService} from '@ngx-translate/core';
 import {ClustersService} from '@app/services/storage/clusters.service';
 import {ShipperClusterServiceValidationModel} from 
'@modules/shipper/models/shipper-cluster-service-validation.model';
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 c2a1366..29a8437 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
@@ -63,6 +63,7 @@ import {RoutingUtilsService} from 
'@app/services/routing-utils.service';
 import {LogsFilteringUtilsService} from 
'@app/services/logs-filtering-utils.service';
 import {BehaviorSubject} from 'rxjs/BehaviorSubject';
 import {LogsStateService} from '@app/services/storage/logs-state.service';
+import {LogLevelComponent} from 
'@app/components/log-level/log-level.component';
 
 @Injectable()
 export class LogsContainerService {
@@ -349,9 +350,12 @@ export class LogsContainerService {
       label: 'filter.levels',
       iconClass: 'fa fa-sort-amount-asc',
       options: this.logLevels.map((level: LogLevelObject): ListItem => {
+        const cssClass = (level.name || 'unknown').toLowerCase();
         return {
           label: level.label,
-          value: level.name
+          value: level.name,
+          cssClass: `log-level-item ${cssClass}`,
+          iconClass: `fa ${LogLevelComponent.classMap[cssClass]}`
         };
       }),
       defaultSelection: [],
diff --git a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json 
b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
index d55ee54..e95c78a 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
+++ b/ambari-logsearch/ambari-logsearch-web/src/assets/i18n/en.json
@@ -10,6 +10,10 @@
 
   "common.form.errors.required": "This field is required",
 
+  "dropdown.selection": "Selected ({{total}})",
+  "dropdown.selection.clear": "(Clear)",
+  "dropdown.selection.all": "All {{listName}} ({{total}})",
+
   "modal.submit": "OK",
   "modal.cancel": "Cancel",
   "modal.apply": "Apply",

-- 
To stop receiving notification emails like this one, please contact
[email protected].

Reply via email to