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) => (
+ {' '}
{pages.length > 1 && (
MAX_DETAIL_ROWS}
+ sortable={false}
+ defaultPageSize={clamp(pages.length, 1, SMALL_TABLE_PAGE_SIZE)}
+ showPagination={pages.length > SMALL_TABLE_PAGE_SIZE}
columns={[
{
Header: 'Page number',
@@ -116,12 +172,11 @@ export const DestinationPagesPane = React.memo(function DestinationPagesPane(
Header: '',
id: 'download',
accessor: 'id',
- sortable: false,
width: 300,
Cell: ({ value }) => (