From bcb46c863d6cbffd6dc8e9efa1d6679f339b8763 Mon Sep 17 00:00:00 2001
From: Luca Restagno <59067245+lucarestagno@users.noreply.github.com>
Date: Thu, 22 Dec 2022 15:29:20 +0100
Subject: [PATCH] [GCU-50] Export to CSV or JSON
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7359
GitOrigin-RevId: d0ebde91206e5ba750301151ddd2ee283fb31be4
---
.../components/DataGrid/DataGrid.tsx | 15 ++
.../components/DataGrid/QueryDialog.tsx | 4 +
.../parts/DataTableOptions.stories.tsx | 4 +
.../DataGrid/parts/DataTableOptions.tsx | 185 ++++++++++++------
.../src/features/BrowseRows/hooks/index.ts | 4 +
.../hooks/useExportRows/useExportRows.test.ts | 22 +--
.../hooks/useExportRows/useExportRows.ts | 22 ++-
console/src/features/BrowseRows/index.tsx | 1 +
console/src/new-components/Button/Button.tsx | 2 +-
.../new-components/Dialog/Dialog.stories.tsx | 3 +
console/src/new-components/Dialog/Dialog.tsx | 14 +-
11 files changed, 186 insertions(+), 90 deletions(-)
diff --git a/console/src/features/BrowseRows/components/DataGrid/DataGrid.tsx b/console/src/features/BrowseRows/components/DataGrid/DataGrid.tsx
index 006cc839670..d428a4494c5 100644
--- a/console/src/features/BrowseRows/components/DataGrid/DataGrid.tsx
+++ b/console/src/features/BrowseRows/components/DataGrid/DataGrid.tsx
@@ -28,6 +28,7 @@ import { ReactTableWrapper } from './parts/ReactTableWrapper';
import { QueryDialog } from './QueryDialog';
import { useRows, useTableColumns } from '../../hooks';
import { transformToOrderByClause } from './utils';
+import { useExportRows } from '../../hooks/useExportRows/useExportRows';
export type DataGridOptions = {
where?: WhereClause[];
@@ -144,6 +145,19 @@ export const DataGrid = (props: DataGridProps) => {
});
}, [pageIndex, pageSize]);
+ const columnNames = (tableColumnQueryResult?.columns || []).map(
+ column => column.name
+ );
+ const { onExportRows } = useExportRows({
+ columns: columnNames,
+ dataSourceName,
+ options: {
+ where: whereClauses,
+ order_by: orderByClauses,
+ },
+ table,
+ });
+
const handleOnRelationshipClick = ({
relationship,
rowData,
@@ -273,6 +287,7 @@ export const DataGrid = (props: DataGridProps) => {
removeOrderByClause: id => {
setOrderClauses(orderByClauses.filter((_, i) => i !== id));
},
+ onExportRows,
}}
/>
diff --git a/console/src/features/BrowseRows/components/DataGrid/QueryDialog.tsx b/console/src/features/BrowseRows/components/DataGrid/QueryDialog.tsx
index 45905ea1060..491c4e0176e 100644
--- a/console/src/features/BrowseRows/components/DataGrid/QueryDialog.tsx
+++ b/console/src/features/BrowseRows/components/DataGrid/QueryDialog.tsx
@@ -5,6 +5,7 @@ import { useConsoleForm } from '@/new-components/Form';
import React from 'react';
import { UseFormTrigger } from 'react-hook-form';
import { z } from 'zod';
+import { RiPlayFill } from 'react-icons/ri';
import { FilterRows } from '../RunQuery/Filter';
import { SortRows } from '../RunQuery/Sort';
import { useTableColumns } from '../../hooks/useTableColumns';
@@ -141,6 +142,9 @@ export const QueryDialog = ({
}
+ callToDeny="Cancel"
onClose={onClose}
onSubmit={() => onSubmitHandler()}
/>
diff --git a/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.stories.tsx b/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.stories.tsx
index 52fad7b6ec8..09320edfe63 100644
--- a/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.stories.tsx
+++ b/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.stories.tsx
@@ -73,6 +73,10 @@ const ComponentWrapper = () => {
removeOrderByClause: id => {
setOrderClauses(orderByClauses.filter((_, i) => i !== id));
},
+ onExportRows: exportFileFormat => {
+ updateStatus(`export to ${exportFileFormat}`);
+ return Promise.resolve(new Error());
+ },
}}
/>
{status}
diff --git a/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.tsx b/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.tsx
index a8f8dc49c69..bae74cb681c 100644
--- a/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.tsx
+++ b/console/src/features/BrowseRows/components/DataGrid/parts/DataTableOptions.tsx
@@ -1,17 +1,22 @@
import { Operator, OrderBy, WhereClause } from '@/features/DataSource';
import { Badge } from '@/new-components/Badge';
import { Button } from '@/new-components/Button';
+import { DropdownButton } from '@/new-components/DropdownButton';
+import { UseExportRowsReturn } from '@/features/BrowseRows';
import clsx from 'clsx';
-import React from 'react';
+import React, { useState } from 'react';
import {
FaChevronLeft,
FaChevronRight,
- FaSearch,
- FaUndo,
- FaRegTimesCircle,
- FaSortAmountUpAlt,
+ FaFileExport,
FaFilter,
+ FaRegTimesCircle,
+ FaSearch,
+ FaSortAmountUpAlt,
+ FaTimes,
} from 'react-icons/fa';
+import type { ExportFileFormat } from '@/features/BrowseRows';
+import { useFireNotification } from '@/new-components/Notifications';
import { DEFAULT_PAGE_SIZES } from '../constants';
interface DataTableOptionsProps {
@@ -24,6 +29,7 @@ interface DataTableOptionsProps {
removeWhereClause: (id: number) => void;
removeOrderByClause: (id: number) => void;
disableRunQuery?: boolean;
+ onExportRows: UseExportRowsReturn['onExportRows'];
};
pagination: {
goToPreviousPage: () => void;
@@ -44,24 +50,27 @@ const DisplayWhereClauses = ({
operatorMap: Record;
removeWhereClause: (id: number) => void;
}) => {
+ const twFlexCenter = 'flex items-center';
return (
<>
{whereClauses.map((whereClause, id) => {
const [columnName, rest] = Object.entries(whereClause)[0];
const [operator, value] = Object.entries(rest)[0];
return (
-
-
-
-
+
+
+
+
+
+
{columnName} {operatorMap[operator]} {value}
- {
- removeWhereClause(id);
- }}
- />
+
+ removeWhereClause(id)}
+ />
+
);
@@ -77,21 +86,24 @@ const DisplayOrderByClauses = ({
orderByClauses: OrderBy[];
removeOrderByClause: (id: number) => void;
}) => {
+ const twFlexCenter = 'flex items-center';
return (
<>
{orderByClauses.map((orderByClause, id) => (
-
-
-
-
+
+
+
+
+
+
{orderByClause.column} ({orderByClause.type})
- {
- removeOrderByClause(id);
- }}
- />
+
+ removeOrderByClause(id)}
+ />
+
))}
@@ -110,55 +122,101 @@ export const DataTableOptions = (props: DataTableOptionsProps) => {
const totalQueriesApplied =
query.whereClauses.length + query.orderByClauses.length;
+
+ const { fireNotification } = useFireNotification();
+ const [isExporting, setExporting] = useState(false);
+ const onExport = (exportFileFormat: ExportFileFormat) => {
+ setExporting(true);
+ query
+ .onExportRows(exportFileFormat)
+ .catch(err =>
+ fireNotification({
+ title: 'An error occurred',
+ message: err?.toString() || err,
+ type: 'error',
+ })
+ )
+ .finally(() => {
+ setExporting(false);
+ });
+ };
+
return (
- {!query.disableRunQuery && (
-
-
}
- onClick={query.onQuerySearch}
- data-testid="@runQueryBtn"
- disabled={query.disableRunQuery}
- title="Update filters and sorts on your row data"
- >
- {`Query ${`(${totalQueriesApplied})` || ''}`}
-
-
}
- data-testid="@resetBtn"
- disabled={query.disableRunQuery}
- title="Reset all filters"
- />
- {!query.disableRunQuery && (
-
-
+ onExport('CSV')}>CSV,
+ onExport('JSON')}>JSON,
+ ],
+ ]}
+ isLoading={isExporting}
+ >
+
+
+
+
+ Export
+
+
+
+
+
+
+ {!query.disableRunQuery && (
+ <>
+ }
+ onClick={query.onQuerySearch}
+ data-testid="@runQueryBtn"
+ disabled={query.disableRunQuery}
+ title="Update filters and sorts on your row data"
+ >
+ {`Query ${`(${totalQueriesApplied})` || ''}`}
+
+ {totalQueriesApplied > 1 && (
+ }
+ data-testid="@resetBtn"
+ disabled={query.disableRunQuery}
+ title="Reset all filters"
+ size="sm"
/>
-
-
- )}
-
- )}
+ )}
+ {!query.disableRunQuery && (
+
+
+
+
+ )}
+ >
+ )}
+
}
onClick={pagination.goToPreviousPage}
disabled={pagination.isPreviousPageDisabled}
@@ -172,7 +230,7 @@ export const DataTableOptions = (props: DataTableOptionsProps) => {
pagination.setPageSize(Number(e.target.value));
}}
data-testid="@rowSizeSelectInput"
- className="block w-full max-w-xl h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-yellow-200 focus-visible:border-yellow-400"
+ className="block w-full max-w-xl h-8 min-h-full shadow-sm rounded pl-3 pr-6 py-0.5 border border-gray-300 hover:border-gray-400 focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-yellow-200 focus-visible:border-yellow-400"
>
{DEFAULT_PAGE_SIZES.map(pageSize => (