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 {
|
.execution-submit-dialog {
|
||||||
&.#{$bp-ns}-dialog {
|
&.#{$bp-ns}-dialog {
|
||||||
top: 5%;
|
top: 5%;
|
||||||
width: 90%;
|
width: 500px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.execution-submit-dialog-textarea {
|
|
||||||
margin-bottom: 10px;
|
.#{$bp-ns}-overlay-backdrop.dragging-file {
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.25);
|
&::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.
|
* 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 * as JSONBig from 'json-bigint-native';
|
||||||
|
import type { DragEvent } from 'react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import AceEditor from 'react-ace';
|
|
||||||
|
|
||||||
import { Execution } from '../../../druid-models';
|
import { Execution } from '../../../druid-models';
|
||||||
import { AppToaster } from '../../../singletons';
|
import { AppToaster } from '../../../singletons';
|
||||||
|
@ -28,6 +28,22 @@ import { offsetToRowColumn } from '../../../utils';
|
||||||
|
|
||||||
import './execution-submit-dialog.scss';
|
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 {
|
export interface ExecutionSubmitDialogProps {
|
||||||
onSubmit(execution: Execution): void;
|
onSubmit(execution: Execution): void;
|
||||||
onClose(): void;
|
onClose(): void;
|
||||||
|
@ -37,14 +53,19 @@ export const ExecutionSubmitDialog = React.memo(function ExecutionSubmitDialog(
|
||||||
props: ExecutionSubmitDialogProps,
|
props: ExecutionSubmitDialogProps,
|
||||||
) {
|
) {
|
||||||
const { onClose, onSubmit } = props;
|
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;
|
let parsed: QueryDetailArchive;
|
||||||
try {
|
try {
|
||||||
parsed = JSONBig.parse(archive);
|
parsed = JSONBig.parse(text);
|
||||||
} catch (e) {
|
} 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({
|
AppToaster.show({
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
message: `Could not parse JSON: ${e.message}${
|
message: `Could not parse JSON: ${e.message}${
|
||||||
|
@ -56,7 +77,8 @@ export const ExecutionSubmitDialog = React.memo(function ExecutionSubmitDialog(
|
||||||
}
|
}
|
||||||
|
|
||||||
let execution: Execution | undefined;
|
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') {
|
if (typeof detailArchiveVersion === 'number') {
|
||||||
try {
|
try {
|
||||||
if (detailArchiveVersion === 2) {
|
if (detailArchiveVersion === 2) {
|
||||||
|
@ -102,42 +124,71 @@ export const ExecutionSubmitDialog = React.memo(function ExecutionSubmitDialog(
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
className="execution-submit-dialog"
|
className="execution-submit-dialog"
|
||||||
|
backdropClassName={dragging ? `dragging-file` : undefined}
|
||||||
|
style={dragging ? { pointerEvents: 'none' } : undefined}
|
||||||
isOpen
|
isOpen
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title="Load query detail archive"
|
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}
|
canOutsideClickClose={false}
|
||||||
>
|
>
|
||||||
<AceEditor
|
<div className={Classes.DIALOG_BODY}>
|
||||||
mode="hjson"
|
<p>
|
||||||
theme="solarized_dark"
|
You can load query detail archive files from other Druid clusters to render the query
|
||||||
className="execution-submit-dialog-textarea placeholder-padding"
|
detail here.
|
||||||
onChange={setArchive}
|
</p>
|
||||||
fontSize={12}
|
<p>
|
||||||
showPrintMargin={false}
|
To download the query detail archive for a query, click on the query in the{' '}
|
||||||
showGutter
|
<Code>Recent query tasks</Code> panel in the query view.
|
||||||
highlightActiveLine
|
</p>
|
||||||
value={archive}
|
<FormGroup label="Select query detail archive file">
|
||||||
width="100%"
|
<FileInput
|
||||||
setOptions={{
|
hasSelection={Boolean(selectedFile)}
|
||||||
showLineNumbers: true,
|
text={selectedFile?.name ?? 'Choose file...'}
|
||||||
tabSize: 2,
|
onInputChange={e => setSelectedFile((e.target as any).files[0])}
|
||||||
newLineMode: 'unix' as any, // newLineMode is incorrectly assumed to be boolean in the typings
|
inputProps={{ accept: '.json' }}
|
||||||
}}
|
fill
|
||||||
style={{}}
|
/>
|
||||||
placeholder="{ Query detail archive or query report... }"
|
</FormGroup>
|
||||||
onLoad={editor => {
|
<p>Alternatively, drag a file directly onto this dialog.</p>
|
||||||
editor.renderer.setPadding(10);
|
</div>
|
||||||
editor.renderer.setScrollMargin(10, 10, 0, 0);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button text="Close" onClick={onClose} />
|
<Button text="Close" onClick={onClose} />
|
||||||
<Button
|
<Button
|
||||||
text="Submit"
|
text="Submit"
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
onClick={handleSubmit}
|
onClick={() => void handleSubmit()}
|
||||||
disabled={!archive}
|
disabled={!selectedFile}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue