diff --git a/src/shared/components/data-table/components/table-controls/components/table-actions/TableActions.tsx b/src/shared/components/data-table/components/table-controls/components/table-actions/TableActions.tsx
index edb2c46761..bb112a7327 100644
--- a/src/shared/components/data-table/components/table-controls/components/table-actions/TableActions.tsx
+++ b/src/shared/components/data-table/components/table-controls/components/table-actions/TableActions.tsx
@@ -21,6 +21,7 @@ import { TableContext } from 'src/shared/components/data-table/DataTable';
import RowVisibilityController from 'src/shared/components/data-table/components/main/components/table-row/components/row-visibility-controller/RowVisibilityController';
import FindInTable from './components/find-in-table/FindInTable';
import ShowHideColumns from './components/show-hide-columns/ShowHideColumns';
+import DownloadData from './components/download-data/DownloadData';
import {
type DataTableState,
@@ -54,7 +55,7 @@ const actionOptions = [
},
{
value: TableAction.DOWNLOAD_ALL_DATA,
- label: 'Download all data'
+ label: 'Download this table'
},
{
value: TableAction.RESTORE_DEFAULTS,
@@ -144,6 +145,10 @@ const getActionComponent = (selectedAction: TableAction) => {
return
;
case TableAction.SHOW_HIDE_ROWS:
return
;
+ case TableAction.DOWNLOAD_ALL_DATA:
+ return
;
+ case TableAction.DOWNLOAD_SHOWN_DATA:
+ return
;
default:
return null;
}
diff --git a/src/shared/components/data-table/components/table-controls/components/table-actions/components/download-data/DownloadData.scss b/src/shared/components/data-table/components/table-controls/components/table-actions/components/download-data/DownloadData.scss
new file mode 100644
index 0000000000..27e4fffcda
--- /dev/null
+++ b/src/shared/components/data-table/components/table-controls/components/table-actions/components/download-data/DownloadData.scss
@@ -0,0 +1,14 @@
+@import 'src/styles/common';
+
+.downloadData {
+ display: flex;
+ column-gap: 10px;
+ margin-left: 10px;
+ align-items: center;
+}
+
+.cancel {
+ color: $blue;
+ cursor: pointer;
+ margin-left: 10px;
+}
diff --git a/src/shared/components/data-table/components/table-controls/components/table-actions/components/download-data/DownloadData.tsx b/src/shared/components/data-table/components/table-controls/components/table-actions/components/download-data/DownloadData.tsx
new file mode 100644
index 0000000000..3736d34952
--- /dev/null
+++ b/src/shared/components/data-table/components/table-controls/components/table-actions/components/download-data/DownloadData.tsx
@@ -0,0 +1,158 @@
+/**
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, { ReactNode, useEffect, useRef, useState } from 'react';
+import ReactDOM from 'react-dom/client';
+import memoize from 'lodash/memoize';
+
+import useDataTable from 'src/shared/components/data-table/hooks/useDataTable';
+import { downloadTextAsFile } from 'src/shared/helpers/downloadAsFile';
+
+import { ControlledLoadingButton } from 'src/shared/components/loading-button';
+
+import { TableAction } from 'src/shared/components/data-table/dataTableTypes';
+import { LoadingState } from 'src/shared/types/loading-state';
+
+import styles from './DownloadData.scss';
+
+const getReactRenderer = memoize(() => {
+ const element = document.createElement('div');
+ const root = ReactDOM.createRoot(element);
+
+ return {
+ element,
+ renderer: root
+ };
+});
+
+const getReactNodeText = (node: ReactNode): string => {
+ const { element, renderer } = getReactRenderer();
+ renderer.render(node);
+ return element.innerText;
+};
+
+const DownloadData = () => {
+ const {
+ dispatch,
+ rows,
+ downloadFileName,
+ columns,
+ selectedAction,
+ hiddenRowIds,
+ downloadHandler
+ } = useDataTable();
+
+ const [downloadState, setDownloadState] = useState
(
+ LoadingState.NOT_REQUESTED
+ );
+ const allowComponentResetRef = useRef(true);
+
+ useEffect(() => {
+ allowComponentResetRef.current = true;
+ return () => {
+ allowComponentResetRef.current = false;
+ };
+ }, []);
+
+ const restoreDefaults = () => {
+ if (allowComponentResetRef.current) {
+ dispatch({
+ type: 'set_selected_action',
+ payload: TableAction.DEFAULT
+ });
+ }
+ };
+
+ const handleDownload = async () => {
+ setDownloadState(LoadingState.LOADING);
+ if (downloadHandler) {
+ try {
+ await downloadHandler();
+ setDownloadState(LoadingState.SUCCESS);
+ setTimeout(restoreDefaults, 1000);
+ } catch {
+ setDownloadState(LoadingState.ERROR);
+ setTimeout(() => {
+ if (allowComponentResetRef.current) {
+ setDownloadState(LoadingState.NOT_REQUESTED);
+ }
+ }, 2000);
+ }
+
+ return;
+ }
+
+ const dataForExport: string[][] = [];
+ dataForExport[0] = [
+ ...columns
+ .filter((column) => column.isExportable !== false)
+ .map((column) => column.title ?? '')
+ ];
+
+ const rowsToDownload =
+ selectedAction === TableAction.DOWNLOAD_ALL_DATA
+ ? rows
+ : rows.filter((row) => !hiddenRowIds[row.rowId]);
+
+ rowsToDownload.forEach((row, rowIndex) => {
+ dataForExport[rowIndex + 1] = [];
+ row.cells.forEach((cell, cellIndex) => {
+ const { renderer, isExportable } = columns[cellIndex];
+
+ if (isExportable !== false) {
+ const cellExportData = renderer
+ ? renderer({
+ rowData: row.cells,
+ rowId: String(row.rowId),
+ cellData: cell
+ })
+ : cell;
+
+ if (typeof cellExportData === 'string') {
+ dataForExport[rowIndex + 1].push(cellExportData);
+ } else if (typeof cellExportData === 'number') {
+ dataForExport[rowIndex + 1].push(String(cellExportData));
+ } else {
+ dataForExport[rowIndex + 1].push(getReactNodeText(cellExportData));
+ }
+ }
+ });
+ });
+
+ const csv = formatCSV(dataForExport);
+
+ downloadTextAsFile(csv, downloadFileName ?? 'Table export.csv');
+ setDownloadState(LoadingState.SUCCESS);
+ setTimeout(restoreDefaults, 1000);
+ };
+
+ return (
+
+ {downloadFileName ?? 'table.csv'}
+
+ Download
+
+
+ cancel
+
+
+ );
+};
+
+const formatCSV = (table: (string | number)[][]) => {
+ return table.map((row) => row.join(',')).join('\n');
+};
+
+export default DownloadData;
diff --git a/src/shared/components/data-table/dataTableTypes.ts b/src/shared/components/data-table/dataTableTypes.ts
index 51736ee41c..b4ae06b264 100644
--- a/src/shared/components/data-table/dataTableTypes.ts
+++ b/src/shared/components/data-table/dataTableTypes.ts
@@ -62,6 +62,7 @@ export type IndividualColumn = {
isSearchable?: boolean;
isFilterable?: boolean;
isHideable?: boolean;
+ isExportable?: boolean;
headerCellClassName?: string;
bodyCellClassName?: string;
helpText?: ReactNode;