From a5cc48778c180c3eadea3d0e896fca4e8d8dcdab Mon Sep 17 00:00:00 2001 From: =?utf8?q?Stephan=20M=C3=BCller?= Date: Thu, 22 Feb 2018 12:47:59 +0100 Subject: [PATCH] mgr/dashboard: Improve table search MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit You can now search for multiple words at a time if you separate them by comma or space. The other improvement is that you can specify which column should be searched by a search term. For example if you wand to filter for a row with the ID 3 you type "id:3". The column name is case insensitive and is separated from the search word by a colon. Signed-off-by: Stephan Müller --- .../datatable/table/table.component.spec.ts | 43 +++++++++++----- .../shared/datatable/table/table.component.ts | 50 ++++++++++++++----- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts index 60ec7d013279b..8340dc6adf1fe 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts @@ -11,18 +11,26 @@ describe('TableComponent', () => { let component: TableComponent; let fixture: ComponentFixture; const columns: TableColumn[] = []; + const createFakeData = (n) => { const data = []; for (let i = 0; i < n; i++) { data.push({ a: i, b: i * i, - c: -(i % 10) + c: [-(i % 10), 'score' + (i % 16 + 6) ] }); } return data; }; + const doSearch = (search: string, expectedLength: number, firstObject: object) => { + component.search = search; + component.updateFilter(true); + expect(component.rows.length).toBe(expectedLength); + expect(component.rows[0]).toEqual(firstObject); + }; + beforeEach( async(() => { TestBed.configureTestingModule({ @@ -41,9 +49,9 @@ describe('TableComponent', () => { component.data = createFakeData(100); component.useData(); component.columns = [ - {prop: 'a'}, - {prop: 'b'}, - {prop: 'c'} + {prop: 'a', name: 'Index'}, + {prop: 'b', name: 'Power ofA'}, + {prop: 'c', name: 'Poker array'} ]; }); @@ -70,20 +78,29 @@ describe('TableComponent', () => { }); it('should search for 13', () => { - component.search = '13'; - expect(component.rows.length).toBe(100); - component.updateFilter(true); - expect(component.rows[0].a).toBe(13); - expect(component.rows[1].b).toBe(1369); - expect(component.rows[2].b).toBe(3136); - expect(component.rows.length).toBe(3); + doSearch('13', 9, {a: 7, b: 49, c: [ -7, 'score13'] }); + expect(component.rows[1].a).toBe(13); + expect(component.rows[8].a).toBe(87); + }); + + it('should search for multiple values', () => { + doSearch('7 5 3', 5, {a: 57, b: 3249, c: [ -7, 'score15']}); + }); + + it('should search with column filter', () => { + doSearch('power:1369', 1, {a: 37, b: 1369, c: [ -7, 'score11']}); + doSearch('ndex:7 ofa:5 poker:3', 3, {a: 71, b: 5041, c: [-1, 'score13']}); + }); + + it('should search with through array', () => { + doSearch('array:score21', 6, {a: 15, b: 225, c: [-5, 'score21']}); }); it('should restore full table after search', () => { - component.search = '13'; expect(component.rows.length).toBe(100); + component.search = '13'; component.updateFilter(true); - expect(component.rows.length).toBe(3); + expect(component.rows.length).toBe(9); component.updateFilter(); expect(component.rows.length).toBe(100); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts index 9f04e91c71ca7..efda07a918071 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts @@ -22,6 +22,7 @@ import * as _ from 'lodash'; import 'rxjs/add/observable/timer'; import { Observable } from 'rxjs/Observable'; +import { CellTemplate } from '../../enum/cell-template.enum'; import { CdTableColumn } from '../../models/cd-table-column'; import { CdTableSelection } from '../../models/cd-table-selection'; @@ -250,27 +251,50 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O ]; } - updateFilter(event?) { + updateFilter(event?: any) { if (!event) { this.search = ''; } - const val = this.search.toLowerCase(); - const columns = this.columns; + const columns = this.columns.filter(c => c.cellTransformation !== CellTemplate.sparkline); // update the rows - this.rows = this.data.filter((d) => { - return ( - columns.filter(c => { - return ( - (_.isString(d[c.prop]) || _.isNumber(d[c.prop])) && - (d[c.prop] + '').toLowerCase().indexOf(val) !== -1 - ); - }).length > 0 - ); - }); + this.rows = this.subSearch(this.data, this.search.toLowerCase().split(/[, ]/), columns); // Whenever the filter changes, always go back to the first page this.table.offset = 0; } + subSearch (data: any[], currentSearch: string[], columns: CdTableColumn[]) { + let tempColumns: CdTableColumn[]; + if (currentSearch.length === 0 || data.length === 0) { + return data; + } + const searchWords: string[] = currentSearch.pop().split(':'); + if (searchWords.length === 2) { + tempColumns = [...columns]; + columns = columns.filter((c) => c.name.toLowerCase().indexOf(searchWords[0]) !== -1); + } + const searchWord: string = _.last(searchWords); + if (searchWord.length > 0) { + data = data.filter(d => { + return columns.filter(c => { + let cellValue: any = _.get(d, c.prop); + if (_.isUndefined(cellValue)) { + return; + } + if (_.isArray(cellValue)) { + cellValue = cellValue.join(''); + } else if (_.isNumber(cellValue)) { + cellValue = cellValue.toString(); + } + return cellValue.toLowerCase().indexOf(searchWord) !== -1; + }).length > 0; + }); + } + if (_.isArray(tempColumns)) { + columns = tempColumns; + } + return this.subSearch(data, currentSearch, columns); + } + getRowClass() { // Return the function used to populate a row's CSS classes. return () => { -- 2.39.5