GridCore data: Relocate filter-controller fields (headerFilter, applyFilter) - #34797
Conversation
There was a problem hiding this comment.
Pull request overview
This PR decouples the base grid DataController from the header-filter and filter-row controllers by moving the “temporarily exclude current column from combined filter” context onto DataController itself.
Changes:
- Removed
DataControllerreferences to_headerFilterControllerand_applyFilterController, replacing them with a new_filterExcludedColumnstate. - Updated header filter and filter row flows to set/reset the excluded column on
DataControllerwhen computing lookup-related combined filters. - Updated filter-sync and filter calculation logic (and QUnit coverage) to use the new excluded-column mechanism.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/filterSync.tests.js | Updates tests to use dataController.setFilterExcludedColumn(...) instead of reaching into headerFilterController state. |
| packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts | Removes per-controller current-column state and switches to DataController excluded-column state during combined filter calculation for header filter lookups. |
| packages/devextreme/js/__internal/grids/grid_core/filter/m_filter_sync.ts | Replaces controller-based “current column” logic with the new excluded-column state when removing current column conditions during filter sync. |
| packages/devextreme/js/__internal/grids/grid_core/filter/m_filter_row.ts | Removes ApplyFilterViewController current-column state and uses DataController excluded-column state when computing combined filters / skipping current column in filter row filters. |
| packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts | Removes controller dependencies and introduces setFilterExcludedColumn / getFilterExcludedColumn plus backing state. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (8)
packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts:267
- Same excluded-column state issue here: if
getCombinedFilter()throws,_filterExcludedColumnstays set. Usetry/finallyand restore the previous excluded column (not alwaysnull).
this._dataController.setFilterExcludedColumn(column);
const filter = this._dataController.getCombinedFilter();
this._dataController.setFilterExcludedColumn(null);
packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts:513
- This code reads the protected field
_filterExcludedColumndirectly even thoughDataControllernow providesgetFilterExcludedColumn(). Using the getter improves encapsulation and makes it possible for mocks/overrides to work correctly.
const currentColumn = this._filterExcludedColumn;
packages/devextreme/js/__internal/grids/grid_core/filter/m_filter_row.ts:786
- Same excluded-column state leak risk here. Consider restoring the previous value (not hard-coding
null) so nested/overlapping computations don’t clobber each other.
this._dataController.setFilterExcludedColumn(column);
const filter = this._dataController.getCombinedFilter() || null;
this._dataController.setFilterExcludedColumn(null);
packages/devextreme/js/__internal/grids/grid_core/filter/m_filter_row.ts:834
- This extender reads
_filterExcludedColumndirectly; usinggetFilterExcludedColumn()makes the intent clearer and keeps the state access centralized onDataController.
const excludedColumn = this._filterExcludedColumn;
packages/devextreme/js/__internal/grids/grid_core/filter/m_filter_sync.ts:316
- This code reads the protected field
_filterExcludedColumndirectly, butDataControllernow hasgetFilterExcludedColumn(). Using the getter reduces coupling to the field name and keeps mocks/overrides viable.
const currentColumn = this._filterExcludedColumn;
packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts:256
setFilterExcludedColumn(null)is executed only on the happy path. IfgetCombinedFilter()throws, the excluded-column state remains set and can affect subsequent filtering operations. Restore the previous excluded column in atry/finally(also preserves nesting).
This issue also appears on line 265 of the same file.
this._dataController.setFilterExcludedColumn(column);
const filter = this._dataController.getCombinedFilter();
this._dataController.setFilterExcludedColumn(null);
packages/devextreme/testing/helpers/gridBaseMocks.js:241
gridBaseMocksexposessetFilterExcludedColumn/getFilterExcludedColumnas no-ops, which doesn’t reflect the realDataControllerbehavior. This can cause tests that rely on the excluded-column context to silently behave differently. Consider storing the value on the mock instance so it can be read back.
setFilterExcludedColumn: commonUtils.noop,
getFilterExcludedColumn: commonUtils.noop,
packages/devextreme/js/__internal/grids/grid_core/filter/m_filter_row.ts:555
setFilterExcludedColumn(null)runs only ifgetCombinedFilter()succeeds. If it throws, the excluded-column state is left set and can leak into later filter computations. Wrap withtry/finallyand restore the previous excluded column value.
This issue also appears on line 784 of the same file.
this._dataController.setFilterExcludedColumn(options);
const filter = this._dataController.getCombinedFilter();
this._dataController.setFilterExcludedColumn(null);
| protected _headerFilterController!: HeaderFilterController; | ||
|
|
||
| protected _applyFilterController!: ApplyFilterViewController; | ||
| protected _filterExcludedColumn: Column | null = null; |
There was a problem hiding this comment.
small suggestion: make _filterExcludedColumn private and use getFilterExcludedColumn() where needed. Currently getFilterExcludedColumn is not used anywhere
| const currentColumnForFilterRow = this._applyFilterController.getCurrentColumnForFiltering(); | ||
| const currentColumn = currentColumnForHeaderFilter || currentColumnForFilterRow; | ||
| const needRemoveCurrentColumnFilter = currentColumnForHeaderFilter || isDefined(currentColumnForFilterRow?.filterValue); | ||
| const currentColumn = this._filterExcludedColumn; |
There was a problem hiding this comment.
minor: rename to excludedColumn
| const columns = that._columnsController.getVisibleColumns(null, true); | ||
| const headerFilterController = this._headerFilterController; | ||
| const currentColumn = headerFilterController.getCurrentColumn(); | ||
| const currentColumn = this._filterExcludedColumn; |
There was a problem hiding this comment.
minor: rename to excludedColumn
| this._dataController.setFilterExcludedColumn(options); | ||
| const filter = this._dataController.getCombinedFilter(); | ||
| this._applyFilterViewController.setCurrentColumnForFiltering(null); | ||
| this._dataController.setFilterExcludedColumn(null); |
There was a problem hiding this comment.
We repeat this construct 4 times, perhaps, we should create another method instead, smth like getCombinedFilterWithExculdedColumn
…ilterWithExcludedColumn
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (2)
packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts:364
- PR description mentions
setFilterExcludedColumn/getFilterExcludedColumn, butDataControlleronly exposesgetFilterExcludedColumnand relies ongetCombinedFilterWithExcludedColumnto mutate internal state. Consider addingsetFilterExcludedColumn(or updating the description) and restoring the previous excluded column infinally(instead of always resetting tonull) so nested/re-entrant calls don’t accidentally clear the state.
public getCombinedFilterWithExcludedColumn(
excludedColumn: Column | null,
returnDataField?: boolean,
): DataFilter {
this._filterExcludedColumn = excludedColumn;
packages/devextreme/testing/helpers/gridBaseMocks.js:241
gridBaseMocksstubsgetFilterExcludedColumn/getCombinedFilterWithExcludedColumnasnoop, but the real controller returnsnull(no excluded column) andgetCombinedFilterWithExcludedColumndelegates togetCombinedFilter. Usingnoophere can make mocks diverge from production behavior if a test overridesgetCombinedFilter.
getFilterExcludedColumn: commonUtils.noop,
getCombinedFilterWithExcludedColumn: commonUtils.noop,
What
Removed the
_headerFilterControllerand_applyFilterControllerreferences from the baseDataController, so it no longer depends on the header-filter and filter-row controllersHow
The temporary excluded-column context those controllers passed through their own state now lives on
DataController(setFilterExcludedColumn/getFilterExcludedColumn), and the filter data-extenders read it from there instead of reaching into those controllers