From ac19bcdb7c8ef23a192b0a4263927b717cb18664 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 15 Jul 2024 08:46:21 +0500 Subject: [PATCH 01/36] [DataGridPremium] Server-side row grouping --- .../ServerSideRowGroupingDataGrid.js | 139 +++++++++++++++ .../ServerSideRowGroupingDataGrid.tsx | 140 +++++++++++++++ .../server-side-data/row-grouping.md | 8 +- .../src/hooks/serverUtils.ts | 113 ++++++++++++- .../src/hooks/useMockServer.ts | 48 ++++-- .../useDataGridPremiumComponent.tsx | 2 + .../GridDataSourceGroupingCriteriaCell.tsx | 160 ++++++++++++++++++ .../rowGrouping/createGroupingColDef.tsx | 21 ++- .../rowGrouping/gridRowGroupingUtils.ts | 10 +- ...eGridDataSourceRowGroupingPreProcessors.ts | 134 +++++++++++++++ .../useGridRowGroupingPreProcessors.ts | 28 +-- .../dataSource/gridDataSourceSelector.ts | 25 ++- .../features/dataSource/useGridDataSource.ts | 20 ++- .../x-data-grid-pro/src/internals/index.ts | 2 + .../src/utils/tree/insertDataRowInTree.ts | 1 + .../components/containers/GridRootStyles.ts | 5 +- .../x-data-grid/src/constants/gridClasses.ts | 6 + 17 files changed, 821 insertions(+), 41 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx create mode 100644 packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx create mode 100644 packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js new file mode 100644 index 000000000000..d2c9b38b2942 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js @@ -0,0 +1,139 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten } from '@mui/material/styles'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + groupFields: encodeURIComponent(JSON.stringify(params.groupFields)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx new file mode 100644 index 000000000000..e68bf69dabc7 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx @@ -0,0 +1,140 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: encodeURIComponent( + JSON.stringify(params.paginationModel), + ), + filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), + sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), + groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), + groupFields: encodeURIComponent(JSON.stringify(params.groupFields)), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }: { error: string }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 537ef8ad0d54..ab49324cc992 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -6,10 +6,4 @@ title: React Server-side row grouping

Lazy-loaded row grouping with server-side data source.

-:::warning -This feature isn't implemented yet. It's coming. - -👍 Upvote [issue #10859](https://github.com/mui/mui-x/issues/10859) if you want to see it land faster. - -Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with your current solution. -::: +{{"demo": "ServerSideRowGroupingDataGrid.js", "bg": "inline"}} diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 095893126643..1b2fa3540d37 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -53,6 +53,7 @@ export interface ServerSideQueryOptions { sortModel?: GridSortModel; firstRowToRender?: number; lastRowToRender?: number; + groupFields?: string[]; } export const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { @@ -333,6 +334,7 @@ const findTreeDataRowChildren = ( parentPath: string[], pathKey: string = 'path', depth: number = 1, // the depth of the children to find relative to parentDepth, `-1` to find all + rowQualifier?: (row: GridRowModel) => boolean, ) => { const parentDepth = parentPath.length; const children = []; @@ -346,7 +348,9 @@ const findTreeDataRowChildren = ( ((depth < 0 && rowPath.length > parentDepth) || rowPath.length === parentDepth + depth) && parentPath.every((value, index) => value === rowPath[index]) ) { - children.push(row); + if (!rowQualifier || rowQualifier(row)) { + children.push(row); + } } } return children; @@ -427,7 +431,7 @@ const getTreeDataFilteredRows: GetTreeDataFilteredRows = ( }; /** - * Simulates server data loading + * Simulates server data for tree-data feature */ export const processTreeDataRows = ( rows: GridRowModel[], @@ -490,3 +494,108 @@ export const processTreeDataRows = ( }, delay); // simulate network latency }); }; + +/** + * Simulates server data for row grouping feature + */ +export const processRowGroupingRows = ( + rows: GridRowModel[], + queryOptions: ServerSideQueryOptions, + serverOptions: ServerOptions, + columnsWithDefaultColDef: GridColDef[], +): Promise => { + const { minDelay = 100, maxDelay = 300 } = serverOptions; + const pathKey = 'path'; + + if (maxDelay < minDelay) { + throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay '); + } + + if (queryOptions.groupKeys == null) { + throw new Error('serverOptions.groupKeys must be defined to compute row grouping data'); + } + + if (queryOptions.groupFields == null) { + throw new Error('serverOptions.groupFields must be defined to compute row grouping data'); + } + + const delay = randomInt(minDelay, maxDelay); + + const pathsToAutogenerate = new Set(); + let rowsWithPaths = rows; + + // add paths and generate parent rows based on `groupFields` + const groupFields = queryOptions.groupFields; + if (groupFields.length > 0) { + rowsWithPaths = rows.map((row) => { + const partialPath = groupFields.map((field) => row[field] as string); + partialPath.forEach((_, index) => { + const parentPath = partialPath.slice(0, index + 1); + const strigifiedPath = parentPath.join(','); + if (!pathsToAutogenerate.has(strigifiedPath)) { + pathsToAutogenerate.add(strigifiedPath); + } + }); + return { ...row, path: [...partialPath, ''] }; + }); + } else { + rowsWithPaths = rows.map((row) => ({ ...row, path: [''] })); + } + + const autogeneratedRows = Array.from(pathsToAutogenerate).map((path) => { + const pathArray = path.split(','); + return { + id: `auto-generated-parent-${pathArray.join('-')}`, + path: pathArray.slice(0, pathArray.length), + group: pathArray.slice(-1)[0], + }; + }); + + // apply plain filtering + const filteredRows = getTreeDataFilteredRows( + [...autogeneratedRows, ...rowsWithPaths], + queryOptions.filterModel, + columnsWithDefaultColDef, + ) as GridValidRowModel[]; + + // get root row count + const rootRowCount = findTreeDataRowChildren(filteredRows, []).length; + + // find direct children referring to the `parentPath` + const childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys); + + let childRowsWithDescendantCounts = childRows.map((row) => { + const descendants = findTreeDataRowChildren( + filteredRows, + row[pathKey], + pathKey, + -1, + (row) => typeof row.id !== 'string' || !row.id.startsWith('auto-generated-parent-'), + ); + const descendantCount = descendants.length; + return { ...row, descendantCount } as GridRowModel; + }); + + if (queryOptions.sortModel) { + // apply sorting + const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef); + childRowsWithDescendantCounts = [...childRowsWithDescendantCounts].sort(rowComparator); + } + + if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) { + // Only paginate root rows, grid should refetch root rows when `paginationModel` updates + const { pageSize, page } = queryOptions.paginationModel; + if (pageSize < childRowsWithDescendantCounts.length) { + childRowsWithDescendantCounts = childRowsWithDescendantCounts.slice( + page * pageSize, + (page + 1) * pageSize, + ); + } + } + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ rows: childRowsWithDescendantCounts, rootRowCount }); + }, delay); // simulate network latency + }); +}; diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index cf38b1fa3c85..9f5172dc8ab9 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -21,11 +21,13 @@ import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; import { loadServerRows, processTreeDataRows, + processRowGroupingRows, DEFAULT_DATASET_OPTIONS, DEFAULT_SERVER_OPTIONS, } from './serverUtils'; import type { ServerOptions } from './serverUtils'; import { randomInt } from '../services'; +import { useMovieData } from './useMovieData'; const dataCache = new LRUCache({ max: 10, @@ -76,8 +78,14 @@ const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) const defaultColDef = getGridDefaultColumnTypes(); +function sendEmptyResponse() { + return new Promise((resolve) => { + resolve({ rows: [], rowCount: 0 }); + }); +} + export const useMockServer = ( - dataSetOptions?: Partial, + dataSetOptions?: Partial & Partial<{ rowGrouping?: boolean }>, serverOptions?: ServerOptions & { verbose?: boolean }, shouldRequestsFail?: boolean, ): UseMockServerResponse => { @@ -85,6 +93,8 @@ export const useMockServer = ( const [index, setIndex] = React.useState(0); const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); + const rowGroupingData = useMovieData(); + React.useEffect(() => { if (shouldRequestsFail !== undefined) { shouldRequestsFailRef.current = shouldRequestsFail; @@ -117,6 +127,7 @@ export const useMockServer = ( ); const isTreeData = options.treeData?.groupingField != null; + const isRowGrouping = dataSetOptions?.rowGrouping; const getGroupKey = React.useMemo(() => { if (isTreeData) { @@ -134,6 +145,9 @@ export const useMockServer = ( }, [isTreeData]); React.useEffect(() => { + if (dataSetOptions?.rowGrouping) { + return undefined; + } const cacheKey = `${options.dataSet}-${options.rowLength}-${index}-${options.maxColumns}`; // Cache to allow fast switch between the JavaScript and TypeScript version @@ -189,14 +203,13 @@ export const useMockServer = ( options.dataSet, options.maxColumns, index, + dataSetOptions?.rowGrouping, ]); const fetchRows = React.useCallback( async (requestUrl: string): Promise => { - if (!data || !requestUrl) { - return new Promise((resolve) => { - resolve({ rows: [], rowCount: 0 }); - }); + if ((!data && !rowGroupingData.rows && isRowGrouping) || !requestUrl) { + sendEmptyResponse(); } const params = decodeParams(requestUrl); const verbose = serverOptions?.verbose ?? true; @@ -224,22 +237,33 @@ export const useMockServer = ( }); } - if (isTreeData /* || TODO: `isRowGrouping` */) { + if (isTreeData) { const { rows, rootRowCount } = await processTreeDataRows( - data.rows, + data!.rows, params, serverOptionsWithDefault, columnsWithDefaultColDef, ); + getRowsResponse = { + rows: rows.slice().map((row) => ({ ...row, path: undefined })), + rowCount: rootRowCount, + }; + } else if (isRowGrouping) { + const { rows, rootRowCount } = await processRowGroupingRows( + rowGroupingData.rows, + params, + serverOptionsWithDefault, + rowGroupingData.columns, + ); + getRowsResponse = { rows: rows.slice().map((row) => ({ ...row, path: undefined })), rowCount: rootRowCount, }; } else { - // plain data const { returnedRows, nextCursor, totalRowCount } = await loadServerRows( - data.rows, + data!.rows, { ...params, ...params.paginationModel }, serverOptionsWithDefault, columnsWithDefaultColDef, @@ -256,18 +280,20 @@ export const useMockServer = ( }, [ data, + rowGroupingData.rows, serverOptions?.verbose, serverOptions?.minDelay, serverOptions?.maxDelay, serverOptions?.useCursorPagination, isTreeData, columnsWithDefaultColDef, + isRowGrouping, ], ); return { - columns: columnsWithDefaultColDef, - initialState, + columns: isRowGrouping ? rowGroupingData.columns : columnsWithDefaultColDef, + initialState: isRowGrouping ? {} : initialState, getGroupKey, getChildrenCount, fetchRows, diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index b61e63a1f927..569b622752de 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -82,6 +82,7 @@ import { rowGroupingStateInitializer, } from '../hooks/features/rowGrouping/useGridRowGrouping'; import { useGridRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridRowGroupingPreProcessors'; +import { useGridDataSourceRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors'; import { useGridExcelExport } from '../hooks/features/export/useGridExcelExport'; import { cellSelectionStateInitializer, @@ -101,6 +102,7 @@ export const useDataGridPremiumComponent = ( useGridRowSelectionPreProcessors(apiRef, props); useGridRowReorderPreProcessors(apiRef, props); useGridRowGroupingPreProcessors(apiRef, props); + useGridDataSourceRowGroupingPreProcessors(apiRef, props); useGridTreeDataPreProcessors(apiRef, props); useGridDataSourceTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); diff --git a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx new file mode 100644 index 000000000000..0b8d53c4137c --- /dev/null +++ b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx @@ -0,0 +1,160 @@ +import * as React from 'react'; +import { unstable_composeClasses as composeClasses } from '@mui/utils'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; +import Badge from '@mui/material/Badge'; +import { useGridPrivateApiContext } from '@mui/x-data-grid-pro/internals'; +import { + useGridSelector, + gridFilteredDescendantCountLookupSelector, + getDataGridUtilityClass, + GridRenderCellParams, + GridGroupNode, +} from '@mui/x-data-grid-pro'; +import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; +import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; +import { GridPrivateApiPremium } from '../models/gridApiPremium'; +import { GridStatePremium } from '../models/gridStatePremium'; + +type OwnerState = DataGridPremiumProcessedProps; + +const useUtilityClasses = (ownerState: OwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['groupingCriteriaCell'], + toggle: ['groupingCriteriaCellToggle'], + loadingContainer: ['groupingCriteriaCellLoadingContainer'], + }; + + return composeClasses(slots, getDataGridUtilityClass, classes); +}; + +interface GridGroupingCriteriaCellProps extends GridRenderCellParams { + hideDescendantCount?: boolean; +} + +interface GridGroupingCriteriaCellIconProps + extends Pick { + descendantCount: number; +} + +function GridGroupingCriteriaCellIcon(props: GridGroupingCriteriaCellIconProps) { + const apiRef = useGridPrivateApiContext() as React.MutableRefObject; + const rootProps = useGridRootProps(); + const classes = useUtilityClasses(rootProps); + const { rowNode, id, field, descendantCount } = props; + + const loadingSelector = (state: GridStatePremium) => state.dataSource.loading[id] ?? false; + const errorSelector = (state: GridStatePremium) => state.dataSource.errors[id]; + const isDataLoading = useGridSelector(apiRef, loadingSelector); + const error = useGridSelector(apiRef, errorSelector); + + const handleClick = (event: React.MouseEvent) => { + if (!rowNode.childrenExpanded) { + // always fetch/get from cache the children when the node is expanded + apiRef.current.unstable_dataSource.fetchRows(id); + } else { + apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); + } + apiRef.current.setCellFocus(id, field); + event.stopPropagation(); + }; + + const Icon = rowNode.childrenExpanded + ? rootProps.slots.groupingCriteriaCollapseIcon + : rootProps.slots.groupingCriteriaExpandIcon; + + if (isDataLoading) { + return ( +
+ +
+ ); + } + + // TODO: Add back `handleKeyDown` + // const handleKeyDown = (event: React.KeyboardEvent) => { + // if (event.key === ' ') { + // // We call event.stopPropagation to avoid unfolding the row and also scrolling to bottom + // // TODO: Remove and add a check inside useGridKeyboardNavigation + // event.stopPropagation(); + // } + // apiRef.current.publishEvent('cellKeyDown', props, event); + // }; + + return descendantCount > 0 ? ( + + + + + + + + ) : null; +} + +export function GridDataSourceGroupingCriteriaCell(props: GridGroupingCriteriaCellProps) { + const { id, field, rowNode, hideDescendantCount, formattedValue } = props; + + const rootProps = useGridRootProps(); + const apiRef = useGridApiContext(); + const rowSelector = (state: GridStatePremium) => state.rows.dataRowIdToModelLookup[id]; + const row = useGridSelector(apiRef, rowSelector); + const classes = useUtilityClasses(rootProps); + + let descendantCount = 0; + if (row) { + descendantCount = Math.max(rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0, 0); + } + + let cellContent: React.ReactNode; + + // FIXME: The value of `rowNode.groupingField` is always `__no_field__` here + const colDef = apiRef.current.getColumn(rowNode.groupingField!); + if (typeof colDef?.renderCell === 'function') { + cellContent = colDef.renderCell(props); + } else if (typeof formattedValue !== 'undefined') { + cellContent = {formattedValue}; + } else { + cellContent = {rowNode.groupingKey}; + } + + return ( + + `calc(var(--DataGrid-cellOffsetMultiplier) * ${theme.spacing(rowNode.depth)})`, + }} + > +
+ +
+ {cellContent} + {!hideDescendantCount && descendantCount > 0 ? ( + ({descendantCount}) + ) : null} +
+ ); +} diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index caa1a42af8f5..f7112cc6b753 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -12,10 +12,13 @@ import { GridColumnRawLookup, isSingleSelectColDef } from '@mui/x-data-grid-pro/ import { GridApiPremium } from '../../../models/gridApiPremium'; import { GridGroupingColumnFooterCell } from '../../../components/GridGroupingColumnFooterCell'; import { GridGroupingCriteriaCell } from '../../../components/GridGroupingCriteriaCell'; +import { GridDataSourceGroupingCriteriaCell } from '../../../components/GridDataSourceGroupingCriteriaCell'; import { GridGroupingColumnLeafCell } from '../../../components/GridGroupingColumnLeafCell'; import { getRowGroupingFieldFromGroupingCriteria, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, + ROW_GROUPING_STRATEGY, + DATA_SOURCE_ROW_GROUPING_STRATEGY, } from './gridRowGroupingUtils'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; @@ -122,6 +125,7 @@ interface CreateGroupingColDefMonoCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; + strategy?: typeof ROW_GROUPING_STRATEGY | typeof DATA_SOURCE_ROW_GROUPING_STRATEGY; } /** @@ -132,11 +136,17 @@ export const createGroupingColDefForOneGroupingCriteria = ({ groupedByColDef, groupingCriteria, colDefOverride, + strategy = ROW_GROUPING_STRATEGY, }: CreateGroupingColDefMonoCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; + const CriteriaCell = + strategy === ROW_GROUPING_STRATEGY + ? GridGroupingCriteriaCell + : GridDataSourceGroupingCriteriaCell; + // The properties that do not depend on the presence of a `leafColDef` and that can be overridden by `colDefOverride` const commonProperties: Partial = { width: Math.max( @@ -170,7 +180,7 @@ export const createGroupingColDefForOneGroupingCriteria = ({ // Render current grouping criteria groups if (params.rowNode.groupingField === groupingCriteria) { return ( - )} hideDescendantCount={hideDescendantCount} /> @@ -246,6 +256,7 @@ interface CreateGroupingColDefSeveralCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; + strategy?: typeof ROW_GROUPING_STRATEGY | typeof DATA_SOURCE_ROW_GROUPING_STRATEGY; } /** @@ -256,11 +267,17 @@ export const createGroupingColDefForAllGroupingCriteria = ({ columnsLookup, rowGroupingModel, colDefOverride, + strategy = ROW_GROUPING_STRATEGY, }: CreateGroupingColDefSeveralCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; + const CriteriaCell = + strategy === ROW_GROUPING_STRATEGY + ? GridGroupingCriteriaCell + : GridDataSourceGroupingCriteriaCell; + // The properties that do not depend on the presence of a `leafColDef` and that can be overridden by `colDefOverride` const commonProperties: Partial = { headerName: apiRef.current.getLocaleText('groupingColumnHeaderName'), @@ -296,7 +313,7 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // Render the groups return ( - )} hideDescendantCount={hideDescendantCount} /> diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts index 361c76b069c5..f7fae13fb390 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts @@ -9,6 +9,7 @@ import { GridRowModel, GridColDef, GridKeyValue, + GridDataSource, } from '@mui/x-data-grid-pro'; import { passFilterLogic, @@ -29,6 +30,7 @@ import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; export const GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD = '__row_group_by_columns_group__'; export const ROW_GROUPING_STRATEGY = 'grouping-columns'; +export const DATA_SOURCE_ROW_GROUPING_STRATEGY = 'data-source-grouping-columns'; export const getRowGroupingFieldFromGroupingCriteria = (groupingCriteria: string | null) => { if (groupingCriteria === null) { @@ -171,10 +173,11 @@ export const filterRowTreeFromGroupingColumns = ( export const getColDefOverrides = ( groupingColDefProp: DataGridPremiumProcessedProps['groupingColDef'], fields: string[], + strategy?: typeof ROW_GROUPING_STRATEGY | typeof DATA_SOURCE_ROW_GROUPING_STRATEGY, ) => { if (typeof groupingColDefProp === 'function') { return groupingColDefProp({ - groupingName: ROW_GROUPING_STRATEGY, + groupingName: strategy ?? ROW_GROUPING_STRATEGY, fields, }); } @@ -192,6 +195,7 @@ export const mergeStateWithRowGroupingModel = export const setStrategyAvailability = ( privateApiRef: React.MutableRefObject, disableRowGrouping: boolean, + dataSource?: GridDataSource, ) => { let isAvailable: () => boolean; if (disableRowGrouping) { @@ -203,7 +207,9 @@ export const setStrategyAvailability = ( }; } - privateApiRef.current.setStrategyAvailability('rowTree', ROW_GROUPING_STRATEGY, isAvailable); + const strategy = dataSource ? DATA_SOURCE_ROW_GROUPING_STRATEGY : ROW_GROUPING_STRATEGY; + + privateApiRef.current.setStrategyAvailability('rowTree', strategy, isAvailable); }; export const getCellGroupingCriteria = ({ diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts new file mode 100644 index 000000000000..edfd87031429 --- /dev/null +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -0,0 +1,134 @@ +import * as React from 'react'; +import { GridRowId, gridRowTreeSelector, gridColumnLookupSelector } from '@mui/x-data-grid-pro'; +import { + GridStrategyProcessor, + useGridRegisterStrategyProcessor, + createRowTree, + updateRowTree, + RowTreeBuilderGroupingCriterion, + getVisibleRowsLookup, + skipSorting, + skipFiltering, + GridRowsPartialUpdates, +} from '@mui/x-data-grid-pro/internals'; +import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; +import { DATA_SOURCE_ROW_GROUPING_STRATEGY, getGroupingRules } from './gridRowGroupingUtils'; +import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; +import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; + +export const useGridDataSourceRowGroupingPreProcessors = ( + apiRef: React.MutableRefObject, + props: Pick< + DataGridPremiumProcessedProps, + | 'disableRowGrouping' + | 'groupingColDef' + | 'rowGroupingColumnMode' + | 'defaultGroupingExpansionDepth' + | 'isGroupExpandedByDefault' + | 'unstable_dataSource' + >, +) => { + const createRowTreeForRowGrouping = React.useCallback>( + (params) => { + const getGroupKey = props.unstable_dataSource?.getGroupKey; + if (!getGroupKey) { + throw new Error('MUI X: No `getGroupKey` method provided with the dataSource.'); + } + + const getChildrenCount = props.unstable_dataSource?.getChildrenCount; + if (!getChildrenCount) { + throw new Error('MUI X: No `getChildrenCount` method provided with the dataSource.'); + } + + const sanitizedRowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef); + const columnsLookup = gridColumnLookupSelector(apiRef); + const groupingRules = getGroupingRules({ + sanitizedRowGroupingModel, + columnsLookup, + }); + apiRef.current.caches.rowGrouping.rulesOnLastRowTreeCreation = groupingRules; + + // TODO: Accomodate `groupingValueGetter` + + const parentPath = (params.updates as GridRowsPartialUpdates).groupKeys ?? []; + + const getRowTreeBuilderNode = (rowId: GridRowId) => { + const count = getChildrenCount(params.dataRowIdToModelLookup[rowId]); + const leafKey = getGroupKey(params.dataRowIdToModelLookup[rowId]); + return { + id: rowId, + path: [...parentPath, leafKey ?? rowId.toString()].map((key, i) => ({ + key, + field: groupingRules[i]?.field ?? null, + })), + hasServerChildren: !!count && count !== 0, + }; + }; + + if (params.updates.type === 'full') { + return createRowTree({ + previousTree: params.previousTree, + nodes: params.updates.rows.map(getRowTreeBuilderNode), + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: DATA_SOURCE_ROW_GROUPING_STRATEGY, + }); + } + + return updateRowTree({ + nodes: { + inserted: (params.updates as GridRowsPartialUpdates).actions.insert.map( + getRowTreeBuilderNode, + ), + modified: (params.updates as GridRowsPartialUpdates).actions.modify.map( + getRowTreeBuilderNode, + ), + removed: (params.updates as GridRowsPartialUpdates).actions.remove, + }, + previousTree: params.previousTree!, + previousGroupsToFetch: params.previousGroupsToFetch, + previousTreeDepth: params.previousTreeDepths!, + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: DATA_SOURCE_ROW_GROUPING_STRATEGY, + }); + }, + [ + props.unstable_dataSource, + props.defaultGroupingExpansionDepth, + props.isGroupExpandedByDefault, + ], + ); + + const filterRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(apiRef); + + return skipFiltering(rowTree); + }, [apiRef]); + + const sortRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(apiRef); + + return skipSorting(rowTree); + }, [apiRef]); + + useGridRegisterStrategyProcessor( + apiRef, + DATA_SOURCE_ROW_GROUPING_STRATEGY, + 'rowTreeCreation', + createRowTreeForRowGrouping, + ); + useGridRegisterStrategyProcessor( + apiRef, + DATA_SOURCE_ROW_GROUPING_STRATEGY, + 'filtering', + filterRows, + ); + useGridRegisterStrategyProcessor(apiRef, DATA_SOURCE_ROW_GROUPING_STRATEGY, 'sorting', sortRows); + useGridRegisterStrategyProcessor( + apiRef, + DATA_SOURCE_ROW_GROUPING_STRATEGY, + 'visibleRowsLookupCreation', + getVisibleRowsLookup, + ); +}; diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts index 2fa403ec72d5..3604c26c8096 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts @@ -32,6 +32,7 @@ import { filterRowTreeFromGroupingColumns, getColDefOverrides, ROW_GROUPING_STRATEGY, + DATA_SOURCE_ROW_GROUPING_STRATEGY, isGroupingColumn, setStrategyAvailability, getCellGroupingCriteria, @@ -48,6 +49,7 @@ export const useGridRowGroupingPreProcessors = ( | 'rowGroupingColumnMode' | 'defaultGroupingExpansionDepth' | 'isGroupExpandedByDefault' + | 'unstable_dataSource' >, ) => { const getGroupingColDefs = React.useCallback( @@ -56,6 +58,10 @@ export const useGridRowGroupingPreProcessors = ( return []; } + const strategy = props.unstable_dataSource + ? DATA_SOURCE_ROW_GROUPING_STRATEGY + : ROW_GROUPING_STRATEGY; + const groupingColDefProp = props.groupingColDef; // We can't use `gridGroupingRowsSanitizedModelSelector` here because the new columns are not in the state yet @@ -73,8 +79,9 @@ export const useGridRowGroupingPreProcessors = ( createGroupingColDefForAllGroupingCriteria({ apiRef, rowGroupingModel, - colDefOverride: getColDefOverrides(groupingColDefProp, rowGroupingModel), + colDefOverride: getColDefOverrides(groupingColDefProp, rowGroupingModel, strategy), columnsLookup: columnsState.lookup, + strategy, }), ]; } @@ -86,6 +93,7 @@ export const useGridRowGroupingPreProcessors = ( colDefOverride: getColDefOverrides(groupingColDefProp, [groupingCriteria]), groupedByColDef: columnsState.lookup[groupingCriteria], columnsLookup: columnsState.lookup, + strategy, }), ); } @@ -95,7 +103,13 @@ export const useGridRowGroupingPreProcessors = ( } } }, - [apiRef, props.groupingColDef, props.rowGroupingColumnMode, props.disableRowGrouping], + [ + apiRef, + props.groupingColDef, + props.rowGroupingColumnMode, + props.disableRowGrouping, + props.unstable_dataSource, + ], ); const updateGroupingColumn = React.useCallback>( @@ -241,20 +255,14 @@ export const useGridRowGroupingPreProcessors = ( getVisibleRowsLookup, ); - /** - * 1ST RENDER - */ useFirstRender(() => { - setStrategyAvailability(apiRef, props.disableRowGrouping); + setStrategyAvailability(apiRef, props.disableRowGrouping, props.unstable_dataSource); }); - /** - * EFFECTS - */ const isFirstRender = React.useRef(true); React.useEffect(() => { if (!isFirstRender.current) { - setStrategyAvailability(apiRef, props.disableRowGrouping); + setStrategyAvailability(apiRef, props.disableRowGrouping, props.unstable_dataSource); } else { isFirstRender.current = false; } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index a7bee9eec1d9..a370587ca1cf 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -3,6 +3,7 @@ import { gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, + gridColumnLookupSelector, } from '@mui/x-data-grid'; import { createSelector } from '@mui/x-data-grid/internals'; import { GridStatePro } from '../../../models/gridStatePro'; @@ -13,15 +14,33 @@ const computeStartEnd = (paginationModel: GridPaginationModel) => { return { start, end }; }; +type GridStateProWithRowGrouping = GridStatePro & { + rowGrouping?: { + model: string[]; + }; +}; + +const EMPTY_ARRAY: string[] = []; + +const gridRowGroupingModelSelector = (state: GridStateProWithRowGrouping) => + state.rowGrouping?.model ?? EMPTY_ARRAY; + +export const gridRowGroupingSanitizedModelSelector = createSelector( + gridRowGroupingModelSelector, + gridColumnLookupSelector, + (model, columnsLookup) => model.filter((field) => !!columnsLookup[field]), +); + export const gridGetRowsParamsSelector = createSelector( gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, - (filterModel, sortModel, paginationModel) => { + gridRowGroupingModelSelector, + gridRowGroupingSanitizedModelSelector, + (filterModel, sortModel, paginationModel, rowGroupingModel) => { return { groupKeys: [], - // TODO: Implement with `rowGrouping` - groupFields: [], + groupFields: rowGroupingModel, paginationModel, sortModel, filterModel, diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index b948c0a48745..32a10149cdb5 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -12,6 +12,7 @@ import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data- import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; +import { gridRowGroupingSanitizedModelSelector } from './gridDataSourceSelector'; import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; import { GridDataSourceCache } from '../../../models'; @@ -59,6 +60,7 @@ export const useGridDataSource = ( () => new NestedDataManager(apiRef), ).current; const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); + const sanitizedRowGroupingModel = useGridSelector(apiRef, gridRowGroupingSanitizedModelSelector); const scheduledGroups = React.useRef(0); const onError = props.unstable_onDataSourceError; @@ -122,7 +124,7 @@ export const useGridDataSource = ( const fetchRowChildren = React.useCallback( async (id) => { - if (!props.treeData) { + if (!props.treeData && sanitizedRowGroupingModel.length === 0) { nestedDataManager.clearPendingRequest(id); return; } @@ -186,7 +188,14 @@ export const useGridDataSource = ( nestedDataManager.setRequestSettled(id); } }, - [nestedDataManager, onError, apiRef, props.treeData, props.unstable_dataSource?.getRows], + [ + nestedDataManager, + onError, + apiRef, + props.treeData, + props.unstable_dataSource?.getRows, + sanitizedRowGroupingModel, + ], ); const setChildrenLoading = React.useCallback( @@ -267,6 +276,7 @@ export const useGridDataSource = ( 'paginationModelChange', runIfServerMode(props.paginationMode, fetchRows), ); + useGridApiEventHandler(apiRef, 'rowGroupingModelChange', () => fetchRows()); const isFirstRender = React.useRef(true); React.useEffect(() => { @@ -279,9 +289,13 @@ export const useGridDataSource = ( }, [props.unstable_dataSourceCache]); React.useEffect(() => { - if (props.unstable_dataSource) { + const refetchRootRows = () => { apiRef.current.unstable_dataSource.cache.clear(); apiRef.current.unstable_dataSource.fetchRows(); + }; + if (props.unstable_dataSource) { + refetchRootRows(); + return; } }, [apiRef, props.unstable_dataSource]); diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index 28b184e2bd42..b82385b66f86 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -54,4 +54,6 @@ export { sortRowTree } from '../utils/tree/sortRowTree'; export { insertNodeInTree, removeNodeFromTree, getVisibleRowsLookup } from '../utils/tree/utils'; export type { RowTreeBuilderGroupingCriterion } from '../utils/tree/models'; +export { skipSorting, skipFiltering } from '../hooks/features/serverSideTreeData/utils'; + export * from './propValidation'; diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts index 86cd27e02026..b76a3dac3638 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -109,6 +109,7 @@ export const insertDataRowInTree = ({ isAutoGenerated: false, groupingKey: key, groupingField: field, + // groupingField: fieldWithDefaultValue, children: [], childrenFromPath: {}, childrenExpanded: false, diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index bdfd79712cd3..2a81d288a03c 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -118,6 +118,9 @@ export const GridRootStyles = styled('div', { { [`& .${c.treeDataGroupingCellLoadingContainer}`]: styles.treeDataGroupingCellLoadingContainer, }, + { + [`& .${c.groupingCriteriaCellLoadingContainer}`]: styles.groupingCriteriaCellLoadingContainer, + }, { [`& .${c.detailPanelToggleCell}`]: styles.detailPanelToggleCell }, { [`& .${c['detailPanelToggleCell--expanded']}`]: styles['detailPanelToggleCell--expanded'], @@ -626,7 +629,7 @@ export const GridRootStyles = styled('div', { alignSelf: 'stretch', marginRight: t.spacing(2), }, - [`& .${c.treeDataGroupingCellLoadingContainer}`]: { + [`& .${c.treeDataGroupingCellLoadingContainer}, .${c.groupingCriteriaCellLoadingContainer}`]: { display: 'flex', alignItems: 'center', justifyContent: 'center', diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts index e18d9ad39c9c..7994f6234f9e 100644 --- a/packages/x-data-grid/src/constants/gridClasses.ts +++ b/packages/x-data-grid/src/constants/gridClasses.ts @@ -600,6 +600,11 @@ export interface GridClasses { * Styles applied to the toggle of the grouping criteria cell */ groupingCriteriaCellToggle: string; + /** + * Styles applied to the loading container of the grouping cell of the tree data. + * @ignore - do not document. + */ + groupingCriteriaCellLoadingContainer: string; /** * Styles applied to the pinned rows container. */ @@ -773,6 +778,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'treeDataGroupingCellLoadingContainer', 'groupingCriteriaCell', 'groupingCriteriaCellToggle', + 'groupingCriteriaCellLoadingContainer', 'pinnedRows', 'pinnedRows--top', 'pinnedRows--bottom', From b83928f8fce18e5a001c9c2f7a1611d0b3126ac5 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 15 Jul 2024 09:06:40 +0500 Subject: [PATCH 02/36] Lint --- .../GridDataSourceGroupingCriteriaCell.tsx | 1 - .../useGridDataSourceRowGroupingPreProcessors.ts | 2 +- .../rowGrouping/useGridRowGroupingPreProcessors.ts | 2 +- .../hooks/features/dataSource/useGridDataSource.ts | 13 ++++++------- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx index 0b8d53c4137c..9581245a7279 100644 --- a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx +++ b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx @@ -6,7 +6,6 @@ import Badge from '@mui/material/Badge'; import { useGridPrivateApiContext } from '@mui/x-data-grid-pro/internals'; import { useGridSelector, - gridFilteredDescendantCountLookupSelector, getDataGridUtilityClass, GridRenderCellParams, GridGroupNode, diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts index edfd87031429..df099e09cbf8 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -5,7 +5,6 @@ import { useGridRegisterStrategyProcessor, createRowTree, updateRowTree, - RowTreeBuilderGroupingCriterion, getVisibleRowsLookup, skipSorting, skipFiltering, @@ -94,6 +93,7 @@ export const useGridDataSourceRowGroupingPreProcessors = ( }); }, [ + apiRef, props.unstable_dataSource, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault, diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts index 3604c26c8096..2576fca62c96 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts @@ -266,5 +266,5 @@ export const useGridRowGroupingPreProcessors = ( } else { isFirstRender.current = false; } - }, [apiRef, props.disableRowGrouping]); + }, [apiRef, props.disableRowGrouping, props.unstable_dataSource]); }; diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 32a10149cdb5..c9052bbe68f7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -11,8 +11,11 @@ import { import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; -import { gridRowGroupingSanitizedModelSelector } from './gridDataSourceSelector'; +import { + gridGetRowsParamsSelector, + gridDataSourceErrorsSelector, + gridRowGroupingSanitizedModelSelector, +} from './gridDataSourceSelector'; import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; import { GridDataSourceCache } from '../../../models'; @@ -289,13 +292,9 @@ export const useGridDataSource = ( }, [props.unstable_dataSourceCache]); React.useEffect(() => { - const refetchRootRows = () => { + if (props.unstable_dataSource) { apiRef.current.unstable_dataSource.cache.clear(); apiRef.current.unstable_dataSource.fetchRows(); - }; - if (props.unstable_dataSource) { - refetchRootRows(); - return; } }, [apiRef, props.unstable_dataSource]); From d8b762b436a93dad4d26dcb48630a87b7a6ab29e Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Wed, 4 Sep 2024 14:54:24 +0500 Subject: [PATCH 03/36] Adjust according to the changes done on master --- .../ServerSideRowGroupingDataGrid.js | 12 +++++------- .../ServerSideRowGroupingDataGrid.tsx | 12 +++++------- .../useGridDataSourceRowGroupingPreProcessors.ts | 3 +-- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js index d2c9b38b2942..d15f3f945ce6 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js @@ -29,13 +29,11 @@ export default function ServerSideRowGroupingDataGrid() { return { getRows: async (params) => { const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent( - JSON.stringify(params.paginationModel), - ), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - groupFields: encodeURIComponent(JSON.stringify(params.groupFields)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx index e68bf69dabc7..43f1f9af282f 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx @@ -30,13 +30,11 @@ export default function ServerSideRowGroupingDataGrid() { return { getRows: async (params) => { const urlParams = new URLSearchParams({ - paginationModel: encodeURIComponent( - JSON.stringify(params.paginationModel), - ), - filterModel: encodeURIComponent(JSON.stringify(params.filterModel)), - sortModel: encodeURIComponent(JSON.stringify(params.sortModel)), - groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)), - groupFields: encodeURIComponent(JSON.stringify(params.groupFields)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts index df099e09cbf8..565675ed25fd 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -52,7 +52,6 @@ export const useGridDataSourceRowGroupingPreProcessors = ( const parentPath = (params.updates as GridRowsPartialUpdates).groupKeys ?? []; const getRowTreeBuilderNode = (rowId: GridRowId) => { - const count = getChildrenCount(params.dataRowIdToModelLookup[rowId]); const leafKey = getGroupKey(params.dataRowIdToModelLookup[rowId]); return { id: rowId, @@ -60,7 +59,7 @@ export const useGridDataSourceRowGroupingPreProcessors = ( key, field: groupingRules[i]?.field ?? null, })), - hasServerChildren: !!count && count !== 0, + serverChildrenCount: getChildrenCount(params.dataRowIdToModelLookup[rowId]) ?? 0, }; }; From 26ed96e96940638f83e949643362635311e158c5 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 6 Sep 2024 15:35:07 +0500 Subject: [PATCH 04/36] Support sort and filter on normal non-grouping columns --- .../src/hooks/serverUtils.ts | 12 ++++++++---- .../src/hooks/useMockServer.ts | 14 +++++++------- .../features/rowGrouping/createGroupingColDef.tsx | 15 ++++++++++++++- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 1b2fa3540d37..2b032a31473f 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -499,7 +499,7 @@ export const processTreeDataRows = ( * Simulates server data for row grouping feature */ export const processRowGroupingRows = ( - rows: GridRowModel[], + rows: GridValidRowModel[], queryOptions: ServerSideQueryOptions, serverOptions: ServerOptions, columnsWithDefaultColDef: GridColDef[], @@ -559,10 +559,14 @@ export const processRowGroupingRows = ( ) as GridValidRowModel[]; // get root row count - const rootRowCount = findTreeDataRowChildren(filteredRows, []).length; + const rootRows = findTreeDataRowChildren(filteredRows, []); + const rootRowCount = rootRows.length; // find direct children referring to the `parentPath` - const childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys); + const childRows = + queryOptions.groupKeys.length > 0 + ? findTreeDataRowChildren(filteredRows, queryOptions.groupKeys) + : rootRows; let childRowsWithDescendantCounts = childRows.map((row) => { const descendants = findTreeDataRowChildren( @@ -570,7 +574,7 @@ export const processRowGroupingRows = ( row[pathKey], pathKey, -1, - (row) => typeof row.id !== 'string' || !row.id.startsWith('auto-generated-parent-'), + ({ id }) => typeof id !== 'string' || !id.startsWith('auto-generated-parent-'), ); const descendantCount = descendants.length; return { ...row, descendantCount } as GridRowModel; diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index ec272aa7be82..c7aaeeea9d16 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -117,18 +117,18 @@ export const useMockServer = ( [columns, options.treeData?.groupingField], ); + const isTreeData = options.treeData?.groupingField != null; + const isRowGrouping = dataSetOptions?.rowGrouping; + const columnsWithDefaultColDef: GridColDef[] = React.useMemo( () => - columns.map((column) => ({ + (isRowGrouping ? rowGroupingData.columns : columns).map((column) => ({ ...defaultColDef[column.type || 'string'], ...column, })), - [columns], + [columns, rowGroupingData.columns, isRowGrouping], ); - const isTreeData = options.treeData?.groupingField != null; - const isRowGrouping = dataSetOptions?.rowGrouping; - const getGroupKey = React.useMemo(() => { if (isTreeData) { return (row: GridRowModel): string => row[options.treeData!.groupingField!]; @@ -254,7 +254,7 @@ export const useMockServer = ( rowGroupingData.rows, params, serverOptionsWithDefault, - rowGroupingData.columns, + columnsWithDefaultColDef, ); getRowsResponse = { @@ -292,7 +292,7 @@ export const useMockServer = ( ); return { - columns: isRowGrouping ? rowGroupingData.columns : columnsWithDefaultColDef, + columns: columnsWithDefaultColDef, initialState: isRowGrouping ? {} : initialState, getGroupKey, getChildrenCount, diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index f7112cc6b753..ae1ba6fbddeb 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -33,6 +33,17 @@ const GROUPING_COL_DEF_FORCED_PROPERTIES: Pick = { + ...GROUPING_COL_DEF_FORCED_PROPERTIES, + // TODO: Support these features on the grouping column(s) + filterable: false, + sortable: false, + aggregable: false, +}; + /** * When sorting two cells with different grouping criteria, we consider that the cell with the grouping criteria coming first in the model should be displayed below. * This can occur when some rows don't have all the fields. In which case we want the rows with the missing field to be displayed above. @@ -361,7 +372,9 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, - ...GROUPING_COL_DEF_FORCED_PROPERTIES, + ...(strategy === ROW_GROUPING_STRATEGY + ? GROUPING_COL_DEF_FORCED_PROPERTIES + : DATA_SOURCE_GROUPING_COL_DEF_FORCED_PROPERTIES), }; return { From ad92678905bce420c495bc2fe31bbafb0947ea89 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 6 Sep 2024 17:06:40 +0500 Subject: [PATCH 05/36] Update documentation --- .../ServerSideRowGroupingDataGrid.js | 90 ++---------- .../ServerSideRowGroupingDataGrid.tsx | 90 ++---------- .../ServerSideRowGroupingDataGrid.tsx.preview | 16 ++ .../ServerSideRowGroupingErrorHandling.js | 137 +++++++++++++++++ .../ServerSideRowGroupingErrorHandling.tsx | 138 ++++++++++++++++++ .../ServerSideRowGroupingGroupExpansion.js | 70 +++++++++ .../ServerSideRowGroupingGroupExpansion.tsx | 71 +++++++++ .../server-side-data/row-grouping.md | 48 ++++++ 8 files changed, 502 insertions(+), 158 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js index d15f3f945ce6..fc75932d136b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js @@ -5,25 +5,14 @@ import { useKeepGroupedColumnsHidden, } from '@mui/x-data-grid-premium'; import { useMockServer } from '@mui/x-data-grid-generator'; -import Snackbar from '@mui/material/Snackbar'; import Button from '@mui/material/Button'; -import Checkbox from '@mui/material/Checkbox'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import { alpha, styled, darken, lighten } from '@mui/material/styles'; export default function ServerSideRowGroupingDataGrid() { const apiRef = useGridApiRef(); - const [rootError, setRootError] = React.useState(); - const [childrenError, setChildrenError] = React.useState(); - const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { fetchRows, columns } = useMockServer( - { - rowGrouping: true, - }, - {}, - shouldRequestsFail, - ); + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); const dataSource = React.useMemo(() => { return { @@ -59,79 +48,22 @@ export default function ServerSideRowGroupingDataGrid() { return (
-
- - setShouldRequestsFail(e.target.checked)} - /> - } - label="Make the requests fail" - /> -
+ +
{ - if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); - } else { - setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, - ); - } - }} - unstable_dataSourceCache={null} apiRef={apiRef} initialState={initialState} /> - {rootError && } - setChildrenError('')} - message={childrenError} - />
); } - -function getBorderColor(theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -const StyledDiv = styled('div')(({ theme: t }) => ({ - position: 'absolute', - zIndex: 10, - fontSize: '0.875em', - top: 0, - height: '100%', - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, - backgroundColor: t.palette.background.default, -})); - -function ErrorOverlay({ error }) { - if (!error) { - return null; - } - return {error}; -} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx index 43f1f9af282f..91c7f66a6d99 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx @@ -6,25 +6,14 @@ import { useKeepGroupedColumnsHidden, } from '@mui/x-data-grid-premium'; import { useMockServer } from '@mui/x-data-grid-generator'; -import Snackbar from '@mui/material/Snackbar'; import Button from '@mui/material/Button'; -import Checkbox from '@mui/material/Checkbox'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; export default function ServerSideRowGroupingDataGrid() { const apiRef = useGridApiRef(); - const [rootError, setRootError] = React.useState(); - const [childrenError, setChildrenError] = React.useState(); - const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); - const { fetchRows, columns } = useMockServer( - { - rowGrouping: true, - }, - {}, - shouldRequestsFail, - ); + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); const dataSource: GridDataSource = React.useMemo(() => { return { @@ -60,79 +49,22 @@ export default function ServerSideRowGroupingDataGrid() { return (
-
- - setShouldRequestsFail(e.target.checked)} - /> - } - label="Make the requests fail" - /> -
+ +
{ - if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); - } else { - setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, - ); - } - }} - unstable_dataSourceCache={null} apiRef={apiRef} initialState={initialState} /> - {rootError && } - setChildrenError('')} - message={childrenError} - />
); } - -function getBorderColor(theme: Theme) { - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - -const StyledDiv = styled('div')(({ theme: t }) => ({ - position: 'absolute', - zIndex: 10, - fontSize: '0.875em', - top: 0, - height: '100%', - width: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, - backgroundColor: t.palette.background.default, -})); - -function ErrorOverlay({ error }: { error: string }) { - if (!error) { - return null; - } - return {error}; -} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview new file mode 100644 index 000000000000..920c80a41342 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview @@ -0,0 +1,16 @@ + + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js new file mode 100644 index 000000000000..d15f3f945ce6 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js @@ -0,0 +1,137 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten } from '@mui/material/styles'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx new file mode 100644 index 000000000000..43f1f9af282f --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx @@ -0,0 +1,138 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(e.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(e.message); + } else { + setChildrenError( + `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }: { error: string }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js new file mode 100644 index 000000000000..92f85ae62540 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx new file mode 100644 index 000000000000..e7ac226ff0da --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index ab49324cc992..874b035a1e08 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -6,4 +6,52 @@ title: React Server-side row grouping

Lazy-loaded row grouping with server-side data source.

+To dynamically load tree data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview](/x/react-data-grid/server-side-data/). + +Just like tree data, you need to pass some additional properties to support the row grouping feature: + +- `getGroupKey`: Pass the group key for the row. +- `getChildrenCount`: Pass the number of children for the row. If the children count is not available for some reason, but there are some children, return -1. + +Apart from the `groupKeys` parameter, the `getRows` callback would recieve an additional parameter `groupFields` which refers to the current `rowGroupingModel`, use it on the server side to extract the appropriate data chunk for a specific `getRows` call. + +```tsx +const customDataSource: GridDataSource = { + getRows: async (params) => { + // Fetch the data from the server + }, + getGroupKey: (row) => { + // Return the group key for the row, e.g. `name` + return row.name; + }, + getChildrenCount: (row) => { + // Return the number of children for the row + return row.childrenCount; + }, +}; +``` + +The following demo showcases how to implement server-side row grouping with a custom data source. + {{"demo": "ServerSideRowGroupingDataGrid.js", "bg": "inline"}} + +## Error handling + +If an error occurs during a `getRows` call, the Data Grid will display an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. + +The following example demonstrates error handling by displaying both a toast notification and the default error message in the grouping cell. For simplicity, caching has been disabled in this example. + +{{"demo": "ServerSideRowGroupingErrorHandling.js", "bg": "inline"}} + +## Group expansion + +The group expansion works in a similar way to the [data source tree data](/x/react-data-grid/server-side-data/tree-data/#group-expansion). +The following demo expands all the groups using `defaultGroupingExpansionDepth='-1'`. + +{{"demo": "ServerSideRowGroupingGroupExpansion.js", "bg": "inline"}} + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) From 02d1ab3af3e7537230b2f844ab2bea35ee4861c6 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 6 Sep 2024 17:09:36 +0500 Subject: [PATCH 06/36] Update tag --- docs/data/pages.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 68a022f3d6aa..803b1794e7b7 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -138,8 +138,7 @@ const pages: MuiPage[] = [ }, { pathname: '/x/react-data-grid/server-side-data/row-grouping', - plan: 'pro', - planned: true, + plan: 'premium', }, { pathname: '/x/react-data-grid/server-side-data/aggregation', From 342594a186c9eff2a3ddb42c90e85c259c75d488 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sat, 7 Sep 2024 19:08:43 +0500 Subject: [PATCH 07/36] Docs update --- docs/data/data-grid/server-side-data/row-grouping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 874b035a1e08..a100906d4373 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -6,9 +6,9 @@ title: React Server-side row grouping

Lazy-loaded row grouping with server-side data source.

-To dynamically load tree data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview](/x/react-data-grid/server-side-data/). +To dynamically load row grouping data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview](/x/react-data-grid/server-side-data/). -Just like tree data, you need to pass some additional properties to support the row grouping feature: +Just like [tree data](/x/react-data-grid/server-side-data/tree-data/), you need to pass some additional properties to support the row grouping feature: - `getGroupKey`: Pass the group key for the row. - `getChildrenCount`: Pass the number of children for the row. If the children count is not available for some reason, but there are some children, return -1. From 5d4220df42a5cfb690cb4780d523a00956939193 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 9 Sep 2024 18:01:04 +0500 Subject: [PATCH 08/36] Add support for groupingValueGetter --- .../data-grid/server-side-data/row-grouping.md | 6 ++++++ .../useGridDataSourceRowGroupingPreProcessors.ts | 16 +++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index a100906d4373..6dc61224fe22 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -35,6 +35,12 @@ The following demo showcases how to implement server-side row grouping with a cu {{"demo": "ServerSideRowGroupingDataGrid.js", "bg": "inline"}} +:::warning +In the case of complex data, you might need to implement a custom `colDef.groupingValueGetter` to get the grouping value for the row which will then be passed in `groupKeys` parameter when calling `getRows`. + +If you use it, make sure the backend understands the `groupKeys` parameters as provided by the `groupingValueGetter` to get the grouping value for the subsequent children rows. +::: + ## Error handling If an error occurs during a `getRows` call, the Data Grid will display an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts index 565675ed25fd..5a6c32111f7f 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -47,12 +47,18 @@ export const useGridDataSourceRowGroupingPreProcessors = ( }); apiRef.current.caches.rowGrouping.rulesOnLastRowTreeCreation = groupingRules; - // TODO: Accomodate `groupingValueGetter` - - const parentPath = (params.updates as GridRowsPartialUpdates).groupKeys ?? []; - const getRowTreeBuilderNode = (rowId: GridRowId) => { - const leafKey = getGroupKey(params.dataRowIdToModelLookup[rowId]); + const parentPath = (params.updates as GridRowsPartialUpdates).groupKeys ?? []; + const row = params.dataRowIdToModelLookup[rowId]; + const groupingRule = groupingRules[parentPath.length]; + const groupingValueGetter = groupingRule?.groupingValueGetter; + const leafKey = + groupingValueGetter?.( + row[groupingRule.field] as never, + row, + columnsLookup[groupingRule.field], + apiRef, + ) ?? getGroupKey(params.dataRowIdToModelLookup[rowId]); return { id: rowId, path: [...parentPath, leafKey ?? rowId.toString()].map((key, i) => ({ From d1712def8f6f81c77ca0df27306c131dc3ab030b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 12 Sep 2024 14:13:45 +0500 Subject: [PATCH 09/36] Typo --- docs/data/data-grid/server-side-data/row-grouping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 6dc61224fe22..ea008cb5d4eb 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -13,7 +13,7 @@ Just like [tree data](/x/react-data-grid/server-side-data/tree-data/), you need - `getGroupKey`: Pass the group key for the row. - `getChildrenCount`: Pass the number of children for the row. If the children count is not available for some reason, but there are some children, return -1. -Apart from the `groupKeys` parameter, the `getRows` callback would recieve an additional parameter `groupFields` which refers to the current `rowGroupingModel`, use it on the server side to extract the appropriate data chunk for a specific `getRows` call. +Apart from the `groupKeys` parameter, the `getRows` callback would recieve an additional parameter `groupFields` which refers to the current `rowGroupingModel`, use it on the server-side to extract the appropriate data chunk for a specific `getRows` call. ```tsx const customDataSource: GridDataSource = { From 66ab0c81257ee093290db05739360e52bf9e6ebc Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 12 Sep 2024 14:51:53 +0500 Subject: [PATCH 10/36] Support rows with missing groups on mock server --- .../src/hooks/serverUtils.ts | 44 ++++++++++++------- .../src/hooks/features/dataSource/cache.ts | 1 + 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 2b032a31473f..b17b5f393957 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -324,7 +324,7 @@ export const loadServerRows = ( }); }; -interface ProcessTreeDataRowsResponse { +interface NestedDataRowsResponse { rows: GridRowModel[]; rootRowCount: number; } @@ -438,7 +438,7 @@ export const processTreeDataRows = ( queryOptions: ServerSideQueryOptions, serverOptions: ServerOptions, columnsWithDefaultColDef: GridColDef[], -): Promise => { +): Promise => { const { minDelay = 100, maxDelay = 300 } = serverOptions; const pathKey = 'path'; // TODO: Support filtering and cursor based pagination @@ -503,7 +503,7 @@ export const processRowGroupingRows = ( queryOptions: ServerSideQueryOptions, serverOptions: ServerOptions, columnsWithDefaultColDef: GridColDef[], -): Promise => { +): Promise => { const { minDelay = 100, maxDelay = 300 } = serverOptions; const pathKey = 'path'; @@ -523,21 +523,30 @@ export const processRowGroupingRows = ( const pathsToAutogenerate = new Set(); let rowsWithPaths = rows; + const rowsWithMissingGroups: GridValidRowModel[] = []; // add paths and generate parent rows based on `groupFields` const groupFields = queryOptions.groupFields; if (groupFields.length > 0) { - rowsWithPaths = rows.map((row) => { + rowsWithPaths = rows.reduce((acc, row) => { const partialPath = groupFields.map((field) => row[field] as string); - partialPath.forEach((_, index) => { + for (let index = 0; index < partialPath.length; index += 1) { + const value = partialPath[index]; + if (value === undefined) { + if (index === 0) { + rowsWithMissingGroups.push({ ...row, group: false }); + } + return acc; + } const parentPath = partialPath.slice(0, index + 1); const strigifiedPath = parentPath.join(','); if (!pathsToAutogenerate.has(strigifiedPath)) { pathsToAutogenerate.add(strigifiedPath); } - }); - return { ...row, path: [...partialPath, ''] }; - }); + } + acc.push({ ...row, path: [...partialPath, ''] }); + return acc; + }, []); } else { rowsWithPaths = rows.map((row) => ({ ...row, path: [''] })); } @@ -553,7 +562,7 @@ export const processRowGroupingRows = ( // apply plain filtering const filteredRows = getTreeDataFilteredRows( - [...autogeneratedRows, ...rowsWithPaths], + [...autogeneratedRows, ...rowsWithPaths, ...rowsWithMissingGroups], queryOptions.filterModel, columnsWithDefaultColDef, ) as GridValidRowModel[]; @@ -562,11 +571,13 @@ export const processRowGroupingRows = ( const rootRows = findTreeDataRowChildren(filteredRows, []); const rootRowCount = rootRows.length; - // find direct children referring to the `parentPath` - const childRows = - queryOptions.groupKeys.length > 0 - ? findTreeDataRowChildren(filteredRows, queryOptions.groupKeys) - : rootRows; + let filteredRowsWithMissingGroups: GridValidRowModel[] = []; + let childRows = rootRows; + if (queryOptions.groupKeys.length === 0) { + filteredRowsWithMissingGroups = filteredRows.filter(({ group }) => group === false); + } else { + childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys); + } let childRowsWithDescendantCounts = childRows.map((row) => { const descendants = findTreeDataRowChildren( @@ -581,9 +592,10 @@ export const processRowGroupingRows = ( }); if (queryOptions.sortModel) { - // apply sorting const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef); - childRowsWithDescendantCounts = [...childRowsWithDescendantCounts].sort(rowComparator); + const sortedMissingGroups = [...filteredRowsWithMissingGroups].sort(rowComparator); + const sortedChildRows = [...childRowsWithDescendantCounts].sort(rowComparator); + childRowsWithDescendantCounts = [...sortedMissingGroups, ...sortedChildRows]; } if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) { diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index dde8cad3d39f..5645235abf01 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -15,6 +15,7 @@ function getKey(params: GridGetRowsParams) { params.filterModel, params.sortModel, params.groupKeys, + params.groupFields, ]); } From f07252367b0127c105111d04b36020db9b10a155 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 12 Sep 2024 16:06:20 +0500 Subject: [PATCH 11/36] Fix argos' regression --- packages/x-data-grid-generator/src/hooks/useMockServer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index c7aaeeea9d16..19bd5a49d1a8 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -208,8 +208,8 @@ export const useMockServer = ( const fetchRows = React.useCallback( async (requestUrl: string): Promise => { - if ((!data && !rowGroupingData.rows && isRowGrouping) || !requestUrl) { - sendEmptyResponse(); + if (!(requestUrl && (data || (isRowGrouping && rowGroupingData.rows)))) { + return sendEmptyResponse(); } const params = decodeParams(requestUrl); const verbose = serverOptions?.verbose ?? true; @@ -239,7 +239,7 @@ export const useMockServer = ( if (isTreeData) { const { rows, rootRowCount } = await processTreeDataRows( - data!.rows, + data?.rows ?? [], params, serverOptionsWithDefault, columnsWithDefaultColDef, @@ -263,7 +263,7 @@ export const useMockServer = ( }; } else { const { returnedRows, nextCursor, totalRowCount } = await loadServerRows( - data!.rows, + data?.rows ?? [], { ...params, ...params.paginationModel }, serverOptionsWithDefault, columnsWithDefaultColDef, From 7580c46fb4e098389f08110d4a188d9e95c81b24 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 12 Sep 2024 16:17:10 +0500 Subject: [PATCH 12/36] Lint --- .../server-side-data/ServerSideRowGroupingErrorHandling.js | 2 +- .../server-side-data/ServerSideRowGroupingErrorHandling.tsx | 2 +- .../server-side-data/ServerSideRowGroupingGroupExpansion.js | 2 +- .../server-side-data/ServerSideRowGroupingGroupExpansion.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js index d15f3f945ce6..c124f5b6ec8e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js @@ -11,7 +11,7 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten } from '@mui/material/styles'; -export default function ServerSideRowGroupingDataGrid() { +export default function ServerSideRowGroupingErrorHandling() { const apiRef = useGridApiRef(); const [rootError, setRootError] = React.useState(); const [childrenError, setChildrenError] = React.useState(); diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx index 43f1f9af282f..be81da802609 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx @@ -12,7 +12,7 @@ import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles'; -export default function ServerSideRowGroupingDataGrid() { +export default function ServerSideRowGroupingErrorHandling() { const apiRef = useGridApiRef(); const [rootError, setRootError] = React.useState(); const [childrenError, setChildrenError] = React.useState(); diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js index 92f85ae62540..bdb42747fc07 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js @@ -7,7 +7,7 @@ import { import { useMockServer } from '@mui/x-data-grid-generator'; import Button from '@mui/material/Button'; -export default function ServerSideRowGroupingDataGrid() { +export default function ServerSideRowGroupingGroupExpansion() { const apiRef = useGridApiRef(); const { fetchRows, columns } = useMockServer({ diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx index e7ac226ff0da..64aa58a45d8e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx @@ -8,7 +8,7 @@ import { import { useMockServer } from '@mui/x-data-grid-generator'; import Button from '@mui/material/Button'; -export default function ServerSideRowGroupingDataGrid() { +export default function ServerSideRowGroupingGroupExpansion() { const apiRef = useGridApiRef(); const { fetchRows, columns } = useMockServer({ From 3780ddde58e900b19dbf53fd06c7df46f8647a9b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 12 Sep 2024 16:22:09 +0500 Subject: [PATCH 13/36] Update sidebar --- docs/data/pages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 803b1794e7b7..79a6006dce0e 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -123,8 +123,9 @@ const pages: MuiPage[] = [ pathname: '/x/react-data-grid/server-side-data-group', title: 'Server-side data', plan: 'pro', + newFeature: true, children: [ - { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, + { pathname: '/x/react-data-grid/server-side-data', title: 'Overview', plan: 'pro' }, { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' }, { pathname: '/x/react-data-grid/server-side-data/lazy-loading', From c92106d78f8f222c438b59f18278b37bfed267fe Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 12 Sep 2024 19:33:45 +0500 Subject: [PATCH 14/36] Improve some documentation --- .../server-side-data/row-grouping.md | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index ea008cb5d4eb..3cf56a282a82 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -13,8 +13,6 @@ Just like [tree data](/x/react-data-grid/server-side-data/tree-data/), you need - `getGroupKey`: Pass the group key for the row. - `getChildrenCount`: Pass the number of children for the row. If the children count is not available for some reason, but there are some children, return -1. -Apart from the `groupKeys` parameter, the `getRows` callback would recieve an additional parameter `groupFields` which refers to the current `rowGroupingModel`, use it on the server-side to extract the appropriate data chunk for a specific `getRows` call. - ```tsx const customDataSource: GridDataSource = { getRows: async (params) => { @@ -31,28 +29,48 @@ const customDataSource: GridDataSource = { }; ``` -The following demo showcases how to implement server-side row grouping with a custom data source. +In addition to `groupKeys`, the `getRows` callback receives a `groupFields` parameter. This corresponds to the current `rowGroupingModel`. Use `groupFields` on the server to group the data for each `getRows` call. + +```tsx +const getRows: async (params) => { + const urlParams = new URLSearchParams({ + // Example: JSON.stringify(['20th Century Fox', 'James Cameron']) + groupKeys: JSON.stringify(params.groupKeys), + // Example: JSON.stringify(['company', 'director']) + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + // Server should group the data based on `groupFields` and + // extract the rows for the nested level based on `groupKeys` + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; +} +``` {{"demo": "ServerSideRowGroupingDataGrid.js", "bg": "inline"}} :::warning -In the case of complex data, you might need to implement a custom `colDef.groupingValueGetter` to get the grouping value for the row which will then be passed in `groupKeys` parameter when calling `getRows`. +For complex data, consider using `colDef.groupingValueGetter` to extract the grouping value. This value will be passed in the `groupKeys` parameter when `getRows` is called. -If you use it, make sure the backend understands the `groupKeys` parameters as provided by the `groupingValueGetter` to get the grouping value for the subsequent children rows. +Ensure your backend can interpret the `groupKeys` parameter generated by `colDef.groupingValueGetter` to retrieve grouping values for child rows. ::: ## Error handling If an error occurs during a `getRows` call, the Data Grid will display an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. -The following example demonstrates error handling by displaying both a toast notification and the default error message in the grouping cell. For simplicity, caching has been disabled in this example. +This example shows error handling with toast notifications and default error messages in grouping cells. Caching is disabled for simplicity. {{"demo": "ServerSideRowGroupingErrorHandling.js", "bg": "inline"}} ## Group expansion -The group expansion works in a similar way to the [data source tree data](/x/react-data-grid/server-side-data/tree-data/#group-expansion). -The following demo expands all the groups using `defaultGroupingExpansionDepth='-1'`. +The group expansion works similar to the [data source tree data](/x/react-data-grid/server-side-data/tree-data/#group-expansion). +The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all the groups. {{"demo": "ServerSideRowGroupingGroupExpansion.js", "bg": "inline"}} From 43c6e1620ecba48154f72435a13792e9c1da99e7 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 12 Sep 2024 19:34:14 +0500 Subject: [PATCH 15/36] Enable client side aggregation --- .../src/hooks/features/rowGrouping/createGroupingColDef.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index ae1ba6fbddeb..603c44e43c5c 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -41,7 +41,6 @@ const DATA_SOURCE_GROUPING_COL_DEF_FORCED_PROPERTIES: Pick< // TODO: Support these features on the grouping column(s) filterable: false, sortable: false, - aggregable: false, }; /** From 0160a0a6b3ccabbb6b39ea94fb9c913d034280d4 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 13 Sep 2024 16:57:11 +0500 Subject: [PATCH 16/36] Cleanup --- packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts index edf98ff7ba52..d006eb177b34 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -109,7 +109,6 @@ export const insertDataRowInTree = ({ isAutoGenerated: false, groupingKey: key, groupingField: field, - // groupingField: fieldWithDefaultValue, children: [], childrenFromPath: {}, childrenExpanded: false, From 4e832b351a95311dab26d3c90e457f954779e578 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 13 Sep 2024 18:59:30 +0500 Subject: [PATCH 17/36] Docs updates --- docs/data/data-grid/row-grouping/row-grouping.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/row-grouping/row-grouping.md b/docs/data/data-grid/row-grouping/row-grouping.md index 8b387aa8d42b..022abb960190 100644 --- a/docs/data/data-grid/row-grouping/row-grouping.md +++ b/docs/data/data-grid/row-grouping/row-grouping.md @@ -252,6 +252,10 @@ Use the `setRowChildrenExpansion` method on `apiRef` to programmatically set the {{"demo": "RowGroupingSetChildrenExpansion.js", "bg": "inline", "defaultCodeOpen": false}} +:::warning +The `apiRef.current.setRowChildrenExpansion` method is not compatible with the [server-side tree data](/x/react-data-grid/server-side-data/tree-data/) and [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/). Use `apiRef.current.unstable_dataSource.fetchRows` instead. +::: + ### Customize grouping cell indent To change the default cell indent, you can use the `--DataGrid-cellOffsetMultiplier` CSS variable: @@ -280,8 +284,8 @@ If you are rendering leaves with the `leafField` property of `groupingColDef`, t You can force the filtering to be applied on another grouping criteria with the `mainGroupingCriteria` property of `groupingColDef` -:::warning -This feature is not yet compatible with `sortingMode = "server"` and `filteringMode = "server"`. +:::info +For server-side lazy fetching, filtering, sorting the row grouped data, use the [server side row grouping](/x/react-data-grid/server-side-data/row-grouping/) feature. ::: {{"demo": "RowGroupingFilteringSingleGroupingColDef.js", "bg": "inline", "defaultCodeOpen": false}} @@ -332,6 +336,10 @@ const rows = apiRef.current.getRowGroupChildren({ {{"demo": "RowGroupingGetRowGroupChildren.js", "bg": "inline", "defaultCodeOpen": false}} +:::warning +The `apiRef.current.getRowGroupChildren` method is not compatible with the [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/) since all the rows might not be available to get at a given instance. +::: + ## Row group panel 🚧 :::warning From 5f6f0c6319d4b16dded4a3f93181d005c94049d4 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 13 Sep 2024 20:53:35 +0500 Subject: [PATCH 18/36] Add commodities demo --- .../ServerSideRowGroupingFullDataGrid.js | 74 ++++++++++++ .../ServerSideRowGroupingFullDataGrid.tsx | 75 ++++++++++++ ...verSideRowGroupingFullDataGrid.tsx.preview | 13 ++ .../server-side-data/row-grouping.md | 6 + .../x-data-grid-generator/src/hooks/index.ts | 3 +- .../src/hooks/serverUtils.ts | 7 -- .../src/hooks/useMockServer.ts | 114 +++++++++++++----- .../src/hooks/useMovieData.ts | 3 + .../src/hooks/useQuery.ts | 8 +- .../src/components/cell/GridBooleanCell.tsx | 4 + 10 files changed, 271 insertions(+), 36 deletions(-) create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx create mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js new file mode 100644 index 000000000000..e4e6f3fcc02d --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js @@ -0,0 +1,74 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingFullDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns, loadNewData } = useMockServer({ + rowGrouping: true, + rowLength: 20000, + dataSet: 'Commodity', + maxColumns: 20, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['commodity', 'status'], + }, + columns: { + columnVisibilityModel: { + id: false, + }, + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx new file mode 100644 index 000000000000..db710391dc4b --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingFullDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns, loadNewData } = useMockServer({ + rowGrouping: true, + rowLength: 20000, + dataSet: 'Commodity', + maxColumns: 20, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['commodity', 'status'], + }, + columns: { + columnVisibilityModel: { + id: false, + }, + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx.preview new file mode 100644 index 000000000000..681709b06ab3 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx.preview @@ -0,0 +1,13 @@ + + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 3cf56a282a82..5d12e1151d55 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -74,6 +74,12 @@ The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all the g {{"demo": "ServerSideRowGroupingGroupExpansion.js", "bg": "inline"}} +## Demo + +In the following demo, use the auto generated data based on the `Commodities` dataset to simulate the server-side row grouping. + +{{"demo": "ServerSideRowGroupingFullDataGrid.js", "bg": "inline"}} + ## API - [DataGrid](/x/api/data-grid/data-grid/) diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts index 84dd7368aea4..223d6170c94b 100644 --- a/packages/x-data-grid-generator/src/hooks/index.ts +++ b/packages/x-data-grid-generator/src/hooks/index.ts @@ -1,6 +1,7 @@ export * from './useDemoData'; export * from './useBasicDemoData'; -export * from './useMovieData'; +export { useMovieData } from './useMovieData'; +export type { Movie } from './useMovieData'; export * from './useQuery'; export * from './useMockServer'; export { loadServerRows } from './serverUtils'; diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index b17b5f393957..5ee55feabc01 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -10,7 +10,6 @@ import { GridValidRowModel, } from '@mui/x-data-grid-pro'; import { GridStateColDef } from '@mui/x-data-grid-pro/internals'; -import { UseDemoDataOptions } from './useDemoData'; import { randomInt } from '../services/random-generator'; export interface FakeServerResponse { @@ -56,12 +55,6 @@ export interface ServerSideQueryOptions { groupFields?: string[]; } -export const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { - dataSet: 'Commodity', - rowLength: 100, - maxColumns: 6, -}; - declare const DISABLE_CHANCE_RANDOM: any; export const disableDelay = typeof DISABLE_CHANCE_RANDOM !== 'undefined' && DISABLE_CHANCE_RANDOM; diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index 19bd5a49d1a8..fa22350c8e11 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -9,25 +9,24 @@ import { GridInitialState, GridColumnVisibilityModel, } from '@mui/x-data-grid-pro'; -import { - UseDemoDataOptions, - getColumnsFromOptions, - extrapolateSeed, - deepFreeze, -} from './useDemoData'; +import { extrapolateSeed, deepFreeze } from './useDemoData'; +import { getCommodityColumns } from '../columns/commodities.columns'; +import { getEmployeeColumns } from '../columns/employees.columns'; import { GridColDefGenerator } from '../services/gridColDefGenerator'; import { getRealGridData, GridDemoData } from '../services/real-data-service'; -import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; +import { + addTreeDataOptionsToDemoData, + AddPathToDemoDataOptions, +} from '../services/tree-data-generator'; import { loadServerRows, processTreeDataRows, processRowGroupingRows, - DEFAULT_DATASET_OPTIONS, DEFAULT_SERVER_OPTIONS, } from './serverUtils'; import type { ServerOptions } from './serverUtils'; import { randomInt } from '../services'; -import { useMovieData } from './useMovieData'; +import { getMovieRows, getMovieColumns } from './useMovieData'; const dataCache = new LRUCache({ max: 10, @@ -45,6 +44,66 @@ type UseMockServerResponse = { loadNewData: () => void; }; +type DataSet = 'Commodity' | 'Employee' | 'Movies'; + +interface UseMockServerOptions { + dataSet: DataSet; + /** + * Has no effect when DataSet='Movies' + */ + rowLength: number; + maxColumns?: number; + visibleFields?: string[]; + editable?: boolean; + treeData?: AddPathToDemoDataOptions; + rowGrouping?: boolean; +} + +interface GridMockServerData { + rows: GridRowModel[]; + columns: GridColDefGenerator[] | GridColDef[]; + initialState?: GridInitialState; +} + +interface ColumnsOptions + extends Pick {} + +const GET_DEFAULT_DATASET_OPTIONS: (isRowGrouping: boolean) => UseMockServerOptions = ( + isRowGrouping, +) => ({ + dataSet: isRowGrouping ? 'Movies' : 'Commodity', + rowLength: isRowGrouping ? getMovieRows().length : 100, + maxColumns: 6, +}); + +const getColumnsFromOptions = (options: ColumnsOptions): GridColDefGenerator[] | GridColDef[] => { + let columns; + + switch (options.dataSet) { + case 'Commodity': + columns = getCommodityColumns(options.editable); + break; + case 'Employee': + columns = getEmployeeColumns(); + break; + case 'Movies': + columns = getMovieColumns(); + break; + default: + throw new Error('Unknown dataset'); + } + + if (options.visibleFields) { + columns = columns.map((col) => + options.visibleFields?.includes(col.field) ? col : { ...col, hide: true }, + ); + } + if (options.maxColumns) { + columns = columns.slice(0, options.maxColumns); + } + return columns; +}; + function decodeParams(url: string): GridGetRowsParams { const params = new URL(url).searchParams; const decodedParams = {} as any; @@ -85,23 +144,25 @@ function sendEmptyResponse() { } export const useMockServer = ( - dataSetOptions?: Partial & Partial<{ rowGrouping?: boolean }>, + dataSetOptions?: Partial, serverOptions?: ServerOptions & { verbose?: boolean }, shouldRequestsFail?: boolean, ): UseMockServerResponse => { - const [data, setData] = React.useState(); + const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); - const rowGroupingData = useMovieData(); - React.useEffect(() => { if (shouldRequestsFail !== undefined) { shouldRequestsFailRef.current = shouldRequestsFail; } }, [shouldRequestsFail]); - const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; + const isRowGrouping = dataSetOptions?.rowGrouping ?? false; + + const options = { ...GET_DEFAULT_DATASET_OPTIONS(isRowGrouping), ...dataSetOptions }; + + const isTreeData = options.treeData?.groupingField != null; const columns = React.useMemo(() => { return getColumnsFromOptions({ @@ -117,16 +178,13 @@ export const useMockServer = ( [columns, options.treeData?.groupingField], ); - const isTreeData = options.treeData?.groupingField != null; - const isRowGrouping = dataSetOptions?.rowGrouping; - const columnsWithDefaultColDef: GridColDef[] = React.useMemo( () => - (isRowGrouping ? rowGroupingData.columns : columns).map((column) => ({ + columns.map((column) => ({ ...defaultColDef[column.type || 'string'], ...column, })), - [columns, rowGroupingData.columns, isRowGrouping], + [columns], ); const getGroupKey = React.useMemo(() => { @@ -145,9 +203,6 @@ export const useMockServer = ( }, [isTreeData]); React.useEffect(() => { - if (dataSetOptions?.rowGrouping) { - return undefined; - } const cacheKey = `${options.dataSet}-${options.rowLength}-${index}-${options.maxColumns}`; // Cache to allow fast switch between the JavaScript and TypeScript version @@ -158,6 +213,13 @@ export const useMockServer = ( return undefined; } + if (options.dataSet === 'Movies') { + const rowsData = { rows: getMovieRows(), columns }; + setData(rowsData); + dataCache.set(cacheKey, rowsData); + return undefined; + } + let active = true; (async () => { @@ -203,12 +265,11 @@ export const useMockServer = ( options.dataSet, options.maxColumns, index, - dataSetOptions?.rowGrouping, ]); const fetchRows = React.useCallback( async (requestUrl: string): Promise => { - if (!(requestUrl && (data || (isRowGrouping && rowGroupingData.rows)))) { + if (!requestUrl || !data?.rows) { return sendEmptyResponse(); } const params = decodeParams(requestUrl); @@ -251,7 +312,7 @@ export const useMockServer = ( }; } else if (isRowGrouping) { const { rows, rootRowCount } = await processRowGroupingRows( - rowGroupingData.rows, + data?.rows ?? [], params, serverOptionsWithDefault, columnsWithDefaultColDef, @@ -280,7 +341,6 @@ export const useMockServer = ( }, [ data, - rowGroupingData.rows, serverOptions?.verbose, serverOptions?.minDelay, serverOptions?.maxDelay, @@ -293,7 +353,7 @@ export const useMockServer = ( return { columns: columnsWithDefaultColDef, - initialState: isRowGrouping ? {} : initialState, + initialState: options.dataSet === 'Movies' ? {} : initialState, getGroupKey, getChildrenCount, fetchRows, diff --git a/packages/x-data-grid-generator/src/hooks/useMovieData.ts b/packages/x-data-grid-generator/src/hooks/useMovieData.ts index 7820a7471fde..b9b196716839 100644 --- a/packages/x-data-grid-generator/src/hooks/useMovieData.ts +++ b/packages/x-data-grid-generator/src/hooks/useMovieData.ts @@ -546,6 +546,9 @@ const ROWS: GridRowModel[] = [ }, ]; +export const getMovieColumns = (): GridColDef[] => COLUMNS; +export const getMovieRows = (): GridRowModel[] => ROWS; + export const useMovieData = () => { return { rows: ROWS, diff --git a/packages/x-data-grid-generator/src/hooks/useQuery.ts b/packages/x-data-grid-generator/src/hooks/useQuery.ts index 62ae140bcdcd..5387a7e1f485 100644 --- a/packages/x-data-grid-generator/src/hooks/useQuery.ts +++ b/packages/x-data-grid-generator/src/hooks/useQuery.ts @@ -7,9 +7,15 @@ import { getColumnsFromOptions, getInitialState, } from './useDemoData'; -import { DEFAULT_DATASET_OPTIONS, DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils'; +import { DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils'; import type { ServerOptions, QueryOptions, PageInfo } from './serverUtils'; +const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 6, +}; + export const createFakeServer = ( dataSetOptions?: Partial, serverOptions?: ServerOptions, diff --git a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx index 543e199229c2..836a900e79ca 100644 --- a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx @@ -54,6 +54,10 @@ function GridBooleanCellRaw(props: GridBooleanCellProps) { [rootProps.slots.booleanCellFalseIcon, rootProps.slots.booleanCellTrueIcon, value], ); + if (value === undefined) { + return null; + } + return ( Date: Sat, 14 Sep 2024 23:04:59 +0500 Subject: [PATCH 19/36] Add toolbar to the demo --- .../ServerSideRowGroupingFullDataGrid.js | 11 ++++++++++- .../ServerSideRowGroupingFullDataGrid.tsx | 11 ++++++++++- .../ServerSideRowGroupingFullDataGrid.tsx.preview | 13 ------------- 3 files changed, 20 insertions(+), 15 deletions(-) delete mode 100644 docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx.preview diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js index e4e6f3fcc02d..f3de3888206c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js @@ -3,6 +3,7 @@ import { DataGridPremium, useGridApiRef, useKeepGroupedColumnsHidden, + GridToolbar, } from '@mui/x-data-grid-premium'; import { useMockServer } from '@mui/x-data-grid-generator'; import Button from '@mui/material/Button'; @@ -58,12 +59,20 @@ export default function ServerSideRowGroupingFullDataGrid() {
-
+
-
+
Regenerate Data - -
- -
\ No newline at end of file From b1a955ca1555e965ba6c321b6de5bc9aef927211 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sat, 14 Sep 2024 23:10:51 +0500 Subject: [PATCH 20/36] Remove in-progress icon --- docs/data/data-grid/server-side-data/row-grouping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 5d12e1151d55..fc4bbcc233c0 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -2,7 +2,7 @@ title: React Server-side row grouping --- -# Data Grid - Server-side row grouping [](/x/introduction/licensing/#pro-plan 'Pro plan')🚧 +# Data Grid - Server-side row grouping [](/x/introduction/licensing/#pro-plan 'Pro plan')

Lazy-loaded row grouping with server-side data source.

From 7dd206dd2e1b5a637e30aac6faccd81bf66c096b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 16 Sep 2024 12:37:22 +0500 Subject: [PATCH 21/36] Add info section to docs --- docs/data/data-grid/row-grouping/row-grouping.md | 8 ++++---- .../ServerSideRowGroupingFullDataGrid.tsx | 2 +- docs/data/data-grid/server-side-data/row-grouping.md | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/data/data-grid/row-grouping/row-grouping.md b/docs/data/data-grid/row-grouping/row-grouping.md index 022abb960190..014aaeb32965 100644 --- a/docs/data/data-grid/row-grouping/row-grouping.md +++ b/docs/data/data-grid/row-grouping/row-grouping.md @@ -11,6 +11,10 @@ In the following example, movies are grouped based on their production `company` {{"demo": "RowGroupingBasicExample.js", "bg": "inline", "defaultCodeOpen": false}} +:::info +If you are looking for row grouping on the server-side, see [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/). +::: + ## Grouping criteria ### Initialize the row grouping @@ -284,10 +288,6 @@ If you are rendering leaves with the `leafField` property of `groupingColDef`, t You can force the filtering to be applied on another grouping criteria with the `mainGroupingCriteria` property of `groupingColDef` -:::info -For server-side lazy fetching, filtering, sorting the row grouped data, use the [server side row grouping](/x/react-data-grid/server-side-data/row-grouping/) feature. -::: - {{"demo": "RowGroupingFilteringSingleGroupingColDef.js", "bg": "inline", "defaultCodeOpen": false}} ### Multiple grouping columns diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx index f2caa504ce9e..0cd2b762cf08 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx @@ -72,7 +72,7 @@ export default function ServerSideRowGroupingFullDataGrid() { slotProps={{ toolbar: { showQuickFilter: true, - } + }, }} groupingColDef={{ width: 250, diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index fc4bbcc233c0..e07ba909b676 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -8,6 +8,10 @@ title: React Server-side row grouping To dynamically load row grouping data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview](/x/react-data-grid/server-side-data/). +:::info +If you are looking for row grouping on the client-side, see [client-side row grouping](/x/react-data-grid/row-grouping/). +::: + Just like [tree data](/x/react-data-grid/server-side-data/tree-data/), you need to pass some additional properties to support the row grouping feature: - `getGroupKey`: Pass the group key for the row. From d3aad071b268a38bfda8b9edc4a1684db5411daa Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 16 Sep 2024 14:44:50 +0500 Subject: [PATCH 22/36] Docs improvement --- docs/data/data-grid/server-side-data/row-grouping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index e07ba909b676..046392cf3ff7 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -12,7 +12,7 @@ To dynamically load row grouping data from the server, including lazy-loading of If you are looking for row grouping on the client-side, see [client-side row grouping](/x/react-data-grid/row-grouping/). ::: -Just like [tree data](/x/react-data-grid/server-side-data/tree-data/), you need to pass some additional properties to support the row grouping feature: +Similar to the [tree data](/x/react-data-grid/server-side-data/tree-data/), you need to pass some additional properties to enable the data source row grouping feature: - `getGroupKey`: Pass the group key for the row. - `getChildrenCount`: Pass the number of children for the row. If the children count is not available for some reason, but there are some children, return -1. From ed631de34bd04ce6a7425eaecc3f0cc5781bc83f Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 16 Sep 2024 14:46:10 +0500 Subject: [PATCH 23/36] Support expansion using keyboard --- .../GridDataSourceGroupingCriteriaCell.tsx | 11 ----------- .../features/rowGrouping/useGridRowGrouping.tsx | 17 +++++++---------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx index 9581245a7279..0b13ab44edfe 100644 --- a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx +++ b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx @@ -73,16 +73,6 @@ function GridGroupingCriteriaCellIcon(props: GridGroupingCriteriaCellIconProps) ); } - // TODO: Add back `handleKeyDown` - // const handleKeyDown = (event: React.KeyboardEvent) => { - // if (event.key === ' ') { - // // We call event.stopPropagation to avoid unfolding the row and also scrolling to bottom - // // TODO: Remove and add a check inside useGridKeyboardNavigation - // event.stopPropagation(); - // } - // apiRef.current.publishEvent('cellKeyDown', props, event); - // }; - return descendantCount > 0 ? ( , ) => { apiRef.current.registerControlState({ @@ -73,9 +74,6 @@ export const useGridRowGrouping = ( changeEvent: 'rowGroupingModelChange', }); - /** - * API METHODS - */ const setRowGroupingModel = React.useCallback( (model) => { const currentModel = gridRowGroupingModelSelector(apiRef); @@ -212,9 +210,6 @@ export const useGridRowGrouping = ( useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); - /** - * EVENTS - */ const handleCellKeyDown = React.useCallback>( (params, event) => { const cellParams = apiRef.current.getCellParams(params.id, params.field); @@ -233,10 +228,15 @@ export const useGridRowGrouping = ( return; } + if (props.unstable_dataSource && !params.rowNode.childrenExpanded) { + apiRef.current.unstable_dataSource.fetchRows(params.id); + return; + } + apiRef.current.setRowChildrenExpansion(params.id, !params.rowNode.childrenExpanded); } }, - [apiRef, props.rowGroupingColumnMode], + [apiRef, props.rowGroupingColumnMode, props.unstable_dataSource], ); const checkGroupingColumnsModelDiff = React.useCallback< @@ -268,9 +268,6 @@ export const useGridRowGrouping = ( useGridApiEventHandler(apiRef, 'columnsChange', checkGroupingColumnsModelDiff); useGridApiEventHandler(apiRef, 'rowGroupingModelChange', checkGroupingColumnsModelDiff); - /** - * EFFECTS - */ React.useEffect(() => { if (props.rowGroupingModel !== undefined) { apiRef.current.setRowGroupingModel(props.rowGroupingModel); From 17f9f332a4da34fafd73ce2ae6999e839cf8fa58 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 16 Sep 2024 16:29:49 +0500 Subject: [PATCH 24/36] Update --- .../src/hooks/features/dataSource/gridDataSourceSelector.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index 6f7a2eb97038..de6bbfa7092c 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -36,7 +36,6 @@ export const gridGetRowsParamsSelector = createSelector( gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, - gridRowGroupingModelSelector, gridRowGroupingSanitizedModelSelector, (filterModel, sortModel, paginationModel, rowGroupingModel) => { return { From e1f7a6a40f1cfb790c0aab7eca1491617188265e Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 20 Sep 2024 07:12:42 +0500 Subject: [PATCH 25/36] Rename constants --- .../rowGrouping/createGroupingColDef.tsx | 18 ++++++++--------- .../rowGrouping/gridRowGroupingUtils.ts | 10 +++++----- ...eGridDataSourceRowGroupingPreProcessors.ts | 14 ++++++------- .../rowGrouping/useGridRowGrouping.tsx | 4 ++-- .../useGridRowGroupingPreProcessors.ts | 20 +++++++++---------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index 603c44e43c5c..348f7196bc5c 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -17,8 +17,8 @@ import { GridGroupingColumnLeafCell } from '../../../components/GridGroupingColu import { getRowGroupingFieldFromGroupingCriteria, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, - ROW_GROUPING_STRATEGY, - DATA_SOURCE_ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DEFAULT, + ROW_GROUPING_STRATEGY_DATA_SOURCE, } from './gridRowGroupingUtils'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; @@ -135,7 +135,7 @@ interface CreateGroupingColDefMonoCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; - strategy?: typeof ROW_GROUPING_STRATEGY | typeof DATA_SOURCE_ROW_GROUPING_STRATEGY; + strategy?: typeof ROW_GROUPING_STRATEGY_DEFAULT | typeof ROW_GROUPING_STRATEGY_DATA_SOURCE; } /** @@ -146,14 +146,14 @@ export const createGroupingColDefForOneGroupingCriteria = ({ groupedByColDef, groupingCriteria, colDefOverride, - strategy = ROW_GROUPING_STRATEGY, + strategy = ROW_GROUPING_STRATEGY_DEFAULT, }: CreateGroupingColDefMonoCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; const CriteriaCell = - strategy === ROW_GROUPING_STRATEGY + strategy === ROW_GROUPING_STRATEGY_DEFAULT ? GridGroupingCriteriaCell : GridDataSourceGroupingCriteriaCell; @@ -266,7 +266,7 @@ interface CreateGroupingColDefSeveralCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; - strategy?: typeof ROW_GROUPING_STRATEGY | typeof DATA_SOURCE_ROW_GROUPING_STRATEGY; + strategy?: typeof ROW_GROUPING_STRATEGY_DEFAULT | typeof ROW_GROUPING_STRATEGY_DATA_SOURCE; } /** @@ -277,14 +277,14 @@ export const createGroupingColDefForAllGroupingCriteria = ({ columnsLookup, rowGroupingModel, colDefOverride, - strategy = ROW_GROUPING_STRATEGY, + strategy = ROW_GROUPING_STRATEGY_DEFAULT, }: CreateGroupingColDefSeveralCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; const CriteriaCell = - strategy === ROW_GROUPING_STRATEGY + strategy === ROW_GROUPING_STRATEGY_DEFAULT ? GridGroupingCriteriaCell : GridDataSourceGroupingCriteriaCell; @@ -371,7 +371,7 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, - ...(strategy === ROW_GROUPING_STRATEGY + ...(strategy === ROW_GROUPING_STRATEGY_DEFAULT ? GROUPING_COL_DEF_FORCED_PROPERTIES : DATA_SOURCE_GROUPING_COL_DEF_FORCED_PROPERTIES), }; diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts index d581dd53316d..d2890ff52fbd 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts @@ -29,8 +29,8 @@ import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; export const GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD = '__row_group_by_columns_group__'; -export const ROW_GROUPING_STRATEGY = 'grouping-columns'; -export const DATA_SOURCE_ROW_GROUPING_STRATEGY = 'data-source-grouping-columns'; +export const ROW_GROUPING_STRATEGY_DEFAULT = 'grouping-columns'; +export const ROW_GROUPING_STRATEGY_DATA_SOURCE = 'grouping-columns-data-source'; export const getRowGroupingFieldFromGroupingCriteria = (groupingCriteria: string | null) => { if (groupingCriteria === null) { @@ -180,11 +180,11 @@ export const filterRowTreeFromGroupingColumns = ( export const getColDefOverrides = ( groupingColDefProp: DataGridPremiumProcessedProps['groupingColDef'], fields: string[], - strategy?: typeof ROW_GROUPING_STRATEGY | typeof DATA_SOURCE_ROW_GROUPING_STRATEGY, + strategy?: typeof ROW_GROUPING_STRATEGY_DEFAULT | typeof ROW_GROUPING_STRATEGY_DATA_SOURCE, ) => { if (typeof groupingColDefProp === 'function') { return groupingColDefProp({ - groupingName: strategy ?? ROW_GROUPING_STRATEGY, + groupingName: strategy ?? ROW_GROUPING_STRATEGY_DEFAULT, fields, }); } @@ -214,7 +214,7 @@ export const setStrategyAvailability = ( }; } - const strategy = dataSource ? DATA_SOURCE_ROW_GROUPING_STRATEGY : ROW_GROUPING_STRATEGY; + const strategy = dataSource ? ROW_GROUPING_STRATEGY_DATA_SOURCE : ROW_GROUPING_STRATEGY_DEFAULT; privateApiRef.current.setStrategyAvailability('rowTree', strategy, isAvailable); }; diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts index 5a6c32111f7f..7c2602580134 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -11,7 +11,7 @@ import { GridRowsPartialUpdates, } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; -import { DATA_SOURCE_ROW_GROUPING_STRATEGY, getGroupingRules } from './gridRowGroupingUtils'; +import { ROW_GROUPING_STRATEGY_DATA_SOURCE, getGroupingRules } from './gridRowGroupingUtils'; import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; @@ -75,7 +75,7 @@ export const useGridDataSourceRowGroupingPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_ROW_GROUPING_STRATEGY, + groupingName: ROW_GROUPING_STRATEGY_DATA_SOURCE, }); } @@ -94,7 +94,7 @@ export const useGridDataSourceRowGroupingPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_ROW_GROUPING_STRATEGY, + groupingName: ROW_GROUPING_STRATEGY_DATA_SOURCE, }); }, [ @@ -119,20 +119,20 @@ export const useGridDataSourceRowGroupingPreProcessors = ( useGridRegisterStrategyProcessor( apiRef, - DATA_SOURCE_ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DATA_SOURCE, 'rowTreeCreation', createRowTreeForRowGrouping, ); useGridRegisterStrategyProcessor( apiRef, - DATA_SOURCE_ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DATA_SOURCE, 'filtering', filterRows, ); - useGridRegisterStrategyProcessor(apiRef, DATA_SOURCE_ROW_GROUPING_STRATEGY, 'sorting', sortRows); + useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY_DATA_SOURCE, 'sorting', sortRows); useGridRegisterStrategyProcessor( apiRef, - DATA_SOURCE_ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DATA_SOURCE, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx index f7ad680f829d..5eb20aff2870 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx @@ -19,7 +19,7 @@ import { import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { getRowGroupingFieldFromGroupingCriteria, - ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DEFAULT, isGroupingColumn, mergeStateWithRowGroupingModel, setStrategyAvailability, @@ -258,7 +258,7 @@ export const useGridRowGrouping = ( // Refresh the row tree creation strategy processing // TODO: Add a clean way to re-run a strategy processing without publishing a private event - if (apiRef.current.getActiveStrategy('rowTree') === ROW_GROUPING_STRATEGY) { + if (apiRef.current.getActiveStrategy('rowTree') === ROW_GROUPING_STRATEGY_DEFAULT) { apiRef.current.publishEvent('activeStrategyProcessorChange', 'rowTreeCreation'); } } diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts index 2576fca62c96..42bc8b221215 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts @@ -31,8 +31,8 @@ import { import { filterRowTreeFromGroupingColumns, getColDefOverrides, - ROW_GROUPING_STRATEGY, - DATA_SOURCE_ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DEFAULT, + ROW_GROUPING_STRATEGY_DATA_SOURCE, isGroupingColumn, setStrategyAvailability, getCellGroupingCriteria, @@ -59,8 +59,8 @@ export const useGridRowGroupingPreProcessors = ( } const strategy = props.unstable_dataSource - ? DATA_SOURCE_ROW_GROUPING_STRATEGY - : ROW_GROUPING_STRATEGY; + ? ROW_GROUPING_STRATEGY_DATA_SOURCE + : ROW_GROUPING_STRATEGY_DEFAULT; const groupingColDefProp = props.groupingColDef; @@ -191,7 +191,7 @@ export const useGridRowGroupingPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY, + groupingName: ROW_GROUPING_STRATEGY_DEFAULT, }); } @@ -205,7 +205,7 @@ export const useGridRowGroupingPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY, + groupingName: ROW_GROUPING_STRATEGY_DEFAULT, }); }, [apiRef, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault], @@ -242,15 +242,15 @@ export const useGridRowGroupingPreProcessors = ( useGridRegisterPipeProcessor(apiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DEFAULT, 'rowTreeCreation', createRowTreeForRowGrouping, ); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'filtering', filterRows); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'sorting', sortRows); + useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY_DEFAULT, 'filtering', filterRows); + useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY_DEFAULT, 'sorting', sortRows); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY, + ROW_GROUPING_STRATEGY_DEFAULT, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); From 900e127914a9ac310c73e5805c2c0615497f43f2 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Sat, 28 Sep 2024 17:36:57 +0500 Subject: [PATCH 26/36] Make the selector memoized --- .../hooks/features/rowGrouping/useGridRowGrouping.tsx | 9 +++++++++ .../features/dataSource/gridDataSourceSelector.ts | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx index 5eb20aff2870..e1259879cbbe 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx @@ -74,6 +74,9 @@ export const useGridRowGrouping = ( changeEvent: 'rowGroupingModelChange', }); + /* + * API METHODS + */ const setRowGroupingModel = React.useCallback( (model) => { const currentModel = gridRowGroupingModelSelector(apiRef); @@ -210,6 +213,9 @@ export const useGridRowGrouping = ( useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); + /* + * EVENTS + */ const handleCellKeyDown = React.useCallback>( (params, event) => { const cellParams = apiRef.current.getCellParams(params.id, params.field); @@ -268,6 +274,9 @@ export const useGridRowGrouping = ( useGridApiEventHandler(apiRef, 'columnsChange', checkGroupingColumnsModelDiff); useGridApiEventHandler(apiRef, 'rowGroupingModelChange', checkGroupingColumnsModelDiff); + /* + * EFFECTS + */ React.useEffect(() => { if (props.rowGroupingModel !== undefined) { apiRef.current.setRowGroupingModel(props.rowGroupingModel); diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index de6bbfa7092c..812b5b47bd56 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -6,7 +6,11 @@ import { gridColumnLookupSelector, GridRowId, } from '@mui/x-data-grid'; -import { createSelector, createSelectorV8 } from '@mui/x-data-grid/internals'; +import { + createSelector, + createSelectorMemoized, + createSelectorV8, +} from '@mui/x-data-grid/internals'; import { GridStatePro } from '../../../models/gridStatePro'; const computeStartEnd = (paginationModel: GridPaginationModel) => { @@ -15,6 +19,8 @@ const computeStartEnd = (paginationModel: GridPaginationModel) => { return { start, end }; }; +// The following code duplicates the row grouping model selector from the premium package +// to be used in the `gridGetRowsParamsSelector` type GridStateProWithRowGrouping = GridStatePro & { rowGrouping?: { model: string[]; @@ -26,7 +32,7 @@ const EMPTY_ARRAY: string[] = []; const gridRowGroupingModelSelector = (state: GridStateProWithRowGrouping) => state.rowGrouping?.model ?? EMPTY_ARRAY; -export const gridRowGroupingSanitizedModelSelector = createSelector( +export const gridRowGroupingSanitizedModelSelector = createSelectorMemoized( gridRowGroupingModelSelector, gridColumnLookupSelector, (model, columnsLookup) => model.filter((field) => !!columnsLookup[field]), From 509d264e3e92450be53097bd06684e456c34de50 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 11 Oct 2024 12:56:06 +0500 Subject: [PATCH 27/36] Resolve a few comments --- .../server-side-data/row-grouping.md | 8 ++--- .../data-grid/server-side-data/tree-data.md | 29 +++++++++++++++++-- .../rowGrouping/createGroupingColDef.tsx | 13 +++++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 046392cf3ff7..c7532c8695e5 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -6,7 +6,7 @@ title: React Server-side row grouping

Lazy-loaded row grouping with server-side data source.

-To dynamically load row grouping data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview](/x/react-data-grid/server-side-data/). +To dynamically load row grouping data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as mentioned in the [overview](/x/react-data-grid/server-side-data/) section. :::info If you are looking for row grouping on the client-side, see [client-side row grouping](/x/react-data-grid/row-grouping/). @@ -14,8 +14,8 @@ If you are looking for row grouping on the client-side, see [client-side row gro Similar to the [tree data](/x/react-data-grid/server-side-data/tree-data/), you need to pass some additional properties to enable the data source row grouping feature: -- `getGroupKey`: Pass the group key for the row. -- `getChildrenCount`: Pass the number of children for the row. If the children count is not available for some reason, but there are some children, return -1. +- `getGroupKey()`: Returns the group key for the row. +- `getChildrenCount()`: Returns the number of children for the row. If the children count is not available for some reason, but there are some children, returns `-1`. ```tsx const customDataSource: GridDataSource = { @@ -33,7 +33,7 @@ const customDataSource: GridDataSource = { }; ``` -In addition to `groupKeys`, the `getRows` callback receives a `groupFields` parameter. This corresponds to the current `rowGroupingModel`. Use `groupFields` on the server to group the data for each `getRows` call. +In addition to `groupKeys`, the `getRows()` callback receives a `groupFields` parameter. This corresponds to the current `rowGroupingModel`. Use `groupFields` on the server to group the data for each `getRows()` call. ```tsx const getRows: async (params) => { diff --git a/docs/data/data-grid/server-side-data/tree-data.md b/docs/data/data-grid/server-side-data/tree-data.md index 8e77fe0542bf..9c840429542d 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -8,8 +8,14 @@ title: React Server-side tree data To dynamically load tree data from the server, including lazy-loading of children, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview section](/x/react-data-grid/server-side-data/). -The data source also requires some additional props to handle tree data, namely `getGroupKey` and `getChildrenCount`. -If the children count is not available for some reason, but there are some children, `getChildrenCount` should return `-1`. +:::info +If you are looking for tree data on the client-side, see [client-side tree data](/x/react-data-grid/tree-data/). +::: + +The data source also requires some additional props to handle tree data: + +- `getGroupKey()`: Returns the group key for the row. +- `getChildrenCount()`: Returns the number of children for the row. If the children count is not available for some reason, but there are some children, returns `-1`. ```tsx const customDataSource: GridDataSource = { @@ -27,6 +33,25 @@ const customDataSource: GridDataSource = { }; ``` +Apart from the other parameters like `filterModel`, `sortModel`, and `paginationModel`, the `getRows()` callback receives a `groupKeys` parameter. This corresponds to the keys provided for each nested level in `getGroupKey()`. Use `groupKeys` on the server to fetch the rows for each nested level. + +```tsx +const getRows: async (params) => { + const urlParams = new URLSearchParams({ + // Example: JSON.stringify(['Billy Houston', 'Lora Dean']) + groupKeys: JSON.stringify(params.groupKeys), + }); + const getRowsResponse = await fetchRows( + // Server should extract the rows for the nested level based on `groupKeys` + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; +} +``` + The following tree data example supports filtering, sorting, and pagination on the server. It also caches the data by default. diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index 348f7196bc5c..6d1ab3c670e9 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -28,16 +28,19 @@ const GROUPING_COL_DEF_DEFAULT_PROPERTIES: Omit = { disableReorder: true, }; -const GROUPING_COL_DEF_FORCED_PROPERTIES: Pick = { +const GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT: Pick< + GridColDef, + 'type' | 'editable' | 'groupable' +> = { editable: false, groupable: false, }; -const DATA_SOURCE_GROUPING_COL_DEF_FORCED_PROPERTIES: Pick< +const GROUPING_COL_DEF_FORCED_PROPERTIES_DATA_SOURCE: Pick< GridColDef, 'type' | 'editable' | 'groupable' | 'filterable' | 'sortable' | 'aggregable' > = { - ...GROUPING_COL_DEF_FORCED_PROPERTIES, + ...GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT, // TODO: Support these features on the grouping column(s) filterable: false, sortable: false, @@ -372,8 +375,8 @@ export const createGroupingColDefForAllGroupingCriteria = ({ const forcedProperties: Pick = { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, ...(strategy === ROW_GROUPING_STRATEGY_DEFAULT - ? GROUPING_COL_DEF_FORCED_PROPERTIES - : DATA_SOURCE_GROUPING_COL_DEF_FORCED_PROPERTIES), + ? GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT + : GROUPING_COL_DEF_FORCED_PROPERTIES_DATA_SOURCE), }; return { From 07448560f350cf8f97fb9e75f8b7b09a4892dc4b Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 11 Oct 2024 13:12:09 +0500 Subject: [PATCH 28/36] Update documentation Co-authored-by: Armin Mehinovic <4390250+arminmeh@users.noreply.github.com> Signed-off-by: Bilal Shafi --- docs/data/data-grid/server-side-data/row-grouping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index c7532c8695e5..25b6044f3f43 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -58,14 +58,14 @@ const getRows: async (params) => { {{"demo": "ServerSideRowGroupingDataGrid.js", "bg": "inline"}} :::warning -For complex data, consider using `colDef.groupingValueGetter` to extract the grouping value. This value will be passed in the `groupKeys` parameter when `getRows` is called. +For complex data, consider using `colDef.groupingValueGetter` to extract the grouping value. This value is passed in the `groupKeys` parameter when `getRows` is called. Ensure your backend can interpret the `groupKeys` parameter generated by `colDef.groupingValueGetter` to retrieve grouping values for child rows. ::: ## Error handling -If an error occurs during a `getRows` call, the Data Grid will display an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. +If an error occurs during a `getRows` call, the Data Grid displays an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. This example shows error handling with toast notifications and default error messages in grouping cells. Caching is disabled for simplicity. From 5f929f191de57042cd36513ae584e7396ee9cba7 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 11 Oct 2024 16:47:20 +0500 Subject: [PATCH 29/36] Fix types + lint --- .../server-side-data/ServerSideRowGroupingErrorHandling.js | 2 +- .../server-side-data/ServerSideRowGroupingErrorHandling.tsx | 2 +- .../src/hooks/features/rowGrouping/createGroupingColDef.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js index c124f5b6ec8e..79ec59e3fdba 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js @@ -72,7 +72,7 @@ export default function ServerSideRowGroupingErrorHandling() { control={ setShouldRequestsFail(e.target.checked)} + onChange={(event) => setShouldRequestsFail(event.target.checked)} /> } label="Make the requests fail" diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx index be81da802609..614ad3c30edb 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx @@ -73,7 +73,7 @@ export default function ServerSideRowGroupingErrorHandling() { control={ setShouldRequestsFail(e.target.checked)} + onChange={(event) => setShouldRequestsFail(event.target.checked)} /> } label="Make the requests fail" diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index 6d1ab3c670e9..c97e512aea1e 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -245,7 +245,7 @@ export const createGroupingColDefForOneGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: getRowGroupingFieldFromGroupingCriteria(groupingCriteria), - ...GROUPING_COL_DEF_FORCED_PROPERTIES, + ...GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT, }; return { From 8c623d04ee4a7fd1016cd1422316faaa9b3f4010 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 11 Oct 2024 17:46:26 +0500 Subject: [PATCH 30/36] Use pipe processing for row grouping params --- .../ServerSideRowGroupingErrorHandling.js | 6 ++-- .../ServerSideRowGroupingErrorHandling.tsx | 6 ++-- .../rowGrouping/useGridRowGrouping.tsx | 11 +++++++ .../dataSource/gridDataSourceSelector.ts | 30 ++----------------- .../features/dataSource/useGridDataSource.ts | 30 ++++++++----------- .../pipeProcessing/gridPipeProcessingApi.ts | 2 ++ 6 files changed, 34 insertions(+), 51 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js index 79ec59e3fdba..793347a26301 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js @@ -82,12 +82,12 @@ export default function ServerSideRowGroupingErrorHandling() { { + unstable_onDataSourceError={(error, params) => { if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); + setRootError(error.message); } else { setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, + `${error.message} (Requested level: ${params.groupKeys.join(' > ')})`, ); } }} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx index 614ad3c30edb..621b74b052f4 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx @@ -83,12 +83,12 @@ export default function ServerSideRowGroupingErrorHandling() { { + unstable_onDataSourceError={(error, params) => { if (!params.groupKeys || params.groupKeys.length === 0) { - setRootError(e.message); + setRootError(error.message); } else { setChildrenError( - `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`, + `${error.message} (Requested level: ${params.groupKeys.join(' > ')})`, ); } }} diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx index e1259879cbbe..454f7045f75c 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx @@ -166,6 +166,16 @@ export const useGridRowGrouping = ( [props.disableRowGrouping], ); + const addGetRowsParams = React.useCallback>( + (params) => { + return { + ...params, + groupFields: gridRowGroupingModelSelector(apiRef), + }; + }, + [apiRef], + ); + const stateExportPreProcessing = React.useCallback>( (prevState, context) => { const rowGroupingModelToExport = gridRowGroupingModelSelector(apiRef); @@ -210,6 +220,7 @@ export const useGridRowGrouping = ( ); useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuButtons); + useGridRegisterPipeProcessor(apiRef, 'getRowsParams', addGetRowsParams); useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts index 812b5b47bd56..09e2d34c9099 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -3,14 +3,9 @@ import { gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, - gridColumnLookupSelector, GridRowId, } from '@mui/x-data-grid'; -import { - createSelector, - createSelectorMemoized, - createSelectorV8, -} from '@mui/x-data-grid/internals'; +import { createSelector, createSelectorV8 } from '@mui/x-data-grid/internals'; import { GridStatePro } from '../../../models/gridStatePro'; const computeStartEnd = (paginationModel: GridPaginationModel) => { @@ -19,34 +14,13 @@ const computeStartEnd = (paginationModel: GridPaginationModel) => { return { start, end }; }; -// The following code duplicates the row grouping model selector from the premium package -// to be used in the `gridGetRowsParamsSelector` -type GridStateProWithRowGrouping = GridStatePro & { - rowGrouping?: { - model: string[]; - }; -}; - -const EMPTY_ARRAY: string[] = []; - -const gridRowGroupingModelSelector = (state: GridStateProWithRowGrouping) => - state.rowGrouping?.model ?? EMPTY_ARRAY; - -export const gridRowGroupingSanitizedModelSelector = createSelectorMemoized( - gridRowGroupingModelSelector, - gridColumnLookupSelector, - (model, columnsLookup) => model.filter((field) => !!columnsLookup[field]), -); - export const gridGetRowsParamsSelector = createSelector( gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, - gridRowGroupingSanitizedModelSelector, - (filterModel, sortModel, paginationModel, rowGroupingModel) => { + (filterModel, sortModel, paginationModel) => { return { groupKeys: [], - groupFields: rowGroupingModel, paginationModel, sortModel, filterModel, diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 2ce02d4cf780..10da6df7c74a 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -11,11 +11,7 @@ import { import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { - gridGetRowsParamsSelector, - gridDataSourceErrorsSelector, - gridRowGroupingSanitizedModelSelector, -} from './gridDataSourceSelector'; +import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces'; import { runIfServerMode, NestedDataManager, RequestStatus } from './utils'; import { GridDataSourceCache } from '../../../models'; @@ -63,7 +59,6 @@ export const useGridDataSource = ( () => new NestedDataManager(apiRef), ).current; const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector); - const sanitizedRowGroupingModel = useGridSelector(apiRef, gridRowGroupingSanitizedModelSelector); const scheduledGroups = React.useRef(0); const onError = props.unstable_onDataSourceError; @@ -90,7 +85,10 @@ export const useGridDataSource = ( apiRef.current.resetDataSourceState(); } - const fetchParams = gridGetRowsParamsSelector(apiRef); + const fetchParams = { + ...gridGetRowsParamsSelector(apiRef), + ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), + }; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); @@ -127,7 +125,8 @@ export const useGridDataSource = ( const fetchRowChildren = React.useCallback( async (id) => { - if (!props.treeData && sanitizedRowGroupingModel.length === 0) { + const pipedParams = apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}); + if (!props.treeData && (pipedParams.groupFields?.length ?? 0) === 0) { nestedDataManager.clearPendingRequest(id); return; } @@ -143,7 +142,11 @@ export const useGridDataSource = ( return; } - const fetchParams = { ...gridGetRowsParamsSelector(apiRef), groupKeys: rowNode.path }; + const fetchParams = { + ...gridGetRowsParamsSelector(apiRef), + ...pipedParams, + groupKeys: rowNode.path, + }; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); @@ -191,14 +194,7 @@ export const useGridDataSource = ( nestedDataManager.setRequestSettled(id); } }, - [ - nestedDataManager, - onError, - apiRef, - props.treeData, - props.unstable_dataSource?.getRows, - sanitizedRowGroupingModel, - ], + [nestedDataManager, onError, apiRef, props.treeData, props.unstable_dataSource?.getRows], ); const setChildrenLoading = React.useCallback( diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts index 5e065f1a572a..4191af59ab3d 100644 --- a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts +++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts @@ -20,6 +20,7 @@ import { import { GridRowEntry, GridRowId } from '../../../models/gridRows'; import { GridHydrateRowsValue } from '../../features/rows/gridRowsInterfaces'; import { GridPreferencePanelsValue } from '../../features/preferencesPanel'; +import { GridGetRowsParams } from '../../../models/gridDataSource'; export type GridPipeProcessorGroup = keyof GridPipeProcessingLookup; @@ -29,6 +30,7 @@ export interface GridPipeProcessingLookup { context: GridColDef; }; exportState: { value: GridInitialStateCommunity; context: GridExportStateParams }; + getRowsParams: { value: Partial }; hydrateColumns: { value: GridHydrateColumnsValue; }; From c60b75754c79bbca7bd4f77c893c44071f52e63c Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 11 Oct 2024 18:08:46 +0500 Subject: [PATCH 31/36] Update a line of doc Signed-off-by: Bilal Shafi --- docs/data/data-grid/server-side-data/tree-data.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/data/data-grid/server-side-data/tree-data.md b/docs/data/data-grid/server-side-data/tree-data.md index 9c840429542d..d5c725ac456e 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -33,7 +33,8 @@ const customDataSource: GridDataSource = { }; ``` -Apart from the other parameters like `filterModel`, `sortModel`, and `paginationModel`, the `getRows()` callback receives a `groupKeys` parameter. This corresponds to the keys provided for each nested level in `getGroupKey()`. Use `groupKeys` on the server to fetch the rows for each nested level. +Like the other parameters such as `filterModel`, `sortModel`, and `paginationModel`, the `getRows()` callback receives a `groupKeys` parameter that corresponds to the keys provided for each nested level in `getGroupKey()`. +Use `groupKeys` on the server to extract the rows for a given nested level. ```tsx const getRows: async (params) => { From ba0ab94ca9cafe886f95d486b68881a00849bc6d Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 18 Oct 2024 16:26:40 +0500 Subject: [PATCH 32/36] Use enums --- .../rowGrouping/createGroupingColDef.tsx | 17 +++++++------ .../rowGrouping/gridRowGroupingUtils.ts | 12 ++++++---- ...eGridDataSourceRowGroupingPreProcessors.ts | 19 ++++++--------- .../rowGrouping/useGridRowGrouping.tsx | 4 ++-- .../useGridRowGroupingPreProcessors.ts | 19 +++++++-------- ...useGridDataSourceTreeDataPreProcessors.tsx | 24 +++++++------------ .../features/treeData/gridTreeDataUtils.ts | 5 +++- .../treeData/useGridTreeDataPreProcessors.tsx | 23 +++++++++++------- .../x-data-grid-pro/src/internals/index.ts | 1 - 9 files changed, 60 insertions(+), 64 deletions(-) diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index c97e512aea1e..194c62efa521 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -17,8 +17,7 @@ import { GridGroupingColumnLeafCell } from '../../../components/GridGroupingColu import { getRowGroupingFieldFromGroupingCriteria, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, - ROW_GROUPING_STRATEGY_DEFAULT, - ROW_GROUPING_STRATEGY_DATA_SOURCE, + RowGroupingStrategy, } from './gridRowGroupingUtils'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; @@ -138,7 +137,7 @@ interface CreateGroupingColDefMonoCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; - strategy?: typeof ROW_GROUPING_STRATEGY_DEFAULT | typeof ROW_GROUPING_STRATEGY_DATA_SOURCE; + strategy?: RowGroupingStrategy; } /** @@ -149,14 +148,14 @@ export const createGroupingColDefForOneGroupingCriteria = ({ groupedByColDef, groupingCriteria, colDefOverride, - strategy = ROW_GROUPING_STRATEGY_DEFAULT, + strategy = RowGroupingStrategy.Default, }: CreateGroupingColDefMonoCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; const CriteriaCell = - strategy === ROW_GROUPING_STRATEGY_DEFAULT + strategy === RowGroupingStrategy.Default ? GridGroupingCriteriaCell : GridDataSourceGroupingCriteriaCell; @@ -269,7 +268,7 @@ interface CreateGroupingColDefSeveralCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; - strategy?: typeof ROW_GROUPING_STRATEGY_DEFAULT | typeof ROW_GROUPING_STRATEGY_DATA_SOURCE; + strategy?: RowGroupingStrategy; } /** @@ -280,14 +279,14 @@ export const createGroupingColDefForAllGroupingCriteria = ({ columnsLookup, rowGroupingModel, colDefOverride, - strategy = ROW_GROUPING_STRATEGY_DEFAULT, + strategy = RowGroupingStrategy.Default, }: CreateGroupingColDefSeveralCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; const CriteriaCell = - strategy === ROW_GROUPING_STRATEGY_DEFAULT + strategy === RowGroupingStrategy.Default ? GridGroupingCriteriaCell : GridDataSourceGroupingCriteriaCell; @@ -374,7 +373,7 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, - ...(strategy === ROW_GROUPING_STRATEGY_DEFAULT + ...(strategy === RowGroupingStrategy.Default ? GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT : GROUPING_COL_DEF_FORCED_PROPERTIES_DATA_SOURCE), }; diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts index d2890ff52fbd..9d1e3ddcafae 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts @@ -29,8 +29,10 @@ import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; export const GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD = '__row_group_by_columns_group__'; -export const ROW_GROUPING_STRATEGY_DEFAULT = 'grouping-columns'; -export const ROW_GROUPING_STRATEGY_DATA_SOURCE = 'grouping-columns-data-source'; +export enum RowGroupingStrategy { + Default = 'grouping-columns', + DataSource = 'grouping-columns-data-source', +} export const getRowGroupingFieldFromGroupingCriteria = (groupingCriteria: string | null) => { if (groupingCriteria === null) { @@ -180,11 +182,11 @@ export const filterRowTreeFromGroupingColumns = ( export const getColDefOverrides = ( groupingColDefProp: DataGridPremiumProcessedProps['groupingColDef'], fields: string[], - strategy?: typeof ROW_GROUPING_STRATEGY_DEFAULT | typeof ROW_GROUPING_STRATEGY_DATA_SOURCE, + strategy?: RowGroupingStrategy, ) => { if (typeof groupingColDefProp === 'function') { return groupingColDefProp({ - groupingName: strategy ?? ROW_GROUPING_STRATEGY_DEFAULT, + groupingName: strategy ?? RowGroupingStrategy.Default, fields, }); } @@ -214,7 +216,7 @@ export const setStrategyAvailability = ( }; } - const strategy = dataSource ? ROW_GROUPING_STRATEGY_DATA_SOURCE : ROW_GROUPING_STRATEGY_DEFAULT; + const strategy = dataSource ? RowGroupingStrategy.DataSource : RowGroupingStrategy.Default; privateApiRef.current.setStrategyAvailability('rowTree', strategy, isAvailable); }; diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts index 7c2602580134..07ad4690d059 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -11,7 +11,7 @@ import { GridRowsPartialUpdates, } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; -import { ROW_GROUPING_STRATEGY_DATA_SOURCE, getGroupingRules } from './gridRowGroupingUtils'; +import { getGroupingRules, RowGroupingStrategy } from './gridRowGroupingUtils'; import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; @@ -75,7 +75,7 @@ export const useGridDataSourceRowGroupingPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY_DATA_SOURCE, + groupingName: RowGroupingStrategy.DataSource, }); } @@ -94,7 +94,7 @@ export const useGridDataSourceRowGroupingPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY_DATA_SOURCE, + groupingName: RowGroupingStrategy.DataSource, }); }, [ @@ -119,20 +119,15 @@ export const useGridDataSourceRowGroupingPreProcessors = ( useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY_DATA_SOURCE, + RowGroupingStrategy.DataSource, 'rowTreeCreation', createRowTreeForRowGrouping, ); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.DataSource, 'filtering', filterRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.DataSource, 'sorting', sortRows); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY_DATA_SOURCE, - 'filtering', - filterRows, - ); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY_DATA_SOURCE, 'sorting', sortRows); - useGridRegisterStrategyProcessor( - apiRef, - ROW_GROUPING_STRATEGY_DATA_SOURCE, + RowGroupingStrategy.DataSource, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx index 454f7045f75c..24bff5926a7b 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx @@ -19,7 +19,7 @@ import { import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { getRowGroupingFieldFromGroupingCriteria, - ROW_GROUPING_STRATEGY_DEFAULT, + RowGroupingStrategy, isGroupingColumn, mergeStateWithRowGroupingModel, setStrategyAvailability, @@ -275,7 +275,7 @@ export const useGridRowGrouping = ( // Refresh the row tree creation strategy processing // TODO: Add a clean way to re-run a strategy processing without publishing a private event - if (apiRef.current.getActiveStrategy('rowTree') === ROW_GROUPING_STRATEGY_DEFAULT) { + if (apiRef.current.getActiveStrategy('rowTree') === RowGroupingStrategy.Default) { apiRef.current.publishEvent('activeStrategyProcessorChange', 'rowTreeCreation'); } } diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts index 42bc8b221215..ac43f038695a 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts @@ -31,8 +31,7 @@ import { import { filterRowTreeFromGroupingColumns, getColDefOverrides, - ROW_GROUPING_STRATEGY_DEFAULT, - ROW_GROUPING_STRATEGY_DATA_SOURCE, + RowGroupingStrategy, isGroupingColumn, setStrategyAvailability, getCellGroupingCriteria, @@ -59,8 +58,8 @@ export const useGridRowGroupingPreProcessors = ( } const strategy = props.unstable_dataSource - ? ROW_GROUPING_STRATEGY_DATA_SOURCE - : ROW_GROUPING_STRATEGY_DEFAULT; + ? RowGroupingStrategy.DataSource + : RowGroupingStrategy.Default; const groupingColDefProp = props.groupingColDef; @@ -191,7 +190,7 @@ export const useGridRowGroupingPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY_DEFAULT, + groupingName: RowGroupingStrategy.Default, }); } @@ -205,7 +204,7 @@ export const useGridRowGroupingPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY_DEFAULT, + groupingName: RowGroupingStrategy.Default, }); }, [apiRef, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault], @@ -242,15 +241,15 @@ export const useGridRowGroupingPreProcessors = ( useGridRegisterPipeProcessor(apiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY_DEFAULT, + RowGroupingStrategy.Default, 'rowTreeCreation', createRowTreeForRowGrouping, ); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY_DEFAULT, 'filtering', filterRows); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY_DEFAULT, 'sorting', sortRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.Default, 'filtering', filterRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.Default, 'sorting', sortRows); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY_DEFAULT, + RowGroupingStrategy.Default, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx index c09bc45b8ded..0c6b841f939d 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx @@ -34,8 +34,7 @@ import { } from '../../../utils/tree/models'; import { updateRowTree } from '../../../utils/tree/updateRowTree'; import { getVisibleRowsLookup } from '../../../utils/tree/utils'; - -const DATA_SOURCE_TREE_DATA_STRATEGY = 'dataSourceTreeData'; +import { TreeDataStrategy } from '../treeData/gridTreeDataUtils'; export const useGridDataSourceTreeDataPreProcessors = ( privateApiRef: React.MutableRefObject, @@ -53,7 +52,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, props.treeData && props.unstable_dataSource ? () => true : () => false, ); }, [privateApiRef, props.treeData, props.unstable_dataSource]); @@ -64,7 +63,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( let colDefOverride: GridGroupingColDefOverride | null | undefined; if (typeof groupingColDefProp === 'function') { const params: GridGroupingColDefOverrideParams = { - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, fields: [], }; @@ -171,7 +170,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, onDuplicatePath, }); } @@ -187,7 +186,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, }); }, [ @@ -212,25 +211,20 @@ export const useGridDataSourceTreeDataPreProcessors = ( useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'rowTreeCreation', createRowTreeForTreeData, ); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'filtering', filterRows, ); + useGridRegisterStrategyProcessor(privateApiRef, TreeDataStrategy.DataSource, 'sorting', sortRows); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, - 'sorting', - sortRows, - ); - useGridRegisterStrategyProcessor( - privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts index ee8224e89b08..6161b317a427 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts @@ -20,7 +20,10 @@ interface FilterRowTreeFromTreeDataParams { apiRef: React.MutableRefObject; } -export const TREE_DATA_STRATEGY = 'tree-data'; +export enum TreeDataStrategy { + Default = 'tree-data', + DataSource = 'tree-data-source', +} /** * A node is visible if one of the following criteria is met: diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx index 74c0fcfcda38..6a5753c9eb60 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx @@ -19,7 +19,7 @@ import { GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES, } from './gridTreeDataGroupColDef'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { filterRowTreeFromTreeData, TREE_DATA_STRATEGY } from './gridTreeDataUtils'; +import { filterRowTreeFromTreeData, TreeDataStrategy } from './gridTreeDataUtils'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { GridGroupingColDefOverride, @@ -52,7 +52,7 @@ export const useGridTreeDataPreProcessors = ( const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, props.treeData && !props.unstable_dataSource ? () => true : () => false, ); }, [privateApiRef, props.treeData, props.unstable_dataSource]); @@ -63,7 +63,7 @@ export const useGridTreeDataPreProcessors = ( let colDefOverride: GridGroupingColDefOverride | null | undefined; if (typeof groupingColDefProp === 'function') { const params: GridGroupingColDefOverrideParams = { - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, fields: [], }; @@ -158,7 +158,7 @@ export const useGridTreeDataPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, onDuplicatePath, }); } @@ -173,7 +173,7 @@ export const useGridTreeDataPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, }); }, [props.getTreeDataPath, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault], @@ -211,15 +211,20 @@ export const useGridTreeDataPreProcessors = ( useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( privateApiRef, - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, 'rowTreeCreation', createRowTreeForTreeData, ); - useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'filtering', filterRows); - useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'sorting', sortRows); useGridRegisterStrategyProcessor( privateApiRef, - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, + 'filtering', + filterRows, + ); + useGridRegisterStrategyProcessor(privateApiRef, TreeDataStrategy.Default, 'sorting', sortRows); + useGridRegisterStrategyProcessor( + privateApiRef, + TreeDataStrategy.Default, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index c0f1ad66c029..ed619bb0cc9e 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -33,7 +33,6 @@ export { useGridRowReorder } from '../hooks/features/rowReorder/useGridRowReorde export { useGridRowReorderPreProcessors } from '../hooks/features/rowReorder/useGridRowReorderPreProcessors'; export { useGridTreeData } from '../hooks/features/treeData/useGridTreeData'; export { useGridTreeDataPreProcessors } from '../hooks/features/treeData/useGridTreeDataPreProcessors'; -export { TREE_DATA_STRATEGY } from '../hooks/features/treeData/gridTreeDataUtils'; export { useGridRowPinning, rowPinningStateInitializer, From 7491df518497d563f76f1056c8e38dbb76e25cdf Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 21 Oct 2024 18:29:47 +0500 Subject: [PATCH 33/36] Adjust as per #15013 --- .../src/components/GridDataSourceGroupingCriteriaCell.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx index 0b13ab44edfe..0f7f159ee84c 100644 --- a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx +++ b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; -import Badge from '@mui/material/Badge'; import { useGridPrivateApiContext } from '@mui/x-data-grid-pro/internals'; import { useGridSelector, @@ -86,9 +85,9 @@ function GridGroupingCriteriaCellIcon(props: GridGroupingCriteriaCellIconProps) {...rootProps?.slotProps?.baseIconButton} > - + - +
) : null; From d849c385fc405d485175c9104eb10ebe6638670e Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 24 Oct 2024 15:29:01 +0500 Subject: [PATCH 34/36] Reduce row size in the commodity demo --- .../server-side-data/ServerSideRowGroupingFullDataGrid.js | 2 +- .../server-side-data/ServerSideRowGroupingFullDataGrid.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js index f3de3888206c..7db1ea09c9c3 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js @@ -13,7 +13,7 @@ export default function ServerSideRowGroupingFullDataGrid() { const { fetchRows, columns, loadNewData } = useMockServer({ rowGrouping: true, - rowLength: 20000, + rowLength: 1000, dataSet: 'Commodity', maxColumns: 20, }); diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx index 0cd2b762cf08..4545cf49f9e7 100644 --- a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx @@ -14,7 +14,7 @@ export default function ServerSideRowGroupingFullDataGrid() { const { fetchRows, columns, loadNewData } = useMockServer({ rowGrouping: true, - rowLength: 20000, + rowLength: 1000, dataSet: 'Commodity', maxColumns: 20, }); From 1b5bd369df398b418867f0a4bf47e73c5e402bcf Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Thu, 24 Oct 2024 17:12:08 +0500 Subject: [PATCH 35/36] Allow grouping number fields --- packages/x-data-grid-generator/src/hooks/serverUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 5ee55feabc01..80651b133af5 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -522,7 +522,7 @@ export const processRowGroupingRows = ( const groupFields = queryOptions.groupFields; if (groupFields.length > 0) { rowsWithPaths = rows.reduce((acc, row) => { - const partialPath = groupFields.map((field) => row[field] as string); + const partialPath = groupFields.map((field) => String(row[field])); for (let index = 0; index < partialPath.length; index += 1) { const value = partialPath[index]; if (value === undefined) { From 10747b1958906d5d7e88ea72de046bf8656b0ca0 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 25 Oct 2024 00:06:50 +0500 Subject: [PATCH 36/36] Remove the breaking change --- .../src/components/cell/GridBooleanCell.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx index 836a900e79ca..69b7b58032a6 100644 --- a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx @@ -2,13 +2,15 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { SvgIconProps } from '@mui/material/SvgIcon'; import composeClasses from '@mui/utils/composeClasses'; +import { useGridSelector } from '../../hooks/utils/useGridSelector'; +import { gridRowMaximumTreeDepthSelector } from '../../hooks/features/rows/gridRowsSelector'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; -import { GridRenderCellParams } from '../../models/params/gridCellParams'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; -import { DataGridProcessedProps } from '../../models/props/DataGridProps'; -import { GridColDef } from '../../models/colDef/gridColDef'; import { isAutogeneratedRowNode } from '../../hooks/features/rows/gridRowsUtils'; +import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; +import type { GridColDef } from '../../models/colDef/gridColDef'; +import type { GridRenderCellParams } from '../../models/params/gridCellParams'; type OwnerState = { classes: DataGridProcessedProps['classes'] }; @@ -49,12 +51,17 @@ function GridBooleanCellRaw(props: GridBooleanCellProps) { const ownerState = { classes: rootProps.classes }; const classes = useUtilityClasses(ownerState); + const maxDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector); + const isServerSideRowGroupingRow = + // @ts-expect-error - Access tree data prop + maxDepth > 0 && rowNode.type === 'group' && rootProps.treeData === false; + const Icon = React.useMemo( () => (value ? rootProps.slots.booleanCellTrueIcon : rootProps.slots.booleanCellFalseIcon), [rootProps.slots.booleanCellFalseIcon, rootProps.slots.booleanCellTrueIcon, value], ); - if (value === undefined) { + if (isServerSideRowGroupingRow && value === undefined) { return null; }