[AMBARI-24887] [Log Search UI] The component filter dropdown does not select any item (#2599)

Change-Id: I9ef62c6e77e3d89d6548734793dc246a7342e80e
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
index 9921d41..f71b946 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/cluster-filter/cluster-filter.component.ts
@@ -128,10 +128,10 @@
         .filter((state: DataAvailabilityValues) => state === DataAvailabilityValues.AVAILABLE)
         .first()
         .subscribe(() => {
-          this.filterDropdown.updateSelection(clusterSelection);
+          this.filterDropdown.writeValue(clusterSelection);
         });
     } else {
-      this.filterDropdown.updateSelection(null);
+      this.filterDropdown.clearSelection();
     }
   }
 
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts
index af14925..d6f24e5 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/filter-button/filter-button.component.ts
@@ -19,7 +19,6 @@
 import {Component, forwardRef} from '@angular/core';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {ListItem} from '@app/classes/list-item';
-import {UtilsService} from '@app/services/utils.service';
 import {MenuButtonComponent} from '@app/components/menu-button/menu-button.component';
 
 @Component({
@@ -36,65 +35,23 @@
 })
 export class FilterButtonComponent extends MenuButtonComponent implements ControlValueAccessor {
 
-  private selectedItems: ListItem[] = [];
-
   private onChange: (fn: any) => void;
 
-  constructor(private utils: UtilsService) {
-    super();
+  updateSelection(items: ListItem | ListItem[], callOnChange = true): void {
+    super.updateSelection(items);
+    if (callOnChange) {
+      this._onChange(this.selection);
+    }
   }
 
-  get selection(): ListItem[] {
-    return this.selectedItems;
-  }
-
-  set selection(items: ListItem[]) {
-    this.selectedItems = items;
+  private _onChange(value) {
     if (this.onChange) {
-      this.onChange(items);
-    }
-  }
-
-  updateSelection(updates: ListItem | ListItem[]): void {
-    if (updates && (!Array.isArray(updates) || updates.length)) {
-      const items: ListItem[] = Array.isArray(updates) ? updates : [updates];
-      if (this.isMultipleChoice) {
-        items.forEach((item: ListItem) => {
-          if (this.subItems && this.subItems.length) {
-            const itemToUpdate: ListItem = this.subItems.find((option: ListItem) => this.utils.isEqual(option.value, item.value));
-            if (itemToUpdate) {
-              itemToUpdate.isChecked = item.isChecked;
-            }
-          }
-        });
-      } else {
-        const selectedItem: ListItem = items.find((item: ListItem) => item.isChecked);
-        this.subItems.forEach((item: ListItem) => {
-          item.isChecked = !!selectedItem && this.utils.isEqual(item.value, selectedItem.value);
-        });
-      }
-    } else {
-      this.subItems.forEach((item: ListItem) => item.isChecked = false);
-    }
-    const checkedItems = this.subItems.filter((option: ListItem): boolean => option.isChecked);
-    this.selection = checkedItems;
-    this.selectItem.emit(checkedItems.map((option: ListItem): any => option.value));
-    if (this.dropdownList) {
-      this.dropdownList.doItemsCheck();
+      this.onChange(value);
     }
   }
 
   writeValue(items: ListItem[]) {
-    let listItems: ListItem[] = [];
-    if (items && items.length) {
-      listItems = items.map((item: ListItem) => {
-        return {
-          ...item,
-          isChecked: true
-        };
-      });
-    }
-    this.updateSelection(listItems);
+    this.selection = items;
   }
 
   registerOnChange(callback: any): void {
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
index 65c22a4..62a1433 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/log-index-filter/log-index-filter.component.ts
@@ -192,7 +192,7 @@
 
   writeValue(filters: HomogeneousObject<LogIndexFilterComponentConfig[]>): void {
     this.configs = filters;
-    this.updateValue();
+    this.setCurrentConfig();
   }
 
   registerOnChange(callback: any): void {
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
index 4e77db5..2691273 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/menu-button/menu-button.component.spec.ts
@@ -40,6 +40,7 @@
 import {AuthService} from '@app/services/auth.service';
 
 import {MenuButtonComponent} from './menu-button.component';
+import { UtilsService } from '@app/services/utils.service';
 
 describe('MenuButtonComponent', () => {
   let component: MenuButtonComponent;
@@ -93,7 +94,8 @@
           useValue: httpClient
         },
         LogsContainerService,
-        AuthService
+        AuthService,
+        UtilsService
       ],
       schemas: [NO_ERRORS_SCHEMA]
     })
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 788494c..faf2165 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
@@ -19,6 +19,7 @@
 import {Component, Input, Output, ViewChild, ElementRef, EventEmitter} from '@angular/core';
 import {ListItem} from '@app/classes/list-item';
 import {DropdownListComponent} from '@modules/shared/components/dropdown-list/dropdown-list.component';
+import {UtilsService} from '@app/services/utils.service';
 
 @Component({
   selector: 'menu-button',
@@ -46,13 +47,13 @@
   subItems?: ListItem[];
 
   @Input()
-  isMultipleChoice: boolean = false;
+  isMultipleChoice = false;
 
   @Input()
-  hideCaret: boolean = false;
+  hideCaret = false;
 
   @Input()
-  isRightAlign: boolean = false;
+  isRightAlign = false;
 
   @Input()
   additionalLabelComponentSetter?: string;
@@ -61,10 +62,10 @@
   badge: string;
 
   @Input()
-  caretClass: string = 'fa-caret-down';
+  caretClass = 'fa-caret-down';
 
   @Input()
-  useDropDownLocalFilter: boolean = false;
+  useDropDownLocalFilter = false;
 
   /**
    * The minimum time to handle a mousedown as a longclick. Default is 500 ms (0.5sec)
@@ -72,7 +73,7 @@
    * @type {number}
    */
   @Input()
-  minLongClickDelay: number = 500;
+  minLongClickDelay = 500;
 
   /**
    * The maximum milliseconds to wait for longclick ends. The default is 0 which means no upper limit.
@@ -80,13 +81,13 @@
    * @type {number}
    */
   @Input()
-  maxLongClickDelay: number = 0;
+  maxLongClickDelay = 0;
 
   @Input()
-  isDisabled: boolean = false;
+  isDisabled = false;
 
   @Input()
-  listClass: string = '';
+  listClass = '';
 
   @Output()
   buttonClick: EventEmitter<void> = new EventEmitter();
@@ -104,7 +105,7 @@
    * Indicates if the dropdown list is open or not. So that we use internal state to display or hide the dropdown.
    * @type {boolean}
    */
-  private dropdownIsOpen: boolean = false;
+  private dropdownIsOpen = false;
 
   get hasSubItems(): boolean {
     return Boolean(this.subItems && this.subItems.length);
@@ -114,6 +115,26 @@
     return this.hasSubItems && !this.hideCaret;
   }
 
+  set selection(items: ListItem[] | null) {
+    const selectedItems = items ? (Array.isArray(items) ? items : [items]) : [];
+    this.subItems.forEach((subItem: ListItem) => {
+      const indexInSelection = this.findItemIndexInList(subItem, selectedItems);
+      subItem.isChecked = indexInSelection > -1;
+    });
+    this.refreshDropdownList();
+  }
+  get selection(): ListItem[] {
+    return this.subItems && this.subItems.filter((option: ListItem): boolean => option.isChecked);
+  }
+
+  constructor(private utils: UtilsService) {}
+
+  findItemIndexInList(item: ListItem, itemList: ListItem[] = this.subItems): number {
+    return itemList.findIndex((subItem) => (
+      item === subItem || this.utils.isEqual(item.value, subItem.value)
+    ));
+  }
+
   /**
    * Handling the click event on the component element.
    * Two goal:
@@ -214,7 +235,7 @@
 
   /**
    * The main goal if this function is tho handle the item change event on the child dropdown list.
-   * Should update the value and close the dropdown if it is not multiple choice type.
+   * Should update the value and close the dropdown.
    * @param {ListItem} item The selected item(s) from the dropdown list.
    */
   onDropdownItemChange(item: ListItem | ListItem[]) {
@@ -224,11 +245,22 @@
     }
   }
 
-  updateSelection(item: ListItem | ListItem[]) {
-    this.selectItem.emit(item);
+  refreshDropdownList() {
     if (this.dropdownList) {
       this.dropdownList.doItemsCheck();
     }
   }
 
+  updateSelection(item: ListItem | ListItem[]) {
+    const changes = Array.isArray(item) ? item : [item];
+    changes.forEach((change: ListItem): void => {
+      const subItemIndex = this.findItemIndexInList(change);
+      if (subItemIndex > -1) {
+        this.subItems[subItemIndex].isChecked = change.isChecked;
+      }
+    });
+    this.selectItem.emit(item);
+    this.refreshDropdownList();
+  }
+
 }
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts
index 5f85da7..b476bf3 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/pagination-controls/pagination-controls.component.ts
@@ -54,9 +54,6 @@
     if (this.isValidValue(newValue)) { // this is the last validation check
       this.currentPage = newValue;
       this.currentPageChange.emit(newValue);
-      if (this.onChange) {
-        this.onChange(newValue);
-      }
     } else {
       throw new Error(`Invalid value ${newValue}. The currentPage should be between 0 and ${this.pagesCount}.`);
     }
@@ -75,14 +72,14 @@
    * The goal is to set the value to the first page... obviously to zero. It is just to have a centralized api for that.
    */
   setFirstPage(): void {
-    this.value = 0;
+    this._setValueByUserInput(0);
   }
 
   /**
    * The goal is to set the value to the last page which is the pagesCount property anyway.
    */
   setLastPage(): void {
-    this.value = this.pagesCount - 1;
+    this._setValueByUserInput(this.pagesCount - 1);
   }
 
   /**
@@ -91,7 +88,7 @@
    */
   setPreviousPage(): number {
     if (this.hasPreviousPage()) {
-      this.value -= 1;
+      this._setValueByUserInput(this.value - 1);
     }
     return this.value;
   }
@@ -101,8 +98,8 @@
    * @returns {number} The new value of the currentPage
    */
   setNextPage(): number {
-    if (this.hasNextPage()){
-      this.value += 1;
+    if (this.hasNextPage()) {
+      this._setValueByUserInput(this.value + 1);
     }
     return this.value;
   }
@@ -123,6 +120,17 @@
     return this.pagesCount > 0 && this.value > 0;
   }
 
+  private _setValueByUserInput(value) {
+    this.value = value;
+    this._onChange(this.value);
+  }
+
+  private _onChange(value) {
+    if (this.onChange) {
+      this.onChange(value);
+    }
+  }
+
   writeValue(value: number) {
     this.value = value;
   }
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 62835bb..da33f60 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
@@ -97,7 +97,7 @@
    * @type {boolean}
    */
   @Input()
-  updateValueImmediately: boolean = true;
+  updateValueImmediately = true;
 
   @ViewChild('parameterInput')
   parameterInputRef: ElementRef;
@@ -121,26 +121,22 @@
    */
   parameters: SearchBoxParameterProcessed[] = [];
 
-  private subscriptions: Subscription[] = [];
+  private onChange;
+
+  private destroyed$ = new Subject();
 
   constructor(private utils: UtilsService) {}
 
   ngOnInit(): void {
     this.parameterInput = this.parameterInputRef.nativeElement;
     this.valueInput = this.valueInputRef.nativeElement;
-    this.subscriptions.push(
-      this.parameterNameChangeSubject.subscribe(this.onParameterNameChange)
-    );
-    this.subscriptions.push(
-      this.parameterAddSubject.subscribe(this.onParameterAdd)
-    );
-    this.subscriptions.push(
-      this.updateValueSubject.subscribe(this.updateValue)
-    );
+    this.parameterNameChangeSubject.takeUntil(this.destroyed$).subscribe(this.onParameterNameChange);
+    this.parameterAddSubject.takeUntil(this.destroyed$).subscribe(this.onParameterAdd);
+    this.updateValueSubject.takeUntil(this.destroyed$).subscribe(this.updateValue);
   }
 
   ngOnDestroy(): void {
-    this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
+    this.destroyed$.next(true);
   }
 
   /**
@@ -152,8 +148,6 @@
       this.itemsOptions[this.activeItem.value] : [];
   }
 
-  private onChange: (fn: any) => void;
-
   @HostListener('click')
   private onRootClick(): void {
     if (!this.isActive) {
@@ -310,7 +304,7 @@
   updateValue = (): void => {
     this.currentValue = '';
     if (this.onChange) {
-      this.onChange(this.parameters.slice());
+      this.onChange([...this.parameters]);
     }
   }
 
@@ -331,8 +325,9 @@
   }
 
   writeValue(parameters: SearchBoxParameterProcessed[] = []): void {
-    this.parameters = parameters.slice();
-    this.updateValueSubject.next();
+    this.currentValue = '';
+    this.parameters = [...parameters];
+    // this.updateValueSubject.next();
   }
 
   registerOnChange(callback: any): void {
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
index e4e146f..3d031b2 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/components/time-range-picker/time-range-picker.component.ts
@@ -61,9 +61,6 @@
 
   set selection(newValue: TimeUnitListItem) {
     this.timeRange = newValue;
-    if (this.onChange) {
-      this.onChange(newValue);
-    }
     this.setEndTime(this.logsFilteringUtilsService.getEndTimeMomentFromTimeUnitListItem(newValue, this.logsContainer.timeZone));
     this.setStartTime(this.logsFilteringUtilsService.getStartTimeMomentFromTimeUnitListItem(
       newValue, this.endTime, this.logsContainer.timeZone
@@ -80,6 +77,7 @@
 
   setTimeRange(value: any, label: string): void {
     this.selection = {label, value};
+    this._onChange(this.selection);
   }
 
   setCustomTimeRange(): void {
@@ -91,6 +89,13 @@
         end: this.endTime
       }
     };
+    this._onChange(this.selection);
+  }
+
+  private _onChange(value: TimeUnitListItem): void {
+    if (this.onChange) {
+      this.onChange(value);
+    }
   }
 
   writeValue(selection: TimeUnitListItem): void {
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
index 534b69d..fefd2e8 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/dropdown-button/dropdown-button.component.ts
@@ -89,7 +89,19 @@
 
   constructor(protected utils: UtilsService) {}
 
-  updateSelection(updates: ListItem | ListItem[]): void {
+  clearSelection(silent: boolean = false) {
+    let hasChange = false;
+    this.options.forEach((item: ListItem) => {
+      hasChange = hasChange || item.isChecked;
+      item.isChecked = false;
+    });
+    if (!silent && hasChange) {
+      this.selectItem.emit(this.isMultipleChoice ? [] : undefined);
+    }
+  }
+
+  updateSelection(updates: ListItem | ListItem[], callOnChange: boolean = true): boolean {
+    let hasChange = false;
     if (updates && (!Array.isArray(updates) || updates.length)) {
       const items: ListItem[] = Array.isArray(updates) ? updates : [updates];
       if (this.isMultipleChoice) {
@@ -97,6 +109,7 @@
           if (this.options && this.options.length) {
             const itemToUpdate: ListItem = this.options.find((option: ListItem) => this.utils.isEqual(option.value, item.value));
             if (itemToUpdate) {
+              hasChange = hasChange || itemToUpdate.isChecked !== item.isChecked;
               itemToUpdate.isChecked = item.isChecked;
             }
           }
@@ -104,7 +117,9 @@
       } else {
         const selectedItem: ListItem = Array.isArray(updates) ? updates[0] : updates;
         this.options.forEach((item: ListItem) => {
+          const checkedStateBefore = item.isChecked;
           item.isChecked = this.utils.isEqual(item.value, selectedItem.value);
+          hasChange = hasChange || checkedStateBefore !== item.isChecked;
         });
       }
     } else {
@@ -112,8 +127,11 @@
     }
     const checkedItems = this.options.filter((option: ListItem): boolean => option.isChecked);
     this.selection = checkedItems;
-    const selectedValues = checkedItems.map((option: ListItem): any => option.value);
-    this.selectItem.emit(this.isMultipleChoice ? selectedValues : selectedValues.shift());
+    if (hasChange) {
+      const selectedValues = checkedItems.map((option: ListItem): any => option.value);
+      this.selectItem.emit(this.isMultipleChoice ? selectedValues : selectedValues.shift());
+    }
+    return hasChange;
   }
 
 }
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 651578a..3da860c 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
@@ -20,9 +20,9 @@
   Component, OnChanges, AfterViewChecked, OnDestroy, SimpleChanges, Input, Output, EventEmitter,
   ViewChildren, ViewContainerRef, QueryList, ChangeDetectorRef, ElementRef, ViewChild, OnInit
 } from '@angular/core';
-import {Subscription} from 'rxjs/Subscription';
 import {ListItem} from '@app/classes/list-item';
 import {ComponentGeneratorService} from '@app/services/component-generator.service';
+import { Subject } from 'rxjs/Subject';
 
 @Component({
   selector: 'ul[data-component="dropdown-list"]',
@@ -77,7 +77,7 @@
 
   private filterRegExp: RegExp;
 
-  private subscriptions: Subscription[] = [];
+  private destroyed$ = new Subject();
 
   constructor(
     private componentGenerator: ComponentGeneratorService,
@@ -91,13 +91,11 @@
     if (this.items.some((item: ListItem) => item.isChecked)) {
       this.selectedItemChange.emit(this.items);
     }
-    this.subscriptions.push(
-      this.selectedItemChange.subscribe(this.separateSelections)
-    );
+    this.selectedItemChange.takeUntil(this.destroyed$).subscribe(this.separateSelections)
   }
 
   ngOnDestroy() {
-    this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
+    this.destroyed$.next(true);
   }
 
   ngOnChanges(changes: SimpleChanges): void {
@@ -182,9 +180,6 @@
   unSelectAll() {
     this.items.forEach((item: ListItem) => {
       item.isChecked = false;
-      if (item.onSelect) {
-        item.onSelect(...this.actionArguments);
-      }
     });
     this.selectedItemChange.emit(this.items);
   }
diff --git a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts
index 6140e7d..669fcc9 100644
--- a/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts
+++ b/ambari-logsearch/ambari-logsearch-web/src/app/modules/shared/components/filter-dropdown/filter-dropdown.component.ts
@@ -34,7 +34,7 @@
 })
 export class FilterDropdownComponent extends DropdownButtonComponent implements ControlValueAccessor {
 
-  private onChange: (fn: any) => void;
+  private onChange;
 
   get selection(): ListItem[] {
     return this.selectedItems;
@@ -48,11 +48,22 @@
         option.isChecked = Boolean(selectionItem);
       });
     }
+  }
+
+  private _onChange(value) {
     if (this.onChange) {
-      this.onChange(items);
+      this.onChange(value);
     }
   }
 
+  updateSelection(updates: ListItem | ListItem[], callOnChange: boolean = true): boolean {
+    const hasChange = super.updateSelection(updates);
+    if (hasChange && callOnChange) {
+      this._onChange(this.selection);
+    }
+    return hasChange;
+  }
+
   writeValue(items: ListItem[]) {
     this.selection = items || [];
   }