mirror of https://github.com/apache/druid.git
Web console: show formatted JSON value (#16632)
* show formatted json value * update snapshot * window functions * count star can also have a window * better edit query context
This commit is contained in:
parent
4eced9b3c9
commit
51c73b5a4e
|
@ -5094,7 +5094,7 @@ license_category: binary
|
||||||
module: web-console
|
module: web-console
|
||||||
license_name: Apache License version 2.0
|
license_name: Apache License version 2.0
|
||||||
copyright: Imply Data
|
copyright: Imply Data
|
||||||
version: 0.22.15
|
version: 0.22.20
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ exports.SQL_KEYWORDS = [
|
||||||
'FULL',
|
'FULL',
|
||||||
'CROSS',
|
'CROSS',
|
||||||
'USING',
|
'USING',
|
||||||
|
'NATURAL',
|
||||||
'FETCH',
|
'FETCH',
|
||||||
'FIRST',
|
'FIRST',
|
||||||
'NEXT',
|
'NEXT',
|
||||||
|
@ -67,6 +68,8 @@ exports.SQL_KEYWORDS = [
|
||||||
'RANGE',
|
'RANGE',
|
||||||
'PRECEDING',
|
'PRECEDING',
|
||||||
'FOLLOWING',
|
'FOLLOWING',
|
||||||
|
'CURRENT',
|
||||||
|
'UNBOUNDED',
|
||||||
'EXTEND',
|
'EXTEND',
|
||||||
'PIVOT',
|
'PIVOT',
|
||||||
'UNPIVOT',
|
'UNPIVOT',
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"@blueprintjs/icons": "^4.16.0",
|
"@blueprintjs/icons": "^4.16.0",
|
||||||
"@blueprintjs/popover2": "^1.14.9",
|
"@blueprintjs/popover2": "^1.14.9",
|
||||||
"@blueprintjs/select": "^4.9.24",
|
"@blueprintjs/select": "^4.9.24",
|
||||||
"@druid-toolkit/query": "^0.22.15",
|
"@druid-toolkit/query": "^0.22.20",
|
||||||
"@druid-toolkit/visuals-core": "^0.3.3",
|
"@druid-toolkit/visuals-core": "^0.3.3",
|
||||||
"@druid-toolkit/visuals-react": "^0.3.3",
|
"@druid-toolkit/visuals-react": "^0.3.3",
|
||||||
"ace-builds": "~1.4.14",
|
"ace-builds": "~1.4.14",
|
||||||
|
@ -1005,9 +1005,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@druid-toolkit/query": {
|
"node_modules/@druid-toolkit/query": {
|
||||||
"version": "0.22.15",
|
"version": "0.22.20",
|
||||||
"resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.20.tgz",
|
||||||
"integrity": "sha512-LyQVIVkVNhduscf2wnBO/oGBvj353tS5ElIws20xQzApvEIwNNxmlkA+8npqwy77BkJj3nRQvlenbSEDHQdqow==",
|
"integrity": "sha512-GmmSd27y7zLVTjgTBQy+XoGeSSGhSDNmwyiwWtSua7I5LX8XqHV7Chi8HIH25YQoVgTK1pLK4RS8eRXxthRAzg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.5.2"
|
"tslib": "^2.5.2"
|
||||||
}
|
}
|
||||||
|
@ -19147,9 +19147,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@druid-toolkit/query": {
|
"@druid-toolkit/query": {
|
||||||
"version": "0.22.15",
|
"version": "0.22.20",
|
||||||
"resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@druid-toolkit/query/-/query-0.22.20.tgz",
|
||||||
"integrity": "sha512-LyQVIVkVNhduscf2wnBO/oGBvj353tS5ElIws20xQzApvEIwNNxmlkA+8npqwy77BkJj3nRQvlenbSEDHQdqow==",
|
"integrity": "sha512-GmmSd27y7zLVTjgTBQy+XoGeSSGhSDNmwyiwWtSua7I5LX8XqHV7Chi8HIH25YQoVgTK1pLK4RS8eRXxthRAzg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^2.5.2"
|
"tslib": "^2.5.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
"@blueprintjs/icons": "^4.16.0",
|
"@blueprintjs/icons": "^4.16.0",
|
||||||
"@blueprintjs/popover2": "^1.14.9",
|
"@blueprintjs/popover2": "^1.14.9",
|
||||||
"@blueprintjs/select": "^4.9.24",
|
"@blueprintjs/select": "^4.9.24",
|
||||||
"@druid-toolkit/query": "^0.22.15",
|
"@druid-toolkit/query": "^0.22.20",
|
||||||
"@druid-toolkit/visuals-core": "^0.3.3",
|
"@druid-toolkit/visuals-core": "^0.3.3",
|
||||||
"@druid-toolkit/visuals-react": "^0.3.3",
|
"@druid-toolkit/visuals-react": "^0.3.3",
|
||||||
"ace-builds": "~1.4.14",
|
"ace-builds": "~1.4.14",
|
||||||
|
|
|
@ -174,7 +174,9 @@ export const RecordTablePane = React.memo(function RecordTablePane(props: Record
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showValue && <ShowValueDialog onClose={() => setShowValue(undefined)} str={showValue} />}
|
{showValue && (
|
||||||
|
<ShowValueDialog onClose={() => setShowValue(undefined)} str={showValue} size="large" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,18 +57,102 @@ exports[`EditContextDialog matches snapshot 1`] = `
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
|
||||||
class="bp4-input"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
</textarea>
|
|
||||||
<div
|
<div
|
||||||
class="bp4-dialog-footer-actions"
|
class=" ace_editor ace_hidpi ace-solarized-dark ace_dark query-string"
|
||||||
|
id="ace-editor"
|
||||||
|
style="width: 100%; height: 100%; font-size: 12px;"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
class="ace_text-input"
|
||||||
|
spellcheck="false"
|
||||||
|
style="opacity: 0; font-size: 1px;"
|
||||||
|
wrap="off"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ace_gutter"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_gutter-layer ace_folding-enabled"
|
||||||
|
style="height: 1000000px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_scroller"
|
||||||
|
style="line-height: 0px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_print-margin-layer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_print-margin"
|
||||||
|
style="left: 4px; visibility: hidden;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_marker-layer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_text-layer"
|
||||||
|
style="height: 1000000px; margin: 0px 4px;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_marker-layer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="ace_layer ace_cursor-layer ace_hidden-cursors"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_cursor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar ace_scrollbar-v"
|
||||||
|
style="display: none; width: 20px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar-inner"
|
||||||
|
style="width: 20px;"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar ace_scrollbar-h"
|
||||||
|
style="display: none; height: 20px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ace_scrollbar-inner"
|
||||||
|
style="height: 20px;"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: hidden;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style="height: auto; width: auto; top: 0px; left: 0px; visibility: hidden; position: absolute; white-space: pre; overflow: visible;"
|
||||||
|
>
|
||||||
|
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bp4-dialog-footer"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="edit-context-dialog-buttons"
|
class="bp4-dialog-footer-actions"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="bp4-button"
|
class="bp4-button"
|
||||||
|
|
|
@ -20,25 +20,6 @@
|
||||||
|
|
||||||
.edit-context-dialog {
|
.edit-context-dialog {
|
||||||
&.#{$bp-ns}-dialog {
|
&.#{$bp-ns}-dialog {
|
||||||
padding-bottom: 10px;
|
height: 60vh;
|
||||||
}
|
|
||||||
|
|
||||||
.#{$bp-ns}-input {
|
|
||||||
margin: 10px;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$bp-ns}-dialog-footer-actions {
|
|
||||||
padding: 0px 10px 0px 10px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 340px 1fr;
|
|
||||||
grid-template-areas: 'error buttons';
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-context-dialog-error {
|
|
||||||
grid-area: error;
|
|
||||||
}
|
|
||||||
.edit-context-dialog-buttons {
|
|
||||||
grid-area: buttons;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,11 @@ import { EditContextDialog } from './edit-context-dialog';
|
||||||
describe('EditContextDialog', () => {
|
describe('EditContextDialog', () => {
|
||||||
it('matches snapshot', () => {
|
it('matches snapshot', () => {
|
||||||
const compactionDialog = (
|
const compactionDialog = (
|
||||||
<EditContextDialog queryContext={{}} onQueryContextChange={() => null} onClose={() => {}} />
|
<EditContextDialog
|
||||||
|
initQueryContext={{}}
|
||||||
|
onQueryContextChange={() => null}
|
||||||
|
onClose={() => {}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
render(compactionDialog);
|
render(compactionDialog);
|
||||||
expect(document.body.lastChild).toMatchSnapshot();
|
expect(document.body.lastChild).toMatchSnapshot();
|
||||||
|
|
|
@ -16,86 +16,72 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Callout, Classes, Dialog, Intent, TextArea } from '@blueprintjs/core';
|
import { Button, Classes, Dialog, Intent } from '@blueprintjs/core';
|
||||||
import Hjson from 'hjson';
|
import Hjson from 'hjson';
|
||||||
import * as JSONBig from 'json-bigint-native';
|
import * as JSONBig from 'json-bigint-native';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import AceEditor from 'react-ace';
|
||||||
|
|
||||||
import type { QueryContext } from '../../druid-models';
|
import type { QueryContext } from '../../druid-models';
|
||||||
|
import { AppToaster } from '../../singletons';
|
||||||
|
|
||||||
import './edit-context-dialog.scss';
|
import './edit-context-dialog.scss';
|
||||||
|
|
||||||
export interface EditContextDialogProps {
|
function formatContext(context: QueryContext | undefined): string {
|
||||||
queryContext: QueryContext;
|
const str = JSONBig.stringify(context || {}, undefined, 2);
|
||||||
onQueryContextChange: (queryContext: QueryContext) => void;
|
return str === '{}' ? '{\n\n}' : str;
|
||||||
onClose: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditContextDialogState {
|
export interface EditContextDialogProps {
|
||||||
queryContextString: string;
|
initQueryContext: QueryContext | undefined;
|
||||||
queryContext?: QueryContext;
|
onQueryContextChange(queryContext: QueryContext): void;
|
||||||
error?: string;
|
onClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditContextDialog = React.memo(function EditContextDialog(
|
export const EditContextDialog = React.memo(function EditContextDialog(
|
||||||
props: EditContextDialogProps,
|
props: EditContextDialogProps,
|
||||||
) {
|
) {
|
||||||
const { onQueryContextChange, onClose } = props;
|
const { initQueryContext, onQueryContextChange, onClose } = props;
|
||||||
const [state, setState] = useState<EditContextDialogState>(() => ({
|
const [queryContextString, setQueryContextString] = useState<string>(
|
||||||
queryContext: props.queryContext,
|
formatContext(initQueryContext),
|
||||||
queryContextString: Object.keys(props.queryContext).length
|
);
|
||||||
? JSONBig.stringify(props.queryContext, undefined, 2)
|
|
||||||
: '{\n\n}',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { queryContext, queryContextString, error } = state;
|
|
||||||
|
|
||||||
function handleTextChange(e: any) {
|
|
||||||
const queryContextString = (e.target as HTMLInputElement).value;
|
|
||||||
|
|
||||||
let error: string | undefined;
|
|
||||||
let queryContext: QueryContext | undefined;
|
|
||||||
try {
|
|
||||||
queryContext = Hjson.parse(queryContextString);
|
|
||||||
} catch (e) {
|
|
||||||
error = e.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!error && (!queryContext || typeof queryContext !== 'object')) {
|
|
||||||
error = 'Input is not a valid object';
|
|
||||||
queryContext = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState({
|
|
||||||
queryContextString,
|
|
||||||
queryContext,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog className="edit-context-dialog" isOpen onClose={onClose} title="Edit query context">
|
<Dialog className="edit-context-dialog" isOpen onClose={onClose} title="Edit query context">
|
||||||
<TextArea value={queryContextString} onChange={handleTextChange} autoFocus />
|
<AceEditor
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
mode="hjson"
|
||||||
{error && (
|
theme="solarized_dark"
|
||||||
<Callout intent={Intent.DANGER} className="edit-context-dialog-error">
|
className="query-string"
|
||||||
{error}
|
name="ace-editor"
|
||||||
</Callout>
|
fontSize={12}
|
||||||
)}
|
width="100%"
|
||||||
<div className="edit-context-dialog-buttons">
|
height="100%"
|
||||||
|
showGutter
|
||||||
|
showPrintMargin={false}
|
||||||
|
value={queryContextString}
|
||||||
|
onChange={v => setQueryContextString(v)}
|
||||||
|
/>
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button text="Close" onClick={onClose} />
|
<Button text="Close" onClick={onClose} />
|
||||||
<Button
|
<Button
|
||||||
text="Save"
|
text="Save"
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
disabled={Boolean(error)}
|
onClick={() => {
|
||||||
onClick={
|
let queryContext: QueryContext;
|
||||||
queryContext
|
try {
|
||||||
? () => {
|
queryContext = Hjson.parse(queryContextString);
|
||||||
onQueryContextChange(queryContext);
|
} catch (e) {
|
||||||
onClose();
|
AppToaster.show({
|
||||||
}
|
message: e.message,
|
||||||
: undefined
|
intent: Intent.DANGER,
|
||||||
}
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryContextChange(queryContext);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -57,53 +57,61 @@ exports[`ShowValueDialog matches snapshot 1`] = `
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
|
||||||
class="bp4-input"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
Bot: Automatska zamjena teksta (-[[Administrativna podjela Meksika|Admin]] +[[Administrativna podjela Meksika|Admi]])
|
|
||||||
</textarea>
|
|
||||||
<div
|
<div
|
||||||
class="bp4-dialog-footer-actions"
|
class="bp4-dialog-body"
|
||||||
>
|
>
|
||||||
<button
|
<textarea
|
||||||
class="bp4-button"
|
class="bp4-input"
|
||||||
type="button"
|
spellcheck="false"
|
||||||
>
|
>
|
||||||
<span
|
Bot: Automatska zamjena teksta (-[[Administrativna podjela Meksika|Admin]] +[[Administrativna podjela Meksika|Admi]])
|
||||||
aria-hidden="true"
|
</textarea>
|
||||||
class="bp4-icon bp4-icon-duplicate"
|
</div>
|
||||||
icon="duplicate"
|
<div
|
||||||
|
class="bp4-dialog-footer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bp4-dialog-footer-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="bp4-button"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<svg
|
<span
|
||||||
data-icon="duplicate"
|
aria-hidden="true"
|
||||||
height="16"
|
class="bp4-icon bp4-icon-duplicate"
|
||||||
role="img"
|
icon="duplicate"
|
||||||
viewBox="0 0 16 16"
|
|
||||||
width="16"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
d="M15 0H5c-.55 0-1 .45-1 1v2h2V2h8v7h-1v2h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 4H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-1 10H2V6h8v8z"
|
data-icon="duplicate"
|
||||||
fill-rule="evenodd"
|
height="16"
|
||||||
/>
|
role="img"
|
||||||
</svg>
|
viewBox="0 0 16 16"
|
||||||
</span>
|
width="16"
|
||||||
<span
|
>
|
||||||
class="bp4-button-text"
|
<path
|
||||||
|
d="M15 0H5c-.55 0-1 .45-1 1v2h2V2h8v7h-1v2h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 4H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-1 10H2V6h8v8z"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="bp4-button-text"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="bp4-button bp4-intent-primary"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Copy
|
<span
|
||||||
</span>
|
class="bp4-button-text"
|
||||||
</button>
|
>
|
||||||
<button
|
Close
|
||||||
class="bp4-button bp4-intent-primary"
|
</span>
|
||||||
type="button"
|
</button>
|
||||||
>
|
</div>
|
||||||
<span
|
|
||||||
class="bp4-button-text"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,10 +19,6 @@
|
||||||
@import '../../variables';
|
@import '../../variables';
|
||||||
|
|
||||||
.show-value-dialog {
|
.show-value-dialog {
|
||||||
&.#{$bp-ns}-dialog {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.normal.#{$bp-ns}-dialog {
|
&.normal.#{$bp-ns}-dialog {
|
||||||
height: 600px;
|
height: 600px;
|
||||||
}
|
}
|
||||||
|
@ -32,12 +28,21 @@
|
||||||
height: 90vh;
|
height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$bp-ns}-input {
|
.#{$bp-ns}-dialog-body {
|
||||||
margin: 10px;
|
display: flex;
|
||||||
flex: 1;
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ace-editor {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$bp-ns}-input {
|
||||||
|
flex: 1;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$bp-ns}-dialog-footer-actions {
|
.#{$bp-ns}-dialog-footer {
|
||||||
padding-right: 10px;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,21 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Classes, Dialog, Intent, TextArea } from '@blueprintjs/core';
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Classes,
|
||||||
|
Dialog,
|
||||||
|
FormGroup,
|
||||||
|
Intent,
|
||||||
|
TextArea,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import React from 'react';
|
import * as JSONBig from 'json-bigint-native';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import AceEditor from 'react-ace';
|
||||||
|
|
||||||
import { AppToaster } from '../../singletons';
|
import { AppToaster } from '../../singletons';
|
||||||
|
|
||||||
|
@ -35,6 +45,15 @@ export interface ShowValueDialogProps {
|
||||||
|
|
||||||
export const ShowValueDialog = React.memo(function ShowValueDialog(props: ShowValueDialogProps) {
|
export const ShowValueDialog = React.memo(function ShowValueDialog(props: ShowValueDialogProps) {
|
||||||
const { title, onClose, str, size } = props;
|
const { title, onClose, str, size } = props;
|
||||||
|
const [tab, setTab] = useState<'formatted' | 'raw'>('formatted');
|
||||||
|
|
||||||
|
const parsed = useMemo(() => {
|
||||||
|
try {
|
||||||
|
return JSONBig.parse(str);
|
||||||
|
} catch {}
|
||||||
|
}, [str]);
|
||||||
|
|
||||||
|
const hasParsed = typeof parsed !== 'undefined';
|
||||||
|
|
||||||
function handleCopy() {
|
function handleCopy() {
|
||||||
copy(str, { format: 'text/plain' });
|
copy(str, { format: 'text/plain' });
|
||||||
|
@ -51,10 +70,41 @@ export const ShowValueDialog = React.memo(function ShowValueDialog(props: ShowVa
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={title || 'Full value'}
|
title={title || 'Full value'}
|
||||||
>
|
>
|
||||||
<TextArea value={str} spellCheck={false} />
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
{hasParsed && (
|
||||||
<Button icon={IconNames.DUPLICATE} text="Copy" onClick={handleCopy} />
|
<FormGroup>
|
||||||
<Button text="Close" intent={Intent.PRIMARY} onClick={onClose} />
|
<ButtonGroup fill>
|
||||||
|
<Button
|
||||||
|
text="Formatted"
|
||||||
|
active={tab === 'formatted'}
|
||||||
|
onClick={() => setTab('formatted')}
|
||||||
|
/>
|
||||||
|
<Button text="Raw" active={tab === 'raw'} onClick={() => setTab('raw')} />
|
||||||
|
</ButtonGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
{hasParsed && tab === 'formatted' && (
|
||||||
|
<AceEditor
|
||||||
|
mode="hjson"
|
||||||
|
theme="solarized_dark"
|
||||||
|
className="query-string"
|
||||||
|
name="ace-editor"
|
||||||
|
fontSize={12}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
showGutter
|
||||||
|
showPrintMargin={false}
|
||||||
|
value={JSONBig.stringify(parsed, undefined, 2)}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(!hasParsed || tab === 'raw') && <TextArea value={str} spellCheck={false} />}
|
||||||
|
</div>
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button icon={IconNames.DUPLICATE} text="Copy" onClick={handleCopy} />
|
||||||
|
<Button text="Close" intent={Intent.PRIMARY} onClick={onClose} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -136,7 +136,7 @@ export const StringMenuItems = React.memo(function StringMenuItems(props: String
|
||||||
<MenuItem icon={IconNames.FUNCTION} text="Aggregate">
|
<MenuItem icon={IconNames.FUNCTION} text="Aggregate">
|
||||||
{aggregateMenuItem(F.countDistinct(column), `dist_${columnName}`)}
|
{aggregateMenuItem(F.countDistinct(column), `dist_${columnName}`)}
|
||||||
{aggregateMenuItem(
|
{aggregateMenuItem(
|
||||||
F.count().addWhereExpression(column.equal(SqlPlaceholder.PLACEHOLDER)),
|
F.count().addWhere(column.equal(SqlPlaceholder.PLACEHOLDER)),
|
||||||
`filtered_dist_${columnName}`,
|
`filtered_dist_${columnName}`,
|
||||||
false,
|
false,
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -657,7 +657,9 @@ export const ResultTablePane = React.memo(function ResultTablePane(props: Result
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showValue && <ShowValueDialog onClose={() => setShowValue(undefined)} str={showValue} />}
|
{showValue && (
|
||||||
|
<ShowValueDialog onClose={() => setShowValue(undefined)} str={showValue} size="large" />
|
||||||
|
)}
|
||||||
{editingExpression && (
|
{editingExpression && (
|
||||||
<ExpressionEditorDialog
|
<ExpressionEditorDialog
|
||||||
includeOutputName
|
includeOutputName
|
||||||
|
|
|
@ -535,7 +535,7 @@ export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
|
||||||
)}
|
)}
|
||||||
{editContextDialogOpen && (
|
{editContextDialogOpen && (
|
||||||
<EditContextDialog
|
<EditContextDialog
|
||||||
queryContext={queryContext}
|
initQueryContext={queryContext}
|
||||||
onQueryContextChange={changeQueryContext}
|
onQueryContextChange={changeQueryContext}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setEditContextDialogOpen(false);
|
setEditContextDialogOpen(false);
|
||||||
|
|
Loading…
Reference in New Issue