From c78f3a33aaab40d705f16dbedb6940f6f23dcf23 Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 09:10:42 +0100 Subject: [PATCH 1/8] [DataGrid] Prepare the tree structure for grouping sorting / filtering + multi field grouping --- .../CustomGroupingColumnTreeData.tsx | 22 ++-- .../cell/GridTreeDataGroupingCell.tsx | 51 ++++---- .../GridCellCheckboxRenderer.tsx | 4 +- .../useGridRowGroupsPreProcessing.ts | 2 +- .../treeData/gridTreeDataGroupColDef.tsx | 18 +-- .../features/treeData/useGridTreeData.ts | 6 +- .../grid/models/colDef/gridColDef.ts | 6 + .../grid/_modules_/grid/models/gridRows.ts | 12 +- .../buildRowTree.test.ts} | 120 ++++++++++++++---- .../{rowTreeUtils.ts => tree/buildRowTree.ts} | 72 +++++++---- scripts/exportsSnapshot.json | 2 +- 11 files changed, 207 insertions(+), 108 deletions(-) rename packages/grid/_modules_/grid/utils/{rowTreeUtils.test.ts => tree/buildRowTree.test.ts} (60%) rename packages/grid/_modules_/grid/utils/{rowTreeUtils.ts => tree/buildRowTree.ts} (64%) diff --git a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.tsx b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.tsx index 3efd3e1c1efbe..062332c624a1a 100644 --- a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.tsx +++ b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.tsx @@ -7,7 +7,8 @@ import { GridColumns, GridRowsProp, DataGridProProps, - GridTreeDataGroupingCellValue, + useGridSelector, + gridFilteredDescendantCountLookupSelector, } from '@mui/x-data-grid-pro'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -19,11 +20,14 @@ export const isNavigationKey = (key: string) => key.indexOf('Page') === 0 || key === ' '; -const CustomGridTreeDataGroupingCell = ( - props: GridRenderCellParams, -) => { - const { id, field, value } = props; +const CustomGridTreeDataGroupingCell = (props: GridRenderCellParams) => { + const { id, field, rowNode } = props; const apiRef = useGridApiContext(); + const filteredDescendantCountLookup = useGridSelector( + apiRef, + gridFilteredDescendantCountLookupSelector, + ); + const filteredDescendantCount = filteredDescendantCountLookup[rowNode.id] ?? 0; const handleKeyDown = (event) => { if (event.key === ' ') { @@ -35,22 +39,22 @@ const CustomGridTreeDataGroupingCell = ( }; const handleClick = (event) => { - apiRef.current.setRowChildrenExpansion(id, !value.expanded); + apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); apiRef.current.setCellFocus(id, field); event.stopPropagation(); }; return ( - +
- {value.filteredDescendantCount > 0 ? ( + {filteredDescendantCount > 0 ? ( ) : ( diff --git a/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx b/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx index 0db1865442033..149945b30c490 100644 --- a/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx @@ -6,6 +6,8 @@ import Box from '@mui/material/Box'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { GridRenderCellParams } from '../../models/params/gridCellParams'; +import { useGridSelector } from '../../hooks/utils/useGridSelector'; +import { gridFilteredDescendantCountLookupSelector } from '../../hooks/features/filter/gridFilterSelector'; import { isNavigationKey, isSpaceKey } from '../../utils/keyboardUtils'; import { GridEvents } from '../../models/events'; import { getDataGridUtilityClass } from '../../gridClasses'; @@ -24,22 +26,21 @@ const useUtilityClasses = (ownerState: OwnerState) => { return composeClasses(slots, getDataGridUtilityClass, classes); }; -export interface GridTreeDataGroupingCellValue { - label: string; - filteredDescendantCount: number; - depth: number; - expanded: boolean; -} - -const GridTreeDataGroupingCell = (props: GridRenderCellParams) => { - const { id, field, value } = props; +const GridTreeDataGroupingCell = (props: GridRenderCellParams) => { + const { id, field, rowNode } = props; const rootProps = useGridRootProps(); const apiRef = useGridApiContext(); const ownerState: OwnerState = { classes: rootProps.classes }; const classes = useUtilityClasses(ownerState); + const filteredDescendantCountLookup = useGridSelector( + apiRef, + gridFilteredDescendantCountLookupSelector, + ); - const Icon = value.expanded + const filteredDescendantCount = filteredDescendantCountLookup[rowNode.id] ?? 0; + + const Icon = rowNode.childrenExpanded ? rootProps.components.TreeDataCollapseIcon : rootProps.components.TreeDataExpandIcon; @@ -53,22 +54,22 @@ const GridTreeDataGroupingCell = (props: GridRenderCellParams { - apiRef.current.setRowChildrenExpansion(id, !value.expanded); + apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); apiRef.current.setCellFocus(id, field); event.stopPropagation(); }; return ( - +
- {value.filteredDescendantCount > 0 && ( + {filteredDescendantCount > 0 && ( - {value.label} - {value.filteredDescendantCount > 0 ? ` (${value.filteredDescendantCount})` : ''} + {rowNode.groupingKey} + {filteredDescendantCount > 0 ? ` (${filteredDescendantCount})` : ''} ); @@ -109,12 +110,7 @@ GridTreeDataGroupingCell.propTypes = { /** * The cell value formatted with the column valueFormatter. */ - formattedValue: PropTypes.shape({ - depth: PropTypes.number.isRequired, - expanded: PropTypes.bool.isRequired, - filteredDescendantCount: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - }).isRequired, + formattedValue: PropTypes.any.isRequired, /** * Get the cell value of a row and field. * @param {GridRowId} id The row id. @@ -147,7 +143,9 @@ GridTreeDataGroupingCell.propTypes = { ), childrenExpanded: PropTypes.bool, depth: PropTypes.number.isRequired, - groupingValue: PropTypes.string.isRequired, + groupingField: PropTypes.string, + groupingKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.bool]) + .isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, isAutoGenerated: PropTypes.bool, parent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), @@ -159,12 +157,7 @@ GridTreeDataGroupingCell.propTypes = { /** * The cell value, but if the column has valueGetter, use getValue. */ - value: PropTypes.shape({ - depth: PropTypes.number.isRequired, - expanded: PropTypes.bool.isRequired, - filteredDescendantCount: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - }).isRequired, + value: PropTypes.any.isRequired, } as any; export { GridTreeDataGroupingCell }; diff --git a/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx b/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx index 8f33619cf3cab..7106fb0a1a98d 100644 --- a/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx +++ b/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx @@ -143,7 +143,9 @@ GridCellCheckboxForwardRef.propTypes = { ), childrenExpanded: PropTypes.bool, depth: PropTypes.number.isRequired, - groupingValue: PropTypes.string.isRequired, + groupingField: PropTypes.string, + groupingKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.bool]) + .isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, isAutoGenerated: PropTypes.bool, parent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), diff --git a/packages/grid/_modules_/grid/hooks/core/rowGroupsPerProcessing/useGridRowGroupsPreProcessing.ts b/packages/grid/_modules_/grid/hooks/core/rowGroupsPerProcessing/useGridRowGroupsPreProcessing.ts index 5c4968bf9bc07..8a0bdc76eac6b 100644 --- a/packages/grid/_modules_/grid/hooks/core/rowGroupsPerProcessing/useGridRowGroupsPreProcessing.ts +++ b/packages/grid/_modules_/grid/hooks/core/rowGroupsPerProcessing/useGridRowGroupsPreProcessing.ts @@ -13,7 +13,7 @@ const getFlatRowTree: GridRowGroupingPreProcessing = ({ ids, idRowsLookup }) => const tree: GridRowTreeConfig = {}; for (let i = 0; i < ids.length; i += 1) { const rowId = ids[i]; - tree[rowId] = { id: rowId, depth: 0, parent: null, groupingValue: '' }; + tree[rowId] = { id: rowId, depth: 0, parent: null, groupingKey: '', groupingField: null }; } return { diff --git a/packages/grid/_modules_/grid/hooks/features/treeData/gridTreeDataGroupColDef.tsx b/packages/grid/_modules_/grid/hooks/features/treeData/gridTreeDataGroupColDef.tsx index f2aefa7295ea6..1e51cdb70610c 100644 --- a/packages/grid/_modules_/grid/hooks/features/treeData/gridTreeDataGroupColDef.tsx +++ b/packages/grid/_modules_/grid/hooks/features/treeData/gridTreeDataGroupColDef.tsx @@ -1,12 +1,7 @@ import * as React from 'react'; import { GridColDef } from '../../../models/colDef/gridColDef'; -import { - GridTreeDataGroupingCell, - GridTreeDataGroupingCellValue, -} from '../../../components/cell/GridTreeDataGroupingCell'; +import { GridTreeDataGroupingCell } from '../../../components/cell/GridTreeDataGroupingCell'; import { GRID_STRING_COL_DEF } from '../../../models/colDef/gridStringColDef'; -import { gridFilteredDescendantCountLookupSelector } from '../filter/gridFilterSelector'; -import { GridRenderCellParams } from '../../../models'; /** * TODO: Add sorting and filtering on the value and the filteredDescendantCount @@ -20,15 +15,8 @@ export const GRID_TREE_DATA_GROUP_COL_DEF: Omit ({ - label: rowNode.groupingValue, - depth: rowNode.depth, - expanded: rowNode.childrenExpanded ?? false, - filteredDescendantCount: gridFilteredDescendantCountLookupSelector(api.state)[rowNode.id] ?? 0, - }), - renderCell: (params: GridRenderCellParams) => ( - - ), + valueGetter: ({ rowNode }) => rowNode.groupingKey, + renderCell: (params) => , }; export const GRID_TREE_DATA_GROUP_COL_DEF_FORCED_FIELDS: Pick = { diff --git a/packages/grid/_modules_/grid/hooks/features/treeData/useGridTreeData.ts b/packages/grid/_modules_/grid/hooks/features/treeData/useGridTreeData.ts index 198ae4e40ae53..eb7dcdce5be8c 100644 --- a/packages/grid/_modules_/grid/hooks/features/treeData/useGridTreeData.ts +++ b/packages/grid/_modules_/grid/hooks/features/treeData/useGridTreeData.ts @@ -10,7 +10,7 @@ import { GridEvents, GridEventListener } from '../../../models/events'; import { GridColDef, GridColDefOverrideParams, GridColumns } from '../../../models'; import { isSpaceKey } from '../../../utils/keyboardUtils'; import { useFirstRender } from '../../utils/useFirstRender'; -import { buildRowTree } from '../../../utils/rowTreeUtils'; +import { buildRowTree, BuildRowTreeGroupingCriteria } from '../../../utils/tree/buildRowTree'; import { GridRowGroupingPreProcessing } from '../../core/rowGroupsPerProcessing'; import { gridFilteredDescendantCountLookupSelector } from '../filter'; import { GridPreProcessingGroup, useGridRegisterPreProcessor } from '../../core/preProcessing'; @@ -79,7 +79,9 @@ export const useGridTreeData = ( const rows = params.ids .map((rowId) => ({ id: rowId, - path: props.getTreeDataPath!(params.idRowsLookup[rowId]), + path: props.getTreeDataPath!(params.idRowsLookup[rowId]).map( + (key): BuildRowTreeGroupingCriteria => ({ key, field: null }), + ), })) .sort((a, b) => a.path.length - b.path.length); diff --git a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts index 508982658ee1e..931c4c310153c 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts @@ -27,6 +27,12 @@ import { GridEditCellProps } from '../gridEditRowModel'; export type GridAlignment = 'left' | 'right' | 'center'; type ValueOptions = string | number | { value: any; label: string }; + +/** + * Value that can be used as a key for grouping rows + */ +export type GridKeyValue = string | number | boolean; + /** * Column Definition interface. */ diff --git a/packages/grid/_modules_/grid/models/gridRows.ts b/packages/grid/_modules_/grid/models/gridRows.ts index b73caa71b0815..954eda4b02f93 100644 --- a/packages/grid/_modules_/grid/models/gridRows.ts +++ b/packages/grid/_modules_/grid/models/gridRows.ts @@ -1,3 +1,5 @@ +import type { GridKeyValue } from './colDef'; + export type GridRowsProp = Readonly; /** @@ -43,9 +45,15 @@ export interface GridRowTreeNodeConfig { depth: number; /** - * The value used to group the children of this row. + * The key used to group the children of this row. + */ + groupingKey: GridKeyValue; + + /** + * The field used to group the children of this row. + * Is `null` if no field has been used to group the children of this row. */ - groupingValue: string; + groupingField: string | null; /** * If `true`, this node has been automatically added to fill a gap in the tree structure. diff --git a/packages/grid/_modules_/grid/utils/rowTreeUtils.test.ts b/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts similarity index 60% rename from packages/grid/_modules_/grid/utils/rowTreeUtils.test.ts rename to packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts index fe646e2ecb195..d40219aa7e94d 100644 --- a/packages/grid/_modules_/grid/utils/rowTreeUtils.test.ts +++ b/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { buildRowTree } from './rowTreeUtils'; +import { buildRowTree } from './buildRowTree'; +// TODO: Add tests for multi-field grouping describe('buildRowTree', () => { it('should not expand the rows when defaultGroupingExpansionDepth === 0', () => { const response = buildRowTree({ @@ -11,9 +12,9 @@ describe('buildRowTree', () => { }, ids: [0, 1, 2], rows: [ - { id: 0, path: ['A'] }, - { id: 1, path: ['A', 'A'] }, - { id: 2, path: ['A', 'A', 'A'] }, + { id: 0, path: [{ key: 'A', field: null }] }, + { id: 1, path: [{ key: 'A', field: null }] }, + { id: 2, path: [{ key: 'A', field: null }] }, ], defaultGroupingExpansionDepth: 0, }); @@ -39,9 +40,22 @@ describe('buildRowTree', () => { }, ids: [0, 1, 2], rows: [ - { id: 0, path: ['A'] }, - { id: 1, path: ['A', 'A'] }, - { id: 2, path: ['A', 'A', 'A'] }, + { id: 0, path: [{ key: 'A', field: null }] }, + { + id: 1, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, + { + id: 2, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, ], defaultGroupingExpansionDepth: 2, }); @@ -67,9 +81,22 @@ describe('buildRowTree', () => { }, ids: [0, 1, 2], rows: [ - { id: 0, path: ['A'] }, - { id: 1, path: ['A', 'A'] }, - { id: 2, path: ['A', 'A', 'A'] }, + { id: 0, path: [{ key: 'A', field: null }] }, + { + id: 1, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, + { + id: 2, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, ], defaultGroupingExpansionDepth: -1, }); @@ -96,10 +123,30 @@ describe('buildRowTree', () => { }, ids: [0, 1, 2, 3], rows: [ - { id: 0, path: ['A'] }, - { id: 1, path: ['A', 'A'] }, - { id: 2, path: ['A', 'A', 'A'] }, - { id: 3, path: ['A', 'A', 'B'] }, + { id: 0, path: [{ key: 'A', field: null }] }, + { + id: 1, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, + { + id: 2, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, + { + id: 3, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + { key: 'B', field: null }, + ], + }, ], defaultGroupingExpansionDepth: 0, }); @@ -128,10 +175,30 @@ describe('buildRowTree', () => { }, ids: [0, 1, 2, 3], rows: [ - { id: 0, path: ['A'] }, - { id: 1, path: ['A', 'A'] }, - { id: 2, path: ['A', 'A', 'A'] }, - { id: 3, path: ['A', 'A', 'B'] }, + { id: 0, path: [{ key: 'A', field: null }] }, + { + id: 1, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, + { + id: 2, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, + { + id: 3, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + { key: 'B', field: null }, + ], + }, ], defaultGroupingExpansionDepth: 0, }); @@ -147,8 +214,15 @@ describe('buildRowTree', () => { }, ids: [0, 1], rows: [ - { id: 0, path: ['A'] }, - { id: 1, path: ['A', 'A', 'A'] }, + { id: 0, path: [{ key: 'A', field: null }] }, + { + id: 2, + path: [ + { key: 'A', field: null }, + { key: 'A', field: null }, + { key: 'A', field: null }, + ], + }, ], defaultGroupingExpansionDepth: 0, }); @@ -166,7 +240,7 @@ describe('buildRowTree', () => { children: ['auto-generated-row-A-A'], depth: 0, childrenExpanded: false, - groupingValue: 'A', + groupingKey: 'A', id: 0, parent: null, }, @@ -174,7 +248,7 @@ describe('buildRowTree', () => { children: [1], depth: 1, childrenExpanded: false, - groupingValue: 'A', + groupingKey: 'A', id: 'auto-generated-row-A-A', isAutoGenerated: true, parent: 0, @@ -183,7 +257,7 @@ describe('buildRowTree', () => { children: undefined, depth: 2, childrenExpanded: false, - groupingValue: 'A', + groupingKey: 'A', id: 1, parent: 'auto-generated-row-A-A', }, diff --git a/packages/grid/_modules_/grid/utils/rowTreeUtils.ts b/packages/grid/_modules_/grid/utils/tree/buildRowTree.ts similarity index 64% rename from packages/grid/_modules_/grid/utils/rowTreeUtils.ts rename to packages/grid/_modules_/grid/utils/tree/buildRowTree.ts index fbf6ab4f31c4c..335095700944e 100644 --- a/packages/grid/_modules_/grid/utils/rowTreeUtils.ts +++ b/packages/grid/_modules_/grid/utils/tree/buildRowTree.ts @@ -1,15 +1,27 @@ -import type { GridRowTreeNodeConfig, GridRowId, GridRowTreeConfig } from '../models'; +import type { + GridRowTreeNodeConfig, + GridRowId, + GridRowTreeConfig, + GridKeyValue, +} from '../../models'; import type { GridRowGroupingResult, GridRowGroupParams, -} from '../hooks/core/rowGroupsPerProcessing'; +} from '../../hooks/core/rowGroupsPerProcessing'; -type GridNodeNameToIdTree = { - [nodeName: string]: { id: GridRowId; children: GridNodeNameToIdTree }; +type GridGroupingCriteriaToIdTree = { + [field: string]: { + [key: string]: { id: GridRowId; children: GridGroupingCriteriaToIdTree }; + }; }; -interface GenerateRowTreeParams extends GridRowGroupParams { - rows: { id: GridRowId; path: string[] }[]; +export interface BuildRowTreeGroupingCriteria { + field: string | null; + key: GridKeyValue; +} + +interface BuildRowTreeParams extends GridRowGroupParams { + rows: { id: GridRowId; path: BuildRowTreeGroupingCriteria[] }[]; defaultGroupingExpansionDepth: number; } @@ -32,23 +44,21 @@ interface TempRowTreeNode extends Omit { ], defaultGroupingExpansionDepth: 0, } - Returns: - { ids: [0, 1, 2, 'auto-generated-row-B'], idRowsLookup: { 0: {...}, 1: {...}, 2: {...}, 'auto-generated-row-B': {} }, tree: { - '0': { id: 0, parent: null, childrenExpanded: false, depth: 0, groupingValue: 'A' }, - 'auto-generated-row-B': { id: 'auto-generated-row-B', parent: null, childrenExpanded: false, depth: 0, groupingValue: 'B' }, - '1': { id: 1, parent: 'auto-generated-row-B', childrenExpanded: false, depth: 1, groupingValue: 'A' }, - '2': { id: 2, parent: 1, childrenExpanded: false, depth: 2, groupingValue: 'A' }, + '0': { id: 0, parent: null, childrenExpanded: false, depth: 0, groupingKey: 'A' }, + 'auto-generated-row-B': { id: 'auto-generated-row-B', parent: null, childrenExpanded: false, depth: 0, groupingKey: 'B' }, + '1': { id: 1, parent: 'auto-generated-row-B', childrenExpanded: false, depth: 1, groupingKey: 'A' }, + '2': { id: 2, parent: 1, childrenExpanded: false, depth: 2, groupingKey: 'A' }, }, treeDepth: 3, } ``` */ -export const buildRowTree = (params: GenerateRowTreeParams): GridRowGroupingResult => { +export const buildRowTree = (params: BuildRowTreeParams): GridRowGroupingResult => { // During the build, we store the children as a Record to avoid linear complexity when checking if a children is already defined. const tempTree: Record = {}; let treeDepth = 1; @@ -56,34 +66,44 @@ export const buildRowTree = (params: GenerateRowTreeParams): GridRowGroupingResu const ids = [...params.ids]; const idRowsLookup = { ...params.idRowsLookup }; - const nodeNameToIdTree: GridNodeNameToIdTree = {}; + const groupingCriteriaToIdTree: GridGroupingCriteriaToIdTree = {}; for (let i = 0; i < params.rows.length; i += 1) { const row = params.rows[i]; - let nodeNameToIdSubTree = nodeNameToIdTree; + let keyToIdSubTree = groupingCriteriaToIdTree; let parentNode: TempRowTreeNode | null = null; for (let depth = 0; depth < row.path.length; depth += 1) { - const nodeName = row.path[depth]; + const { key, field: rawField } = row.path[depth]; + const field = rawField ?? '__no_field__'; + let nodeId: GridRowId; const childrenExpanded = params.defaultGroupingExpansionDepth === -1 || params.defaultGroupingExpansionDepth > depth; - let nodeNameConfig = nodeNameToIdSubTree[nodeName]; + let fieldSubTree = keyToIdSubTree[field]; + if (!fieldSubTree) { + fieldSubTree = {}; + keyToIdSubTree[field] = fieldSubTree; + } - if (!nodeNameConfig) { + let keyConfig = fieldSubTree[key.toString()]; + if (!keyConfig) { nodeId = depth === row.path.length - 1 ? row.id - : `auto-generated-row-${row.path.slice(0, depth + 1).join('-')}`; + : `auto-generated-row-${row.path + .map((groupingCriteria) => `${groupingCriteria.field}/${groupingCriteria.key}`) + .slice(0, depth + 1) + .join('-')}`; - nodeNameConfig = { id: nodeId, children: {} }; - nodeNameToIdSubTree[nodeName] = nodeNameConfig; + keyConfig = { id: nodeId, children: {} }; + fieldSubTree[key.toString()] = keyConfig; } else { - nodeId = nodeNameConfig.id; + nodeId = keyConfig.id; } - nodeNameToIdSubTree = nodeNameConfig.children; + keyToIdSubTree = keyConfig.children; if (depth < row.path.length - 1) { let node = tempTree[nodeId] ?? null; @@ -93,7 +113,8 @@ export const buildRowTree = (params: GenerateRowTreeParams): GridRowGroupingResu isAutoGenerated: true, childrenExpanded, parent: parentNode?.id ?? null, - groupingValue: row.path[depth], + groupingKey: key, + groupingField: rawField, depth, }; @@ -106,7 +127,8 @@ export const buildRowTree = (params: GenerateRowTreeParams): GridRowGroupingResu id: row.id, childrenExpanded, parent: parentNode?.id ?? null, - groupingValue: row.path[depth], + groupingKey: key, + groupingField: rawField, depth, }; } diff --git a/scripts/exportsSnapshot.json b/scripts/exportsSnapshot.json index 5509a57523a9b..dcf61980060b1 100644 --- a/scripts/exportsSnapshot.json +++ b/scripts/exportsSnapshot.json @@ -138,7 +138,6 @@ { "name": "GridTabIndexState", "kind": "Interface" }, { "name": "GridToolbarExportProps", "kind": "Interface" }, { "name": "GridToolbarFilterButtonProps", "kind": "Interface" }, - { "name": "GridTreeDataGroupingCellValue", "kind": "Interface" }, { "name": "GridTypeFilterInputValueProps", "kind": "Interface" }, { "name": "GridValueFormatterParams", "kind": "Interface" }, { "name": "GridValueOptionsParams", "kind": "Interface" }, @@ -179,6 +178,7 @@ { "name": "GridFilterActiveItemsLookup", "kind": "Type alias" }, { "name": "GridFooterContainerProps", "kind": "Type alias" }, { "name": "GridInputSelectionModel", "kind": "Type alias" }, + { "name": "GridKeyValue", "kind": "Type alias" }, { "name": "GridNativeColTypes", "kind": "Type alias" }, { "name": "GridOverlayProps", "kind": "Type alias" }, { "name": "GridPreferencePanelInitialState", "kind": "Type alias" }, From 47c6dfb3ba169cb4d2e3e0ded5adbc5ef3411b8a Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 09:14:20 +0100 Subject: [PATCH 2/8] docs ts --- .../CustomGroupingColumnTreeData.js | 74 ++++++++++++++++--- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js index 02e11ac62beb2..a36f59986c8aa 100644 --- a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js +++ b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js @@ -1,6 +1,12 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { DataGridPro, useGridApiContext, GridEvents } from '@mui/x-data-grid-pro'; +import { + DataGridPro, + useGridApiContext, + GridEvents, + useGridSelector, + gridFilteredDescendantCountLookupSelector, +} from '@mui/x-data-grid-pro'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; @@ -12,8 +18,14 @@ export const isNavigationKey = (key) => key === ' '; const CustomGridTreeDataGroupingCell = (props) => { - const { id, field, value } = props; + const { id, field, rowNode } = props; const apiRef = useGridApiContext(); + const filteredDescendantCountLookup = useGridSelector( + apiRef, + gridFilteredDescendantCountLookupSelector, + ); + + const filteredDescendantCount = filteredDescendantCountLookup[rowNode.id] ?? 0; const handleKeyDown = (event) => { if (event.key === ' ') { @@ -25,22 +37,22 @@ const CustomGridTreeDataGroupingCell = (props) => { }; const handleClick = (event) => { - apiRef.current.setRowChildrenExpansion(id, !value.expanded); + apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); apiRef.current.setCellFocus(id, field); event.stopPropagation(); }; return ( - +
- {value.filteredDescendantCount > 0 ? ( + {filteredDescendantCount > 0 ? ( ) : ( @@ -60,13 +72,53 @@ CustomGridTreeDataGroupingCell.propTypes = { */ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, /** - * The cell value, but if the column has valueGetter, use getValue. + * The node of the row that the current cell belongs to. */ - value: PropTypes.shape({ + rowNode: PropTypes.shape({ + /** + * The id of the row children. + */ + children: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + ), + /** + * Current expansion status of the row. + */ + childrenExpanded: PropTypes.bool, + /** + * 0-based depth of the row in the tree. + */ depth: PropTypes.number.isRequired, - expanded: PropTypes.bool.isRequired, - filteredDescendantCount: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, + /** + * The field used to group the children of this row. + * Is `null` if no field has been used to group the children of this row. + */ + groupingField: PropTypes.oneOfType([PropTypes.oneOf([null]), PropTypes.string]) + .isRequired, + /** + * The key used to group the children of this row. + */ + groupingKey: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.bool, + ]).isRequired, + /** + * The grid row id. + */ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + /** + * If `true`, this node has been automatically added to fill a gap in the tree structure. + */ + isAutoGenerated: PropTypes.bool, + /** + * The row id of the parent (null if this row is a top level row). + */ + parent: PropTypes.oneOfType([ + PropTypes.oneOf([null]), + PropTypes.number, + PropTypes.string, + ]).isRequired, }).isRequired, }; From db70f677b2d71355339a7554dac820c6aaa377e0 Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 09:29:45 +0100 Subject: [PATCH 3/8] Fix tests --- .../grid/utils/tree/buildRowTree.test.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts b/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts index d40219aa7e94d..3af251a4a899e 100644 --- a/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts +++ b/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { buildRowTree } from './buildRowTree'; // TODO: Add tests for multi-field grouping -describe('buildRowTree', () => { +describe.only('buildRowTree', () => { it('should not expand the rows when defaultGroupingExpansionDepth === 0', () => { const response = buildRowTree({ idRowsLookup: { @@ -216,7 +216,7 @@ describe('buildRowTree', () => { rows: [ { id: 0, path: [{ key: 'A', field: null }] }, { - id: 2, + id: 1, path: [ { key: 'A', field: null }, { key: 'A', field: null }, @@ -231,25 +231,27 @@ describe('buildRowTree', () => { idRowsLookup: { 0: {}, 1: {}, - 'auto-generated-row-A-A': {}, + 'auto-generated-row-null/A-null/A': {}, }, - ids: [0, 1, 'auto-generated-row-A-A'], + ids: [0, 1, 'auto-generated-row-null/A-null/A'], treeDepth: 3, tree: { 0: { - children: ['auto-generated-row-A-A'], + children: ['auto-generated-row-null/A-null/A'], depth: 0, childrenExpanded: false, + groupingField: null, groupingKey: 'A', id: 0, parent: null, }, - 'auto-generated-row-A-A': { + 'auto-generated-row-null/A-null/A': { children: [1], depth: 1, childrenExpanded: false, + groupingField: null, groupingKey: 'A', - id: 'auto-generated-row-A-A', + id: 'auto-generated-row-null/A-null/A', isAutoGenerated: true, parent: 0, }, @@ -257,9 +259,10 @@ describe('buildRowTree', () => { children: undefined, depth: 2, childrenExpanded: false, + groupingField: null, groupingKey: 'A', id: 1, - parent: 'auto-generated-row-A-A', + parent: 'auto-generated-row-null/A-null/A', }, }, }); From cf8347655b17fafc67ded4a50750e823098e980f Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 09:44:34 +0100 Subject: [PATCH 4/8] Fix test --- .../src/tests/columnHeaders.DataGridPro.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx index 468fd2056fb6b..ae6fcda43fd85 100644 --- a/packages/grid/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx @@ -43,7 +43,7 @@ describe(' - Column Headers', () => { fireEvent.click(menuIconButton); await waitFor(() => expect(screen.queryByRole('menu')).not.to.equal(null)); const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.dispatchEvent(new Event('scroll')); + fireEvent.scroll(virtualScroller); await waitFor(() => expect(screen.queryByRole('menu')).to.equal(null)); }); From a0bc0b3a0e02f26eeb9e0a7037f99c13af009812 Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 10:15:35 +0100 Subject: [PATCH 5/8] Fix --- packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts b/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts index 3af251a4a899e..802e4d7bad3fe 100644 --- a/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts +++ b/packages/grid/_modules_/grid/utils/tree/buildRowTree.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { buildRowTree } from './buildRowTree'; // TODO: Add tests for multi-field grouping -describe.only('buildRowTree', () => { +describe('buildRowTree', () => { it('should not expand the rows when defaultGroupingExpansionDepth === 0', () => { const response = buildRowTree({ idRowsLookup: { From d5a088214ab0937b0a4a5d1dda521d7835d37218 Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 14:36:46 +0100 Subject: [PATCH 6/8] Code review --- docs/scripts/generateProptypes.ts | 1 + .../components/cell/GridTreeDataGroupingCell.tsx | 14 +------------- .../columnSelection/GridCellCheckboxRenderer.tsx | 14 +------------- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/docs/scripts/generateProptypes.ts b/docs/scripts/generateProptypes.ts index 007ca732c71c8..a7b8e4d857b1c 100644 --- a/docs/scripts/generateProptypes.ts +++ b/docs/scripts/generateProptypes.ts @@ -33,6 +33,7 @@ async function generateProptypes(program: ttp.ts.Program, sourceFile: string) { 'printOptions', 'column', 'groupingColDef', + 'rowNode', ]; if (propsToNotResolve.includes(name)) { return false; diff --git a/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx b/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx index 149945b30c490..d44a319fd77e4 100644 --- a/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridTreeDataGroupingCell.tsx @@ -137,19 +137,7 @@ GridTreeDataGroupingCell.propTypes = { /** * The node of the row that the current cell belongs to. */ - rowNode: PropTypes.shape({ - children: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - ), - childrenExpanded: PropTypes.bool, - depth: PropTypes.number.isRequired, - groupingField: PropTypes.string, - groupingKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.bool]) - .isRequired, - id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - isAutoGenerated: PropTypes.bool, - parent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - }).isRequired, + rowNode: PropTypes.object.isRequired, /** * the tabIndex value. */ diff --git a/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx b/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx index 7106fb0a1a98d..b8c14b3f05f7d 100644 --- a/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx +++ b/packages/grid/_modules_/grid/components/columnSelection/GridCellCheckboxRenderer.tsx @@ -137,19 +137,7 @@ GridCellCheckboxForwardRef.propTypes = { /** * The node of the row that the current cell belongs to. */ - rowNode: PropTypes.shape({ - children: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - ), - childrenExpanded: PropTypes.bool, - depth: PropTypes.number.isRequired, - groupingField: PropTypes.string, - groupingKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.bool]) - .isRequired, - id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - isAutoGenerated: PropTypes.bool, - parent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - }).isRequired, + rowNode: PropTypes.object.isRequired, /** * the tabIndex value. */ From 75a62fff9703105e9a2088fcd3380f2b7ac9b6f0 Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 14:47:47 +0100 Subject: [PATCH 7/8] Fix --- .../CustomGroupingColumnTreeData.js | 47 +------------------ 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js index a36f59986c8aa..55a8b0a08374d 100644 --- a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js +++ b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js @@ -74,52 +74,7 @@ CustomGridTreeDataGroupingCell.propTypes = { /** * The node of the row that the current cell belongs to. */ - rowNode: PropTypes.shape({ - /** - * The id of the row children. - */ - children: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - ), - /** - * Current expansion status of the row. - */ - childrenExpanded: PropTypes.bool, - /** - * 0-based depth of the row in the tree. - */ - depth: PropTypes.number.isRequired, - /** - * The field used to group the children of this row. - * Is `null` if no field has been used to group the children of this row. - */ - groupingField: PropTypes.oneOfType([PropTypes.oneOf([null]), PropTypes.string]) - .isRequired, - /** - * The key used to group the children of this row. - */ - groupingKey: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - PropTypes.bool, - ]).isRequired, - /** - * The grid row id. - */ - id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - /** - * If `true`, this node has been automatically added to fill a gap in the tree structure. - */ - isAutoGenerated: PropTypes.bool, - /** - * The row id of the parent (null if this row is a top level row). - */ - parent: PropTypes.oneOfType([ - PropTypes.oneOf([null]), - PropTypes.number, - PropTypes.string, - ]).isRequired, - }).isRequired, + rowNode: PropTypes.object.isRequired, }; const rows = [ From 15a1efa13a0dd7d3aa2d560de35e7f0689382ea1 Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 29 Nov 2021 15:06:05 +0100 Subject: [PATCH 8/8] Fix --- .../CustomGroupingColumnTreeData.js | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js index 55a8b0a08374d..a36f59986c8aa 100644 --- a/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js +++ b/docs/src/pages/components/data-grid/group-pivot/CustomGroupingColumnTreeData.js @@ -74,7 +74,52 @@ CustomGridTreeDataGroupingCell.propTypes = { /** * The node of the row that the current cell belongs to. */ - rowNode: PropTypes.object.isRequired, + rowNode: PropTypes.shape({ + /** + * The id of the row children. + */ + children: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + ), + /** + * Current expansion status of the row. + */ + childrenExpanded: PropTypes.bool, + /** + * 0-based depth of the row in the tree. + */ + depth: PropTypes.number.isRequired, + /** + * The field used to group the children of this row. + * Is `null` if no field has been used to group the children of this row. + */ + groupingField: PropTypes.oneOfType([PropTypes.oneOf([null]), PropTypes.string]) + .isRequired, + /** + * The key used to group the children of this row. + */ + groupingKey: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + PropTypes.bool, + ]).isRequired, + /** + * The grid row id. + */ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + /** + * If `true`, this node has been automatically added to fill a gap in the tree structure. + */ + isAutoGenerated: PropTypes.bool, + /** + * The row id of the parent (null if this row is a top level row). + */ + parent: PropTypes.oneOfType([ + PropTypes.oneOf([null]), + PropTypes.number, + PropTypes.string, + ]).isRequired, + }).isRequired, }; const rows = [