mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.15k stars 1.3k forks source link

[data grid] Option to customize CSV export column headers independent of rendered ones #12789

Open ianroberts opened 6 months ago

ianroberts commented 6 months ago

Summary

Summary

Exporting a data grid as CSV uses the column headers exactly as they appear in the rendered grid. In most cases this is the correct behaviour, but I have a use case where it would be useful to be able to use different column headings in the CSV compared to the interactive table. Specifically my grid is used as a more human-friendly editor for a dataset, then the CSV export of that dataset is passed to another tool that expects particular column headers. As such I want the column headers in the editor to be "friendly" names like "User's Full Name" (taken from an i18n message bundle), but the CSV to export using the machine readable field names like userFullName.

Desired behaviour

Currently the CSV exporter is hard-coded to always prefer the column's headerName, if one is set:

https://github.com/mui/mui-x/blob/4d4ff0e4093b4475c5bc66bcd9795b325795c4ed/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts#L209-L213

Ideally there would be a third option as part of the GridColDef besides field and headerName to separately specify a csvHeaderName, or alternatively allow headerName to be an object as an alternative to a plain string, along the lines of

const col = {
  field: "fullName",
  headerName: {
    grid: t("field.names.fullName"),  // resolves to "User's Full Name" in English
    csv: "userFullName",
  }
}

Workarounds

I tried specifying the headerName as the CSV name and then supplying a renderHeader function to do the "pretty" names, but then the "columns" selector and the "filters" popup use the CSV names instead of the human-facing ones.

It may be possible to use a custom action instead of the default Export action, that calls the API to generate a CSV without headers and then prepends the custom headers, but this would require duplicating much of the logic for determining which columns to export and in which order.

Examples

No response

Motivation

No response

Search keywords: csv column header

michelengelen commented 6 months ago

Hey @ianroberts ... afaik this is currently not possible. Another approach to this could be to add a new option to the GridCsvExportOptions. Something like useFieldForHeaderName which then simply returns the field name. WDYT @mui/xgrid ?

ianroberts commented 6 months ago

Something like useFieldForHeaderName which then simply returns the field name.

This would work nicely for my case, the only reason I suggested a separate csvHeaderName is that it's a bit more flexible - if you want the CSV header to be the field name then you can easily set the two properties to the same value, but if you want the CSV header to be something different then useFieldForHeaderName wouldn't be sufficient.

michelengelen commented 6 months ago

True that it does provide a bit more flexibilty. I just suggested that to not add something that would impact a lot of places in the code. We could also do a hybrid approach where we provide the GridCsvExportOptions with a getter function when needed: getHeaderName or similar. I will add this to our board for the team to have a look.

Here is a proposal how this could look like:

diff --git a/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts b/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts
index 88eea0e80..bda5a31e3 100644
--- a/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts
+++ b/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts
@@ -135,6 +135,8 @@ interface BuildCSVOptions {
   delimiterCharacter: NonNullable<GridCsvExportOptions['delimiter']>;
   includeHeaders: NonNullable<GridCsvExportOptions['includeHeaders']>;
   includeColumnGroupsHeaders: NonNullable<GridCsvExportOptions['includeColumnGroupsHeaders']>;
+  getHeaderName: NonNullable<GridCsvExportOptions['getHeaderName']>;
+  getColumnGroupHeaderName: NonNullable<GridCsvExportOptions['getColumnGroupHeaderName']>;
   ignoreValueFormatter: boolean;
   apiRef: React.MutableRefObject<GridApiCommunity>;
   shouldAppendQuotes: boolean;
@@ -146,7 +148,9 @@ export function buildCSV(options: BuildCSVOptions): string {
     rowIds,
     delimiterCharacter,
     includeHeaders,
+    getHeaderName,
     includeColumnGroupsHeaders,
+    getColumnGroupHeaderName,
     ignoreValueFormatter,
     apiRef,
     shouldAppendQuotes,
@@ -200,14 +204,20 @@ export function buildCSV(options: BuildCSVOptions): string {
       filteredColumns.forEach((column) => {
         const columnGroupId = (columnGroupPathsLookup[column.field] || [])[i];
         const columnGroup = columnGroupLookup[columnGroupId];
-        headerGroupRow.addValue(columnGroup ? columnGroup.headerName || columnGroup.groupId : '');
+        headerGroupRow.addValue(
+          columnGroup
+            ? getColumnGroupHeaderName?.(columnGroup) ||
+                columnGroup.headerName ||
+                columnGroup.groupId
+            : '',
+        );
       });
     }
   }

   const mainHeaderRow = new CSVRow({ delimiterCharacter, sanitizeCellValue, shouldAppendQuotes });
   filteredColumns.forEach((column) => {
-    mainHeaderRow.addValue(column.headerName || column.field);
+    mainHeaderRow.addValue(getHeaderName?.(column) || column.headerName || column.field);
   });
   headerRows.push(mainHeaderRow);

diff --git a/packages/x-data-grid/src/models/gridExport.ts b/packages/x-data-grid/src/models/gridExport.ts
index 07aea4967..6a469c1b4 100644
--- a/packages/x-data-grid/src/models/gridExport.ts
+++ b/packages/x-data-grid/src/models/gridExport.ts
@@ -1,4 +1,6 @@
 import * as React from 'react';
+import { GridColDef } from '@mui/x-data-grid/models/colDef';
+import { GridColumnGroup } from '@mui/x-data-grid/models/gridColumnGrouping';
 import { GridRowId } from './gridRows';
 import type { GridApiCommon } from './api';
 import type { GridApiCommunity } from './api/gridApiCommunity';
@@ -83,12 +85,24 @@ export interface GridCsvExportOptions extends GridFileExportOptions {
    * @default true
    */
   includeHeaders?: boolean;
+  /**
+   * Can be used to get a custom headerName value for a column.
+   * @param {GridColDef} colDef The column definition.
+   * @returns {string} The header name.
+   */
+  getHeaderName?: (colDef: GridColDef) => string;
   /**
    * If `true`, the CSV will include the column groups.
    * @see See {@link https://mui.com/x/react-data-grid/column-groups/ column groups docs} for more details.
    * @default true
    */
   includeColumnGroupsHeaders?: boolean;
+  /**
+   * Can be used to get a custom headerName value for a column.
+   * @param {Omit<GridColumnGroup, 'children'>} columnGroup The column definition.
+   * @returns {string} The header name.
+   */
+  getColumnGroupHeaderName?: (columnGroup: Omit<GridColumnGroup, 'children'>) => string;
   /**
    * Function that returns the list of row ids to export on the order they should be exported.
    * @param {GridCsvGetRowsToExportParams} params With all properties from [[GridCsvGetRowsToExportParams]].

@mui/xgrid WDYT?