diff --git a/docs/data/data-grid/aggregation/aggregation.md b/docs/data/data-grid/aggregation/aggregation.md
index 0dad0c32852c2..0f9b5de14250b 100644
--- a/docs/data/data-grid/aggregation/aggregation.md
+++ b/docs/data/data-grid/aggregation/aggregation.md
@@ -12,6 +12,10 @@ The aggregated values are rendered in a footer row at the bottom of the Data Gri
{{"demo": "AggregationInitialState.js", "bg": "inline", "defaultCodeOpen": false}}
+:::info
+If you're looking for aggregation on the server side, see [Server-side data—Aggregation](/x/react-data-grid/server-side-data/aggregation/).
+:::
+
## Pass aggregation to the Data Grid
### Structure of the model
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.js
new file mode 100644
index 0000000000000..abeb33447b124
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.js
@@ -0,0 +1,70 @@
+import * as React from 'react';
+import { DataGridPremium } from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregation() {
+ const { columns, initialState, fetchRows } = useMockServer(
+ {},
+ { useCursorPagination: false },
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: JSON.stringify(params.paginationModel),
+ filterModel: JSON.stringify(params.filterModel),
+ sortModel: JSON.stringify(params.sortModel),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getAggregatedValue: (row, field) => {
+ return row[`${field}Aggregate`];
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ aggregation: {
+ model: { commodity: 'size', quantity: 'sum' },
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx
new file mode 100644
index 0000000000000..c24b761cb58a9
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx
@@ -0,0 +1,70 @@
+import * as React from 'react';
+import { DataGridPremium, GridDataSource } from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregation() {
+ const { columns, initialState, fetchRows } = useMockServer(
+ {},
+ { useCursorPagination: false },
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: JSON.stringify(params.paginationModel),
+ filterModel: JSON.stringify(params.filterModel),
+ sortModel: JSON.stringify(params.sortModel),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getAggregatedValue: (row, field) => {
+ return row[`${field}Aggregate`];
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ aggregation: {
+ model: { commodity: 'size', quantity: 'sum' },
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx.preview
new file mode 100644
index 0000000000000..aee05887a7c35
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx.preview
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.js
new file mode 100644
index 0000000000000..0b7c242658a8b
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.js
@@ -0,0 +1,71 @@
+import * as React from 'react';
+import { DataGridPremium } from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregationLazyLoading() {
+ const {
+ columns,
+ initialState: initState,
+ fetchRows,
+ } = useMockServer({}, { useCursorPagination: false });
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ filterModel: JSON.stringify(params.filterModel),
+ sortModel: JSON.stringify(params.sortModel),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ start: `${params.start}`,
+ end: `${params.end}`,
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getAggregatedValue: (row, field) => {
+ return row[`${field}Aggregate`];
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialState = React.useMemo(
+ () => ({
+ ...initState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ aggregation: {
+ model: { commodity: 'size', quantity: 'sum' },
+ },
+ }),
+ [initState],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx
new file mode 100644
index 0000000000000..eaeda275e5281
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx
@@ -0,0 +1,71 @@
+import * as React from 'react';
+import { DataGridPremium, GridDataSource } from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregationLazyLoading() {
+ const {
+ columns,
+ initialState: initState,
+ fetchRows,
+ } = useMockServer({}, { useCursorPagination: false });
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ filterModel: JSON.stringify(params.filterModel),
+ sortModel: JSON.stringify(params.sortModel),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ start: `${params.start}`,
+ end: `${params.end}`,
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getAggregatedValue: (row, field) => {
+ return row[`${field}Aggregate`];
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialState = React.useMemo(
+ () => ({
+ ...initState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ aggregation: {
+ model: { commodity: 'size', quantity: 'sum' },
+ },
+ }),
+ [initState],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx.preview
new file mode 100644
index 0000000000000..1856db25a89aa
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx.preview
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.js
new file mode 100644
index 0000000000000..a2467cefcc01a
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.js
@@ -0,0 +1,76 @@
+import * as React from 'react';
+import {
+ DataGridPremium,
+ useKeepGroupedColumnsHidden,
+ useGridApiRef,
+} from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregationRowGrouping() {
+ const apiRef = useGridApiRef();
+ const {
+ columns,
+ initialState: initState,
+ fetchRows,
+ } = useMockServer({ rowGrouping: true });
+
+ const dataSource = React.useMemo(
+ () => ({
+ 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),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getGroupKey: (row) => row.group,
+ getChildrenCount: (row) => row.descendantCount,
+ getAggregatedValue: (row, field) => row[`${field}Aggregate`],
+ }),
+ [fetchRows],
+ );
+
+ const initialState = useKeepGroupedColumnsHidden({
+ apiRef,
+ initialState: {
+ ...initState,
+ rowGrouping: {
+ model: ['company', 'director'],
+ },
+ aggregation: {
+ model: { title: 'size', gross: 'sum', year: 'max' },
+ },
+ },
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx
new file mode 100644
index 0000000000000..afca284bd7259
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx
@@ -0,0 +1,77 @@
+import * as React from 'react';
+import {
+ DataGridPremium,
+ useKeepGroupedColumnsHidden,
+ useGridApiRef,
+ GridDataSource,
+} from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregationRowGrouping() {
+ const apiRef = useGridApiRef();
+ const {
+ columns,
+ initialState: initState,
+ fetchRows,
+ } = useMockServer({ rowGrouping: true });
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ 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),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getGroupKey: (row) => row.group,
+ getChildrenCount: (row) => row.descendantCount,
+ getAggregatedValue: (row, field) => row[`${field}Aggregate`],
+ }),
+ [fetchRows],
+ );
+
+ const initialState = useKeepGroupedColumnsHidden({
+ apiRef,
+ initialState: {
+ ...initState,
+ rowGrouping: {
+ model: ['company', 'director'],
+ },
+ aggregation: {
+ model: { title: 'size', gross: 'sum', year: 'max' },
+ },
+ },
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx.preview
new file mode 100644
index 0000000000000..b2d4bf7135a06
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx.preview
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.js
new file mode 100644
index 0000000000000..3834980e55020
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.js
@@ -0,0 +1,75 @@
+import * as React from 'react';
+import { DataGridPremium, useGridApiRef } from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const dataSetOptions = {
+ dataSet: 'Employee',
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregationTreeData() {
+ const apiRef = useGridApiRef();
+ const {
+ fetchRows,
+ columns,
+ initialState: initState,
+ } = useMockServer(dataSetOptions);
+
+ const initialState = React.useMemo(
+ () => ({
+ ...initState,
+ aggregation: {
+ model: { rating: 'avg', website: 'size' },
+ },
+ }),
+ [initState],
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ 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),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ getAggregatedValue: (row, field) => row[`${field}Aggregate`],
+ }),
+ [fetchRows],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx
new file mode 100644
index 0000000000000..cf55fabc541d5
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx
@@ -0,0 +1,80 @@
+import * as React from 'react';
+import {
+ DataGridPremium,
+ useGridApiRef,
+ GridInitialState,
+ GridDataSource,
+} from '@mui/x-data-grid-premium';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const dataSetOptions = {
+ dataSet: 'Employee' as const,
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+const aggregationFunctions = {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+};
+
+export default function ServerSideDataGridAggregationTreeData() {
+ const apiRef = useGridApiRef();
+ const {
+ fetchRows,
+ columns,
+ initialState: initState,
+ } = useMockServer(dataSetOptions);
+
+ const initialState: GridInitialState = React.useMemo(
+ () => ({
+ ...initState,
+ aggregation: {
+ model: { rating: 'avg', website: 'size' },
+ },
+ }),
+ [initState],
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ 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),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ getAggregatedValue: (row, field) => row[`${field}Aggregate`],
+ }),
+ [fetchRows],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx.preview
new file mode 100644
index 0000000000000..6eee2299964e8
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx.preview
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/server-side-data/aggregation.md b/docs/data/data-grid/server-side-data/aggregation.md
index e11485b7d80ce..7b94fbaf0e76d 100644
--- a/docs/data/data-grid/server-side-data/aggregation.md
+++ b/docs/data/data-grid/server-side-data/aggregation.md
@@ -2,14 +2,103 @@
title: React Data Grid - Server-side aggregation
---
-# Data Grid - Server-side aggregation [](/x/introduction/licensing/#premium-plan 'Premium plan')🚧
+# Data Grid - Server-side aggregation [](/x/introduction/licensing/#premium-plan 'Premium plan')🧪
Aggregation with server-side data source.
-:::warning
-This feature isn't implemented yet. It's coming.
+To dynamically load tree data from the server, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [Server-side data overview](/x/react-data-grid/server-side-data/).
-đź‘Ť Upvote [issue #10860](https://github.com/mui/mui-x/issues/10860) if you want to see it land faster.
+:::info
+If you're looking for aggregation on the client side, see [Aggregation](/x/react-data-grid/aggregation/).
+:::
+
+Server-side aggregation requires some additional steps to implement:
+
+1. Pass the available aggregation functions of type `GridAggregationFunctionDataSource` to the Data Grid using the `aggregationFunctions` prop. Its default value is empty when the Data Grid is used with server-side data.
+
+ ```tsx
+ const aggregationFunctions: Record = {
+ size: { label: 'Size' },
+ sum: { label: 'Sum', columnTypes: ['number'] },
+ }
+
+
+ ```
+
+ The `GridAggregationFunctionDataSource` interface is similar to `GridAggregationFunction`, but it doesn't have `apply` or `getCellValue` properties because the computation is done on the server.
+
+ See the [GridAggregationFunctionDataSource API page](/x/api/data-grid/grid-aggregation-function-data-source/) for more details.
+
+2. Use `aggregationModel` passed in the `getRows` method of `GridDataSource` to fetch the aggregated values.
+ For the root level footer aggregation row, pass `aggregateRow` containing the aggregated values in the `GetRowsResponse`.
+
+ ```diff
+ const dataSource = {
+ getRows: async ({
+ sortModel,
+ filterModel,
+ paginationModel,
+ + aggregationModel,
+ }) => {
+ const rows = await fetchRows();
+ - const response = await fetchData({ sortModel, filterModel, paginationModel });
+ + const response = await fetchData({ sortModel, filterModel, paginationModel, aggregationModel });
+ return {
+ rows: response.rows,
+ rowCount: getRowsResponse.totalCount,
+ + aggregateRow: response.aggregateRow,
+ }
+ }
+ }
+ ```
+
+3. Pass the getter method `getAggregatedValue` in `GridDataSource` that defines how to get the aggregated value for a parent row (including the `aggregateRow`).
+
+ ```tsx
+ const dataSource = {
+ getRows: async ({
+ ...
+ }) => {
+ ...
+ },
+ getAggregatedValue: (row, field) => {
+ return row[`${field}Aggregate`];
+ },
+ }
+ ```
+
+The following example demonstrates basic server-side aggregation.
-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": "ServerSideDataGridAggregation.js", "bg": "inline"}}
+
+:::info
+The data source mock server (`useMockServer()`) mocks the built-in aggregation functions listed in the [built-in functions section](/x/react-data-grid/aggregation/#built-in-functions) of the client-side aggregation documentation.
+Provide the function names and minimal configuration to demonstrate the aggregation, as shown in the demo.
:::
+
+## Usage with lazy loading
+
+Server-side aggregation can be implemented along with [server-side lazy loading](/x/react-data-grid/server-side-data/lazy-loading/) as shown in the demo below.
+
+{{"demo": "ServerSideDataGridAggregationLazyLoading.js", "bg": "inline"}}
+
+## Usage with row grouping
+
+Server-side aggregation works with row grouping in a similar way as described in [Aggregation—usage with row grouping](/x/react-data-grid/aggregation/#usage-with-row-grouping).
+The aggregated values are acquired from the parent rows using the `getAggregatedValue` method.
+
+{{"demo": "ServerSideDataGridAggregationRowGrouping.js", "bg": "inline"}}
+
+## Usage with tree data
+
+Server-side aggregation can be used with tree data in a similar way as described in [Aggregation—usage with tree data](/x/react-data-grid/aggregation/#usage-with-tree-data).
+The aggregated values are acquired from the parent rows using the `getAggregatedValue` method.
+
+{{"demo": "ServerSideDataGridAggregationTreeData.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/)
+- [GridAggregationFunctionDataSource](/x/api/data-grid/grid-aggregation-function-data-source/)
diff --git a/docs/data/pages.ts b/docs/data/pages.ts
index e0d0eabaaf69a..bb8364631030c 100644
--- a/docs/data/pages.ts
+++ b/docs/data/pages.ts
@@ -164,7 +164,7 @@ const pages: MuiPage[] = [
{
pathname: '/x/react-data-grid/server-side-data/aggregation',
plan: 'premium',
- planned: true,
+ unstable: true,
},
],
},
@@ -234,6 +234,10 @@ const pages: MuiPage[] = [
pathname: '/x/api/data-grid/grid-aggregation-function',
title: 'GridAggregationFunction',
},
+ {
+ pathname: '/x/api/data-grid/grid-aggregation-function-data-source',
+ title: 'GridAggregationFunctionDataSource',
+ },
{
pathname: '/x/api/data-grid/grid-csv-export-options',
title: 'GridCsvExportOptions',
diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json
index 950b63871a3ce..a2c48c6a533db 100644
--- a/docs/pages/x/api/data-grid/data-grid-premium.json
+++ b/docs/pages/x/api/data-grid/data-grid-premium.json
@@ -6,7 +6,7 @@
},
"aggregationFunctions": {
"type": { "name": "object" },
- "default": "GRID_AGGREGATION_FUNCTIONS"
+ "default": "GRID_AGGREGATION_FUNCTIONS when `unstable_dataSource` is not provided, `{}` when `unstable_dataSource` is provided"
},
"aggregationModel": { "type": { "name": "object" } },
"aggregationRowsScope": {
diff --git a/docs/pages/x/api/data-grid/grid-aggregation-function-data-source.js b/docs/pages/x/api/data-grid/grid-aggregation-function-data-source.js
new file mode 100644
index 0000000000000..c78470ce999b9
--- /dev/null
+++ b/docs/pages/x/api/data-grid/grid-aggregation-function-data-source.js
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import InterfaceApiPage from 'docsx/src/modules/components/InterfaceApiPage';
+import layoutConfig from 'docsx/src/modules/utils/dataGridLayoutConfig';
+import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
+import jsonPageContent from './grid-aggregation-function-data-source.json';
+
+export default function Page(props) {
+ const { descriptions, pageContent } = props;
+ return (
+
+ );
+}
+
+Page.getInitialProps = () => {
+ const req = require.context(
+ 'docsx/translations/api-docs/data-grid/',
+ false,
+ /\.\/grid-aggregation-function-data-source.*.json$/,
+ );
+ const descriptions = mapApiPageTranslations(req);
+
+ return {
+ descriptions,
+ pageContent: jsonPageContent,
+ };
+};
diff --git a/docs/pages/x/api/data-grid/grid-aggregation-function-data-source.json b/docs/pages/x/api/data-grid/grid-aggregation-function-data-source.json
new file mode 100644
index 0000000000000..75ba83bcced16
--- /dev/null
+++ b/docs/pages/x/api/data-grid/grid-aggregation-function-data-source.json
@@ -0,0 +1,19 @@
+{
+ "name": "GridAggregationFunctionDataSource",
+ "imports": ["import { GridAggregationFunctionDataSource } from '@mui/x-data-grid-premium'"],
+ "demos": "",
+ "properties": {
+ "columnTypes": { "type": { "description": "string[]" }, "isPremiumPlan": true },
+ "hasCellUnit": {
+ "type": { "description": "boolean" },
+ "default": "true",
+ "isPremiumPlan": true
+ },
+ "label": {
+ "type": { "description": "string" },
+ "default": "apiRef.current.getLocaleText('aggregationFunctionLabel{capitalize(name)})",
+ "isPremiumPlan": true
+ },
+ "valueFormatter": { "type": { "description": "GridValueFormatter" }, "isPremiumPlan": true }
+ }
+}
diff --git a/docs/pages/x/api/data-grid/index.md b/docs/pages/x/api/data-grid/index.md
index 59c68e9a9b1fe..b8b5142ec3fc5 100644
--- a/docs/pages/x/api/data-grid/index.md
+++ b/docs/pages/x/api/data-grid/index.md
@@ -26,6 +26,7 @@
- [GridRowParams](/x/api/data-grid/grid-row-params/)
- [GridRowSpacingParams](/x/api/data-grid/grid-row-spacing-params/)
- [GridAggregationFunction](/x/api/data-grid/grid-aggregation-function/)
+- [GridAggregationFunctionDataSource](/x/api/data-grid/grid-aggregation-function-data-source/)
- [GridCsvExportOptions](/x/api/data-grid/grid-csv-export-options/)
- [GridPrintExportOptions](/x/api/data-grid/grid-print-export-options/)
- [GridExcelExportOptions](/x/api/data-grid/grid-excel-export-options/)
diff --git a/docs/scripts/createXTypeScriptProjects.ts b/docs/scripts/createXTypeScriptProjects.ts
index c0f24cb61a2d5..8fc0a872f29ca 100644
--- a/docs/scripts/createXTypeScriptProjects.ts
+++ b/docs/scripts/createXTypeScriptProjects.ts
@@ -152,6 +152,7 @@ export const interfacesToDocument: InterfacesToDocumentType[] = [
// Aggregation
'GridAggregationFunction',
+ 'GridAggregationFunctionDataSource',
],
},
{
diff --git a/docs/translations/api-docs/data-grid/grid-aggregation-function-data-source.json b/docs/translations/api-docs/data-grid/grid-aggregation-function-data-source.json
new file mode 100644
index 0000000000000..2491dd0ed1396
--- /dev/null
+++ b/docs/translations/api-docs/data-grid/grid-aggregation-function-data-source.json
@@ -0,0 +1,17 @@
+{
+ "interfaceDescription": "Grid aggregation function data source definition interface.",
+ "propertiesDescriptions": {
+ "columnTypes": {
+ "description": "Column types supported by this aggregation function.
If not defined, all types are supported (in most cases this property should be defined)."
+ },
+ "hasCellUnit": {
+ "description": "Indicates if the aggregated value has the same unit as the cells used to generate it.
It can be used to apply a custom cell renderer only if the aggregated value has the same unit."
+ },
+ "label": {
+ "description": "Label of the aggregation function.
Used for adding a label to the footer of the grouping column when this aggregation function is the only one being used."
+ },
+ "valueFormatter": {
+ "description": "Function for applying a formatter to the aggregated value.
If not defined, the grid uses the formatter of the column."
+ }
+ }
+}
diff --git a/docs/translations/api-docs/data-grid/grid-aggregation-function.json b/docs/translations/api-docs/data-grid/grid-aggregation-function.json
index 4a5bee820d1e9..7cb6c76d06a19 100644
--- a/docs/translations/api-docs/data-grid/grid-aggregation-function.json
+++ b/docs/translations/api-docs/data-grid/grid-aggregation-function.json
@@ -11,13 +11,13 @@
"description": "Function that allows to transform the value of the cell passed to the aggregation function applier.
Useful for aggregating data from multiple row fields."
},
"hasCellUnit": {
- "description": "Indicates if the aggregated value have the same unit as the cells used to generate it.
It can be used to apply a custom cell renderer only if the aggregated value has the same unit."
+ "description": "Indicates if the aggregated value has the same unit as the cells used to generate it.
It can be used to apply a custom cell renderer only if the aggregated value has the same unit."
},
"label": {
- "description": "Label of the aggregation function.
Will be used to add a label on the footer of the grouping column when this aggregation function is the only one being used."
+ "description": "Label of the aggregation function.
Used for adding a label to the footer of the grouping column when this aggregation function is the only one being used."
},
"valueFormatter": {
- "description": "Function that allows to apply a formatter to the aggregated value.
If not defined, the grid will use the formatter of the column."
+ "description": "Function for applying a formatter to the aggregated value.
If not defined, the grid uses the formatter of the column."
}
}
}
diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts
index c0643d608e923..b44a26a1bf7f4 100644
--- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts
+++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts
@@ -8,12 +8,32 @@ import {
GridRowId,
GridPaginationModel,
GridValidRowModel,
-} from '@mui/x-data-grid-pro';
+ GRID_AGGREGATION_FUNCTIONS,
+ GridAggregationModel,
+ GridAggregationFunction,
+} from '@mui/x-data-grid-premium';
import { GridStateColDef } from '@mui/x-data-grid-pro/internals';
import { randomInt } from '../services/random-generator';
+const getAvailableAggregationFunctions = (columnType: GridColDef['type']) => {
+ const availableAggregationFunctions = new Map();
+ Object.keys(GRID_AGGREGATION_FUNCTIONS).forEach((functionName) => {
+ const columnTypes =
+ GRID_AGGREGATION_FUNCTIONS[functionName as keyof typeof GRID_AGGREGATION_FUNCTIONS]
+ .columnTypes;
+ if (!columnTypes || columnTypes.includes(columnType ?? 'string')) {
+ availableAggregationFunctions.set(
+ functionName,
+ GRID_AGGREGATION_FUNCTIONS[functionName as keyof typeof GRID_AGGREGATION_FUNCTIONS],
+ );
+ }
+ });
+ return availableAggregationFunctions;
+};
+
export interface FakeServerResponse {
returnedRows: GridRowModel[];
+ aggregateRow?: GridValidRowModel;
nextCursor?: string;
hasNextPage?: boolean;
totalRowCount: number;
@@ -39,6 +59,7 @@ export interface QueryOptions {
page?: number;
pageSize?: number;
filterModel?: GridFilterModel;
+ aggregationModel?: GridAggregationModel;
sortModel?: GridSortModel;
start?: number;
end?: number;
@@ -50,6 +71,7 @@ export interface ServerSideQueryOptions {
groupKeys?: string[];
filterModel?: GridFilterModel;
sortModel?: GridSortModel;
+ aggregationModel?: GridAggregationModel;
start?: number;
end?: number;
groupFields?: string[];
@@ -261,6 +283,41 @@ const getFilteredRows = (
);
};
+const applyAggregation = (
+ aggregationModel: GridAggregationModel,
+ colDefs: GridColDef[],
+ rows: GridRowModel[],
+ groupId: string = 'root',
+) => {
+ const columnsToAggregate = Object.keys(aggregationModel);
+ if (columnsToAggregate.length === 0) {
+ return {};
+ }
+
+ const aggregateValues: GridValidRowModel = {};
+ columnsToAggregate.forEach((field) => {
+ const type = colDefs.find(({ field: f }) => f === field)?.type;
+ if (!type) {
+ return;
+ }
+ const availableAggregationFunctions = getAvailableAggregationFunctions(type);
+ if (!availableAggregationFunctions.has(aggregationModel[field])) {
+ return;
+ }
+ const aggregationFunction = availableAggregationFunctions.get(aggregationModel[field]);
+ if (!aggregationFunction) {
+ return;
+ }
+ const values = rows.map((row) => row[field]);
+ aggregateValues[`${field}Aggregate`] = aggregationFunction.apply({
+ values,
+ field,
+ groupId,
+ });
+ });
+ return aggregateValues;
+};
+
/**
* Simulates server data loading
*/
@@ -288,6 +345,15 @@ export const loadServerRows = (
const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef);
filteredRows = [...filteredRows].sort(rowComparator);
+ let aggregateRow = {};
+ if (queryOptions.aggregationModel) {
+ aggregateRow = applyAggregation(
+ queryOptions.aggregationModel,
+ columnsWithDefaultColDef,
+ filteredRows,
+ );
+ }
+
const totalRowCount = filteredRows.length;
if (start !== undefined && end !== undefined) {
firstRowIndex = start;
@@ -311,6 +377,7 @@ export const loadServerRows = (
hasNextPage,
nextCursor,
totalRowCount,
+ ...(queryOptions.aggregationModel ? { aggregateRow } : {}),
};
return new Promise((resolve) => {
@@ -323,6 +390,7 @@ export const loadServerRows = (
interface NestedDataRowsResponse {
rows: GridRowModel[];
rootRowCount: number;
+ aggregateRow?: GridRowModel;
}
const findTreeDataRowChildren = (
@@ -464,6 +532,19 @@ export const processTreeDataRows = (
let childRowsWithDescendantCounts = childRows.map((row) => {
const descendants = findTreeDataRowChildren(filteredRows, row[pathKey], pathKey, -1);
const descendantCount = descendants.length;
+ if (descendantCount > 0 && queryOptions.aggregationModel) {
+ // Parent row, compute aggregation
+ return {
+ ...row,
+ descendantCount,
+ ...applyAggregation(
+ queryOptions.aggregationModel,
+ columnsWithDefaultColDef,
+ descendants,
+ row.id,
+ ),
+ };
+ }
return { ...row, descendantCount } as GridRowModel;
});
@@ -473,6 +554,15 @@ export const processTreeDataRows = (
childRowsWithDescendantCounts = [...childRowsWithDescendantCounts].sort(rowComparator);
}
+ let aggregateRow: GridRowModel | undefined;
+ if (queryOptions.aggregationModel) {
+ aggregateRow = applyAggregation(
+ queryOptions.aggregationModel,
+ columnsWithDefaultColDef,
+ filteredRows,
+ );
+ }
+
if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) {
// Only paginate root rows, grid should refetch root rows when `paginationModel` updates
const { pageSize, page } = queryOptions.paginationModel;
@@ -486,7 +576,7 @@ export const processTreeDataRows = (
return new Promise((resolve) => {
setTimeout(() => {
- resolve({ rows: childRowsWithDescendantCounts, rootRowCount });
+ resolve({ rows: childRowsWithDescendantCounts, rootRowCount, aggregateRow });
}, delay); // simulate network latency
});
};
@@ -584,6 +674,19 @@ export const processRowGroupingRows = (
({ id }) => typeof id !== 'string' || !id.startsWith('auto-generated-parent-'),
);
const descendantCount = descendants.length;
+ if (descendantCount > 0 && queryOptions.aggregationModel) {
+ // Parent row, compute aggregation
+ return {
+ ...row,
+ descendantCount,
+ ...applyAggregation(
+ queryOptions.aggregationModel,
+ columnsWithDefaultColDef,
+ descendants,
+ row.id,
+ ),
+ };
+ }
return { ...row, descendantCount } as GridRowModel;
});
@@ -594,6 +697,15 @@ export const processRowGroupingRows = (
childRowsWithDescendantCounts = [...sortedMissingGroups, ...sortedChildRows];
}
+ let aggregateRow: GridRowModel | undefined;
+ if (queryOptions.aggregationModel) {
+ aggregateRow = applyAggregation(
+ queryOptions.aggregationModel,
+ columnsWithDefaultColDef,
+ filteredRows,
+ );
+ }
+
if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) {
// Only paginate root rows, grid should refetch root rows when `paginationModel` updates
const { pageSize, page } = queryOptions.paginationModel;
@@ -607,7 +719,7 @@ export const processRowGroupingRows = (
return new Promise((resolve) => {
setTimeout(() => {
- resolve({ rows: childRowsWithDescendantCounts, rootRowCount });
+ resolve({ rows: childRowsWithDescendantCounts, rootRowCount, aggregateRow });
}, 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 9fb8aff79a8c2..a150839bb0eb4 100644
--- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts
+++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts
@@ -7,7 +7,7 @@ import {
GridColDef,
GridInitialState,
GridColumnVisibilityModel,
-} from '@mui/x-data-grid-pro';
+} from '@mui/x-data-grid-premium';
import { extrapolateSeed, deepFreeze } from './useDemoData';
import { getCommodityColumns } from '../columns/commodities.columns';
import { getEmployeeColumns } from '../columns/employees.columns';
@@ -298,7 +298,7 @@ export const useMockServer = (
}
if (isTreeData) {
- const { rows, rootRowCount } = await processTreeDataRows(
+ const { rows, rootRowCount, aggregateRow } = await processTreeDataRows(
data?.rows ?? [],
params,
serverOptionsWithDefault,
@@ -308,9 +308,10 @@ export const useMockServer = (
getRowsResponse = {
rows: rows.slice().map((row) => ({ ...row, path: undefined })),
rowCount: rootRowCount,
+ ...(aggregateRow ? { aggregateRow } : {}),
};
} else if (isRowGrouping) {
- const { rows, rootRowCount } = await processRowGroupingRows(
+ const { rows, rootRowCount, aggregateRow } = await processRowGroupingRows(
data?.rows ?? [],
params,
serverOptionsWithDefault,
@@ -320,15 +321,21 @@ export const useMockServer = (
getRowsResponse = {
rows: rows.slice().map((row) => ({ ...row, path: undefined })),
rowCount: rootRowCount,
+ ...(aggregateRow ? { aggregateRow } : {}),
};
} else {
- const { returnedRows, nextCursor, totalRowCount } = await loadServerRows(
+ const { returnedRows, nextCursor, totalRowCount, aggregateRow } = await loadServerRows(
data?.rows ?? [],
{ ...params, ...params.paginationModel },
serverOptionsWithDefault,
columnsWithDefaultColDef,
);
- getRowsResponse = { rows: returnedRows, rowCount: totalRowCount, pageInfo: { nextCursor } };
+ getRowsResponse = {
+ rows: returnedRows,
+ rowCount: totalRowCount,
+ pageInfo: { nextCursor },
+ ...(aggregateRow ? { aggregateRow } : {}),
+ };
}
return new Promise((resolve) => {
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
index c91e68a462c5a..6160575fb7ff9 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
+++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
@@ -80,7 +80,7 @@ DataGridPremiumRaw.propTypes = {
// ----------------------------------------------------------------------
/**
* Aggregation functions available on the grid.
- * @default GRID_AGGREGATION_FUNCTIONS
+ * @default GRID_AGGREGATION_FUNCTIONS when `unstable_dataSource` is not provided, `{}` when `unstable_dataSource` is provided
*/
aggregationFunctions: PropTypes.object,
/**
@@ -1080,6 +1080,7 @@ DataGridPremiumRaw.propTypes = {
*/
treeData: PropTypes.bool,
unstable_dataSource: PropTypes.shape({
+ getAggregatedValue: PropTypes.func,
getChildrenCount: PropTypes.func,
getGroupKey: PropTypes.func,
getRows: PropTypes.func.isRequired,
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx
index 5e3f20c6ea49d..d1b931e524a69 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx
+++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx
@@ -67,7 +67,6 @@ import {
virtualizationStateInitializer,
useGridVirtualization,
useGridDataSourceTreeDataPreProcessors,
- useGridDataSource,
dataSourceStateInitializer,
useGridRowSpanning,
rowSpanningStateInitializer,
@@ -76,6 +75,7 @@ import {
} from '@mui/x-data-grid-pro/internals';
import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium';
import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps';
+import { useGridDataSourcePremium as useGridDataSource } from '../hooks/features/dataSource/useGridDataSourcePremium';
// Premium-only features
import {
useGridAggregation,
@@ -153,6 +153,7 @@ export const useDataGridPremiumComponent = (
useGridRowGrouping(apiRef, props);
useGridHeaderFiltering(apiRef, props);
useGridTreeData(apiRef, props);
+ useGridDataSource(apiRef, props);
useGridAggregation(apiRef, props);
useGridKeyboardNavigation(apiRef, props);
useGridRowSelection(apiRef, props);
@@ -190,7 +191,6 @@ export const useDataGridPremiumComponent = (
useGridDimensions(apiRef, props);
useGridEvents(apiRef, props);
useGridStatePersistence(apiRef);
- useGridDataSource(apiRef, props);
useGridVirtualization(apiRef, props);
useGridListView(apiRef, props);
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts
index c047e56429235..bb316ad4c9e3e 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts
+++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts
@@ -84,6 +84,7 @@ export const useDataGridPremiumProps = (inProps: DataGridPremiumProps) => {
return React.useMemo(
() => ({
...DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES,
+ ...(themedProps.unstable_dataSource ? { aggregationFunctions: {} } : {}),
...themedProps,
localeText,
slots,
diff --git a/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx b/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx
index 522edfd53b492..4bbe750029925 100644
--- a/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx
+++ b/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx
@@ -27,8 +27,9 @@ function GridColumnMenuAggregationItem(props: GridColumnMenuItemProps) {
getAvailableAggregationFunctions({
aggregationFunctions: rootProps.aggregationFunctions,
colDef,
+ isDataSource: !!rootProps.unstable_dataSource,
}),
- [colDef, rootProps.aggregationFunctions],
+ [colDef, rootProps.aggregationFunctions, rootProps.unstable_dataSource],
);
const { native: isBaseSelectNative = false, ...baseSelectProps } =
rootProps.slotProps?.baseSelect || {};
@@ -45,13 +46,14 @@ function GridColumnMenuAggregationItem(props: GridColumnMenuItemProps) {
colDef,
aggregationFunctionName,
aggregationFunction: rootProps.aggregationFunctions[aggregationFunctionName],
+ isDataSource: !!rootProps.unstable_dataSource,
})
) {
return aggregationFunctionName;
}
return '';
- }, [rootProps.aggregationFunctions, aggregationModel, colDef]);
+ }, [rootProps.aggregationFunctions, rootProps.unstable_dataSource, aggregationModel, colDef]);
const handleAggregationItemChange = (event: SelectChangeEvent) => {
const newAggregationItem = (event.target as HTMLSelectElement | null)?.value || undefined;
diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/createAggregationLookup.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/createAggregationLookup.ts
index 4294bf38db5ca..1a8b81c3585fd 100644
--- a/packages/x-data-grid-premium/src/hooks/features/aggregation/createAggregationLookup.ts
+++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/createAggregationLookup.ts
@@ -8,10 +8,11 @@ import {
gridRowTreeSelector,
GRID_ROOT_GROUP_ID,
} from '@mui/x-data-grid-pro';
-import { GridApiPremium } from '../../../models/gridApiPremium';
+import { GridPrivateApiPremium } from '../../../models/gridApiPremium';
import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps';
import {
GridAggregationFunction,
+ GridAggregationFunctionDataSource,
GridAggregationLookup,
GridAggregationPosition,
GridAggregationRules,
@@ -21,7 +22,7 @@ import { gridAggregationModelSelector } from './gridAggregationSelectors';
const getGroupAggregatedValue = (
groupId: GridRowId,
- apiRef: React.MutableRefObject,
+ apiRef: React.MutableRefObject,
aggregationRowsScope: DataGridPremiumProcessedProps['aggregationRowsScope'],
aggregatedFields: string[],
aggregationRules: GridAggregationRules,
@@ -56,7 +57,8 @@ const getGroupAggregatedValue = (
const aggregatedField = aggregatedFields[j];
const columnAggregationRules = aggregationRules[aggregatedField];
- const aggregationFunction = columnAggregationRules.aggregationFunction;
+ const aggregationFunction =
+ columnAggregationRules.aggregationFunction as GridAggregationFunction;
const field = aggregatedField;
if (aggregatedValues[j] === undefined) {
@@ -77,7 +79,8 @@ const getGroupAggregatedValue = (
for (let i = 0; i < aggregatedValues.length; i += 1) {
const { aggregatedField, values } = aggregatedValues[i];
- const aggregationFunction = aggregationRules[aggregatedField].aggregationFunction;
+ const aggregationFunction = aggregationRules[aggregatedField]
+ .aggregationFunction as GridAggregationFunction;
const value = aggregationFunction.apply({
values,
groupId,
@@ -93,21 +96,46 @@ const getGroupAggregatedValue = (
return groupAggregationLookup;
};
+const getGroupAggregatedValueDataSource = (
+ groupId: GridRowId,
+ apiRef: React.MutableRefObject,
+ aggregatedFields: string[],
+ position: GridAggregationPosition,
+) => {
+ const groupAggregationLookup: GridAggregationLookup[GridRowId] = {};
+
+ for (let j = 0; j < aggregatedFields.length; j += 1) {
+ const aggregatedField = aggregatedFields[j];
+
+ groupAggregationLookup[aggregatedField] = {
+ position,
+ value: apiRef.current.resolveGroupAggregation(groupId, aggregatedField),
+ };
+ }
+
+ return groupAggregationLookup;
+};
+
export const createAggregationLookup = ({
apiRef,
aggregationFunctions,
aggregationRowsScope,
getAggregationPosition,
+ isDataSource,
}: {
- apiRef: React.MutableRefObject;
- aggregationFunctions: Record;
+ apiRef: React.MutableRefObject;
+ aggregationFunctions:
+ | Record
+ | Record;
aggregationRowsScope: DataGridPremiumProcessedProps['aggregationRowsScope'];
getAggregationPosition: DataGridPremiumProcessedProps['getAggregationPosition'];
+ isDataSource: boolean;
}): GridAggregationLookup => {
const aggregationRules = getAggregationRules(
gridColumnLookupSelector(apiRef),
gridAggregationModelSelector(apiRef),
aggregationFunctions,
+ isDataSource,
);
const aggregatedFields = Object.keys(aggregationRules);
@@ -128,10 +156,17 @@ export const createAggregationLookup = ({
}
}
- const hasAggregableChildren = groupNode.children.length;
- if (hasAggregableChildren) {
- const position = getAggregationPosition(groupNode);
- if (position != null) {
+ const position = getAggregationPosition(groupNode);
+
+ if (position !== null) {
+ if (isDataSource) {
+ aggregationLookup[groupNode.id] = getGroupAggregatedValueDataSource(
+ groupNode.id,
+ apiRef,
+ aggregatedFields,
+ position,
+ );
+ } else if (groupNode.children.length) {
aggregationLookup[groupNode.id] = getGroupAggregatedValue(
groupNode.id,
apiRef,
diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts
index 212bdef1a0083..96e584d233037 100644
--- a/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts
+++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts
@@ -22,6 +22,13 @@ export interface GridAggregationApi {
setAggregationModel: (model: GridAggregationModel) => void;
}
+export interface GridAggregationPrivateApi {
+ /**
+ * Applies the aggregation to the rows.
+ */
+ applyAggregation: () => void;
+}
+
export interface GridAggregationGetCellValueParams {
/**
* The row model of the row that the current cell belongs to.
@@ -44,7 +51,7 @@ export interface GridAggregationFunction {
apply: (params: GridAggregationParams) => AV | null | undefined;
/**
* Label of the aggregation function.
- * Will be used to add a label on the footer of the grouping column when this aggregation function is the only one being used.
+ * Used for adding a label to the footer of the grouping column when this aggregation function is the only one being used.
* @default apiRef.current.getLocaleText('aggregationFunctionLabel{capitalize(name)})
*/
label?: string;
@@ -54,12 +61,12 @@ export interface GridAggregationFunction {
*/
columnTypes?: string[];
/**
- * Function that allows to apply a formatter to the aggregated value.
- * If not defined, the grid will use the formatter of the column.
+ * Function for applying a formatter to the aggregated value.
+ * If not defined, the grid uses the formatter of the column.
*/
valueFormatter?: GridValueFormatter;
/**
- * Indicates if the aggregated value have the same unit as the cells used to generate it.
+ * Indicates if the aggregated value has the same unit as the cells used to generate it.
* It can be used to apply a custom cell renderer only if the aggregated value has the same unit.
* @default true
*/
@@ -74,6 +81,14 @@ export interface GridAggregationFunction {
getCellValue?: (params: GridAggregationGetCellValueParams) => V;
}
+/**
+ * Grid aggregation function data source definition interface.
+ * @demos
+ * - [Server-side aggregation](/x/react-data-grid/server-side-data/aggregation/)
+ */
+export interface GridAggregationFunctionDataSource
+ extends Omit {}
+
export interface GridAggregationParams {
values: (V | undefined)[];
groupId: GridRowId;
@@ -115,7 +130,7 @@ export interface GridAggregationHeaderMeta {
export interface GridAggregationRule {
aggregationFunctionName: string;
- aggregationFunction: GridAggregationFunction;
+ aggregationFunction: GridAggregationFunction | GridAggregationFunctionDataSource;
}
/**
diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationUtils.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationUtils.ts
index c84aae5d5e30b..408b7e1950d6d 100644
--- a/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationUtils.ts
+++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationUtils.ts
@@ -18,6 +18,7 @@ import {
} from '@mui/x-data-grid-pro/internals';
import {
GridAggregationFunction,
+ GridAggregationFunctionDataSource,
GridAggregationModel,
GridAggregationRule,
GridAggregationRules,
@@ -36,20 +37,29 @@ export const getAggregationFooterRowIdFromGroupId = (groupId: GridRowId | null)
return `auto-generated-group-footer-${groupId}`;
};
+type AggregationFunction = GridAggregationFunction | GridAggregationFunctionDataSource | undefined;
+
+const isClientSideAggregateFunction = (
+ aggregationFunction: AggregationFunction,
+): aggregationFunction is GridAggregationFunction =>
+ !!aggregationFunction && 'apply' in aggregationFunction;
+
export const canColumnHaveAggregationFunction = ({
colDef,
aggregationFunctionName,
aggregationFunction,
+ isDataSource,
}: {
colDef: GridColDef | undefined;
aggregationFunctionName: string;
- aggregationFunction: GridAggregationFunction | undefined;
+ aggregationFunction: AggregationFunction;
+ isDataSource: boolean;
}): boolean => {
if (!colDef) {
return false;
}
- if (!aggregationFunction) {
+ if (!isClientSideAggregateFunction(aggregationFunction) && !isDataSource) {
return false;
}
@@ -57,7 +67,7 @@ export const canColumnHaveAggregationFunction = ({
return colDef.availableAggregationFunctions.includes(aggregationFunctionName);
}
- if (!aggregationFunction.columnTypes) {
+ if (!aggregationFunction?.columnTypes) {
return true;
}
@@ -67,15 +77,20 @@ export const canColumnHaveAggregationFunction = ({
export const getAvailableAggregationFunctions = ({
aggregationFunctions,
colDef,
+ isDataSource,
}: {
- aggregationFunctions: Record;
+ aggregationFunctions:
+ | Record
+ | Record;
colDef: GridColDef;
+ isDataSource: boolean;
}) =>
Object.keys(aggregationFunctions).filter((aggregationFunctionName) =>
canColumnHaveAggregationFunction({
colDef,
aggregationFunctionName,
aggregationFunction: aggregationFunctions[aggregationFunctionName],
+ isDataSource,
}),
);
@@ -89,7 +104,10 @@ export const mergeStateWithAggregationModel =
export const getAggregationRules = (
columnsLookup: GridColumnRawLookup,
aggregationModel: GridAggregationModel,
- aggregationFunctions: Record,
+ aggregationFunctions:
+ | Record
+ | Record,
+ isDataSource: boolean,
) => {
const aggregationRules: GridAggregationRules = {};
@@ -102,6 +120,7 @@ export const getAggregationRules = (
colDef: columnsLookup[field],
aggregationFunctionName: columnItem,
aggregationFunction: aggregationFunctions[columnItem],
+ isDataSource,
})
) {
aggregationRules[field] = {
diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/index.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/index.ts
index 0334e48bacb90..ee9155e14144c 100644
--- a/packages/x-data-grid-premium/src/hooks/features/aggregation/index.ts
+++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/index.ts
@@ -1,4 +1,20 @@
-export * from './gridAggregationInterfaces';
+export type {
+ GridAggregationState,
+ GridAggregationInitialState,
+ GridAggregationInternalCache,
+ GridAggregationApi,
+ GridAggregationGetCellValueParams,
+ GridAggregationFunction,
+ GridAggregationFunctionDataSource,
+ GridAggregationParams,
+ GridAggregationModel,
+ GridAggregationLookup,
+ GridAggregationPosition,
+ GridAggregationCellMeta,
+ GridAggregationHeaderMeta,
+ GridAggregationRule,
+ GridAggregationRules,
+} from './gridAggregationInterfaces';
export * from './gridAggregationSelectors';
export * from './gridAggregationFunctions';
export {
diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts
index 8b41493cc2b96..345f9b718509f 100644
--- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts
+++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts
@@ -4,11 +4,15 @@ import {
useGridApiEventHandler,
useGridApiMethod,
} from '@mui/x-data-grid-pro';
-import { GridStateInitializer } from '@mui/x-data-grid-pro/internals';
+import {
+ useGridRegisterPipeProcessor,
+ GridStateInitializer,
+ GridPipeProcessor,
+} from '@mui/x-data-grid-pro/internals';
import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps';
import { GridPrivateApiPremium } from '../../../models/gridApiPremium';
import { gridAggregationModelSelector } from './gridAggregationSelectors';
-import { GridAggregationApi } from './gridAggregationInterfaces';
+import { GridAggregationApi, GridAggregationPrivateApi } from './gridAggregationInterfaces';
import {
getAggregationRules,
mergeStateWithAggregationModel,
@@ -45,6 +49,7 @@ export const useGridAggregation = (
| 'aggregationRowsScope'
| 'disableAggregation'
| 'rowGroupingColumnMode'
+ | 'unstable_dataSource'
>,
) => {
apiRef.current.registerControlState({
@@ -75,6 +80,7 @@ export const useGridAggregation = (
getAggregationPosition: props.getAggregationPosition,
aggregationFunctions: props.aggregationFunctions,
aggregationRowsScope: props.aggregationRowsScope,
+ isDataSource: !!props.unstable_dataSource,
});
apiRef.current.setState((state) => ({
@@ -86,13 +92,31 @@ export const useGridAggregation = (
props.getAggregationPosition,
props.aggregationFunctions,
props.aggregationRowsScope,
+ props.unstable_dataSource,
]);
const aggregationApi: GridAggregationApi = {
setAggregationModel,
};
+ const aggregationPrivateApi: GridAggregationPrivateApi = {
+ applyAggregation,
+ };
+
useGridApiMethod(apiRef, aggregationApi, 'public');
+ useGridApiMethod(apiRef, aggregationPrivateApi, 'private');
+
+ const addGetRowsParams = React.useCallback>(
+ (params) => {
+ return {
+ ...params,
+ aggregationModel: gridAggregationModelSelector(apiRef),
+ };
+ },
+ [apiRef],
+ );
+
+ useGridRegisterPipeProcessor(apiRef, 'getRowsParams', addGetRowsParams);
/**
* EVENTS
@@ -107,12 +131,17 @@ export const useGridAggregation = (
gridColumnLookupSelector(apiRef),
gridAggregationModelSelector(apiRef),
props.aggregationFunctions,
+ !!props.unstable_dataSource,
);
// Re-apply the row hydration to add / remove the aggregation footers
if (!areAggregationRulesEqual(rulesOnLastRowHydration, aggregationRules)) {
- apiRef.current.requestPipeProcessorsApplication('hydrateRows');
- applyAggregation();
+ if (props.unstable_dataSource) {
+ apiRef.current.unstable_dataSource.fetchRows();
+ } else {
+ apiRef.current.requestPipeProcessorsApplication('hydrateRows');
+ applyAggregation();
+ }
}
// Re-apply the column hydration to wrap / unwrap the aggregated columns
@@ -120,7 +149,13 @@ export const useGridAggregation = (
apiRef.current.caches.aggregation.rulesOnLastColumnHydration = aggregationRules;
apiRef.current.requestPipeProcessorsApplication('hydrateColumns');
}
- }, [apiRef, applyAggregation, props.aggregationFunctions, props.disableAggregation]);
+ }, [
+ apiRef,
+ applyAggregation,
+ props.aggregationFunctions,
+ props.disableAggregation,
+ props.unstable_dataSource,
+ ]);
useGridApiEventHandler(apiRef, 'aggregationModelChange', checkAggregationRulesDiff);
useGridApiEventHandler(apiRef, 'columnsChange', checkAggregationRulesDiff);
diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx
index 5ae196fb91ce1..b40cfb1ac99b6 100644
--- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx
+++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx
@@ -25,7 +25,12 @@ export const useGridAggregationPreProcessors = (
apiRef: React.MutableRefObject,
props: Pick<
DataGridPremiumProcessedProps,
- 'aggregationFunctions' | 'disableAggregation' | 'getAggregationPosition' | 'slotProps' | 'slots'
+ | 'aggregationFunctions'
+ | 'disableAggregation'
+ | 'getAggregationPosition'
+ | 'slotProps'
+ | 'slots'
+ | 'unstable_dataSource'
>,
) => {
// apiRef.current.caches.aggregation.rulesOnLastColumnHydration is not used because by the time
@@ -40,6 +45,7 @@ export const useGridAggregationPreProcessors = (
columnsState.lookup,
gridAggregationModelSelector(apiRef),
props.aggregationFunctions,
+ !!props.unstable_dataSource,
);
columnsState.orderedFields.forEach((field) => {
@@ -69,7 +75,7 @@ export const useGridAggregationPreProcessors = (
return columnsState;
},
- [apiRef, props.aggregationFunctions, props.disableAggregation],
+ [apiRef, props.aggregationFunctions, props.disableAggregation, props.unstable_dataSource],
);
const addGroupFooterRows = React.useCallback>(
@@ -80,6 +86,7 @@ export const useGridAggregationPreProcessors = (
gridColumnLookupSelector(apiRef),
gridAggregationModelSelector(apiRef),
props.aggregationFunctions,
+ !!props.unstable_dataSource,
);
const hasAggregationRule = Object.keys(aggregationRules).length > 0;
@@ -102,7 +109,13 @@ export const useGridAggregationPreProcessors = (
hasAggregationRule,
});
},
- [apiRef, props.disableAggregation, props.getAggregationPosition, props.aggregationFunctions],
+ [
+ apiRef,
+ props.disableAggregation,
+ props.getAggregationPosition,
+ props.aggregationFunctions,
+ props.unstable_dataSource,
+ ],
);
const addColumnMenuButtons = React.useCallback>(
@@ -114,6 +127,7 @@ export const useGridAggregationPreProcessors = (
const availableAggregationFunctions = getAvailableAggregationFunctions({
aggregationFunctions: props.aggregationFunctions,
colDef,
+ isDataSource: !!props.unstable_dataSource,
});
if (availableAggregationFunctions.length === 0) {
@@ -122,7 +136,7 @@ export const useGridAggregationPreProcessors = (
return [...columnMenuItems, 'columnMenuAggregationItem'];
},
- [props.aggregationFunctions, props.disableAggregation],
+ [props.aggregationFunctions, props.disableAggregation, props.unstable_dataSource],
);
const stateExportPreProcessing = React.useCallback>(
diff --git a/packages/x-data-grid-premium/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-premium/src/hooks/features/dataSource/cache.ts
new file mode 100644
index 0000000000000..db35e1db051d9
--- /dev/null
+++ b/packages/x-data-grid-premium/src/hooks/features/dataSource/cache.ts
@@ -0,0 +1,13 @@
+import { GridGetRowsParamsPremium } from './models';
+
+export function getKeyPremium(params: GridGetRowsParamsPremium) {
+ return JSON.stringify([
+ params.filterModel,
+ params.sortModel,
+ params.groupKeys,
+ params.groupFields,
+ params.start,
+ params.end,
+ params.aggregationModel,
+ ]);
+}
diff --git a/packages/x-data-grid-premium/src/hooks/features/dataSource/models.ts b/packages/x-data-grid-premium/src/hooks/features/dataSource/models.ts
new file mode 100644
index 0000000000000..ef69f680ad459
--- /dev/null
+++ b/packages/x-data-grid-premium/src/hooks/features/dataSource/models.ts
@@ -0,0 +1,63 @@
+import type {
+ GridColDef,
+ GridRowId,
+ GridValidRowModel,
+ GridDataSource,
+ GridGetRowsResponse,
+ GridGetRowsParams,
+ GridDataSourceApiBase,
+ GridDataSourcePrivateApi,
+} from '@mui/x-data-grid-pro';
+
+import type { GridAggregationModel } from '../aggregation/gridAggregationInterfaces';
+
+export interface GridGetRowsResponsePremium extends GridGetRowsResponse {
+ /**
+ * Row to be used for aggregation footer row.
+ * It must provide the values for the aggregated columns passed in
+ * `GridGetRowsParams.aggregationModel`.
+ */
+ aggregateRow?: GridValidRowModel;
+}
+
+export interface GridGetRowsParamsPremium extends GridGetRowsParams {
+ aggregationModel?: GridAggregationModel;
+}
+
+export interface GridDataSourcePremium extends Omit {
+ /**
+ * This method will be called when the grid needs to fetch some rows.
+ * @param {GridGetRowsParamsPremium} params The parameters required to fetch the rows.
+ * @returns {Promise} A promise that resolves to the data of type [GridGetRowsResponsePremium].
+ */
+ getRows(params: GridGetRowsParamsPremium): Promise;
+ /**
+ * Used to get the aggregated value for a parent row.
+ * @param {GridValidRowModel} row The row to extract the aggregated value from.
+ * @param {GridColDef['field']} field The field to extract the aggregated value for.
+ * @returns {string} The aggregated value for a specific aggregated column.
+ */
+ getAggregatedValue?: (row: GridValidRowModel, field: GridColDef['field']) => string;
+}
+
+export interface GridDataSourceApiBasePremium extends Omit {
+ /**
+ * Fetches the rows from the server.
+ * If no `parentId` option is provided, it fetches the root rows.
+ * Any missing parameter from `params` will be filled from the state (sorting, filtering, etc.).
+ * @param {GridRowId} parentId The id of the parent node (default: `GRID_ROOT_GROUP_ID`).
+ * @param {Partial} params Request parameters override.
+ */
+ fetchRows: (parentId?: GridRowId, params?: Partial) => void;
+}
+
+export interface GridDataSourceApiPremium {
+ /**
+ * The data source API.
+ */
+ unstable_dataSource: GridDataSourceApiBasePremium;
+}
+
+export interface GridDataSourcePremiumPrivateApi extends GridDataSourcePrivateApi {
+ resolveGroupAggregation: (groupId: GridRowId, field: string) => any;
+}
diff --git a/packages/x-data-grid-premium/src/hooks/features/dataSource/useGridDataSourcePremium.tsx b/packages/x-data-grid-premium/src/hooks/features/dataSource/useGridDataSourcePremium.tsx
new file mode 100644
index 0000000000000..09bfb1d025279
--- /dev/null
+++ b/packages/x-data-grid-premium/src/hooks/features/dataSource/useGridDataSourcePremium.tsx
@@ -0,0 +1,102 @@
+import * as React from 'react';
+import {
+ useGridApiEventHandler as addEventHandler,
+ useGridApiMethod,
+ GridEventLookup,
+ GRID_ROOT_GROUP_ID,
+ GridValidRowModel,
+} from '@mui/x-data-grid-pro';
+import {
+ useGridDataSourceBase,
+ useGridRegisterStrategyProcessor,
+ GridPipeProcessor,
+ useGridRegisterPipeProcessor,
+} from '@mui/x-data-grid-pro/internals';
+import { GridPrivateApiPremium } from '../../../models/gridApiPremium';
+import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps';
+import {
+ GridDataSourcePremiumPrivateApi,
+ GridGetRowsParamsPremium,
+ GridGetRowsResponsePremium,
+} from './models';
+import { getKeyPremium } from './cache';
+
+const options = {
+ cacheOptions: {
+ getKey: getKeyPremium,
+ },
+};
+
+export const useGridDataSourcePremium = (
+ apiRef: React.MutableRefObject,
+ props: DataGridPremiumProcessedProps,
+) => {
+ const { api, strategyProcessor, events } = useGridDataSourceBase(
+ apiRef,
+ props,
+ options,
+ );
+ const aggregateRowRef = React.useRef({});
+
+ const processDataSourceRows = React.useCallback>(
+ (
+ {
+ params,
+ response,
+ }: {
+ params: GridGetRowsParamsPremium;
+ response: GridGetRowsResponsePremium;
+ },
+ applyRowHydration: boolean,
+ ) => {
+ if (response.aggregateRow) {
+ aggregateRowRef.current = response.aggregateRow;
+ }
+ if (Object.keys(params.aggregationModel || {}).length > 0) {
+ if (applyRowHydration) {
+ apiRef.current.requestPipeProcessorsApplication('hydrateRows');
+ }
+ apiRef.current.applyAggregation();
+ }
+
+ return {
+ params,
+ response,
+ };
+ },
+ [apiRef],
+ );
+
+ const resolveGroupAggregation = React.useCallback<
+ GridDataSourcePremiumPrivateApi['resolveGroupAggregation']
+ >(
+ (groupId, field) => {
+ if (groupId === GRID_ROOT_GROUP_ID) {
+ return props.unstable_dataSource?.getAggregatedValue?.(aggregateRowRef.current, field);
+ }
+ const row = apiRef.current.getRow(groupId);
+ return props.unstable_dataSource?.getAggregatedValue?.(row, field);
+ },
+ [apiRef, props.unstable_dataSource],
+ );
+
+ const privateApi: GridDataSourcePremiumPrivateApi = {
+ ...api.private,
+ resolveGroupAggregation,
+ };
+
+ useGridApiMethod(apiRef, api.public, 'public');
+ useGridApiMethod(apiRef, privateApi, 'private');
+
+ useGridRegisterStrategyProcessor(
+ apiRef,
+ strategyProcessor.strategyName,
+ strategyProcessor.group,
+ strategyProcessor.processor,
+ );
+ useGridRegisterPipeProcessor(apiRef, 'processDataSourceRows', processDataSourceRows);
+
+ Object.entries(events).forEach(([event, handler]) => {
+ addEventHandler(apiRef, event as keyof GridEventLookup, handler);
+ });
+};
diff --git a/packages/x-data-grid-premium/src/index.ts b/packages/x-data-grid-premium/src/index.ts
index 0def4c628a755..36842cea3ac1a 100644
--- a/packages/x-data-grid-premium/src/index.ts
+++ b/packages/x-data-grid-premium/src/index.ts
@@ -40,3 +40,14 @@ export {
GRID_COLUMN_MENU_SLOTS,
GRID_COLUMN_MENU_SLOT_PROPS,
} from './components/reexports';
+
+export type { GridDataSourceCache } from '@mui/x-data-grid-pro';
+
+export type {
+ GridGetRowsParamsPremium as GridGetRowsParams,
+ GridGetRowsResponsePremium as GridGetRowsResponse,
+ GridDataSourcePremium as GridDataSource,
+ GridDataSourceApiPremium as GridDataSourceApi,
+ GridDataSourceApiBasePremium as GridDataSourceApiBase,
+ GridDataSourcePremiumPrivateApi as GridDataSourcePrivateApi,
+} from './hooks/features/dataSource/models';
diff --git a/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts b/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts
index 8ed5498364dab..da879f08ed001 100644
--- a/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts
+++ b/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts
@@ -17,12 +17,14 @@ import type { GridRowGroupingModel } from '../hooks/features/rowGrouping';
import type {
GridAggregationModel,
GridAggregationFunction,
+ GridAggregationFunctionDataSource,
GridAggregationPosition,
} from '../hooks/features/aggregation';
import { GridPremiumSlotsComponent } from './gridPremiumSlotsComponent';
import { GridInitialStatePremium } from './gridStatePremium';
import { GridApiPremium } from './gridApiPremium';
import { GridCellSelectionModel } from '../hooks/features/cellSelection';
+import { GridDataSourcePremium as GridDataSource } from '../hooks/features/dataSource/models';
export interface GridExperimentalPremiumFeatures extends GridExperimentalProFeatures {}
@@ -86,9 +88,11 @@ export interface DataGridPremiumPropsWithDefaultValue;
+ aggregationFunctions:
+ | Record
+ | Record;
/**
* Rows used to generate the aggregated value.
* If `filtered`, the aggregated values are generated using only the rows currently passing the filtering process.
@@ -118,7 +122,10 @@ export interface DataGridPremiumPropsWithDefaultValue
- extends Omit, 'initialState' | 'apiRef'> {
+ extends Omit<
+ DataGridProPropsWithoutDefaultValue,
+ 'initialState' | 'apiRef' | 'unstable_dataSource'
+ > {
/**
* The ref object that allows grid manipulation. Can be instantiated with `useGridApiRef()`.
*/
@@ -188,4 +195,5 @@ export interface DataGridPremiumPropsWithoutDefaultValue;
+ unstable_dataSource?: GridDataSource;
}
diff --git a/packages/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/x-data-grid-premium/src/models/gridApiPremium.ts
index 98ec11cdd1dce..1ea6dd05158ce 100644
--- a/packages/x-data-grid-premium/src/models/gridApiPremium.ts
+++ b/packages/x-data-grid-premium/src/models/gridApiPremium.ts
@@ -8,13 +8,16 @@ import {
GridRowMultiSelectionApi,
GridColumnReorderApi,
GridRowProApi,
- GridDataSourceApi,
- GridDataSourcePrivateApi,
} from '@mui/x-data-grid-pro';
import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium';
import type { GridRowGroupingApi, GridExcelExportApi, GridAggregationApi } from '../hooks';
import { GridCellSelectionApi } from '../hooks/features/cellSelection/gridCellSelectionInterfaces';
import type { DataGridPremiumProcessedProps } from './dataGridPremiumProps';
+import type {
+ GridDataSourcePremiumPrivateApi,
+ GridDataSourceApiPremium,
+} from '../hooks/features/dataSource/models';
+import type { GridAggregationPrivateApi } from '../hooks/features/aggregation/gridAggregationInterfaces';
/**
* The api of Data Grid Premium.
@@ -29,7 +32,7 @@ export interface GridApiPremium
GridExcelExportApi,
GridAggregationApi,
GridRowPinningApi,
- GridDataSourceApi,
+ GridDataSourceApiPremium,
GridCellSelectionApi,
// APIs that are private in Community plan, but public in Pro and Premium plans
GridRowMultiSelectionApi,
@@ -38,5 +41,6 @@ export interface GridApiPremium
export interface GridPrivateApiPremium
extends GridApiPremium,
GridPrivateOnlyApiCommon,
- GridDataSourcePrivateApi,
+ GridDataSourcePremiumPrivateApi,
+ GridAggregationPrivateApi,
GridDetailPanelPrivateApi {}
diff --git a/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx
new file mode 100644
index 0000000000000..f5cd1c40ee08d
--- /dev/null
+++ b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx
@@ -0,0 +1,157 @@
+import * as React from 'react';
+import { useMockServer } from '@mui/x-data-grid-generator';
+import { createRenderer, waitFor, screen, within } from '@mui/internal-test-utils';
+import { expect } from 'chai';
+import {
+ DataGridPremium,
+ DataGridPremiumProps,
+ GridApi,
+ GridDataSource,
+ GridGetRowsParams,
+ useGridApiRef,
+ GRID_AGGREGATION_ROOT_FOOTER_ROW_ID,
+ GRID_ROOT_GROUP_ID,
+} from '@mui/x-data-grid-premium';
+import { SinonSpy, spy } from 'sinon';
+import { getColumnHeaderCell } from 'test/utils/helperFn';
+
+const isJSDOM = /jsdom/.test(window.navigator.userAgent);
+
+describe(' - Data source aggregation', () => {
+ const { render } = createRenderer();
+
+ let apiRef: React.MutableRefObject;
+ let getRowsSpy: SinonSpy;
+ let mockServer: ReturnType;
+
+ function TestDataSourceAggregation(
+ props: Partial & {
+ getAggregatedValue?: GridDataSource['getAggregatedValue'];
+ },
+ ) {
+ apiRef = useGridApiRef();
+ const { getAggregatedValue: getAggregatedValueProp, ...rest } = props;
+ mockServer = useMockServer(
+ { rowLength: 10, maxColumns: 1 },
+ { useCursorPagination: false, minDelay: 0, maxDelay: 0, verbose: false },
+ );
+
+ const { fetchRows } = mockServer;
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params: GridGetRowsParams) => {
+ const urlParams = new URLSearchParams({
+ filterModel: JSON.stringify(params.filterModel),
+ sortModel: JSON.stringify(params.sortModel),
+ paginationModel: JSON.stringify(params.paginationModel),
+ aggregationModel: JSON.stringify(params.aggregationModel),
+ });
+
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ aggregateRow: getRowsResponse.aggregateRow,
+ };
+ },
+ getAggregatedValue:
+ getAggregatedValueProp ??
+ ((row, field) => {
+ return row[`${field}Aggregate`];
+ }),
+ }),
+ [fetchRows, getAggregatedValueProp],
+ );
+
+ getRowsSpy?.restore();
+ getRowsSpy = spy(dataSource, 'getRows');
+
+ const baselineProps = {
+ unstable_dataSource: dataSource,
+ columns: mockServer.columns,
+ disableVirtualization: true,
+ aggregationFunctions: {
+ sum: { columnTypes: ['number'] },
+ avg: { columnTypes: ['number'] },
+ min: { columnTypes: ['number', 'date', 'dateTime'] },
+ max: { columnTypes: ['number', 'date', 'dateTime'] },
+ size: {},
+ },
+ };
+
+ return (
+
+
+
+ );
+ }
+
+ beforeEach(function beforeTest() {
+ if (isJSDOM) {
+ this.skip(); // Needs layout
+ }
+ });
+
+ it('should show aggregation option in the column menu', async () => {
+ const { user } = render();
+ await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu'));
+ expect(screen.queryByLabelText('Aggregation')).not.to.equal(null);
+ });
+
+ it('should not show aggregation option in the column menu when no aggregation function is defined', async () => {
+ const { user } = render();
+ await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu'));
+ expect(screen.queryByLabelText('Aggregation')).to.equal(null);
+ });
+
+ it('should provide the `aggregationModel` in the `getRows` params', async () => {
+ render(
+ ,
+ );
+ await waitFor(() => {
+ expect(getRowsSpy.callCount).to.be.greaterThan(0);
+ });
+ expect(getRowsSpy.args[0][0].aggregationModel).to.deep.equal({ id: 'size' });
+ });
+
+ it('should show the aggregation footer row when aggregation is enabled', async () => {
+ render(
+ ,
+ );
+ await waitFor(() => {
+ expect(Object.keys(apiRef.current.state.aggregation.lookup).length).to.be.greaterThan(0);
+ });
+ expect(apiRef.current.state.rows.tree[GRID_AGGREGATION_ROOT_FOOTER_ROW_ID]).not.to.equal(null);
+ const footerRow = apiRef.current.state.aggregation.lookup[GRID_ROOT_GROUP_ID];
+ expect(footerRow.id).to.deep.equal({ position: 'footer', value: 10 });
+ });
+
+ it('should derive the aggregation values using `dataSource.getAggregatedValue`', async () => {
+ render(
+ 'Agg value'}
+ />,
+ );
+ await waitFor(() => {
+ expect(Object.keys(apiRef.current.state.aggregation.lookup).length).to.be.greaterThan(0);
+ });
+ expect(apiRef.current.state.aggregation.lookup[GRID_ROOT_GROUP_ID].id.value).to.equal(
+ 'Agg value',
+ );
+ });
+});
diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx
index c423c5f58d566..58a36ea9d2b21 100644
--- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx
+++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx
@@ -82,10 +82,8 @@ import {
rowPinningStateInitializer,
} from '../hooks/features/rowPinning/useGridRowPinning';
import { useGridRowPinningPreProcessors } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors';
-import {
- useGridDataSource,
- dataSourceStateInitializer,
-} from '../hooks/features/dataSource/useGridDataSource';
+import { useGridDataSourcePro as useGridDataSource } from '../hooks/features/dataSource/useGridDataSourcePro';
+import { dataSourceStateInitializer } from '../hooks/features/dataSource/useGridDataSourceBase';
import { useGridDataSourceLazyLoader } from '../hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader';
export const useDataGridProComponent = (
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 e6e0c02e48d35..4e9757c2a4456 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
@@ -1,4 +1,4 @@
-import { GridGetRowsParams, GridGetRowsResponse } from '../../../models';
+import { GridGetRowsParams, GridGetRowsResponse } from '@mui/x-data-grid/internals';
export type GridDataSourceCacheDefaultConfig = {
/**
@@ -7,9 +7,16 @@ export type GridDataSourceCacheDefaultConfig = {
* @default 300000 (5 minutes)
*/
ttl?: number;
+ /**
+ * Function to generate a cache key from the params.
+ * @param {GridGetRowsParams} params The params to generate the cache key from.
+ * @returns {string} The cache key.
+ * @default `getKeyDefault()`
+ */
+ getKey?: (params: GridGetRowsParams) => string;
};
-export function getKey(params: GridGetRowsParams) {
+export function getKeyDefault(params: GridGetRowsParams) {
return JSON.stringify([
params.filterModel,
params.sortModel,
@@ -25,19 +32,22 @@ export class GridDataSourceCacheDefault {
private ttl: number;
- constructor({ ttl = 300000 }: GridDataSourceCacheDefaultConfig) {
+ private getKey: (params: GridGetRowsParams) => string;
+
+ constructor({ ttl = 300000, getKey = getKeyDefault }: GridDataSourceCacheDefaultConfig) {
this.cache = {};
this.ttl = ttl;
+ this.getKey = getKey;
}
set(key: GridGetRowsParams, value: GridGetRowsResponse) {
- const keyString = getKey(key);
+ const keyString = this.getKey(key);
const expiry = Date.now() + this.ttl;
this.cache[keyString] = { value, expiry };
}
get(key: GridGetRowsParams): GridGetRowsResponse | undefined {
- const keyString = getKey(key);
+ const keyString = this.getKey(key);
const entry = this.cache[keyString];
if (!entry) {
return undefined;
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts
index 08eaf7c33f42d..c83b5f8219552 100644
--- a/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts
@@ -1,5 +1,5 @@
import { GridRowId } from '@mui/x-data-grid';
-import { GridDataSourceCache, GridGetRowsParams } from '../../../models';
+import { GridDataSourceCache, GridGetRowsParams } from '@mui/x-data-grid/internals';
export interface GridDataSourceState {
loading: Record;
@@ -26,7 +26,7 @@ export interface GridDataSourceApiBase {
* Fetches the rows from the server.
* If no `parentId` option is provided, it fetches the root rows.
* Any missing parameter from `params` will be filled from the state (sorting, filtering, etc.).
- * @param {GridRowId} parentId The id of the parent node.
+ * @param {GridRowId} parentId The id of the parent node (default: `GRID_ROOT_GROUP_ID`).
* @param {Partial} params Request parameters override.
*/
fetchRows: (parentId?: GridRowId, params?: Partial) => void;
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceBase.ts
similarity index 90%
rename from packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts
rename to packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceBase.ts
index d13d64e1895d7..d98f319e7b2b1 100644
--- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceBase.ts
@@ -1,8 +1,6 @@
import * as React from 'react';
import useLazyRef from '@mui/utils/useLazyRef';
import {
- useGridApiEventHandler,
- useGridApiMethod,
GridDataSourceGroupNode,
useGridSelector,
gridPaginationModelSelector,
@@ -15,7 +13,7 @@ import {
GridStateInitializer,
GridStrategyGroup,
GridStrategyProcessor,
- useGridRegisterStrategyProcessor,
+ GridDataSourceCache,
runIf,
} from '@mui/x-data-grid/internals';
import { GridPrivateApiPro } from '../../../models/gridApiPro';
@@ -28,7 +26,6 @@ import {
NestedDataManager,
RequestStatus,
} from './utils';
-import { GridDataSourceCache } from '../../../models';
import { GridDataSourceCacheDefault, GridDataSourceCacheDefaultConfig } from './cache';
const INITIAL_STATE = {
@@ -59,8 +56,8 @@ export const dataSourceStateInitializer: GridStateInitializer = (state) => {
};
};
-export const useGridDataSource = (
- apiRef: React.MutableRefObject,
+export const useGridDataSourceBase = (
+ apiRef: React.MutableRefObject,
props: Pick<
DataGridProProcessedProps,
| 'unstable_dataSource'
@@ -73,6 +70,9 @@ export const useGridDataSource = (
| 'treeData'
| 'unstable_lazyLoading'
>,
+ options: {
+ cacheOptions?: GridDataSourceCacheDefaultConfig;
+ } = {},
) => {
const setStrategyAvailability = React.useCallback(() => {
apiRef.current.setStrategyAvailability(
@@ -103,7 +103,7 @@ export const useGridDataSource = (
return new CacheChunkManager(cacheChunkSize);
}).current;
const [cache, setCache] = React.useState(() =>
- getCache(props.unstable_dataSourceCache),
+ getCache(props.unstable_dataSourceCache, options.cacheOptions),
);
const fetchRows = React.useCallback(
@@ -344,6 +344,11 @@ export const useGridDataSource = (
apiRef.current.setRowCount(response.rowCount);
}
apiRef.current.setRows(response.rows);
+ apiRef.current.unstable_applyPipeProcessors(
+ 'processDataSourceRows',
+ { params: params.fetchParams, response },
+ true,
+ );
},
[apiRef],
);
@@ -371,42 +376,18 @@ export const useGridDataSource = (
resetDataSourceState,
};
- useGridApiMethod(apiRef, dataSourceApi, 'public');
- useGridApiMethod(apiRef, dataSourcePrivateApi, 'private');
-
- useGridRegisterStrategyProcessor(
- apiRef,
- DataSourceRowsUpdateStrategy.Default,
- 'dataSourceRowsUpdate',
- handleDataUpdate,
- );
-
- useGridApiEventHandler(apiRef, 'strategyAvailabilityChange', handleStrategyActivityChange);
- useGridApiEventHandler(
- apiRef,
- 'sortModelChange',
- runIf(defaultRowsUpdateStrategyActive, () => fetchRows()),
- );
- useGridApiEventHandler(
- apiRef,
- 'filterModelChange',
- runIf(defaultRowsUpdateStrategyActive, () => fetchRows()),
- );
- useGridApiEventHandler(
- apiRef,
- 'paginationModelChange',
- runIf(defaultRowsUpdateStrategyActive, () => fetchRows()),
- );
-
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
- const newCache = getCache(props.unstable_dataSourceCache);
+ if (props.unstable_dataSourceCache === undefined) {
+ return;
+ }
+ const newCache = getCache(props.unstable_dataSourceCache, options.cacheOptions);
setCache((prevCache) => (prevCache !== newCache ? newCache : prevCache));
- }, [props.unstable_dataSourceCache]);
+ }, [props.unstable_dataSourceCache, options.cacheOptions]);
React.useEffect(() => {
setStrategyAvailability();
@@ -430,4 +411,19 @@ export const useGridDataSource = (
scheduledGroups.current = groupsToAutoFetch.length;
}
}, [apiRef, nestedDataManager, groupsToAutoFetch]);
+
+ return {
+ api: { public: dataSourceApi, private: dataSourcePrivateApi },
+ strategyProcessor: {
+ strategyName: DataSourceRowsUpdateStrategy.Default,
+ group: 'dataSourceRowsUpdate' as const,
+ processor: handleDataUpdate,
+ },
+ events: {
+ strategyAvailabilityChange: handleStrategyActivityChange,
+ sortModelChange: runIf(defaultRowsUpdateStrategyActive, () => fetchRows()),
+ filterModelChange: runIf(defaultRowsUpdateStrategyActive, () => fetchRows()),
+ paginationModelChange: runIf(defaultRowsUpdateStrategyActive, () => fetchRows()),
+ },
+ };
};
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourcePro.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourcePro.ts
new file mode 100644
index 0000000000000..5b3ee2b7c4c3d
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourcePro.ts
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import {
+ useGridApiEventHandler as addEventHandler,
+ useGridApiMethod,
+ GridEventLookup,
+} from '@mui/x-data-grid';
+import { useGridRegisterStrategyProcessor } from '@mui/x-data-grid/internals';
+import { GridPrivateApiPro } from '../../../models/gridApiPro';
+import { DataGridProProcessedProps } from '../../../models/dataGridProProps';
+import { useGridDataSourceBase } from './useGridDataSourceBase';
+
+export const useGridDataSourcePro = (
+ apiRef: React.MutableRefObject,
+ props: DataGridProProcessedProps,
+) => {
+ const { api, strategyProcessor, events } = useGridDataSourceBase(apiRef, props);
+
+ useGridApiMethod(apiRef, api.public, 'public');
+ useGridApiMethod(apiRef, api.private, 'private');
+
+ useGridRegisterStrategyProcessor(
+ apiRef,
+ strategyProcessor.strategyName,
+ strategyProcessor.group,
+ strategyProcessor.processor,
+ );
+
+ Object.entries(events).forEach(([event, handler]) => {
+ addEventHandler(apiRef, event as keyof GridEventLookup, handler);
+ });
+};
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts
index ce2d8de133391..d542538f584da 100644
--- a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts
@@ -1,5 +1,6 @@
import { GridRowId } from '@mui/x-data-grid';
-import { GridPrivateApiPro, GridGetRowsParams, GridGetRowsResponse } from '../../../models';
+import { GridGetRowsParams, GridGetRowsResponse } from '@mui/x-data-grid/internals';
+import { GridPrivateApiPro } from '../../../models';
const MAX_CONCURRENT_REQUESTS = Infinity;
diff --git a/packages/x-data-grid-pro/src/hooks/features/index.ts b/packages/x-data-grid-pro/src/hooks/features/index.ts
index 456da75b157c1..a09be50773318 100644
--- a/packages/x-data-grid-pro/src/hooks/features/index.ts
+++ b/packages/x-data-grid-pro/src/hooks/features/index.ts
@@ -5,5 +5,5 @@ export * from './rowReorder';
export * from './treeData';
export * from './detailPanel';
export * from './rowPinning';
-export * from './dataSource/interfaces';
+export type { GridDataSourceState } from './dataSource/interfaces';
export { GridDataSourceCacheDefault } from './dataSource/cache';
diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts
index c36a874f5ef82..d6f0b1d800f57 100644
--- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts
+++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts
@@ -285,6 +285,11 @@ export const useGridDataSourceLazyLoader = (
addSkeletonRows();
privateApiRef.current.setLoading(false);
+ privateApiRef.current.unstable_applyPipeProcessors(
+ 'processDataSourceRows',
+ { params: params.fetchParams, response },
+ false,
+ );
privateApiRef.current.requestPipeProcessorsApplication('hydrateRows');
},
[
diff --git a/packages/x-data-grid-pro/src/index.ts b/packages/x-data-grid-pro/src/index.ts
index 0b9505fd84d70..e5910805e2ba0 100644
--- a/packages/x-data-grid-pro/src/index.ts
+++ b/packages/x-data-grid-pro/src/index.ts
@@ -35,3 +35,17 @@ export {
export { GridColumnHeaders } from './components/GridColumnHeaders';
export type { GridColumnHeadersProps } from './components/GridColumnHeaders';
+
+// Reexportable exports
+export type {
+ GridGetRowsParams,
+ GridGetRowsResponse,
+ GridDataSource,
+ GridDataSourceCache,
+} from '@mui/x-data-grid/internals';
+
+export type {
+ GridDataSourceApiBase,
+ GridDataSourceApi,
+ GridDataSourcePrivateApi,
+} from './hooks/features/dataSource/interfaces';
diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts
index f821f34170111..6db29e8ae55d2 100644
--- a/packages/x-data-grid-pro/src/internals/index.ts
+++ b/packages/x-data-grid-pro/src/internals/index.ts
@@ -45,9 +45,9 @@ export { useGridLazyLoader } from '../hooks/features/lazyLoader/useGridLazyLoade
export { useGridLazyLoaderPreProcessors } from '../hooks/features/lazyLoader/useGridLazyLoaderPreProcessors';
export { useGridDataSourceLazyLoader } from '../hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader';
export {
- useGridDataSource,
+ useGridDataSourceBase,
dataSourceStateInitializer,
-} from '../hooks/features/dataSource/useGridDataSource';
+} from '../hooks/features/dataSource/useGridDataSourceBase';
export type {
GridExperimentalProFeatures,
diff --git a/packages/x-data-grid-pro/src/models/gridApiPro.ts b/packages/x-data-grid-pro/src/models/gridApiPro.ts
index 07f0d250f57a5..f0e0159eaf799 100644
--- a/packages/x-data-grid-pro/src/models/gridApiPro.ts
+++ b/packages/x-data-grid-pro/src/models/gridApiPro.ts
@@ -11,9 +11,11 @@ import type {
GridDetailPanelApi,
GridRowPinningApi,
GridDetailPanelPrivateApi,
+} from '../hooks';
+import type {
GridDataSourceApi,
GridDataSourcePrivateApi,
-} from '../hooks';
+} from '../hooks/features/dataSource/interfaces';
import type { DataGridProProcessedProps } from './dataGridProProps';
/**
diff --git a/packages/x-data-grid-pro/src/models/index.ts b/packages/x-data-grid-pro/src/models/index.ts
index 8110b6c70a918..36deff5c944b6 100644
--- a/packages/x-data-grid-pro/src/models/index.ts
+++ b/packages/x-data-grid-pro/src/models/index.ts
@@ -1,9 +1,3 @@
-export type {
- GridGetRowsParams,
- GridGetRowsResponse,
- GridDataSource,
- GridDataSourceCache,
-} from '@mui/x-data-grid/internals';
export * from './gridApiPro';
export * from './gridGroupingColDefOverride';
export * from './gridRowScrollEndParams';
diff --git a/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx
index fbbc993501f36..ec14b20f1daa2 100644
--- a/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx
@@ -14,13 +14,13 @@ import {
} from '@mui/x-data-grid-pro';
import { SinonSpy, spy } from 'sinon';
import { describeSkipIf, isJSDOM } from 'test/utils/skipIf';
-import { getKey } from '../hooks/features/dataSource/cache';
+import { getKeyDefault } from '../hooks/features/dataSource/cache';
const cache = new Map();
const testCache: GridDataSourceCache = {
- set: (key, value) => cache.set(getKey(key), value),
- get: (key) => cache.get(getKey(key)),
+ set: (key, value) => cache.set(getKeyDefault(key), value),
+ get: (key) => cache.get(getKeyDefault(key)),
clear: () => cache.clear(),
};
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 78300aa77e2a1..7b9eb49a37b0b 100644
--- a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts
+++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts
@@ -20,7 +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';
+import { GridGetRowsParams, GridGetRowsResponse } from '../../../models/gridDataSource';
import { HeightEntry } from '../../features/rows/gridRowsMetaInterfaces';
export type GridPipeProcessorGroup = keyof GridPipeProcessingLookup;
@@ -68,6 +68,11 @@ export interface GridPipeProcessingLookup {
context: { event: React.KeyboardEvent; cellParams: GridCellParams; editMode: GridEditMode };
};
isColumnPinned: { value: GridPinnedColumnPosition | false; context: string };
+ processDataSourceRows: {
+ value: { params: GridGetRowsParams; response: GridGetRowsResponse };
+ // `true` if the row hydration should be re-applied
+ context: boolean;
+ };
}
export type GridPipeProcessor = (
diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts
index 60848709e1927..6b7457bd3c945 100644
--- a/packages/x-data-grid/src/internals/index.ts
+++ b/packages/x-data-grid/src/internals/index.ts
@@ -21,7 +21,10 @@ export {
useGridRegisterStrategyProcessor,
GRID_DEFAULT_STRATEGY,
} from '../hooks/core/strategyProcessing';
-export type { GridStrategyProcessor } from '../hooks/core/strategyProcessing';
+export type {
+ GridStrategyProcessor,
+ GridStrategyProcessorName,
+} from '../hooks/core/strategyProcessing';
export { useGridInitialization } from '../hooks/core/useGridInitialization';
export { unwrapPrivateAPI } from '../hooks/core/useGridApiInitialization';
diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json
index b19e1c6650c81..e9d6c4df85d34 100644
--- a/scripts/x-data-grid-premium.exports.json
+++ b/scripts/x-data-grid-premium.exports.json
@@ -94,6 +94,7 @@
{ "name": "GridAggregationApi", "kind": "Interface" },
{ "name": "GridAggregationCellMeta", "kind": "Interface" },
{ "name": "GridAggregationFunction", "kind": "Interface" },
+ { "name": "GridAggregationFunctionDataSource", "kind": "Interface" },
{ "name": "GridAggregationGetCellValueParams", "kind": "Interface" },
{ "name": "GridAggregationHeaderMeta", "kind": "Interface" },
{ "name": "GridAggregationInitialState", "kind": "Interface" },