Skip to content

Commit b2c7e4e

Browse files
authored
CardView: hotfix filterSync issues (#30154)
1 parent d5a839f commit b2c7e4e

File tree

9 files changed

+96
-132
lines changed

9 files changed

+96
-132
lines changed

packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.test.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ describe('ColumnsController', () => {
3535

3636
it('should infer dataType and format from firstItems', () => {
3737
const { columnsController } = setup({
38+
dataSource: [
39+
{
40+
id: 1,
41+
price: 9.99,
42+
createdAt: new Date('2023-01-01T00:00:00Z'),
43+
},
44+
],
3845
columns: ['id', 'price', 'createdAt'],
3946
});
4047

41-
columnsController.setColumnOptionsFromDataItem({
42-
id: 1,
43-
price: 9.99,
44-
createdAt: new Date('2023-01-01T00:00:00Z'),
45-
});
46-
4748
const columns = columnsController.columns.peek();
4849

4950
expect(columns).toMatchObject([
@@ -64,13 +65,15 @@ describe('ColumnsController', () => {
6465
});
6566

6667
it('should generate columns from firstItems when no columns config is provided', () => {
67-
const { columnsController } = setup();
68-
69-
columnsController.setColumnOptionsFromDataItem({
70-
id: 1,
71-
title: 'Hello',
72-
price: 99.99,
73-
createdAt: new Date('2024-01-01T00:00:00Z'),
68+
const { columnsController } = setup({
69+
dataSource: [
70+
{
71+
id: 1,
72+
title: 'Hello',
73+
price: 99.99,
74+
createdAt: new Date('2024-01-01T00:00:00Z'),
75+
},
76+
],
7477
});
7578

7679
const columns = columnsController.columns.peek();

packages/devextreme/js/__internal/grids/new/grid_core/data_controller/data_controller.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export class DataController {
241241
const pageIndex = this.pageIndex.value;
242242
const pageSize = this.pageSize.value;
243243
const isLoaded = this.isLoaded.value;
244-
const displayFilter = this.normalizedDisplayFilter.value;
244+
const displayFilter = this.filterController.displayFilter.value;
245245
const pagingEnabled = this.pagingEnabled.value;
246246
const sortParameters = this.sortingController.sortParameters.value;
247247

@@ -348,9 +348,7 @@ export class DataController {
348348

349349
const firstItem = items[0];
350350

351-
if (firstItem) {
352-
this.columnsController.setColumnOptionsFromDataItem(firstItem);
353-
}
351+
this.columnsController.setColumnOptionsFromDataItem(firstItem ?? {});
354352

355353
this._items.value = items;
356354
this.pageIndex.value = dataSource.pageIndex();

packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_sync/__intergation__/date_type.ts

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -186,32 +186,8 @@ export const DATE_HEADER_FILTER: TestCaseHeaderFilter[] = [
186186
];
187187

188188
export const DATE_FILTER_PANEL: TestCaseFilterPanel[] = [
189-
{
190-
caseName: 'equal year',
191-
filteredIds: [0, 1, 2, 3],
192-
changes: {
193-
filterPanel: ['value', '=', 2024],
194-
},
195-
expected: {
196-
headerFilterType: 'include',
197-
headerFilter: [2024],
198-
// Same filter by meaning as initial one
199-
filterPanel: ['value', 'anyof', [2024]],
200-
},
201-
},
202-
{
203-
caseName: 'equal month',
204-
filteredIds: [2, 3],
205-
changes: {
206-
filterPanel: ['value', '=', '2024/2'],
207-
},
208-
expected: {
209-
headerFilterType: 'include',
210-
headerFilter: ['2024/2'],
211-
// Same filter by meaning as initial one
212-
filterPanel: ['value', 'anyof', ['2024/2']],
213-
},
214-
},
189+
// NOTE: FilterPanel filter that equal part of date is an incorrect configuration (filter)
190+
// E.g: ['value', '=', '2024/2'] or ['value', '=', 2024]
215191
{
216192
caseName: 'equal date',
217193
filteredIds: [4],
@@ -221,8 +197,7 @@ export const DATE_FILTER_PANEL: TestCaseFilterPanel[] = [
221197
expected: {
222198
headerFilterType: 'include',
223199
headerFilter: ['2025/1/1'],
224-
// Same filter by meaning as initial one
225-
filterPanel: ['value', 'anyof', ['2025/1/1']],
200+
filterPanel: ['value', '=', '2025/1/1'],
226201
},
227202
},
228203
{
@@ -297,32 +272,8 @@ export const DATE_FILTER_PANEL: TestCaseFilterPanel[] = [
297272
filterPanel: ['value', 'anyof', ['2025/1/1', '2026/1/2']],
298273
},
299274
},
300-
{
301-
caseName: 'not equal year',
302-
filteredIds: [4, 5, 6, 7],
303-
changes: {
304-
filterPanel: ['value', '<>', 2024],
305-
},
306-
expected: {
307-
headerFilterType: 'exclude',
308-
headerFilter: [2024],
309-
// Same filter by meaning as initial one
310-
filterPanel: ['value', 'noneof', [2024]],
311-
},
312-
},
313-
{
314-
caseName: 'not equal month',
315-
filteredIds: [0, 1, 4, 5, 6, 7],
316-
changes: {
317-
filterPanel: ['value', '<>', '2024/2'],
318-
},
319-
expected: {
320-
headerFilterType: 'exclude',
321-
headerFilter: ['2024/2'],
322-
// Same filter by meaning as initial one
323-
filterPanel: ['value', 'noneof', ['2024/2']],
324-
},
325-
},
275+
// NOTE: FilterPanel filter that not equal part of date is an incorrect configuration (filter)
276+
// E.g: ['value', '<>', '2024/2'] or ['value', '<>', 2024]
326277
{
327278
caseName: 'not equal date',
328279
filteredIds: [0, 1, 2, 3, 5, 6, 7],
@@ -333,7 +284,7 @@ export const DATE_FILTER_PANEL: TestCaseFilterPanel[] = [
333284
headerFilterType: 'exclude',
334285
headerFilter: ['2025/1/1'],
335286
// Same filter by meaning as initial one
336-
filterPanel: ['value', 'noneof', ['2025/1/1']],
287+
filterPanel: ['value', '<>', '2025/1/1'],
337288
},
338289
},
339290
{

packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_sync/__intergation__/number_type.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,7 @@ export const NUMBER_FILTER_PANEL: TestCaseFilterPanel[] = [
275275
expected: {
276276
headerFilterType: 'include',
277277
headerFilter: [1],
278-
// Same filter by meaning as initial one
279-
filterPanel: ['value', '=', 1],
278+
filterPanel: ['value', 'anyof', [1]],
280279
},
281280
},
282281
{
@@ -300,8 +299,7 @@ export const NUMBER_FILTER_PANEL: TestCaseFilterPanel[] = [
300299
expected: {
301300
headerFilterType: 'exclude',
302301
headerFilter: [1],
303-
// Same filter by meaning as initial one
304-
filterPanel: ['value', '<>', 1],
302+
filterPanel: ['value', 'noneof', [1]],
305303
},
306304
},
307305
{

packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_sync/__intergation__/string_type.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,7 @@ export const STRING_FILTER_PANEL: TestCaseFilterPanel[] = [
222222
expected: {
223223
headerFilterType: 'include',
224224
headerFilter: ['A_0'],
225-
// Same filter by meaning as initial one
226-
filterPanel: ['value', '=', 'A_0'],
225+
filterPanel: ['value', 'anyof', ['A_0']],
227226
},
228227
},
229228
{
@@ -247,8 +246,7 @@ export const STRING_FILTER_PANEL: TestCaseFilterPanel[] = [
247246
expected: {
248247
headerFilterType: 'exclude',
249248
headerFilter: ['A_0'],
250-
// Same filter by meaning as initial one
251-
filterPanel: ['value', '<>', 'A_0'],
249+
filterPanel: ['value', 'noneof', ['A_0']],
252250
},
253251
},
254252
{
@@ -330,8 +328,7 @@ export const STRING_DS_VALUES_FILTER_PANEL: TestCaseFilterPanel[] = [
330328
expected: {
331329
headerFilterType: 'include',
332330
headerFilter: ['A_0'],
333-
// anyof here because we select one of custom ds values
334-
filterPanel: ['value', 'anyof', ['A_0']],
331+
filterPanel: ['value', '=', 'A_0'],
335332
},
336333
},
337334
{
@@ -379,7 +376,7 @@ export const STRING_DS_VALUES_FILTER_PANEL: TestCaseFilterPanel[] = [
379376
expected: {
380377
headerFilterType: 'include',
381378
headerFilter: ['A_200'],
382-
filterPanel: ['value', 'anyof', ['A_200']],
379+
filterPanel: ['value', '=', 'A_200'],
383380
},
384381
},
385382
];

packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_sync/controller.ts

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// import type { ReadonlySignal } from '@preact/signals-core';
22
// import { computed } from '@preact/signals-core';
33
import { equalByValue } from '@js/core/utils/common';
4-
import { batch } from '@preact/signals-core';
4+
import { batch, effect } from '@preact/signals-core';
55
import { getMatchedConditions } from '@ts/filter_builder/m_utils';
66
import type { HeaderFilterInfo } from '@ts/grids/new/grid_core/filtering/header_filter/types';
77
import { SearchController } from '@ts/grids/new/grid_core/search/index';
@@ -31,6 +31,10 @@ export class FilterSyncController {
3131

3232
private previousFilterPanelValue: FilterValue | null = null;
3333

34+
private previousFilterPanelEnabled: boolean = this.filterController
35+
.filterPanelFilterEnabled
36+
.peek();
37+
3438
private previousHeaderFilterInfoArray: HeaderFilterInfo[] = [];
3539

3640
// 🚨🚨🚨 This controller was hotfixed during severe issues in filterSync feature.
@@ -44,56 +48,52 @@ export class FilterSyncController {
4448
private readonly searchController: SearchController,
4549
) {
4650
// --- FilterPanel -> HeaderFilter ---
47-
this.filterController.filterPanelValue.subscribe((filterPanelValue) => {
48-
// NOTE: Handle first load with empty FilterPanel value
49-
if (this.previousFilterPanelValue === null && filterPanelValue === null) {
51+
effect(() => {
52+
const filterPanelValue = this.filterController.filterValueOption.value;
53+
const isFilterPanelEnabled = this.filterController.filterPanelFilterEnabled.value;
54+
55+
if (equalByValue(
56+
this.previousFilterPanelValue,
57+
filterPanelValue,
58+
FILTER_DEEP_COMPARISON_OPTS,
59+
) && this.previousFilterPanelEnabled === isFilterPanelEnabled) {
5060
return;
5161
}
5262

5363
this.previousFilterPanelValue = filterPanelValue;
64+
this.previousFilterPanelEnabled = isFilterPanelEnabled;
5465

5566
// NOTE: If filterSync is disabled -> do nothing
5667
const isSyncEnabled = this.filterController.filterSyncEnabled.peek();
5768
if (!isSyncEnabled) {
5869
return;
5970
}
6071

61-
// NOTE: If FilterPanel value is empty -> clear HeaderFilter values
62-
if (filterPanelValue === null) {
72+
// NOTE: If FilterPanel value is empty or disabled -> clear HeaderFilter values
73+
if (!isFilterPanelEnabled || filterPanelValue === null) {
6374
this.headerFilterController.clearHeaderFilters();
64-
return;
65-
}
66-
67-
// NOTE: If HeaderFilter is empty and FilterPanel isn't
68-
// sync FilterPanel -> HeaderFilter
69-
const headerFilterInfoArray = this.headerFilterController.headerFilterInfoArray.peek();
70-
if (!headerFilterInfoArray.length) {
71-
this.handleFilterPanelSync(filterPanelValue);
72-
return;
73-
}
74-
75-
// NOTE: If merged from HeaderFilter values equals current FilterPanel values
76-
// do nothing
77-
const newFilterPanelValue = mergeFilterPanelWithHeaderFilterValues(
78-
filterPanelValue ?? [],
79-
headerFilterInfoArray,
80-
);
81-
if (equalByValue(
82-
filterPanelValue ?? [],
83-
newFilterPanelValue,
84-
FILTER_DEEP_COMPARISON_OPTS,
85-
)) {
75+
this.previousHeaderFilterInfoArray = this.headerFilterController
76+
.headerFilterInfoArray
77+
.peek();
8678
return;
8779
}
8880

8981
// NOTE: If all conditions above passed sync FilterPanel -> HeaderFilter values
9082
this.handleFilterPanelSync(filterPanelValue);
83+
this.previousHeaderFilterInfoArray = this.headerFilterController
84+
.headerFilterInfoArray
85+
.peek();
9186
});
9287

9388
// --- HeaderFilter -> FilterPanel ---
94-
this.headerFilterController.headerFilterInfoArray.subscribe((headerFilterInfoArray) => {
95-
// NOTE: Handle first load with empty HeaderFilter values
96-
if (!this.previousHeaderFilterInfoArray?.length && !headerFilterInfoArray.length) {
89+
effect(() => {
90+
const headerFilterInfoArray = this.headerFilterController.headerFilterInfoArray.value;
91+
92+
if (equalByValue(
93+
this.previousHeaderFilterInfoArray,
94+
headerFilterInfoArray,
95+
FILTER_DEEP_COMPARISON_OPTS,
96+
)) {
9797
return;
9898
}
9999

@@ -105,13 +105,6 @@ export class FilterSyncController {
105105
return;
106106
}
107107

108-
// NOTE: If HeaderFilter values is empty & filter panel disabled -> clear FilterPanel value
109-
const filterPanelEnabled = this.filterController.filterPanelFilterEnabled.value;
110-
if (!headerFilterInfoArray.length && filterPanelEnabled) {
111-
this.filterController.filterValueOption.value = null;
112-
return;
113-
}
114-
115108
// NOTE: If merged from HeaderFilter values equals current FilterPanel values
116109
// do nothing
117110
const filterPanelValue = this.filterController.filterPanelValue.peek() ?? [];
@@ -125,6 +118,7 @@ export class FilterSyncController {
125118

126119
// NOTE: If all conditions above passed sync HeaderFilter -> FilterPanel values
127120
this.handleHeaderFilterSync(newFilterPanelValue);
121+
this.previousFilterPanelValue = newFilterPanelValue;
128122
});
129123
}
130124

@@ -154,20 +148,22 @@ export class FilterSyncController {
154148

155149
return {
156150
...column,
157-
filterValues,
158151
filterType,
152+
filterValues,
159153
};
160154
}),
161155
);
162156
}
163157

164-
private handleHeaderFilterSync(headerFilter: FilterValue): void {
158+
private handleHeaderFilterSync(newFilterPanelValue: FilterValue): void {
159+
const normalizedValue = !newFilterPanelValue?.length
160+
? null
161+
: newFilterPanelValue;
162+
165163
// NOTE: If we update filters from HeaderFilter side
166164
// For better UX the filter panel will be enabled
167165
batch(() => {
168-
this.filterController.filterValueOption.value = headerFilter.length === 0
169-
? null
170-
: headerFilter;
166+
this.filterController.filterValueOption.value = normalizedValue;
171167
this.filterController.filterPanelFilterEnabled.value = true;
172168
});
173169
}

packages/devextreme/js/__internal/grids/new/grid_core/filtering/filter_sync/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ export const getConditionFromHeaderFilter = ({
6363
case type === 'values-or-condition' && filterType === 'exclude':
6464
return [columnId, 'noneof', filterValues];
6565
case type === 'values-or-condition' && filterType === 'include':
66-
default:
6766
return [columnId, 'anyof', filterValues];
67+
case type === 'empty':
68+
default:
69+
return null;
6870
}
6971
};
7072

packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export interface HeaderFilterRootOptions extends HeaderFilterBaseOptions {
8888
visible?: boolean;
8989
}
9090

91-
export type HeaderFilterValuesType = 'single-value' | 'values-or-condition';
91+
export type HeaderFilterValuesType = 'single-value' | 'values-or-condition' | 'empty';
9292

9393
export interface HeaderFilterInfo {
9494
type: HeaderFilterValuesType;

0 commit comments

Comments
 (0)