Skip to content

Commit

Permalink
[DataGrid] Do not apply filters on rowExpansionChange (#8671)
Browse files Browse the repository at this point in the history
  • Loading branch information
cherniavskii committed Jun 1, 2023
1 parent 4f80f5a commit 4ecaf68
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ export const filterRowTreeFromGroupingColumns = (
params: FilterRowTreeFromTreeDataParams,
): Omit<GridFilterState, 'filterModel'> => {
const { rowTree, isRowMatchingFilters, filterModel } = params;
const visibleRowsLookup: Record<GridRowId, boolean> = {};
const filteredRowsLookup: Record<GridRowId, boolean> = {};
const filteredDescendantCountLookup: Record<GridRowId, number> = {};

Expand Down Expand Up @@ -138,15 +137,8 @@ export const filterRowTreeFromGroupingColumns = (
}
}

visibleRowsLookup[node.id] = isPassingFiltering && areAncestorsExpanded;
filteredRowsLookup[node.id] = isPassingFiltering;

// TODO rows v6: Should we keep storing the visibility status of footer independently or rely on the group visibility in the selector ?
if (node.type === 'group' && node.footerId != null) {
visibleRowsLookup[node.footerId] =
isPassingFiltering && areAncestorsExpanded && !!node.childrenExpanded;
}

if (!isPassingFiltering) {
return 0;
}
Expand All @@ -169,7 +161,6 @@ export const filterRowTreeFromGroupingColumns = (
}

return {
visibleRowsLookup,
filteredRowsLookup,
filteredDescendantCountLookup,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
createRowTree,
updateRowTree,
RowTreeBuilderGroupingCriterion,
getVisibleRowsLookup,
} from '@mui/x-data-grid-pro/internals';
import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps';
import {
Expand Down Expand Up @@ -233,6 +234,12 @@ export const useGridRowGroupingPreProcessors = (
);
useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'filtering', filterRows);
useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'sorting', sortRows);
useGridRegisterStrategyProcessor(
apiRef,
ROW_GROUPING_STRATEGY,
'visibleRowsLookupCreation',
getVisibleRowsLookup,
);

/**
* 1ST RENDER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2459,6 +2459,39 @@ describe('<DataGridPremium /> - Row Grouping', () => {
expect(getColumnValues(1)).to.deep.equal(['', 'Cat 1 (1)', '', 'Cat 2 (2)', '', '']);
});
});

it('should not apply filters when the row is expanded', () => {
render(
<Test
initialState={{
rowGrouping: { model: ['category1'] },
}}
/>,
);

const onFilteredRowsSet = spy();
apiRef.current.subscribeEvent('filteredRowsSet', onFilteredRowsSet);

fireEvent.click(getCell(0, 0).querySelector('button')!);
expect(onFilteredRowsSet.callCount).to.equal(0);
});

it('should not apply filters when the row is collapsed', () => {
render(
<Test
initialState={{
rowGrouping: { model: ['category1'] },
}}
defaultGroupingExpansionDepth={-1}
/>,
);

const onFilteredRowsSet = spy();
apiRef.current.subscribeEvent('filteredRowsSet', onFilteredRowsSet);

fireEvent.click(getCell(0, 0).querySelector('button')!);
expect(onFilteredRowsSet.callCount).to.equal(0);
});
});

describe('apiRef: addRowGroupingCriteria', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export const filterRowTreeFromTreeData = (
params: FilterRowTreeFromTreeDataParams,
): Omit<GridFilterState, 'filterModel'> => {
const { rowTree, disableChildrenFiltering, isRowMatchingFilters } = params;
const visibleRowsLookup: Record<GridRowId, boolean> = {};
const filteredRowsLookup: Record<GridRowId, boolean> = {};
const filteredDescendantCountLookup: Record<GridRowId, number> = {};

Expand Down Expand Up @@ -86,15 +85,8 @@ export const filterRowTreeFromTreeData = (
}
}

visibleRowsLookup[node.id] = shouldPassFilters && areAncestorsExpanded;
filteredRowsLookup[node.id] = shouldPassFilters;

// TODO: Should we keep storing the visibility status of footer independently or rely on the group visibility in the selector ?
if (node.type === 'group' && node.footerId != null) {
visibleRowsLookup[node.footerId] =
shouldPassFilters && areAncestorsExpanded && !!node.childrenExpanded;
}

if (!shouldPassFilters) {
return 0;
}
Expand All @@ -117,7 +109,6 @@ export const filterRowTreeFromTreeData = (
}

return {
visibleRowsLookup,
filteredRowsLookup,
filteredDescendantCountLookup,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from '../../../utils/tree/models';
import { sortRowTree } from '../../../utils/tree/sortRowTree';
import { updateRowTree } from '../../../utils/tree/updateRowTree';
import { getVisibleRowsLookup } from '../../../utils/tree/utils';

export const useGridTreeDataPreProcessors = (
privateApiRef: React.MutableRefObject<GridPrivateApiPro>,
Expand Down Expand Up @@ -212,6 +213,12 @@ export const useGridTreeDataPreProcessors = (
);
useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'filtering', filterRows);
useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'sorting', sortRows);
useGridRegisterStrategyProcessor(
privateApiRef,
TREE_DATA_STRATEGY,
'visibleRowsLookupCreation',
getVisibleRowsLookup,
);

/**
* 1ST RENDER
Expand Down
2 changes: 1 addition & 1 deletion packages/grid/x-data-grid-pro/src/internals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ export type {
export { createRowTree } from '../utils/tree/createRowTree';
export { updateRowTree } from '../utils/tree/updateRowTree';
export { sortRowTree } from '../utils/tree/sortRowTree';
export { insertNodeInTree, removeNodeFromTree } from '../utils/tree/utils';
export { insertNodeInTree, removeNodeFromTree, getVisibleRowsLookup } from '../utils/tree/utils';
export type { RowTreeBuilderGroupingCriterion } from '../utils/tree/models';
44 changes: 44 additions & 0 deletions packages/grid/x-data-grid-pro/src/utils/tree/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
GRID_ROOT_GROUP_ID,
GridChildrenFromPathLookup,
GridFilterState,
GridGroupNode,
GridLeafNode,
GridRowId,
GridRowTreeConfig,
GridRowsState,
GridTreeNode,
} from '@mui/x-data-grid';
import {
Expand Down Expand Up @@ -235,3 +237,45 @@ export const createUpdatedGroupsManager = (): GridRowTreeUpdatedGroupsManager =>
this.value[groupId][action] = true;
},
});

export const getVisibleRowsLookup = ({
tree,
filteredRowsLookup,
}: {
tree: GridRowsState['tree'];
filteredRowsLookup: GridFilterState['filteredRowsLookup'];
}) => {
if (!filteredRowsLookup) {
return {};
}

const visibleRowsLookup: Record<GridRowId, boolean> = {};

const handleTreeNode = (node: GridTreeNode, areAncestorsExpanded: boolean) => {
const isPassingFiltering = filteredRowsLookup[node.id];

if (node.type === 'group') {
node.children.forEach((childId) => {
const childNode = tree[childId];
handleTreeNode(childNode, areAncestorsExpanded && !!node.childrenExpanded);
});
}

visibleRowsLookup[node.id] = isPassingFiltering && areAncestorsExpanded;

// TODO rows v6: Should we keep storing the visibility status of footer independently or rely on the group visibility in the selector ?
if (node.type === 'group' && node.footerId != null) {
visibleRowsLookup[node.footerId] =
isPassingFiltering && areAncestorsExpanded && !!node.childrenExpanded;
}
};

const nodes = Object.values(tree);
for (let i = 0; i < nodes.length; i += 1) {
const node = nodes[i];
if (node.depth === 0) {
handleTreeNode(node, true);
}
}
return visibleRowsLookup;
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {
GridRowTreeCreationParams,
GridRowTreeCreationValue,
GridRowsState,
} from '../../features/rows/gridRowsInterfaces';
import {
GridFilteringMethodParams,
GridFilteringMethodValue,
GridFilterState,
GridVisibleRowsLookupState,
} from '../../features/filter/gridFilterState';
import {
GridSortingMethodParams,
Expand Down Expand Up @@ -32,6 +35,14 @@ export interface GridStrategyProcessingLookup {
params: GridSortingMethodParams;
value: GridSortingMethodValue;
};
visibleRowsLookupCreation: {
group: 'rowTree';
params: {
tree: GridRowsState['tree'];
filteredRowsLookup: GridFilterState['filteredRowsLookup'];
};
value: GridVisibleRowsLookupState;
};
}

export type GridStrategyProcessor<P extends GridStrategyProcessorName> = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const GRID_STRATEGIES_PROCESSORS: {
rowTreeCreation: 'rowTree',
filtering: 'rowTree',
sorting: 'rowTree',
visibleRowsLookupCreation: 'rowTree',
};

type UntypedStrategyProcessors = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,10 @@ export const gridQuickFilterValuesSelector = createSelector(
);

/**
* @category Filtering
* @category Visible rows
* @ignore - do not document.
*/
export const gridVisibleRowsLookupSelector = createSelector(
gridFilterStateSelector,
(filterState) => filterState.visibleRowsLookup,
);
export const gridVisibleRowsLookupSelector = (state: GridStateCommunity) => state.visibleRowsLookup;

/**
* @category Filtering
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ export interface GridFilterState {
* This is the equivalent of the `visibleRowsLookup` if all the groups were expanded.
*/
filteredRowsLookup: Record<GridRowId, boolean>;
/**
* Visibility status for each row.
* A row is visible if it is passing the filters AND if its parents are expanded.
* If a row is not registered in this lookup, it is visible.
*/
visibleRowsLookup: Record<GridRowId, boolean>;
/**
* Amount of descendants that are passing the filters.
* For the Tree Data, it includes all the intermediate depth levels (= amount of children + amount of grand children + ...).
Expand Down Expand Up @@ -59,3 +53,10 @@ export interface GridFilteringMethodParams {
}

export type GridFilteringMethodValue = Omit<GridFilterState, 'filterModel'>;

/**
* Visibility status for each row.
* A row is visible if it is passing the filters AND if its parents are expanded.
* If a row is not registered in this lookup, it is visible.
*/
export type GridVisibleRowsLookupState = Record<GridRowId, boolean>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { GridFilterApi } from '../../../models/api/gridFilterApi';
import { GridFilterItem } from '../../../models/gridFilterItem';
import { GridGroupNode, GridRowId } from '../../../models/gridRows';
import { GridStateCommunity } from '../../../models/gridStateCommunity';
import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
Expand Down Expand Up @@ -40,12 +41,27 @@ export const filterStateInitializer: GridStateInitializer<
...state,
filter: {
filterModel: sanitizeFilterModel(filterModel, props.disableMultipleColumnsFiltering, apiRef),
visibleRowsLookup: {},
filteredDescendantCountLookup: {},
},
visibleRowsLookup: {},
};
};

const getVisibleRowsLookup: GridStrategyProcessor<'visibleRowsLookupCreation'> = (params) => {
// For flat tree, the `visibleRowsLookup` and the `filteredRowsLookup` are equals since no row is collapsed.
return params.filteredRowsLookup;
};

function getVisibleRowsLookupState(
apiRef: React.MutableRefObject<GridPrivateApiCommunity>,
state: GridStateCommunity,
) {
return apiRef.current.applyStrategyProcessor('visibleRowsLookupCreation', {
tree: state.rows.tree,
filteredRowsLookup: state.filter.filteredRowsLookup,
});
}

/**
* @requires useGridColumns (method, event)
* @requires useGridParamsApi (method)
Expand Down Expand Up @@ -86,13 +102,20 @@ export const useGridFilter = (
filterModel: filterModel ?? getDefaultGridFilterModel(),
});

return {
const newState = {
...state,
filter: {
...state.filter,
...filteringResult,
},
};

const visibleRowsLookupState = getVisibleRowsLookupState(apiRef, newState);

return {
...newState,
visibleRowsLookup: visibleRowsLookupState,
};
});
apiRef.current.publishEvent('filteredRowsSet');
}, [props.filterMode, apiRef]);
Expand Down Expand Up @@ -385,14 +408,11 @@ export const useGridFilter = (
}
return {
filteredRowsLookup,
// For flat tree, the `visibleRowsLookup` and the `filteredRowsLookup` are equals since no row is collapsed.
visibleRowsLookup: filteredRowsLookup,
filteredDescendantCountLookup: {},
};
}

return {
visibleRowsLookup: {},
filteredRowsLookup: {},
filteredDescendantCountLookup: {},
};
Expand All @@ -405,6 +425,12 @@ export const useGridFilter = (
useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
useGridRegisterPipeProcessor(apiRef, 'preferencePanel', preferencePanelPreProcessing);
useGridRegisterStrategyProcessor(apiRef, GRID_DEFAULT_STRATEGY, 'filtering', flatFilteringMethod);
useGridRegisterStrategyProcessor(
apiRef,
GRID_DEFAULT_STRATEGY,
'visibleRowsLookupCreation',
getVisibleRowsLookup,
);

/**
* EVENTS
Expand Down Expand Up @@ -432,12 +458,22 @@ export const useGridFilter = (
[apiRef],
);

const updateVisibleRowsLookupState = React.useCallback(() => {
apiRef.current.setState((state) => {
return {
...state,
visibleRowsLookup: getVisibleRowsLookupState(apiRef, state),
};
});
apiRef.current.forceUpdate();
}, [apiRef]);

// Do not call `apiRef.current.forceUpdate` to avoid re-render before updating the sorted rows.
// Otherwise, the state is not consistent during the render
useGridApiEventHandler(apiRef, 'rowsSet', updateFilteredRows);
useGridApiEventHandler(apiRef, 'rowExpansionChange', apiRef.current.unstable_applyFilters);
useGridApiEventHandler(apiRef, 'columnsChange', handleColumnsChange);
useGridApiEventHandler(apiRef, 'activeStrategyProcessorChange', handleStrategyProcessorChange);
useGridApiEventHandler(apiRef, 'rowExpansionChange', updateVisibleRowsLookupState);

/**
* 1ST RENDER
Expand Down
Loading

0 comments on commit 4ecaf68

Please sign in to comment.