mirror of https://github.com/apache/druid.git
Update load query detail archive dialog for file input support (#15632)
* Update execution-submit-dialog for file input support Modified the execution-submit-dialog to support file inputs instead of text inputs for better usability. Users can now submit their queries by selecting a JSON file directly or dragging the file into the dialog. Made appropriate UI adjustments to accommodate this change in execution-submit-dialog styles file. * Update web-console/src/views/workbench-view/execution-submit-dialog/execution-submit-dialog.tsx Co-authored-by: Charles Smith <techdocsmith@gmail.com> * Update web-console/src/views/workbench-view/execution-submit-dialog/execution-submit-dialog.tsx Co-authored-by: Charles Smith <techdocsmith@gmail.com> * Update web-console/src/views/workbench-view/execution-submit-dialog/execution-submit-dialog.tsx Co-authored-by: Charles Smith <techdocsmith@gmail.com> * Update drag-and-drop instructions in execution-submit-dialog * Add snapshot tests for ExecutionSubmitDialog * prettify --------- Co-authored-by: Charles Smith <techdocsmith@gmail.com>
This commit is contained in:
parent
f445ba4d6b
commit
5b769a7d32
|
@ -0,0 +1,70 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ExecutionSubmitDialog matches snapshot 1`] = `
|
||||
<Blueprint4.Dialog
|
||||
backdropProps={
|
||||
Object {
|
||||
"onDragLeave": [Function],
|
||||
"onDragOver": [Function],
|
||||
"onDrop": [Function],
|
||||
}
|
||||
}
|
||||
canOutsideClickClose={false}
|
||||
className="execution-submit-dialog"
|
||||
isOpen={true}
|
||||
onClose={[Function]}
|
||||
title="Load query detail archive"
|
||||
>
|
||||
<div
|
||||
className="bp4-dialog-body"
|
||||
>
|
||||
<p>
|
||||
You can load query detail archive files from other Druid clusters to render the query detail here.
|
||||
</p>
|
||||
<p>
|
||||
To download the query detail archive for a query, click on the query in the
|
||||
|
||||
<Unknown>
|
||||
Recent query tasks
|
||||
</Unknown>
|
||||
panel in the query view.
|
||||
</p>
|
||||
<Blueprint4.FormGroup
|
||||
label="Select query detail archive file"
|
||||
>
|
||||
<Blueprint4.FileInput
|
||||
fill={true}
|
||||
hasSelection={false}
|
||||
inputProps={
|
||||
Object {
|
||||
"accept": ".json",
|
||||
}
|
||||
}
|
||||
onInputChange={[Function]}
|
||||
text="Choose file..."
|
||||
/>
|
||||
</Blueprint4.FormGroup>
|
||||
<p>
|
||||
Alternatively, drag a file directly onto this dialog.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="bp4-dialog-footer"
|
||||
>
|
||||
<div
|
||||
className="bp4-dialog-footer-actions"
|
||||
>
|
||||
<Blueprint4.Button
|
||||
onClick={[Function]}
|
||||
text="Close"
|
||||
/>
|
||||
<Blueprint4.Button
|
||||
disabled={true}
|
||||
intent="primary"
|
||||
onClick={[Function]}
|
||||
text="Submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Blueprint4.Dialog>
|
||||
`;
|
|
@ -21,11 +21,21 @@
|
|||
.execution-submit-dialog {
|
||||
&.#{$bp-ns}-dialog {
|
||||
top: 5%;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.execution-submit-dialog-textarea {
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$bp-ns}-overlay-backdrop.dragging-file {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
border: 3px dashed cyan;
|
||||
border-radius: 20px;
|
||||
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from '../../../utils/shallow-renderer';
|
||||
|
||||
import { ExecutionSubmitDialog } from './execution-submit-dialog';
|
||||
|
||||
describe('ExecutionSubmitDialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
const comp = shallow(<ExecutionSubmitDialog onSubmit={() => {}} onClose={() => {}} />);
|
||||
|
||||
expect(comp).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -16,10 +16,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
|
||||
import { Button, Classes, Code, Dialog, FileInput, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import * as JSONBig from 'json-bigint-native';
|
||||
import type { DragEvent } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
|
||||
import { Execution } from '../../../druid-models';
|
||||
import { AppToaster } from '../../../singletons';
|
||||
|
@ -28,6 +28,22 @@ import { offsetToRowColumn } from '../../../utils';
|
|||
|
||||
import './execution-submit-dialog.scss';
|
||||
|
||||
function getDraggedFile(ev: DragEvent<HTMLDivElement>): File | undefined {
|
||||
if (!ev.dataTransfer) return;
|
||||
|
||||
if (ev.dataTransfer.items) {
|
||||
// Use DataTransferItemList interface to access the file(s)
|
||||
const item = ev.dataTransfer.items[0];
|
||||
if (item.kind === 'file') {
|
||||
return item.getAsFile() || undefined;
|
||||
}
|
||||
} else {
|
||||
return ev.dataTransfer.files[0];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export interface ExecutionSubmitDialogProps {
|
||||
onSubmit(execution: Execution): void;
|
||||
onClose(): void;
|
||||
|
@ -37,14 +53,19 @@ export const ExecutionSubmitDialog = React.memo(function ExecutionSubmitDialog(
|
|||
props: ExecutionSubmitDialogProps,
|
||||
) {
|
||||
const { onClose, onSubmit } = props;
|
||||
const [archive, setArchive] = useState('');
|
||||
const [selectedFile, setSelectedFile] = useState<File | undefined>();
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
async function handleSubmit(): Promise<void> {
|
||||
if (!selectedFile) return;
|
||||
|
||||
const text = await selectedFile.text();
|
||||
|
||||
function handleSubmit(): void {
|
||||
let parsed: QueryDetailArchive;
|
||||
try {
|
||||
parsed = JSONBig.parse(archive);
|
||||
parsed = JSONBig.parse(text);
|
||||
} catch (e) {
|
||||
const rowColumn = typeof e.at === 'number' ? offsetToRowColumn(archive, e.at) : undefined;
|
||||
const rowColumn = typeof e.at === 'number' ? offsetToRowColumn(text, e.at) : undefined;
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: `Could not parse JSON: ${e.message}${
|
||||
|
@ -56,7 +77,8 @@ export const ExecutionSubmitDialog = React.memo(function ExecutionSubmitDialog(
|
|||
}
|
||||
|
||||
let execution: Execution | undefined;
|
||||
const detailArchiveVersion = parsed.detailArchiveVersion ?? (parsed as any).profileVersion;
|
||||
const detailArchiveVersion: unknown =
|
||||
parsed.detailArchiveVersion ?? (parsed as any).profileVersion;
|
||||
if (typeof detailArchiveVersion === 'number') {
|
||||
try {
|
||||
if (detailArchiveVersion === 2) {
|
||||
|
@ -102,42 +124,71 @@ export const ExecutionSubmitDialog = React.memo(function ExecutionSubmitDialog(
|
|||
return (
|
||||
<Dialog
|
||||
className="execution-submit-dialog"
|
||||
backdropClassName={dragging ? `dragging-file` : undefined}
|
||||
style={dragging ? { pointerEvents: 'none' } : undefined}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
title="Load query detail archive"
|
||||
backdropProps={{
|
||||
onDrop(ev: DragEvent<HTMLDivElement>) {
|
||||
// Prevent default behavior (Prevent file from being opened)
|
||||
ev.preventDefault();
|
||||
if (dragging) setDragging(false);
|
||||
|
||||
const droppedFile = getDraggedFile(ev);
|
||||
|
||||
if (droppedFile) {
|
||||
if (!droppedFile.name.endsWith('.json')) {
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: `The Query Detail Archive must be a .json file`,
|
||||
timeout: 5000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedFile(droppedFile);
|
||||
}
|
||||
},
|
||||
onDragOver(ev: DragEvent<HTMLDivElement>) {
|
||||
ev.preventDefault(); // Prevent default behavior (Prevent file from being opened)
|
||||
if (!dragging) setDragging(true);
|
||||
},
|
||||
onDragLeave(ev: DragEvent<HTMLDivElement>) {
|
||||
ev.preventDefault(); // Prevent default behavior (Prevent file from being opened)
|
||||
if (dragging) setDragging(false);
|
||||
},
|
||||
}}
|
||||
canOutsideClickClose={false}
|
||||
>
|
||||
<AceEditor
|
||||
mode="hjson"
|
||||
theme="solarized_dark"
|
||||
className="execution-submit-dialog-textarea placeholder-padding"
|
||||
onChange={setArchive}
|
||||
fontSize={12}
|
||||
showPrintMargin={false}
|
||||
showGutter
|
||||
highlightActiveLine
|
||||
value={archive}
|
||||
width="100%"
|
||||
setOptions={{
|
||||
showLineNumbers: true,
|
||||
tabSize: 2,
|
||||
newLineMode: 'unix' as any, // newLineMode is incorrectly assumed to be boolean in the typings
|
||||
}}
|
||||
style={{}}
|
||||
placeholder="{ Query detail archive or query report... }"
|
||||
onLoad={editor => {
|
||||
editor.renderer.setPadding(10);
|
||||
editor.renderer.setScrollMargin(10, 10, 0, 0);
|
||||
}}
|
||||
/>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<p>
|
||||
You can load query detail archive files from other Druid clusters to render the query
|
||||
detail here.
|
||||
</p>
|
||||
<p>
|
||||
To download the query detail archive for a query, click on the query in the{' '}
|
||||
<Code>Recent query tasks</Code> panel in the query view.
|
||||
</p>
|
||||
<FormGroup label="Select query detail archive file">
|
||||
<FileInput
|
||||
hasSelection={Boolean(selectedFile)}
|
||||
text={selectedFile?.name ?? 'Choose file...'}
|
||||
onInputChange={e => setSelectedFile((e.target as any).files[0])}
|
||||
inputProps={{ accept: '.json' }}
|
||||
fill
|
||||
/>
|
||||
</FormGroup>
|
||||
<p>Alternatively, drag a file directly onto this dialog.</p>
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button text="Close" onClick={onClose} />
|
||||
<Button
|
||||
text="Submit"
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={handleSubmit}
|
||||
disabled={!archive}
|
||||
onClick={() => void handleSubmit()}
|
||||
disabled={!selectedFile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue