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:
Vadim Ogievetsky 2024-01-10 20:48:46 -08:00 committed by GitHub
parent f445ba4d6b
commit 5b769a7d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 200 additions and 38 deletions

View File

@ -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>
`;

View File

@ -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: '';
}
}

View File

@ -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();
});
});

View File

@ -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>