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].