From db712678a31ab3d0a57c9224eb987aacd88dd5a1 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Tue, 19 May 2026 10:10:08 +0530 Subject: [PATCH] mgr/dashboard: carbonize table filters Fixes: https://tracker.ceph.com/issues/76687 Signed-off-by: Nizamudeen A --- .../frontend/cypress/e2e/page-helper.po.ts | 9 +- .../app/shared/datatable/datatable.module.ts | 8 +- .../datatable/table/table.component.html | 163 +++++++++++++----- .../datatable/table/table.component.scss | 4 +- .../datatable/table/table.component.spec.ts | 4 +- .../shared/datatable/table/table.component.ts | 68 +++++++- .../shared/models/cd-table-column-filter.ts | 17 +- 7 files changed, 212 insertions(+), 61 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts index 99f00bc0640..d1ad20b7a47 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts @@ -290,8 +290,13 @@ export abstract class PageHelper { filterTable(name: string, option: string) { this.waitDataTableToLoad(); - cy.get('select#filter_name').select(name); - cy.get('select#filter_option').select(option); + cy.get('[data-testid=filter-button]').click(); + cy.get('cds-popover-content') + .should('be.visible') + .within(() => { + cy.get(`[data-testid="filter-select-${name}"]`).find('select').select(option); + cy.get('[data-testid="apply-filters"]').click(); + }); } setPageSize(size: string) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/datatable.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/datatable.module.ts index 7aa8949ad47..c9342c4a4d9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/datatable.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/datatable.module.ts @@ -19,7 +19,9 @@ import { InputModule, GridModule, LayoutModule, - InlineLoadingModule + InlineLoadingModule, + PopoverModule, + TooltipModule } from 'carbon-components-angular'; import AddIcon from '@carbon/icons/es/add/16'; import FilterIcon from '@carbon/icons/es/filter/16'; @@ -109,7 +111,9 @@ import { TableDetailDirective } from './directives/table-detail.directive'; InputModule, GridModule, LayoutModule, - InlineLoadingModule + InlineLoadingModule, + PopoverModule, + TooltipModule ], declarations: [ TableComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html index ebace42a6b7..1460f9ca77a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html @@ -51,31 +51,82 @@ - -
- - - - - - - - - - - - + @if (columnFilters.length !== 0) { +
+ + + + +
+ @for (filter of columnFilters; track filter.column.name; let i = $index) { +
+ + +
+ Filter + + +
+ + +
+ + + @for (option of filter.options; track option.raw) { + + } + + +
+
+ } +
+ + +
+
+
- + } -
-
- - - {{ filter.column.name }}: {{ filter.value.formatted }} - - - - -
-
+
+ + } { ) => { component.search = search; _.forEach(changes, (change) => { - component.onSelectFilter(change.filter.column.name); - component.onChangeFilter(change.value || undefined); + component.onChangeFilter(change.value || undefined, change.filter); + component.onSubmitFilter(); }); expect(component.rows).toEqual(results); component.onClearSearch(); 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 3ee596f212e..7d4c8284784 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 @@ -25,7 +25,12 @@ import { CellTemplate } from '~/app/shared/enum/cell-template.enum'; import { Icons, IconSize, EMPTY_STATE_IMAGE } from '~/app/shared/enum/icons.enum'; import { CdTableColumn } from '~/app/shared/models/cd-table-column'; -import { CdTableColumnFilter } from '~/app/shared/models/cd-table-column-filter'; +import { + CdTableColumnFilter, + CdTableColumnFilterOption, + CdTableColumnSelectedFilter, + CdTableColumnStagedFilter +} from '~/app/shared/models/cd-table-column-filter'; import { CdTableColumnFiltersChange } from '~/app/shared/models/cd-table-column-filters-change'; import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context'; import { CdTableSelection } from '~/app/shared/models/cd-table-selection'; @@ -434,6 +439,14 @@ export class TableComponent implements AfterViewInit, OnInit, OnChanges, OnDestr editStates: EditState = {}; formGroup: CdFormGroup = new CdFormGroup({}); + openFilterPopover = false; + stagedFilters: CdTableColumnStagedFilter = {}; + selectedFilters: CdTableColumnSelectedFilter = {}; + + get activeFilters() { + return this.columnFilters.filter((filter) => filter.value); + } + constructor( // private ngZone: NgZone, private cdRef: ChangeDetectorRef, @@ -754,6 +767,10 @@ export class TableComponent implements AfterViewInit, OnInit, OnChanges, OnDestr this.tableColumns = this.localColumns; } + toggleFilterPopover() { + this.openFilterPopover = !this.openFilterPopover; + } + initColumnFilters() { let filterableColumns = _.filter(this.localColumns, { filterable: true }); filterableColumns = [...filterableColumns, ...this.extraFilterableColumns]; @@ -767,6 +784,13 @@ export class TableComponent implements AfterViewInit, OnInit, OnChanges, OnDestr }; }); this.selectedFilter = _.first(this.columnFilters); + this.initSelectedColumnFilters(); + } + + private initSelectedColumnFilters() { + this.columnFilters.forEach((filter) => { + this.selectedFilters[filter.column.name] = filter.value?.raw; + }); } private createColumnFilterOption( @@ -805,14 +829,43 @@ export class TableComponent implements AfterViewInit, OnInit, OnChanges, OnDestr }); } - onSelectFilter(filter: string) { - const value = this.columnFilters.find((x) => x.column.name === filter); - this.selectedFilter = value; + // saving the filters to a staged variable so they are not applied immediately + onChangeFilter(selectedValue: string, filter: CdTableColumnFilter) { + const filterName = filter.column.name; + const selectedFilter = this.selectedFilters[filterName]; + const newSelectedFilter = selectedFilter === selectedValue ? undefined : selectedValue; + + this.selectedFilters[filterName] = newSelectedFilter; + + const option = filter.options.find( + (x: CdTableColumnFilterOption) => x.raw === newSelectedFilter + ); + this.stagedFilters[filterName] = option; + } + + onSubmitFilter() { + this.columnFilters.forEach((filter) => { + const filterName = filter.column.name; + + if (this.stagedFilters.hasOwnProperty(filterName)) { + filter.value = this.stagedFilters[filterName]; + this.selectedFilter = filter; + } + }); + + this.stagedFilters = {}; + this.updateFilter(); + this.openFilterPopover = false; } - onChangeFilter(filter: string) { - const option = this.selectedFilter.options.find((x) => x.raw === filter); - this.selectedFilter.value = _.isEqual(this.selectedFilter.value, option) ? undefined : option; + onRemoveFilter(filter: CdTableColumnFilter) { + const filterName = filter.column.name; + filter.value = undefined; + this.selectedFilters[filterName] = undefined; + delete this.stagedFilters[filterName]; + if (this.selectedFilter?.column.name === filterName) { + this.selectedFilter = undefined; + } this.updateFilter(); } @@ -1288,6 +1341,7 @@ export class TableComponent implements AfterViewInit, OnInit, OnChanges, OnDestr }); this.selectedFilter = _.first(this.columnFilters); this.updateFilter(); + this.initSelectedColumnFilters(); } updateFilter() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column-filter.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column-filter.ts index ccdbe82fc1b..d4d45ac91ba 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column-filter.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/cd-table-column-filter.ts @@ -2,6 +2,19 @@ import { CdTableColumn } from './cd-table-column'; export interface CdTableColumnFilter { column: CdTableColumn; - options: { raw: string; formatted: string }[]; // possible options of a filter - value?: { raw: string; formatted: string }; // selected option + options: CdTableColumnFilterOption[]; // possible options of a filter + value?: CdTableColumnFilterOption; // selected option +} + +export interface CdTableColumnStagedFilter { + [filterName: string]: CdTableColumnFilterOption; +} + +export interface CdTableColumnSelectedFilter { + [filterName: string]: string | undefined; +} + +export interface CdTableColumnFilterOption { + raw: string; + formatted: string; } -- 2.47.3