This is an automated email from the ASF dual-hosted git repository.
vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new ec28672d07 Web console: allow format picking for download (#14794)
ec28672d07 is described below
commit ec28672d072eeb75ac1a4d84a201c7d051ebe629
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Fri Aug 11 09:43:29 2023 -0700
Web console: allow format picking for download (#14794)
* allow format picking for download
* better popover
* ux review tweaks
---
.../destination-pages-dialog.tsx | 7 +-
.../destination-pages-pane.tsx | 83 ++++++++++++++++++----
2 files changed, 70 insertions(+), 20 deletions(-)
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 1e40aefdd0..68c66487e3 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
<div className={Classes.DIALOG_BODY}>
<DestinationPagesPane execution={execution} />
</div>
- <div className={Classes.DIALOG_FOOTER}>
- <div className={Classes.DIALOG_FOOTER_ACTIONS}>
- <Button text="Close" onClick={onClose} />
- </div>
- </div>
</Dialog>
);
});
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 20f68a8af6..f850097da2 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<ResultFormat, string> = {
+ 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<ResultFormat>('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')}. `}
+ </p>
+ <p>
+ Format when downloading:{' '}
+ <Popover2
+ minimal
+ position={Position.BOTTOM_LEFT}
+ content={
+ <Menu>
+ {RESULT_FORMATS.map((resultFormat, i) => (
+ <MenuItem
+ key={i}
+ icon={tickIcon(desiredResultFormat === resultFormat)}
+ text={RESULT_FORMAT_LABEL[resultFormat]}
+ label={resultFormat}
+ onClick={() => setDesiredResultFormat(resultFormat)}
+ />
+ ))}
+ </Menu>
+ }
+ >
+ <Button
+ text={RESULT_FORMAT_LABEL[desiredResultFormat]}
+ rightIcon={IconNames.CARET_DOWN}
+ />
+ </Popover2>{' '}
{pages.length > 1 && (
<Button
+ intent={Intent.PRIMARY}
icon={IconNames.DOWNLOAD}
- text={`Download all data (as ${pluralIfNeeded(pages.length,
'file')})`}
- minimal
+ text={`Download all data (${pluralIfNeeded(pages.length,
'file')})`}
onClick={() => void downloadAllPages()}
/>
)}
</p>
<ReactTable
- className="padded-header"
data={pages}
loading={false}
- sortable
- defaultSorted={[{ id: 'id', desc: false }]}
- defaultPageSize={clamp(pages.length, 1, MAX_DETAIL_ROWS)}
- showPagination={pages.length > 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 }) => (
<AnchorButton
icon={IconNames.DOWNLOAD}
- text="download .jsonl"
+ text="Download"
minimal
href={getPageUrl(value)}
download={getPageFilename(value)}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]