This is an automated email from the ASF dual-hosted git repository. tobiasistvan pushed a commit to branch branch-2.7 in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/branch-2.7 by this push: new 4295bb1 [AMBARI-24887] [Log Search UI] The component filter dropdown does not select any item (#2599) 4295bb1 is described below commit 4295bb16c439cbc8fb0e7362f19768dde1477868 Author: Istvan Tobias <tobias.ist...@gmail.com> AuthorDate: Tue Nov 13 13:10:01 2018 +0100 [AMBARI-24887] [Log Search UI] The component filter dropdown does not select any item (#2599) Change-Id: I9ef62c6e77e3d89d6548734793dc246a7342e80e --- .../cluster-filter/cluster-filter.component.ts | 4 +- .../filter-button/filter-button.component.ts | 59 +++------------------- .../log-index-filter/log-index-filter.component.ts | 2 +- .../menu-button/menu-button.component.spec.ts | 4 +- .../menu-button/menu-button.component.ts | 58 ++++++++++++++++----- .../pagination-controls.component.ts | 24 ++++++--- .../components/search-box/search-box.component.ts | 29 +++++------ .../time-range-picker.component.ts | 11 ++-- .../dropdown-button/dropdown-button.component.ts | 24 +++++++-- .../dropdown-list/dropdown-list.component.ts | 13 ++--- .../filter-dropdown/filter-dropdown.component.ts | 15 +++++- 11 files changed, 133 insertions(+), 110 deletions(-) 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 @@ export class ClusterFilterComponent implements OnInit, OnDestroy { .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 @@ import {MenuButtonComponent} from '@app/components/menu-button/menu-button.compo }) export class FilterButtonComponent extends MenuButtonComponent implements ControlValueAccessor { - private selectedItems: ListItem[] = []; - private onChange: (fn: any) => void; - constructor(private utils: UtilsService) { - super(); - } - - get selection(): ListItem[] { - return this.selectedItems; - } - - set selection(items: ListItem[]) { - this.selectedItems = items; - if (this.onChange) { - this.onChange(items); + updateSelection(items: ListItem | ListItem[], callOnChange = true): void { + super.updateSelection(items); + if (callOnChange) { + this._onChange(this.selection); } } - 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(); + private _onChange(value) { + if (this.onChange) { + 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 @@ export class LogIndexFilterComponent implements OnInit, OnDestroy, OnChanges, Co 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 {LogsContainerService} from '@app/services/logs-container.service'; 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 @@ describe('MenuButtonComponent', () => { 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 @@ export class MenuButtonComponent { 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 @@ export class MenuButtonComponent { 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 @@ export class MenuButtonComponent { * @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 @@ export class MenuButtonComponent { * @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 @@ export class MenuButtonComponent { * 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 @@ export class MenuButtonComponent { 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 @@ export class MenuButtonComponent { /** * 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 @@ export class MenuButtonComponent { } } - 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 @@ export class PaginationControlsComponent implements ControlValueAccessor { 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 @@ export class PaginationControlsComponent implements ControlValueAccessor { * 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 @@ export class PaginationControlsComponent implements ControlValueAccessor { */ setPreviousPage(): number { if (this.hasPreviousPage()) { - this.value -= 1; + this._setValueByUserInput(this.value - 1); } return this.value; } @@ -101,8 +98,8 @@ export class PaginationControlsComponent implements ControlValueAccessor { * @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 @@ export class PaginationControlsComponent implements ControlValueAccessor { 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 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess * @type {boolean} */ @Input() - updateValueImmediately: boolean = true; + updateValueImmediately = true; @ViewChild('parameterInput') parameterInputRef: ElementRef; @@ -121,26 +121,22 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess */ 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 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess this.itemsOptions[this.activeItem.value] : []; } - private onChange: (fn: any) => void; - @HostListener('click') private onRootClick(): void { if (!this.isActive) { @@ -310,7 +304,7 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess updateValue = (): void => { this.currentValue = ''; if (this.onChange) { - this.onChange(this.parameters.slice()); + this.onChange([...this.parameters]); } } @@ -331,8 +325,9 @@ export class SearchBoxComponent implements OnInit, OnDestroy, ControlValueAccess } 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 @@ export class TimeRangePickerComponent implements ControlValueAccessor { 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 @@ export class TimeRangePickerComponent implements ControlValueAccessor { setTimeRange(value: any, label: string): void { this.selection = {label, value}; + this._onChange(this.selection); } setCustomTimeRange(): void { @@ -91,6 +89,13 @@ export class TimeRangePickerComponent implements ControlValueAccessor { 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 @@ export class DropdownButtonComponent { 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 @@ export class DropdownButtonComponent { 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 @@ export class DropdownButtonComponent { } 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 @@ export class DropdownButtonComponent { } 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 @@ import { 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 @@ export class DropdownListComponent implements OnInit, OnChanges, AfterViewChecke private filterRegExp: RegExp; - private subscriptions: Subscription[] = []; + private destroyed$ = new Subject(); constructor( private componentGenerator: ComponentGeneratorService, @@ -91,13 +91,11 @@ export class DropdownListComponent implements OnInit, OnChanges, AfterViewChecke 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 @@ export class DropdownListComponent implements OnInit, OnChanges, AfterViewChecke 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 @@ import {ListItem} from '@app/classes/list-item'; }) export class FilterDropdownComponent extends DropdownButtonComponent implements ControlValueAccessor { - private onChange: (fn: any) => void; + private onChange; get selection(): ListItem[] { return this.selectedItems; @@ -48,9 +48,20 @@ export class FilterDropdownComponent extends DropdownButtonComponent implements 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[]) {