This is an automated email from the ASF dual-hosted git repository. sardell pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/metron.git
The following commit(s) were added to refs/heads/master by this push: new 888c4bc METRON-2199 [UI] Add ability to turn off query building in Alerts UI search input (sardell) closes apache/metron#1477 888c4bc is described below commit 888c4bc8f353ea0424f28919ab33b5f4ad861d25 Author: sardell <shane.m.ard...@gmail.com> AuthorDate: Fri Aug 30 17:09:46 2019 +0200 METRON-2199 [UI] Add ability to turn off query building in Alerts UI search input (sardell) closes apache/metron#1477 --- .../alert-details/alert-details.component.spec.ts | 4 +- .../alerts/alerts-list/alerts-list.component.html | 22 ++++-- .../alerts/alerts-list/alerts-list.component.scss | 24 ++++++ .../alerts-list/alerts-list.component.spec.ts | 86 ++++++++++++++++++++-- .../alerts/alerts-list/alerts-list.component.ts | 72 +++++++++++++++--- .../table-view/table-view.component.spec.ts | 7 +- .../tree-view/tree-view.component.spec.ts | 7 +- 7 files changed, 193 insertions(+), 29 deletions(-) diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts index 63c22e3..92843e3 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.spec.ts @@ -104,7 +104,9 @@ describe('AlertDetailsComponent', () => { AuthenticationService, AlertsService, UpdateService, - GlobalConfigService, + { provide: GlobalConfigService, useValue: { + get: () => { return of({})} + }}, { provide: DialogService, useValue: { diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html index 721426d..e56fb1b 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html @@ -19,16 +19,28 @@ <span class="input-group-prepend"> <button class="btn btn-secondary btn-saved-searches" type="button" (click)="showSavedSearches()">Searches</button> </span> - <div appAceEditor style="width:100%;" placeholder="Search Alerts" [text]="queryBuilder.displayQuery" (textChanged)="onSearch($event)"> </div> + <div appAceEditor *ngIf="!hideQueryBuilder" class="flex-fill" placeholder="Search Alerts" [text]="queryBuilder.displayQuery" (textChanged)="onSearch($event)"> </div> + <div class="flex-fill" [class.d-none]="!hideQueryBuilder"> + <input class="manual-query-input" data-qe-id="manual-query-input" type="text" #manualQuery > + </div> + <span class="input-group-append"> + <button class="btn btn-secondary btn-options" (click)="toggleQueryBuilder()"> + <span *ngIf="hideQueryBuilder">Use Query Builder</span> + <span *ngIf="!hideQueryBuilder">Use Manual Query</span> + </button> + </span> <span class="input-group-append"> <button class="btn btn-secondary btn-search-clear" type="button" (click)="onClear()"></button> </span> - <span class="input-group-append" style="white-space: nowrap;"> + <span class="input-group-append" style="white-space: nowrap;" [class.d-none]="hideQueryBuilder"> <app-time-range class="d-flex position-relative" (timeRangeChange)="onTimeRangeChange($event)" [disabled]="timeStampFilterPresent" [selectedTimeRange]="selectedTimeRange"> </app-time-range> </span> - <span class="input-group-append"> + <span class="input-group-append" [class.d-none]="hideQueryBuilder"> <button data-qe-id="alert-search-btn" class="btn btn-secondary btn-search rounded-right" type="button" data-name="search" (click)="onSearch(alertSearchDirective.getSeacrhText())"></button> </span> + <span class="input-group-append" [class.d-none]="!hideQueryBuilder"> + <button class="btn btn-secondary btn-search rounded-right" type="button" data-name="search" (click)="search(false, null)"></button> + </span> <div class="input-group-append"> <span class="save-button" (click)="showSaveSearch()"> </span> @@ -72,7 +84,7 @@ <div class="container-fluid no-gutters"> <div class="row"> - <div class="px-0" style="width: 200px;max-width: 200px;"> + <div class="px-0" style="width: 200px;max-width: 200px;" [class.d-none]="hideQueryBuilder"> <app-alert-filters [facets]="searchResponse.facetCounts" (facetFilterChange)="onAddFacetFilter($event)"> </app-alert-filters> </div> <div class="col px-0 pl-4" style="overflow: auto;"> @@ -100,7 +112,7 @@ [alertsColumnsToDisplay]="alertsColumnsToDisplay" [selectedAlerts]="selectedAlerts" [globalConfig]="globalConfig" - [query]="queryBuilder.generateSelect()" + [query]="queryForTreeView()" [groups]="groups" (onResize)="onResize()" (onAddFilter)="onAddFilter($event)" diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss index fe2f54c..c39887d 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss @@ -83,6 +83,19 @@ $searchbox-height: 42px; } } + .btn-options { + background: $mine-shaft-1; + border-bottom: 1px solid $tundora; + border-left: none; + border-right: none; + border-top: 1px solid $tundora; + color: $gothic; + + &:focus { + box-shadow: none; + } + } + .btn-search-clear { border-top: 1px solid $tundora; border-bottom: 1px solid $tundora; @@ -300,4 +313,15 @@ $searchbox-height: 42px; .ace_hidden-cursors .ace_cursor { opacity: 0; } + +} + +.manual-query-input { + background: $mine-shaft-1; + border: 1px solid $tundora; + color: #F8F8F2; + font-size: 12px; + height: 100%; + padding-left: .5rem; + width: 100%; } diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts index 7fbf9cc..8cbff8f 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.spec.ts @@ -28,6 +28,7 @@ import { SaveSearchService } from 'app/service/save-search.service'; import { MetaAlertService } from 'app/service/meta-alert.service'; import { GlobalConfigService } from 'app/service/global-config.service'; import { DialogService } from 'app/service/dialog.service'; +import { SearchRequest } from 'app/model/search-request'; import { Observable, of, Subject } from 'rxjs'; import { Filter } from 'app/model/filter'; import { QueryBuilder } from './query-builder'; @@ -39,6 +40,26 @@ describe('AlertsListComponent', () => { let component: AlertsListComponent; let fixture: ComponentFixture<AlertsListComponent>; + let searchServiceStub = { + search() { return of({ + total: 0, + groupedBy: '', + results: [], + facetCounts: [], + groups: [] + }) }, + pollSearch() { return of({}) } + } + let queryBuilderStub = { + addOrUpdateFilter() { return {} }, + clearSearch() { return {} }, + generateSelect() { return '*' }, + isTimeStampFieldPresent() { return {} }, + filters: [{}], + searchRequest: { + from: 0 + } + } let queryBuilder: QueryBuilder; let searchService: SearchService; @@ -53,9 +74,7 @@ describe('AlertsListComponent', () => { AlertsListComponent, ], providers: [ - { provide: SearchService, useClass: () => { return { - search: () => {}, - } } }, + { provide: SearchService, useValue: searchServiceStub }, { provide: UpdateService, useClass: () => { return { alertChanged$: new Observable(), } } }, @@ -77,9 +96,7 @@ describe('AlertsListComponent', () => { get: () => new Observable(), } } }, { provide: DialogService, useClass: () => { return {} } }, - { provide: QueryBuilder, useClass: () => { return { - addOrUpdateFilter: () => {} - } } }, + { provide: QueryBuilder, useValue: queryBuilderStub }, ] }) .compileComponents(); @@ -122,6 +139,62 @@ describe('AlertsListComponent', () => { expect(fixture.nativeElement.querySelector('[data-qe-id="alert-subgroup-total"]')).toBeNull(); }); + it('should toggle the query builder with toggleQueryBuilder', () => { + component.toggleQueryBuilder(); + fixture.detectChanges(); + expect(component.hideQueryBuilder).toBe(true); + + component.hideQueryBuilder = true; + component.pagination.from = 0; + component.pagination.size = 25; + + fixture.detectChanges(); + component.toggleQueryBuilder(); + expect(component.hideQueryBuilder).toBe(false); + }); + + it('should pass the manual query value when hideQueryBuilder is true', () => { + const input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]')); + const el = input.nativeElement; + + expect(component.queryForTreeView()).toBe('*'); + + component.toggleQueryBuilder(); + fixture.detectChanges(); + expect(component.hideQueryBuilder).toBe(true); + + el.value = 'test'; + expect(component.queryForTreeView()).toBe('test'); + }); + + it('should build a new search request if hideQueryBuilder is true', () => { + const input = fixture.debugElement.query(By.css('[data-qe-id="manual-query-input"]')); + const el = input.nativeElement; + const searchServiceSpy = spyOn(searchService, 'search').and.returnValue(of()); + const newSearch = new SearchRequest(); + + el.value = 'test'; + component.hideQueryBuilder = true; + component.pagination.size = 25; + newSearch.query = 'test' + newSearch.size = 25 + newSearch.from = 0; + + fixture.detectChanges(); + component.search(); + expect(searchServiceSpy).toHaveBeenCalledWith(newSearch); + }); + + it('should poll with new search request if isRefreshPaused is true and manualSearch is present', () => { + const searchServiceSpy = spyOn(searchService, 'pollSearch').and.returnValue(of()); + const newSearch = new SearchRequest(); + + component.isRefreshPaused = false; + fixture.detectChanges(); + component.tryStartPolling(newSearch); + expect(searchServiceSpy).toHaveBeenCalledWith(newSearch); + }); + describe('stale data state', () => { it('should set staleDataState flag to true on filter change', () => { @@ -145,7 +218,6 @@ describe('AlertsListComponent', () => { }); it('should set staleDataState flag to false when the query resolves', () => { - const fakeObservable = new Subject(); spyOn(searchService, 'search').and.returnValue(of(new SearchResponse())); spyOn(component, 'saveCurrentSearch'); spyOn(component, 'setSearchRequestSize'); diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts index 61f57c2..2cd34a5 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts @@ -46,6 +46,7 @@ import { DialogService } from 'app/service/dialog.service'; import { DialogType } from 'app/model/dialog-type'; import { Utils } from 'app/utils/utils'; import { AlertSource } from '../../model/alert-source'; +import { SearchRequest } from 'app/model/search-request'; @Component({ selector: 'app-alerts-list', @@ -74,6 +75,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { @ViewChild('table') table: ElementRef; @ViewChild('dataViewComponent') dataViewComponent: TableViewComponent; @ViewChild(AlertSearchDirective) alertSearchDirective: AlertSearchDirective; + @ViewChild('manualQuery') manualQuery: ElementRef; tableMetaData = new TableMetadata(); pagination: Pagination = new Pagination(); @@ -83,6 +85,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { configSubscription: Subscription; groups = []; subgroupTotal = 0; + hideQueryBuilder = false; staleDataState = false; @@ -223,6 +226,8 @@ export class AlertsListComponent implements OnInit, OnDestroy { onClear() { this.timeStampFilterPresent = false; this.queryBuilder.clearSearch(); + if (this.hideQueryBuilder) { this.manualQuery.nativeElement.value = '*'; } + this.search(); this.staleDataState = true; } @@ -261,7 +266,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { } onAddFilter(filter: Filter) { - this.timeStampFilterPresent = (filter.field === TIMESTAMP_FIELD_NAME); + this.timeStampFilterPresent = this.queryBuilder.isTimeStampFieldPresent(); this.queryBuilder.addOrUpdateFilter(filter); this.staleDataState = true; } @@ -374,22 +379,39 @@ export class AlertsListComponent implements OnInit, OnDestroy { } search(resetPaginationParams = true, savedSearch?: SaveSearch) { - this.saveCurrentSearch(savedSearch); + if (savedSearch) { this.saveCurrentSearch(savedSearch); } if (resetPaginationParams) { this.pagination.from = 0; } this.setSearchRequestSize(); - this.searchService.search(this.queryBuilder.searchRequest).subscribe(results => { - this.setData(results); - this.staleDataState = false; - }, error => { - this.setData(new SearchResponse()); - this.dialogService.launchDialog(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error); - }); + if (this.hideQueryBuilder) { + const newSearch = new SearchRequest(); + newSearch.query = this.manualQuery.nativeElement.value; + newSearch.size = this.pagination.size; + newSearch.from = 0; - this.tryStartPolling(); + this.searchService.search(newSearch).subscribe(results => { + this.setData(results); + this.staleDataState = false; + }, error => { + this.setData(new SearchResponse()); + this.dialogService.launchDialog(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error); + }); + + this.tryStartPolling(newSearch); + } else { + this.searchService.search(this.queryBuilder.searchRequest).subscribe(results => { + this.setData(results); + this.staleDataState = false; + }, error => { + this.setData(new SearchResponse()); + this.dialogService.launchDialog(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error); + }); + + this.tryStartPolling(); + } } setSearchRequestSize() { @@ -477,12 +499,17 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.router.navigateByUrl('/alerts-list(dialog:save-search)'); } - tryStartPolling() { - if (!this.isRefreshPaused) { + tryStartPolling(manualSearch?: SearchRequest) { + if (!this.isRefreshPaused && !manualSearch) { this.tryStopPolling(); this.refreshTimer = this.searchService.pollSearch(this.queryBuilder.searchRequest).subscribe(results => { this.setData(results); }); + } else if (!this.isRefreshPaused && manualSearch) { + this.tryStopPolling(); + this.refreshTimer = this.searchService.pollSearch(manualSearch).subscribe(results => { + this.setData(results); + }); } } @@ -521,4 +548,25 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.subgroupTotal = subgroupTotal; this.cdRef.detectChanges(); } + + toggleQueryBuilder() { + this.setSelectedTimeRange([this.selectedTimeRange]); + if (!this.hideQueryBuilder) { + this.hideQueryBuilder = true; + this.manualQuery.nativeElement.value = this.queryBuilder.query; + } else { + this.hideQueryBuilder = false; + this.queryBuilder.clearSearch(); + this.search(); + } + } + + queryForTreeView() { + if (!this.hideQueryBuilder) { + return this.queryBuilder.generateSelect(); + } else { + return this.manualQuery.nativeElement.value; + } + } + } diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts index 29c0ffe..604359b 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts @@ -33,6 +33,7 @@ import { MetaAlertService } from '../../../service/meta-alert.service'; import { DialogService } from 'app/service/dialog.service'; import { AppConfigService } from '../../../service/app-config.service'; import { ContextMenuComponent } from 'app/shared/context-menu/context-menu.component'; +import { of } from 'rxjs'; @Component({selector: 'metron-table-pagination', template: ''}) class MetronTablePaginationComponent { @@ -57,10 +58,12 @@ describe('TableViewComponent', () => { providers: [ SearchService, UpdateService, - GlobalConfigService, MetaAlertService, DialogService, - { provide: AppConfigService, useClass: FakeAppConfigService } + { provide: AppConfigService, useClass: FakeAppConfigService }, + { provide: GlobalConfigService, useValue: { + get: () => { return of({})} + }} ], declarations: [ MetronTableDirective, diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts index cdea081..0d33eba 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts @@ -33,6 +33,7 @@ import { GlobalConfigService } from '../../../service/global-config.service'; import { MetaAlertService } from '../../../service/meta-alert.service'; import { DialogService } from 'app/service/dialog.service'; import { AppConfigService } from '../../../service/app-config.service'; +import { of } from 'rxjs'; class FakeAppConfigService { @@ -51,10 +52,12 @@ describe('TreeViewComponent', () => { providers: [ SearchService, UpdateService, - GlobalConfigService, MetaAlertService, DialogService, - { provide: AppConfigService, useClass: FakeAppConfigService } + { provide: AppConfigService, useClass: FakeAppConfigService }, + { provide: GlobalConfigService, useValue: { + get: () => { return of({})} + }} ], declarations: [ MetronTableDirective,