mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 17:31:56 +03:00
[GCU-50] Export to CSV or JSON
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7359 GitOrigin-RevId: d0ebde91206e5ba750301151ddd2ee283fb31be4
This commit is contained in:
parent
8490874eab
commit
bcb46c863d
@ -28,6 +28,7 @@ import { ReactTableWrapper } from './parts/ReactTableWrapper';
|
|||||||
import { QueryDialog } from './QueryDialog';
|
import { QueryDialog } from './QueryDialog';
|
||||||
import { useRows, useTableColumns } from '../../hooks';
|
import { useRows, useTableColumns } from '../../hooks';
|
||||||
import { transformToOrderByClause } from './utils';
|
import { transformToOrderByClause } from './utils';
|
||||||
|
import { useExportRows } from '../../hooks/useExportRows/useExportRows';
|
||||||
|
|
||||||
export type DataGridOptions = {
|
export type DataGridOptions = {
|
||||||
where?: WhereClause[];
|
where?: WhereClause[];
|
||||||
@ -144,6 +145,19 @@ export const DataGrid = (props: DataGridProps) => {
|
|||||||
});
|
});
|
||||||
}, [pageIndex, pageSize]);
|
}, [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 = ({
|
const handleOnRelationshipClick = ({
|
||||||
relationship,
|
relationship,
|
||||||
rowData,
|
rowData,
|
||||||
@ -273,6 +287,7 @@ export const DataGrid = (props: DataGridProps) => {
|
|||||||
removeOrderByClause: id => {
|
removeOrderByClause: id => {
|
||||||
setOrderClauses(orderByClauses.filter((_, i) => i !== id));
|
setOrderClauses(orderByClauses.filter((_, i) => i !== id));
|
||||||
},
|
},
|
||||||
|
onExportRows,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { useConsoleForm } from '@/new-components/Form';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { UseFormTrigger } from 'react-hook-form';
|
import { UseFormTrigger } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { RiPlayFill } from 'react-icons/ri';
|
||||||
import { FilterRows } from '../RunQuery/Filter';
|
import { FilterRows } from '../RunQuery/Filter';
|
||||||
import { SortRows } from '../RunQuery/Sort';
|
import { SortRows } from '../RunQuery/Sort';
|
||||||
import { useTableColumns } from '../../hooks/useTableColumns';
|
import { useTableColumns } from '../../hooks/useTableColumns';
|
||||||
@ -141,6 +142,9 @@ export const QueryDialog = ({
|
|||||||
</div>
|
</div>
|
||||||
<Dialog.Footer
|
<Dialog.Footer
|
||||||
callToAction="Run Query"
|
callToAction="Run Query"
|
||||||
|
callToActionIconPosition="start"
|
||||||
|
callToActionIcon={<RiPlayFill />}
|
||||||
|
callToDeny="Cancel"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSubmit={() => onSubmitHandler()}
|
onSubmit={() => onSubmitHandler()}
|
||||||
/>
|
/>
|
||||||
|
@ -73,6 +73,10 @@ const ComponentWrapper = () => {
|
|||||||
removeOrderByClause: id => {
|
removeOrderByClause: id => {
|
||||||
setOrderClauses(orderByClauses.filter((_, i) => i !== id));
|
setOrderClauses(orderByClauses.filter((_, i) => i !== id));
|
||||||
},
|
},
|
||||||
|
onExportRows: exportFileFormat => {
|
||||||
|
updateStatus(`export to ${exportFileFormat}`);
|
||||||
|
return Promise.resolve(new Error());
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div data-testid="status">{status}</div>
|
<div data-testid="status">{status}</div>
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
import { Operator, OrderBy, WhereClause } from '@/features/DataSource';
|
import { Operator, OrderBy, WhereClause } from '@/features/DataSource';
|
||||||
import { Badge } from '@/new-components/Badge';
|
import { Badge } from '@/new-components/Badge';
|
||||||
import { Button } from '@/new-components/Button';
|
import { Button } from '@/new-components/Button';
|
||||||
|
import { DropdownButton } from '@/new-components/DropdownButton';
|
||||||
|
import { UseExportRowsReturn } from '@/features/BrowseRows';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FaChevronLeft,
|
FaChevronLeft,
|
||||||
FaChevronRight,
|
FaChevronRight,
|
||||||
FaSearch,
|
FaFileExport,
|
||||||
FaUndo,
|
|
||||||
FaRegTimesCircle,
|
|
||||||
FaSortAmountUpAlt,
|
|
||||||
FaFilter,
|
FaFilter,
|
||||||
|
FaRegTimesCircle,
|
||||||
|
FaSearch,
|
||||||
|
FaSortAmountUpAlt,
|
||||||
|
FaTimes,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
|
import type { ExportFileFormat } from '@/features/BrowseRows';
|
||||||
|
import { useFireNotification } from '@/new-components/Notifications';
|
||||||
import { DEFAULT_PAGE_SIZES } from '../constants';
|
import { DEFAULT_PAGE_SIZES } from '../constants';
|
||||||
|
|
||||||
interface DataTableOptionsProps {
|
interface DataTableOptionsProps {
|
||||||
@ -24,6 +29,7 @@ interface DataTableOptionsProps {
|
|||||||
removeWhereClause: (id: number) => void;
|
removeWhereClause: (id: number) => void;
|
||||||
removeOrderByClause: (id: number) => void;
|
removeOrderByClause: (id: number) => void;
|
||||||
disableRunQuery?: boolean;
|
disableRunQuery?: boolean;
|
||||||
|
onExportRows: UseExportRowsReturn['onExportRows'];
|
||||||
};
|
};
|
||||||
pagination: {
|
pagination: {
|
||||||
goToPreviousPage: () => void;
|
goToPreviousPage: () => void;
|
||||||
@ -44,24 +50,27 @@ const DisplayWhereClauses = ({
|
|||||||
operatorMap: Record<string, string>;
|
operatorMap: Record<string, string>;
|
||||||
removeWhereClause: (id: number) => void;
|
removeWhereClause: (id: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const twFlexCenter = 'flex items-center';
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{whereClauses.map((whereClause, id) => {
|
{whereClauses.map((whereClause, id) => {
|
||||||
const [columnName, rest] = Object.entries(whereClause)[0];
|
const [columnName, rest] = Object.entries(whereClause)[0];
|
||||||
const [operator, value] = Object.entries(rest)[0];
|
const [operator, value] = Object.entries(rest)[0];
|
||||||
return (
|
return (
|
||||||
<Badge color="indigo" className="mb-3 mr-sm" key={id}>
|
<Badge color="indigo" key={id}>
|
||||||
<div className="flex gap-3 items-center">
|
<div className={`gap-3 ${twFlexCenter}`}>
|
||||||
<FaFilter />
|
<span className={`min-h-3 ${twFlexCenter}`}>
|
||||||
<span>
|
<FaFilter />
|
||||||
|
</span>
|
||||||
|
<span className={twFlexCenter}>
|
||||||
{columnName} {operatorMap[operator]} {value}
|
{columnName} {operatorMap[operator]} {value}
|
||||||
</span>
|
</span>
|
||||||
<FaRegTimesCircle
|
<span className={`min-h-3 ${twFlexCenter}`}>
|
||||||
className="cursor-pointer"
|
<FaRegTimesCircle
|
||||||
onClick={() => {
|
className="cursor-pointer"
|
||||||
removeWhereClause(id);
|
onClick={() => removeWhereClause(id)}
|
||||||
}}
|
/>
|
||||||
/>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
@ -77,21 +86,24 @@ const DisplayOrderByClauses = ({
|
|||||||
orderByClauses: OrderBy[];
|
orderByClauses: OrderBy[];
|
||||||
removeOrderByClause: (id: number) => void;
|
removeOrderByClause: (id: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const twFlexCenter = 'flex items-center';
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{orderByClauses.map((orderByClause, id) => (
|
{orderByClauses.map((orderByClause, id) => (
|
||||||
<Badge color="yellow" className="mb-3 mr-sm" key={id}>
|
<Badge color="yellow" key={id}>
|
||||||
<div className="flex gap-3 items-center">
|
<div className={`gap-3 ${twFlexCenter}`}>
|
||||||
<FaSortAmountUpAlt />
|
<span className={`min-h-3 ${twFlexCenter}`}>
|
||||||
<span>
|
<FaSortAmountUpAlt />
|
||||||
|
</span>
|
||||||
|
<span className={twFlexCenter}>
|
||||||
{orderByClause.column} ({orderByClause.type})
|
{orderByClause.column} ({orderByClause.type})
|
||||||
</span>
|
</span>
|
||||||
<FaRegTimesCircle
|
<span className={`min-h-3 ${twFlexCenter}`}>
|
||||||
className="cursor-pointer"
|
<FaRegTimesCircle
|
||||||
onClick={() => {
|
className="cursor-pointer"
|
||||||
removeOrderByClause(id);
|
onClick={() => removeOrderByClause(id)}
|
||||||
}}
|
/>
|
||||||
/>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
@ -110,55 +122,101 @@ export const DataTableOptions = (props: DataTableOptionsProps) => {
|
|||||||
|
|
||||||
const totalQueriesApplied =
|
const totalQueriesApplied =
|
||||||
query.whereClauses.length + query.orderByClauses.length;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex items-center p-4 bg-white border',
|
'flex items-center px-3.5 py-2 bg-white border',
|
||||||
query.disableRunQuery ? 'justify-end' : ' justify-between'
|
query.disableRunQuery ? 'justify-end' : ' justify-between'
|
||||||
)}
|
)}
|
||||||
id="query-options"
|
id="query-options"
|
||||||
>
|
>
|
||||||
{!query.disableRunQuery && (
|
<div className="flex space-x-1.5 items-center">
|
||||||
<div className="flex space-x-1.5">
|
<DropdownButton
|
||||||
<Button
|
items={[
|
||||||
type="button"
|
[
|
||||||
mode="primary"
|
<span onClick={() => onExport('CSV')}>CSV</span>,
|
||||||
icon={<FaSearch />}
|
<span onClick={() => onExport('JSON')}>JSON</span>,
|
||||||
onClick={query.onQuerySearch}
|
],
|
||||||
data-testid="@runQueryBtn"
|
]}
|
||||||
disabled={query.disableRunQuery}
|
isLoading={isExporting}
|
||||||
title="Update filters and sorts on your row data"
|
>
|
||||||
>
|
<span className="items-center">
|
||||||
{`Query ${`(${totalQueriesApplied})` || ''}`}
|
<span className="mr-1.5">
|
||||||
</Button>
|
<FaFileExport />
|
||||||
<Button
|
</span>
|
||||||
type="button"
|
Export
|
||||||
mode="default"
|
</span>
|
||||||
onClick={query.onRefreshQueryOptions}
|
</DropdownButton>
|
||||||
icon={<FaUndo />}
|
|
||||||
data-testid="@resetBtn"
|
<span className="pl-2 pr-2">
|
||||||
disabled={query.disableRunQuery}
|
<span className="h-6 border-r-slate-300 border-r border-solid" />
|
||||||
title="Reset all filters"
|
</span>
|
||||||
/>
|
{!query.disableRunQuery && (
|
||||||
{!query.disableRunQuery && (
|
<>
|
||||||
<div className="flex-wrap pl-3">
|
<Button
|
||||||
<DisplayWhereClauses
|
type="button"
|
||||||
operatorMap={operatorMap}
|
mode="primary"
|
||||||
whereClauses={query.whereClauses}
|
size="sm"
|
||||||
removeWhereClause={query.removeWhereClause}
|
icon={<FaSearch />}
|
||||||
|
onClick={query.onQuerySearch}
|
||||||
|
data-testid="@runQueryBtn"
|
||||||
|
disabled={query.disableRunQuery}
|
||||||
|
title="Update filters and sorts on your row data"
|
||||||
|
>
|
||||||
|
{`Query ${`(${totalQueriesApplied})` || ''}`}
|
||||||
|
</Button>
|
||||||
|
{totalQueriesApplied > 1 && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
mode="default"
|
||||||
|
onClick={query.onRefreshQueryOptions}
|
||||||
|
icon={<FaTimes />}
|
||||||
|
data-testid="@resetBtn"
|
||||||
|
disabled={query.disableRunQuery}
|
||||||
|
title="Reset all filters"
|
||||||
|
size="sm"
|
||||||
/>
|
/>
|
||||||
<DisplayOrderByClauses
|
)}
|
||||||
orderByClauses={query.orderByClauses}
|
{!query.disableRunQuery && (
|
||||||
removeOrderByClause={query.removeOrderByClause}
|
<div className="flex flex-wrap gap-3 pl-3 items-center">
|
||||||
/>
|
<DisplayWhereClauses
|
||||||
</div>
|
operatorMap={operatorMap}
|
||||||
)}
|
whereClauses={query.whereClauses}
|
||||||
</div>
|
removeWhereClause={query.removeWhereClause}
|
||||||
)}
|
/>
|
||||||
|
<DisplayOrderByClauses
|
||||||
|
orderByClauses={query.orderByClauses}
|
||||||
|
removeOrderByClause={query.removeOrderByClause}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 items-center min-w-max">
|
<div className="flex gap-2 items-center min-w-max">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
size="sm"
|
||||||
icon={<FaChevronLeft />}
|
icon={<FaChevronLeft />}
|
||||||
onClick={pagination.goToPreviousPage}
|
onClick={pagination.goToPreviousPage}
|
||||||
disabled={pagination.isPreviousPageDisabled}
|
disabled={pagination.isPreviousPageDisabled}
|
||||||
@ -172,7 +230,7 @@ export const DataTableOptions = (props: DataTableOptionsProps) => {
|
|||||||
pagination.setPageSize(Number(e.target.value));
|
pagination.setPageSize(Number(e.target.value));
|
||||||
}}
|
}}
|
||||||
data-testid="@rowSizeSelectInput"
|
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 => (
|
{DEFAULT_PAGE_SIZES.map(pageSize => (
|
||||||
<option key={pageSize} value={pageSize}>
|
<option key={pageSize} value={pageSize}>
|
||||||
@ -182,6 +240,7 @@ export const DataTableOptions = (props: DataTableOptionsProps) => {
|
|||||||
</select>
|
</select>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
size="sm"
|
||||||
icon={<FaChevronRight />}
|
icon={<FaChevronRight />}
|
||||||
onClick={pagination.goToNextPage}
|
onClick={pagination.goToNextPage}
|
||||||
disabled={pagination.isNextPageDisabled}
|
disabled={pagination.isNextPageDisabled}
|
||||||
|
@ -1,2 +1,6 @@
|
|||||||
export { useRows } from './useRows';
|
export { useRows } from './useRows';
|
||||||
export { useTableColumns } from './useTableColumns';
|
export { useTableColumns } from './useTableColumns';
|
||||||
|
export type {
|
||||||
|
ExportFileFormat,
|
||||||
|
UseExportRowsReturn,
|
||||||
|
} from './useExportRows/useExportRows';
|
||||||
|
@ -8,14 +8,15 @@ import {
|
|||||||
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
|
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
|
||||||
import { TableRow } from '../../../DataSource';
|
import { TableRow } from '../../../DataSource';
|
||||||
import { Metadata } from '../../../hasura-metadata-types';
|
import { Metadata } from '../../../hasura-metadata-types';
|
||||||
import { useExportRows, UseExportRowsProps } from './useExportRows';
|
import { useExportRows } from './useExportRows';
|
||||||
|
import { UseRowsPropType } from '../useRows';
|
||||||
|
|
||||||
jest.mock('../../../../components/Common/utils/export.utils', () => ({
|
jest.mock('../../../../components/Common/utils/export.utils', () => ({
|
||||||
downloadObjectAsCsvFile: jest.fn(),
|
downloadObjectAsCsvFile: jest.fn(),
|
||||||
downloadObjectAsJsonFile: jest.fn(),
|
downloadObjectAsJsonFile: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const baseUseExportRowsPros: UseExportRowsProps = {
|
const baseUseExportRowsPros: UseRowsPropType = {
|
||||||
dataSourceName: 'chinook',
|
dataSourceName: 'chinook',
|
||||||
table: { name: 'Album', schema: 'public' },
|
table: { name: 'Album', schema: 'public' },
|
||||||
options: {
|
options: {
|
||||||
@ -24,7 +25,6 @@ const baseUseExportRowsPros: UseExportRowsProps = {
|
|||||||
order_by: [{ column: 'Title', type: 'desc' }],
|
order_by: [{ column: 'Title', type: 'desc' }],
|
||||||
offset: 15,
|
offset: 15,
|
||||||
},
|
},
|
||||||
exportFileFormat: 'CSV',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('useExportRows', () => {
|
describe('useExportRows', () => {
|
||||||
@ -91,15 +91,11 @@ describe('useExportRows', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('runs the CSV download function', async () => {
|
it('runs the CSV download function', async () => {
|
||||||
const props: UseExportRowsProps = {
|
const { result } = renderHook(() => useExportRows(baseUseExportRowsPros), {
|
||||||
...baseUseExportRowsPros,
|
|
||||||
exportFileFormat: 'CSV',
|
|
||||||
};
|
|
||||||
const { result } = renderHook(() => useExportRows(props), {
|
|
||||||
wrapper,
|
wrapper,
|
||||||
});
|
});
|
||||||
|
|
||||||
await result.current.onExportRows();
|
await result.current.onExportRows('CSV');
|
||||||
|
|
||||||
expect(downloadObjectAsJsonFile).not.toHaveBeenCalled();
|
expect(downloadObjectAsJsonFile).not.toHaveBeenCalled();
|
||||||
expect(downloadObjectAsCsvFile).toHaveBeenCalledWith(
|
expect(downloadObjectAsCsvFile).toHaveBeenCalledWith(
|
||||||
@ -109,15 +105,11 @@ describe('useExportRows', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('runs the JSON download function', async () => {
|
it('runs the JSON download function', async () => {
|
||||||
const props: UseExportRowsProps = {
|
const { result } = renderHook(() => useExportRows(baseUseExportRowsPros), {
|
||||||
...baseUseExportRowsPros,
|
|
||||||
exportFileFormat: 'JSON',
|
|
||||||
};
|
|
||||||
const { result } = renderHook(() => useExportRows(props), {
|
|
||||||
wrapper,
|
wrapper,
|
||||||
});
|
});
|
||||||
|
|
||||||
await result.current.onExportRows();
|
await result.current.onExportRows('JSON');
|
||||||
|
|
||||||
expect(downloadObjectAsCsvFile).not.toHaveBeenCalled();
|
expect(downloadObjectAsCsvFile).not.toHaveBeenCalled();
|
||||||
expect(downloadObjectAsJsonFile).toHaveBeenCalledWith(
|
expect(downloadObjectAsJsonFile).toHaveBeenCalledWith(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { getTableDisplayName } from '@/features/DatabaseRelationships';
|
import { getTableDisplayName } from '@/features/DatabaseRelationships';
|
||||||
import { useHttpClient } from '@/features/Network';
|
import { useHttpClient } from '@/features/Network';
|
||||||
|
import { TableRow } from '@/features/DataSource';
|
||||||
import { fetchRows, UseRowsPropType } from '../useRows';
|
import { fetchRows, UseRowsPropType } from '../useRows';
|
||||||
import { getFileName } from './useExportRows.utils';
|
import { getFileName } from './useExportRows.utils';
|
||||||
import {
|
import {
|
||||||
@ -7,20 +8,25 @@ import {
|
|||||||
downloadObjectAsJsonFile,
|
downloadObjectAsJsonFile,
|
||||||
} from '../../../../components/Common/utils/export.utils';
|
} from '../../../../components/Common/utils/export.utils';
|
||||||
|
|
||||||
export type UseExportRowsProps = {
|
export type ExportFileFormat = 'CSV' | 'JSON';
|
||||||
exportFileFormat: 'CSV' | 'JSON';
|
|
||||||
} & UseRowsPropType;
|
export type UseExportRowsReturn = {
|
||||||
|
onExportRows: (
|
||||||
|
exportFileFormat: ExportFileFormat
|
||||||
|
) => Promise<TableRow[] | Error>;
|
||||||
|
};
|
||||||
|
|
||||||
export const useExportRows = ({
|
export const useExportRows = ({
|
||||||
columns,
|
columns,
|
||||||
dataSourceName,
|
dataSourceName,
|
||||||
exportFileFormat,
|
|
||||||
options,
|
options,
|
||||||
table,
|
table,
|
||||||
}: UseExportRowsProps) => {
|
}: UseRowsPropType): UseExportRowsReturn => {
|
||||||
const httpClient = useHttpClient();
|
const httpClient = useHttpClient();
|
||||||
|
|
||||||
const onExportRows = async () =>
|
const onExportRows = async (
|
||||||
|
exportFileFormat: ExportFileFormat
|
||||||
|
): Promise<TableRow[] | Error> =>
|
||||||
new Promise(async (resolve, reject) => {
|
new Promise(async (resolve, reject) => {
|
||||||
const rows = await fetchRows({
|
const rows = await fetchRows({
|
||||||
columns,
|
columns,
|
||||||
@ -43,9 +49,7 @@ export const useExportRows = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
reject(
|
reject(new Error(rows));
|
||||||
new Error(`Unexpected fetch rows result: ${JSON.stringify(rows)}`)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -11,3 +11,4 @@ export {
|
|||||||
} from './components/RunQuery/LegacyRunQueryContainer/LegacyRunQueryContainer.utils';
|
} from './components/RunQuery/LegacyRunQueryContainer/LegacyRunQueryContainer.utils';
|
||||||
export { UserQuery } from './components/RunQuery/types';
|
export { UserQuery } from './components/RunQuery/types';
|
||||||
export { useTableColumns } from './hooks';
|
export { useTableColumns } from './hooks';
|
||||||
|
export type { ExportFileFormat, UseExportRowsReturn } from './hooks';
|
||||||
|
@ -5,7 +5,7 @@ import clsx from 'clsx';
|
|||||||
type ButtonModes = 'default' | 'destructive' | 'primary';
|
type ButtonModes = 'default' | 'destructive' | 'primary';
|
||||||
type ButtonSize = 'sm' | 'md' | 'lg';
|
type ButtonSize = 'sm' | 'md' | 'lg';
|
||||||
|
|
||||||
interface ButtonProps extends React.ComponentProps<'button'> {
|
export interface ButtonProps extends React.ComponentProps<'button'> {
|
||||||
/**
|
/**
|
||||||
* Flag indicating whether the button is disabled
|
* Flag indicating whether the button is disabled
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ComponentMeta, Story } from '@storybook/react';
|
import { ComponentMeta, Story } from '@storybook/react';
|
||||||
|
import { RiPlayFill } from 'react-icons/ri';
|
||||||
import { Dialog, DialogProps, FooterProps } from './Dialog';
|
import { Dialog, DialogProps, FooterProps } from './Dialog';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -29,6 +30,8 @@ export const Complete: Story<DialogProps & FooterProps> = args => (
|
|||||||
<Dialog.Footer
|
<Dialog.Footer
|
||||||
callToDeny={args.callToDeny}
|
callToDeny={args.callToDeny}
|
||||||
callToAction={args.callToAction}
|
callToAction={args.callToAction}
|
||||||
|
callToActionIconPosition="start"
|
||||||
|
callToActionIcon={<RiPlayFill />}
|
||||||
onClose={args.onClose}
|
onClose={args.onClose}
|
||||||
onSubmit={args.onSubmit}
|
onSubmit={args.onSubmit}
|
||||||
isLoading={args.isLoading}
|
isLoading={args.isLoading}
|
||||||
|
@ -4,11 +4,13 @@ import * as RadixDialog from '@radix-ui/react-dialog';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FaTimes } from 'react-icons/fa';
|
import { FaTimes } from 'react-icons/fa';
|
||||||
import { Button } from '../Button/Button';
|
import { Button, ButtonProps } from '../Button/Button';
|
||||||
|
|
||||||
export type FooterProps = {
|
export type FooterProps = {
|
||||||
callToAction: string;
|
callToAction: string;
|
||||||
callToActionLoadingText?: string;
|
callToActionLoadingText?: string;
|
||||||
|
callToActionIcon?: ButtonProps['icon'];
|
||||||
|
callToActionIconPosition?: ButtonProps['iconPosition'];
|
||||||
callToDeny?: string;
|
callToDeny?: string;
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -19,9 +21,13 @@ export type FooterProps = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const noop = () => null;
|
||||||
|
|
||||||
const Footer: React.VFC<FooterProps> = ({
|
const Footer: React.VFC<FooterProps> = ({
|
||||||
callToAction,
|
callToAction,
|
||||||
callToActionLoadingText = '',
|
callToActionLoadingText = '',
|
||||||
|
callToActionIcon,
|
||||||
|
callToActionIconPosition,
|
||||||
callToDeny,
|
callToDeny,
|
||||||
onClose,
|
onClose,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -31,7 +37,11 @@ const Footer: React.VFC<FooterProps> = ({
|
|||||||
onCancelAnalyticsName,
|
onCancelAnalyticsName,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const callToActionProps = onSubmit ? { onClick: onSubmit } : {};
|
const callToActionProps: ButtonProps = {
|
||||||
|
icon: callToActionIcon,
|
||||||
|
iconPosition: callToActionIconPosition,
|
||||||
|
onClick: onSubmit || noop,
|
||||||
|
};
|
||||||
|
|
||||||
const onSubmitAnalyticsAttributes = useGetAnalyticsAttributes(
|
const onSubmitAnalyticsAttributes = useGetAnalyticsAttributes(
|
||||||
onSubmitAnalyticsName
|
onSubmitAnalyticsName
|
||||||
|
Loading…
Reference in New Issue
Block a user