diff --git a/web-console/src/views/workbench-view/destination-pages-dialog/destination-pages-dialog.tsx b/web-console/src/views/workbench-view/destination-pages-dialog/destination-pages-dialog.tsx index 1e40aefdd08..68c66487e3c 100644 --- a/web-console/src/views/workbench-view/destination-pages-dialog/destination-pages-dialog.tsx +++ b/web-console/src/views/workbench-view/destination-pages-dialog/destination-pages-dialog.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import { Button, Classes, Dialog } from '@blueprintjs/core'; +import { Classes, Dialog } from '@blueprintjs/core'; import React from 'react'; import type { Execution } from '../../../druid-models'; @@ -39,11 +39,6 @@ export const DestinationPagesDialog = React.memo(function DestinationPagesDialog
-
-
-
-
); }); diff --git a/web-console/src/views/workbench-view/destination-pages-pane/destination-pages-pane.tsx b/web-console/src/views/workbench-view/destination-pages-pane/destination-pages-pane.tsx index 20f68a8af69..f850097da26 100644 --- a/web-console/src/views/workbench-view/destination-pages-pane/destination-pages-pane.tsx +++ b/web-console/src/views/workbench-view/destination-pages-pane/destination-pages-pane.tsx @@ -16,12 +16,14 @@ * limitations under the License. */ -import { AnchorButton, Button } from '@blueprintjs/core'; +import { AnchorButton, Button, Intent, Menu, MenuItem, Position } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import React from 'react'; +import { Popover2 } from '@blueprintjs/popover2'; +import React, { useState } from 'react'; import ReactTable from 'react-table'; import type { Execution } from '../../../druid-models'; +import { SMALL_TABLE_PAGE_SIZE } from '../../../react-table'; import { Api, UrlBaser } from '../../../singletons'; import { clamp, @@ -29,10 +31,36 @@ import { formatBytes, formatInteger, pluralIfNeeded, + tickIcon, wait, } from '../../../utils'; -const MAX_DETAIL_ROWS = 20; +type ResultFormat = 'object' | 'array' | 'objectLines' | 'arrayLines' | 'csv'; + +const RESULT_FORMATS: ResultFormat[] = ['objectLines', 'object', 'arrayLines', 'array', 'csv']; + +function resultFormatToExtension(resultFormat: ResultFormat): string { + switch (resultFormat) { + case 'object': + case 'array': + return 'json'; + + case 'objectLines': + case 'arrayLines': + return 'jsonl'; + + case 'csv': + return 'csv'; + } +} + +const RESULT_FORMAT_LABEL: Record = { + object: 'Array of objects', + array: 'Array of arrays', + objectLines: 'JSON Lines', + arrayLines: 'JSON Lines but every row is an array', + csv: 'CSV', +}; interface DestinationPagesPaneProps { execution: Execution; @@ -42,6 +70,9 @@ export const DestinationPagesPane = React.memo(function DestinationPagesPane( props: DestinationPagesPaneProps, ) { const { execution } = props; + const [desiredResultFormat, setDesiredResultFormat] = useState('objectLines'); + const desiredExtension = resultFormatToExtension(desiredResultFormat); + const destination = execution.destination; const pages = execution.destinationPages; if (!pages) return null; @@ -50,11 +81,13 @@ export const DestinationPagesPane = React.memo(function DestinationPagesPane( const numTotalRows = destination?.numTotalRows; function getPageUrl(pageIndex: number) { - return UrlBaser.base(`/druid/v2/sql/statements/${id}/results?page=${pageIndex}`); + return UrlBaser.base( + `/druid/v2/sql/statements/${id}/results?page=${pageIndex}&resultFormat=${desiredResultFormat}`, + ); } function getPageFilename(pageIndex: number) { - return `${id}_page${pageIndex}.jsonl`; + return `${id}_page${pageIndex}.${desiredExtension}`; } async function downloadAllPages() { @@ -71,23 +104,46 @@ export const DestinationPagesPane = React.memo(function DestinationPagesPane( {`${ typeof numTotalRows === 'number' ? pluralIfNeeded(numTotalRows, 'row') : 'Results' } have been written to ${pluralIfNeeded(pages.length, 'page')}. `} +

+

+ Format when downloading:{' '} + + {RESULT_FORMATS.map((resultFormat, i) => ( + setDesiredResultFormat(resultFormat)} + /> + ))} + + } + > +