mirror of https://github.com/apache/druid.git
Web console: more explicit limit on run button (#8378)
* update sql doc parsing * keyword fixes * fix header default * tidy * fix tests
This commit is contained in:
parent
368ace4e87
commit
20ea90a5a6
|
@ -10,7 +10,7 @@ coordinator-console/
|
|||
pages/
|
||||
index.html
|
||||
|
||||
lib/sql-function-doc.ts
|
||||
lib/sql-function-doc.js
|
||||
|
||||
.tscache
|
||||
tscommand-*.tmp.txt
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const SQL_KEYWORDS: string[];
|
||||
export const SQL_EXPRESSION_PARTS: string[];
|
||||
export const SQL_CONSTANTS: string[];
|
||||
export const SQL_DYNAMICS: string[];
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
// Hand picked from https://druid.apache.org/docs/latest/querying/sql.html
|
||||
|
||||
export const SQL_KEYWORDS: string[] = [
|
||||
exports.SQL_KEYWORDS = [
|
||||
'EXPLAIN PLAN FOR',
|
||||
'WITH',
|
||||
'AS',
|
||||
|
@ -36,7 +36,7 @@ export const SQL_KEYWORDS: string[] = [
|
|||
'UNION ALL',
|
||||
];
|
||||
|
||||
export const SQL_EXPRESSION_PARTS: string[] = [
|
||||
exports.SQL_EXPRESSION_PARTS = [
|
||||
'FILTER',
|
||||
'END',
|
||||
'ELSE',
|
||||
|
@ -69,6 +69,6 @@ export const SQL_EXPRESSION_PARTS: string[] = [
|
|||
'INTERVAL',
|
||||
];
|
||||
|
||||
export const SQL_CONSTANTS: string[] = ['NULL', 'FALSE', 'TRUE'];
|
||||
exports.SQL_CONSTANTS = ['NULL', 'FALSE', 'TRUE'];
|
||||
|
||||
export const SQL_DYNAMICS: string[] = ['CURRENT_TIMESTAMP', 'CURRENT_DATE'];
|
||||
exports.SQL_DYNAMICS = ['CURRENT_TIMESTAMP', 'CURRENT_DATE'];
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface SyntaxDescription {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface FunctionSyntaxDescription extends SyntaxDescription {
|
||||
arguments: string;
|
||||
}
|
||||
|
||||
export const SQL_DATA_TYPES: SyntaxDescription[];
|
||||
export const SQL_FUNCTIONS: FunctionSyntaxDescription[];
|
|
@ -4428,9 +4428,9 @@
|
|||
"integrity": "sha512-0sYnfUHHMoajaud/i5BHKA12bUxiWEHJ9rxGqVEppFxsEcxef0TZQ5J59lU+UniEBcz/sG5fTESRyS7cOm3tSQ=="
|
||||
},
|
||||
"druid-query-toolkit": {
|
||||
"version": "0.3.23",
|
||||
"resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.3.23.tgz",
|
||||
"integrity": "sha512-6wVAGFw1sjLT9U5f7QNaIKCS0VgUfWLn/X8YWf3YQN3awSCxClzFUQnLKnHeIEb7ot0ca4H6axlb7NpzGqmtzA==",
|
||||
"version": "0.3.24",
|
||||
"resolved": "https://registry.npmjs.org/druid-query-toolkit/-/druid-query-toolkit-0.3.24.tgz",
|
||||
"integrity": "sha512-kFvEXAjjNuJYpeRsAzzO/cJ2rr4nHBGTSCAA4UPxyt4pKNZE/OUap7IQbsdnxYmhkHgfjUBGcFteufaVHSn7SA==",
|
||||
"requires": {
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
"d3": "^5.10.1",
|
||||
"d3-array": "^2.3.1",
|
||||
"druid-console": "0.0.2",
|
||||
"druid-query-toolkit": "^0.3.23",
|
||||
"druid-query-toolkit": "^0.3.24",
|
||||
"file-saver": "^2.0.2",
|
||||
"has-own-prop": "^2.0.0",
|
||||
"hjson": "^3.1.2",
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
# limitations under the License.
|
||||
|
||||
rm -rf \
|
||||
lib/*.css \
|
||||
lib/*.ts \
|
||||
lib/react-table.css \
|
||||
lib/sql-function-doc.js \
|
||||
node_modules \
|
||||
coordinator-console \
|
||||
pages \
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
const fs = require('fs-extra');
|
||||
|
||||
const readfile = '../docs/querying/sql.md';
|
||||
const writefile = 'lib/sql-function-doc.ts';
|
||||
const writefile = 'lib/sql-function-doc.js';
|
||||
|
||||
const readDoc = async () => {
|
||||
const data = await fs.readFile(readfile, 'utf-8');
|
||||
|
@ -30,18 +30,19 @@ const readDoc = async () => {
|
|||
const functionDocs = [];
|
||||
const dataTypeDocs = [];
|
||||
for (let line of lines) {
|
||||
const functionMatch = line.match(/^\|`(.+\(.*\))`\|(.+)\|$/);
|
||||
const functionMatch = line.match(/^\|`(\w+)(\(.*\))`\|(.+)\|$/);
|
||||
if (functionMatch) {
|
||||
functionDocs.push({
|
||||
syntax: functionMatch[1],
|
||||
description: functionMatch[2],
|
||||
name: functionMatch[1],
|
||||
arguments: functionMatch[2],
|
||||
description: functionMatch[3],
|
||||
});
|
||||
}
|
||||
|
||||
const dataTypeMatch = line.match(/^\|([A-Z]+)\|([A-Z]+)\|(.*)\|(.*)\|$/);
|
||||
if (dataTypeMatch) {
|
||||
dataTypeDocs.push({
|
||||
syntax: dataTypeMatch[1],
|
||||
name: dataTypeMatch[1],
|
||||
description: dataTypeMatch[4] || `Druid runtime type: ${dataTypeMatch[2]}`,
|
||||
});
|
||||
}
|
||||
|
@ -81,16 +82,11 @@ const readDoc = async () => {
|
|||
|
||||
// This file is auto generated and should not be modified
|
||||
|
||||
export interface SyntaxDescription {
|
||||
syntax: string;
|
||||
description: string;
|
||||
}
|
||||
// prettier-ignore
|
||||
exports.SQL_DATA_TYPES = ${JSON.stringify(dataTypeDocs, null, 2)};
|
||||
|
||||
// prettier-ignore
|
||||
export const SQL_FUNCTIONS: SyntaxDescription[] = ${JSON.stringify(functionDocs, null, 2)};
|
||||
|
||||
// prettier-ignore
|
||||
export const SQL_DATE_TYPES: SyntaxDescription[] = ${JSON.stringify(dataTypeDocs, null, 2)};
|
||||
exports.SQL_FUNCTIONS = ${JSON.stringify(functionDocs, null, 2)};
|
||||
`;
|
||||
|
||||
await fs.writeFile(writefile, content, 'utf-8');
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
// Originally licensed under the MIT license (https://github.com/thlorenz/brace/blob/master/LICENSE)
|
||||
// This file was modified to make the list of keywords more closely adhere to what is found in DruidSQL
|
||||
|
||||
var druidKeywords = require('../../lib/keywords');
|
||||
var druidFunctions = require('../../lib/sql-function-doc');
|
||||
|
||||
ace.define(
|
||||
'ace/mode/dsql_highlight_rules',
|
||||
['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'],
|
||||
|
@ -31,20 +34,25 @@ ace.define(
|
|||
var TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules;
|
||||
|
||||
var SqlHighlightRules = function() {
|
||||
var keywords =
|
||||
'select|from|where|and|or|group|by|order|limit|offset|having|as|case|' +
|
||||
'when|else|end|type|on|desc|asc|union|create|table|if|' +
|
||||
'foreign|not|references|default|null|inner|cross|drop|grant';
|
||||
// Stuff like: 'with|select|from|where|and|or|group|by|order|limit|having|as|case|'
|
||||
var keywords = druidKeywords.SQL_KEYWORDS.concat(druidKeywords.SQL_EXPRESSION_PARTS)
|
||||
.join('|')
|
||||
.replace(/\s/g, '|');
|
||||
|
||||
var builtinConstants = 'true|false';
|
||||
// Stuff like: 'true|false'
|
||||
var builtinConstants = druidKeywords.SQL_CONSTANTS.join('|');
|
||||
|
||||
var builtinFunctions =
|
||||
'avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|format|' +
|
||||
'coalesce|ifnull|isnull|nvl';
|
||||
// Stuff like: 'avg|count|first|last|max|min'
|
||||
var builtinFunctions = druidKeywords.SQL_DYNAMICS.concat(
|
||||
druidFunctions.SQL_FUNCTIONS.map(function(f) {
|
||||
return f.name;
|
||||
}),
|
||||
).join('|');
|
||||
|
||||
var dataTypes =
|
||||
'int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|' +
|
||||
'money|real|number|integer';
|
||||
// Stuff like: 'int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp'
|
||||
var dataTypes = druidFunctions.SQL_DATA_TYPES.map(function(f) {
|
||||
return f.name;
|
||||
}).join('|');
|
||||
|
||||
var keywordMapper = this.createKeywordMapper(
|
||||
{
|
||||
|
|
|
@ -36,8 +36,6 @@ function removeFirstPartialLine(log: string): string {
|
|||
return lines.join('\n');
|
||||
}
|
||||
|
||||
let interval: number | undefined;
|
||||
|
||||
export interface ShowLogProps {
|
||||
endpoint: string;
|
||||
downloadFilename?: string;
|
||||
|
@ -53,8 +51,11 @@ export interface ShowLogState {
|
|||
}
|
||||
|
||||
export class ShowLog extends React.PureComponent<ShowLogProps, ShowLogState> {
|
||||
static CHECK_INTERVAL = 2500;
|
||||
|
||||
private showLogQueryManager: QueryManager<null, string>;
|
||||
public log = React.createRef<HTMLTextAreaElement>();
|
||||
private log = React.createRef<HTMLTextAreaElement>();
|
||||
private interval: number | undefined;
|
||||
|
||||
constructor(props: ShowLogProps, context: any) {
|
||||
super(props, context);
|
||||
|
@ -87,24 +88,40 @@ export class ShowLog extends React.PureComponent<ShowLogProps, ShowLogState> {
|
|||
const { status } = this.props;
|
||||
|
||||
if (status === 'RUNNING') {
|
||||
interval = Number(setInterval(() => this.showLogQueryManager.runQuery(null), 2000));
|
||||
this.addTailer();
|
||||
}
|
||||
|
||||
this.showLogQueryManager.runQuery(null);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (interval) clearInterval(interval);
|
||||
this.removeTailer();
|
||||
}
|
||||
|
||||
addTailer() {
|
||||
if (this.interval) return;
|
||||
this.interval = Number(
|
||||
setInterval(() => this.showLogQueryManager.runQuery(null), ShowLog.CHECK_INTERVAL),
|
||||
);
|
||||
}
|
||||
|
||||
removeTailer() {
|
||||
if (!this.interval) return;
|
||||
clearInterval(this.interval);
|
||||
delete this.interval;
|
||||
}
|
||||
|
||||
private handleCheckboxChange = () => {
|
||||
const { tail } = this.state;
|
||||
|
||||
const nextTail = !tail;
|
||||
this.setState({
|
||||
tail: !this.state.tail,
|
||||
tail: nextTail,
|
||||
});
|
||||
if (!this.state.tail) {
|
||||
interval = Number(setInterval(() => this.showLogQueryManager.runQuery(null), 2000));
|
||||
if (nextTail) {
|
||||
this.addTailer();
|
||||
} else {
|
||||
if (interval) clearInterval(interval);
|
||||
this.removeTailer();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -75,10 +75,11 @@ export class QueryHistoryDialog extends React.PureComponent<
|
|||
}
|
||||
|
||||
private handleSelect = () => {
|
||||
const { queryRecords, setQueryString } = this.props;
|
||||
const { queryRecords, setQueryString, onClose } = this.props;
|
||||
const { activeTab } = this.state;
|
||||
|
||||
setQueryString(queryRecords[activeTab].queryString);
|
||||
onClose();
|
||||
};
|
||||
|
||||
private handleTabChange = (tab: number) => {
|
||||
|
|
|
@ -148,7 +148,10 @@ export class QueryPlanDialog extends React.PureComponent<
|
|||
<Button
|
||||
text="Open"
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={() => setQueryString(this.queryString)}
|
||||
onClick={() => {
|
||||
setQueryString(this.queryString);
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -30,6 +30,8 @@ import {
|
|||
} from './druid-time';
|
||||
import { deepGet, deepSet } from './object-change';
|
||||
|
||||
export const MAX_INLINE_DATA_LENGTH = 65536;
|
||||
|
||||
// These constants are used to make sure that they are not constantly recreated thrashing the pure components
|
||||
export const EMPTY_OBJECT: any = {};
|
||||
export const EMPTY_ARRAY: any[] = [];
|
||||
|
@ -290,7 +292,7 @@ const PARSE_SPEC_FORM_FIELDS: Field<ParseSpec>[] = [
|
|||
{
|
||||
name: 'hasHeaderRow',
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
defaultValue: false,
|
||||
defined: (p: ParseSpec) => p.format === 'csv' || p.format === 'tsv',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -105,6 +105,7 @@ import {
|
|||
issueWithIoConfig,
|
||||
issueWithParser,
|
||||
joinFilter,
|
||||
MAX_INLINE_DATA_LENGTH,
|
||||
MetricSpec,
|
||||
normalizeSpec,
|
||||
Parser,
|
||||
|
@ -947,7 +948,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
placeholder="Paste your data here"
|
||||
value={deepGet(spec, 'ioConfig.firehose.data')}
|
||||
onChange={(e: any) => {
|
||||
const stringValue = e.target.value.substr(0, 65536);
|
||||
const stringValue = e.target.value.substr(0, MAX_INLINE_DATA_LENGTH);
|
||||
this.updateSpec(deepSet(spec, 'ioConfig.firehose.data', stringValue));
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -10,10 +10,8 @@ exports[`sql view matches snapshot 1`] = `
|
|||
addToGroupBy={[Function]}
|
||||
clear={[Function]}
|
||||
columnMetadataLoading={true}
|
||||
currentFilters={[Function]}
|
||||
defaultSchema="druid"
|
||||
filterByRow={[Function]}
|
||||
hasGroupBy={[Function]}
|
||||
onQueryStringChange={[Function]}
|
||||
queryAst={[Function]}
|
||||
replaceFrom={[Function]}
|
||||
|
@ -44,6 +42,7 @@ exports[`sql view matches snapshot 1`] = `
|
|||
>
|
||||
<HotkeysTarget(RunButton)
|
||||
autoRun={true}
|
||||
onAutoRunChange={[Function]}
|
||||
onEditContext={[Function]}
|
||||
onExplain={[Function]}
|
||||
onHistory={[Function]}
|
||||
|
@ -51,10 +50,20 @@ exports[`sql view matches snapshot 1`] = `
|
|||
onRun={[Function]}
|
||||
queryContext={Object {}}
|
||||
runeMode={false}
|
||||
setAutoRun={[Function]}
|
||||
setWrapQuery={[Function]}
|
||||
wrapQuery={true}
|
||||
/>
|
||||
<Blueprint3.Tooltip
|
||||
content="Automatically wrap the query with a limit to protect against queries with very large result sets"
|
||||
hoverCloseDelay={0}
|
||||
hoverOpenDelay={800}
|
||||
transitionDuration={100}
|
||||
>
|
||||
<Blueprint3.Switch
|
||||
checked={true}
|
||||
className="smart-query-limit"
|
||||
label="Smart query limit"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Blueprint3.Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<QueryOutput
|
||||
|
|
|
@ -27,9 +27,7 @@ describe('column tree', () => {
|
|||
it('matches snapshot', () => {
|
||||
const columnTree = (
|
||||
<ColumnTree
|
||||
currentFilters={() => []}
|
||||
queryAst={() => undefined}
|
||||
hasGroupBy={() => false}
|
||||
clear={() => null}
|
||||
addFunctionToGroupBy={() => null}
|
||||
filterByRow={() => null}
|
||||
|
|
|
@ -126,7 +126,6 @@ export interface ColumnTreeProps {
|
|||
onQueryStringChange: (queryString: string, run: boolean) => void;
|
||||
defaultSchema?: string;
|
||||
defaultTable?: string;
|
||||
currentFilters: () => string[];
|
||||
addFunctionToGroupBy: (
|
||||
functionName: string,
|
||||
spacing: string[],
|
||||
|
@ -145,7 +144,6 @@ export interface ColumnTreeProps {
|
|||
) => void;
|
||||
filterByRow: (filters: RowFilter[], preferablyRun: boolean) => void;
|
||||
replaceFrom: (table: RefExpression, preferablyRun: boolean) => void;
|
||||
hasGroupBy: () => boolean;
|
||||
queryAst: () => SqlQuery | undefined;
|
||||
clear: (column: string, preferablyRun: boolean) => void;
|
||||
}
|
||||
|
@ -160,6 +158,7 @@ export interface ColumnTreeState {
|
|||
export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeState> {
|
||||
static getDerivedStateFromProps(props: ColumnTreeProps, state: ColumnTreeState) {
|
||||
const { columnMetadata, defaultSchema, defaultTable } = props;
|
||||
|
||||
if (columnMetadata && columnMetadata !== state.prevColumnMetadata) {
|
||||
const columnTree = groupBy(
|
||||
columnMetadata,
|
||||
|
@ -241,81 +240,79 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
|
|||
targetClassName={'bp3-popover-open'}
|
||||
content={
|
||||
<Deferred
|
||||
content={() => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={IconNames.FULLSCREEN}
|
||||
text={`Show: ${columnData.COLUMN_NAME}`}
|
||||
onClick={() => {
|
||||
handleColumnClick(
|
||||
schema,
|
||||
table,
|
||||
{
|
||||
id: columnData.COLUMN_NAME,
|
||||
icon: ColumnTree.dataTypeToIcon(columnData.DATA_TYPE),
|
||||
label: columnData.COLUMN_NAME,
|
||||
},
|
||||
props.onQueryStringChange,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{columnData.DATA_TYPE === 'BIGINT' && (
|
||||
<NumberMenuItems
|
||||
addFunctionToGroupBy={props.addFunctionToGroupBy}
|
||||
addToGroupBy={props.addToGroupBy}
|
||||
addAggregateColumn={props.addAggregateColumn}
|
||||
filterByRow={props.filterByRow}
|
||||
columnName={columnData.COLUMN_NAME}
|
||||
queryAst={props.queryAst()}
|
||||
clear={props.clear}
|
||||
hasFilter={
|
||||
props.currentFilters() &&
|
||||
props.currentFilters().includes(columnData.COLUMN_NAME)
|
||||
}
|
||||
content={() => {
|
||||
const queryAst = props.queryAst();
|
||||
const hasFilter = queryAst
|
||||
? queryAst.getCurrentFilters().includes(columnData.COLUMN_NAME)
|
||||
: false;
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={IconNames.FULLSCREEN}
|
||||
text={`Show: ${columnData.COLUMN_NAME}`}
|
||||
onClick={() => {
|
||||
handleColumnClick(
|
||||
schema,
|
||||
table,
|
||||
{
|
||||
id: columnData.COLUMN_NAME,
|
||||
icon: ColumnTree.dataTypeToIcon(columnData.DATA_TYPE),
|
||||
label: columnData.COLUMN_NAME,
|
||||
},
|
||||
props.onQueryStringChange,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{columnData.DATA_TYPE === 'VARCHAR' && (
|
||||
<StringMenuItems
|
||||
addFunctionToGroupBy={props.addFunctionToGroupBy}
|
||||
addToGroupBy={props.addToGroupBy}
|
||||
addAggregateColumn={props.addAggregateColumn}
|
||||
filterByRow={props.filterByRow}
|
||||
columnName={columnData.COLUMN_NAME}
|
||||
queryAst={props.queryAst()}
|
||||
clear={props.clear}
|
||||
hasFilter={
|
||||
props.currentFilters() &&
|
||||
props.currentFilters().includes(columnData.COLUMN_NAME)
|
||||
}
|
||||
{columnData.DATA_TYPE === 'BIGINT' && (
|
||||
<NumberMenuItems
|
||||
addFunctionToGroupBy={props.addFunctionToGroupBy}
|
||||
addToGroupBy={props.addToGroupBy}
|
||||
addAggregateColumn={props.addAggregateColumn}
|
||||
filterByRow={props.filterByRow}
|
||||
columnName={columnData.COLUMN_NAME}
|
||||
queryAst={props.queryAst()}
|
||||
clear={props.clear}
|
||||
hasFilter={hasFilter}
|
||||
/>
|
||||
)}
|
||||
{columnData.DATA_TYPE === 'VARCHAR' && (
|
||||
<StringMenuItems
|
||||
addFunctionToGroupBy={props.addFunctionToGroupBy}
|
||||
addToGroupBy={props.addToGroupBy}
|
||||
addAggregateColumn={props.addAggregateColumn}
|
||||
filterByRow={props.filterByRow}
|
||||
columnName={columnData.COLUMN_NAME}
|
||||
queryAst={props.queryAst()}
|
||||
clear={props.clear}
|
||||
hasFilter={hasFilter}
|
||||
/>
|
||||
)}
|
||||
{columnData.DATA_TYPE === 'TIMESTAMP' && (
|
||||
<TimeMenuItems
|
||||
clear={props.clear}
|
||||
addFunctionToGroupBy={props.addFunctionToGroupBy}
|
||||
addToGroupBy={props.addToGroupBy}
|
||||
addAggregateColumn={props.addAggregateColumn}
|
||||
filterByRow={props.filterByRow}
|
||||
columnName={columnData.COLUMN_NAME}
|
||||
queryAst={props.queryAst()}
|
||||
hasFilter={hasFilter}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
icon={IconNames.CLIPBOARD}
|
||||
text={`Copy: ${columnData.COLUMN_NAME}`}
|
||||
onClick={() => {
|
||||
copyAndAlert(
|
||||
columnData.COLUMN_NAME,
|
||||
`${columnData.COLUMN_NAME} query copied to clipboard`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{columnData.DATA_TYPE === 'TIMESTAMP' && (
|
||||
<TimeMenuItems
|
||||
clear={props.clear}
|
||||
addFunctionToGroupBy={props.addFunctionToGroupBy}
|
||||
addToGroupBy={props.addToGroupBy}
|
||||
addAggregateColumn={props.addAggregateColumn}
|
||||
filterByRow={props.filterByRow}
|
||||
columnName={columnData.COLUMN_NAME}
|
||||
queryAst={props.queryAst()}
|
||||
hasFilter={
|
||||
props.currentFilters() &&
|
||||
props.currentFilters().includes(columnData.COLUMN_NAME)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
icon={IconNames.CLIPBOARD}
|
||||
text={`Copy: ${columnData.COLUMN_NAME}`}
|
||||
onClick={() => {
|
||||
copyAndAlert(
|
||||
columnData.COLUMN_NAME,
|
||||
`${columnData.COLUMN_NAME} query copied to clipboard`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
@ -361,7 +358,6 @@ export class ColumnTree extends React.PureComponent<ColumnTreeProps, ColumnTreeS
|
|||
columnTree,
|
||||
selectedTreeIndex,
|
||||
currentSchemaSubtree,
|
||||
prevGroupByStatus: props.hasGroupBy,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -17,7 +17,7 @@ exports[`query extra info matches snapshot 1`] = `
|
|||
class=""
|
||||
tabindex="0"
|
||||
>
|
||||
1999+ results in 8.00s
|
||||
999+ results in 8.00s
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -29,8 +29,8 @@ describe('query extra info', () => {
|
|||
queryId: 'e3ee781b-c0b6-4385-9d99-a8a1994bebac',
|
||||
startTime: new Date('1986-04-26T01:23:40+03:00'),
|
||||
endTime: new Date('1986-04-26T01:23:48+03:00'),
|
||||
numResults: 2000,
|
||||
wrappedLimit: 2000,
|
||||
numResults: 1000,
|
||||
wrapQueryLimit: 1000,
|
||||
}}
|
||||
onDownload={() => {}}
|
||||
/>
|
||||
|
|
|
@ -41,7 +41,7 @@ export interface QueryExtraInfoData {
|
|||
startTime: Date;
|
||||
endTime: Date;
|
||||
numResults: number;
|
||||
wrappedLimit?: number;
|
||||
wrapQueryLimit: number | undefined;
|
||||
}
|
||||
|
||||
export interface QueryExtraInfoProps {
|
||||
|
@ -63,7 +63,10 @@ export class QueryExtraInfo extends React.PureComponent<QueryExtraInfoProps> {
|
|||
);
|
||||
|
||||
let resultCount: string;
|
||||
if (queryExtraInfo.wrappedLimit && queryExtraInfo.numResults === queryExtraInfo.wrappedLimit) {
|
||||
if (
|
||||
queryExtraInfo.wrapQueryLimit &&
|
||||
queryExtraInfo.numResults === queryExtraInfo.wrapQueryLimit
|
||||
) {
|
||||
resultCount = `${queryExtraInfo.numResults - 1}+ results`;
|
||||
} else {
|
||||
resultCount = pluralIfNeeded(queryExtraInfo.numResults, 'result');
|
||||
|
|
|
@ -22,12 +22,16 @@ import escape from 'lodash.escape';
|
|||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
|
||||
import { SQL_DATE_TYPES, SQL_FUNCTIONS, SyntaxDescription } from '../../../../lib/sql-function-doc';
|
||||
import {
|
||||
SQL_CONSTANTS,
|
||||
SQL_DYNAMICS,
|
||||
SQL_EXPRESSION_PARTS,
|
||||
SQL_KEYWORDS,
|
||||
} from '../../../../lib/keywords';
|
||||
import { SQL_DATA_TYPES, SQL_FUNCTIONS } from '../../../../lib/sql-function-doc';
|
||||
import { uniq } from '../../../utils';
|
||||
import { ColumnMetadata } from '../../../utils/column-metadata';
|
||||
|
||||
import { SQL_CONSTANTS, SQL_DYNAMICS, SQL_EXPRESSION_PARTS, SQL_KEYWORDS } from './keywords';
|
||||
|
||||
import './query-input.scss';
|
||||
|
||||
const langTools = ace.acequire('ace/ext/language_tools');
|
||||
|
@ -60,7 +64,7 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
|||
SQL_EXPRESSION_PARTS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })),
|
||||
SQL_CONSTANTS.map(v => ({ name: v, value: v, score: 0, meta: 'constant' })),
|
||||
SQL_DYNAMICS.map(v => ({ name: v, value: v, score: 0, meta: 'dynamic' })),
|
||||
SQL_DATE_TYPES.map(v => ({ name: v.syntax, value: v.syntax, score: 0, meta: 'keyword' })),
|
||||
SQL_DATA_TYPES.map(v => ({ name: v.name, value: v.name, score: 0, meta: 'type' })),
|
||||
);
|
||||
|
||||
const keywordCompleter = {
|
||||
|
@ -79,13 +83,12 @@ export class QueryInput extends React.PureComponent<QueryInputProps, QueryInputS
|
|||
static addFunctionAutoCompleter(): void {
|
||||
if (!langTools) return;
|
||||
|
||||
const functionList: any[] = SQL_FUNCTIONS.map((entry: SyntaxDescription) => {
|
||||
const funcName: string = entry.syntax.replace(/\(.*\)/, '()');
|
||||
const functionList: any[] = SQL_FUNCTIONS.map(entry => {
|
||||
return {
|
||||
value: funcName,
|
||||
value: entry.name,
|
||||
score: 80,
|
||||
meta: 'function',
|
||||
syntax: entry.syntax,
|
||||
syntax: entry.name + entry.arguments,
|
||||
description: entry.description,
|
||||
completer: {
|
||||
insertMatch: (editor: any, data: any) => {
|
||||
|
|
|
@ -58,6 +58,16 @@ $nav-width: 250px;
|
|||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
& > * {
|
||||
vertical-align: bottom;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.smart-query-limit {
|
||||
display: inline-block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.query-extra-info {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { Intent, Switch, Tooltip } from '@blueprintjs/core';
|
||||
import axios from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
|
@ -38,7 +38,7 @@ import memoizeOne from 'memoize-one';
|
|||
import React from 'react';
|
||||
import SplitterLayout from 'react-splitter-layout';
|
||||
|
||||
import { SQL_FUNCTIONS, SyntaxDescription } from '../../../lib/sql-function-doc';
|
||||
import { SQL_FUNCTIONS } from '../../../lib/sql-function-doc';
|
||||
import { QueryPlanDialog } from '../../dialogs';
|
||||
import { EditContextDialog } from '../../dialogs/edit-context-dialog/edit-context-dialog';
|
||||
import {
|
||||
|
@ -69,11 +69,7 @@ import { RunButton } from './run-button/run-button';
|
|||
|
||||
import './query-view.scss';
|
||||
|
||||
const parserRaw = sqlParserFactory(
|
||||
SQL_FUNCTIONS.map((sqlFunction: SyntaxDescription) => {
|
||||
return sqlFunction.syntax.substr(0, sqlFunction.syntax.indexOf('('));
|
||||
}),
|
||||
);
|
||||
const parserRaw = sqlParserFactory(SQL_FUNCTIONS.map(sqlFunction => sqlFunction.name));
|
||||
|
||||
const parser = memoizeOne((sql: string) => {
|
||||
try {
|
||||
|
@ -86,7 +82,7 @@ const parser = memoizeOne((sql: string) => {
|
|||
interface QueryWithContext {
|
||||
queryString: string;
|
||||
queryContext: QueryContext;
|
||||
wrapQuery: boolean;
|
||||
wrapQueryLimit: number | undefined;
|
||||
}
|
||||
|
||||
export interface QueryViewProps {
|
||||
|
@ -103,7 +99,7 @@ export interface QueryViewState {
|
|||
queryString: string;
|
||||
queryAst: SqlQuery;
|
||||
queryContext: QueryContext;
|
||||
wrapQuery: boolean;
|
||||
wrapQueryLimit: number | undefined;
|
||||
autoRun: boolean;
|
||||
|
||||
columnMetadataLoading: boolean;
|
||||
|
@ -147,8 +143,9 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
return /EXPLAIN\sPLAN\sFOR/i.test(query);
|
||||
}
|
||||
|
||||
static wrapInLimitIfNeeded(query: string, limit = 1000): string {
|
||||
static wrapInLimitIfNeeded(query: string, limit: number | undefined): string {
|
||||
query = QueryView.trimSemicolon(query);
|
||||
if (!limit) return query;
|
||||
if (QueryView.isExplainQuery(query)) return query;
|
||||
return `SELECT * FROM (${query}\n) LIMIT ${limit}`;
|
||||
}
|
||||
|
@ -198,12 +195,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
constructor(props: QueryViewProps, context: any) {
|
||||
super(props, context);
|
||||
|
||||
let queryString: string | undefined;
|
||||
if (props.initQuery) {
|
||||
queryString = props.initQuery;
|
||||
} else if (localStorageGet(LocalStorageKeys.QUERY_KEY)) {
|
||||
queryString = localStorageGet(LocalStorageKeys.QUERY_KEY);
|
||||
}
|
||||
const queryString = props.initQuery || localStorageGet(LocalStorageKeys.QUERY_KEY) || '';
|
||||
const queryAst = queryString ? parser(queryString) : undefined;
|
||||
|
||||
const localStorageQueryHistory = localStorageGet(LocalStorageKeys.QUERY_HISTORY);
|
||||
|
@ -227,10 +219,10 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
}
|
||||
|
||||
this.state = {
|
||||
queryString: queryString ? queryString : '',
|
||||
queryString,
|
||||
queryAst,
|
||||
queryContext: {},
|
||||
wrapQuery: true,
|
||||
wrapQueryLimit: 100,
|
||||
autoRun,
|
||||
|
||||
columnMetadataLoading: false,
|
||||
|
@ -268,27 +260,22 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
|
||||
this.sqlQueryManager = new QueryManager({
|
||||
processQuery: async (queryWithContext: QueryWithContext): Promise<QueryResult> => {
|
||||
const { queryString, queryContext, wrapQuery } = queryWithContext;
|
||||
const { queryString, queryContext, wrapQueryLimit } = queryWithContext;
|
||||
|
||||
let ast: SqlQuery | undefined;
|
||||
let wrappedLimit: number | undefined;
|
||||
let parsedQuery: SqlQuery | undefined;
|
||||
let jsonQuery: any;
|
||||
|
||||
try {
|
||||
ast = parser(queryString);
|
||||
parsedQuery = parser(queryString);
|
||||
} catch {}
|
||||
|
||||
if (!(ast instanceof SqlQuery)) {
|
||||
ast = undefined;
|
||||
if (!(parsedQuery instanceof SqlQuery)) {
|
||||
parsedQuery = undefined;
|
||||
}
|
||||
if (QueryView.isJsonLike(queryString)) {
|
||||
jsonQuery = Hjson.parse(queryString);
|
||||
} else {
|
||||
const actualQuery = wrapQuery
|
||||
? QueryView.wrapInLimitIfNeeded(queryString)
|
||||
: QueryView.trimSemicolon(queryString);
|
||||
|
||||
if (wrapQuery) wrappedLimit = 1000;
|
||||
const actualQuery = QueryView.wrapInLimitIfNeeded(queryString, wrapQueryLimit);
|
||||
|
||||
jsonQuery = {
|
||||
query: actualQuery,
|
||||
|
@ -339,9 +326,9 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
startTime,
|
||||
endTime,
|
||||
numResults: queryResult.rows.length,
|
||||
wrappedLimit,
|
||||
wrapQueryLimit,
|
||||
},
|
||||
parsedQuery: ast,
|
||||
parsedQuery,
|
||||
};
|
||||
},
|
||||
onStateChange: ({ result, loading, error }) => {
|
||||
|
@ -355,11 +342,9 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
|
||||
this.explainQueryManager = new QueryManager({
|
||||
processQuery: async (queryWithContext: QueryWithContext) => {
|
||||
const { queryString, queryContext, wrapQuery } = queryWithContext;
|
||||
const { queryString, queryContext, wrapQueryLimit } = queryWithContext;
|
||||
|
||||
const actualQuery = wrapQuery
|
||||
? QueryView.wrapInLimitIfNeeded(queryString)
|
||||
: QueryView.trimSemicolon(queryString);
|
||||
const actualQuery = QueryView.wrapInLimitIfNeeded(queryString, wrapQueryLimit);
|
||||
|
||||
const explainPayload: Record<string, any> = {
|
||||
query: QueryView.wrapInExplainIfNeeded(actualQuery),
|
||||
|
@ -431,10 +416,8 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
<QueryPlanDialog
|
||||
explainResult={explainResult}
|
||||
explainError={explainError}
|
||||
setQueryString={this.handleQueryStringChange}
|
||||
onClose={() => this.setState({ explainDialogOpen: false })}
|
||||
setQueryString={(queryString: string) =>
|
||||
this.setState({ queryString, explainDialogOpen: false, queryAst: parser(queryString) })
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -446,9 +429,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
return (
|
||||
<QueryHistoryDialog
|
||||
queryRecords={queryHistory}
|
||||
setQueryString={queryString =>
|
||||
this.setState({ queryString, queryAst: parser(queryString), historyDialogOpen: false })
|
||||
}
|
||||
setQueryString={this.handleQueryStringChange}
|
||||
onClose={() => this.setState({ historyDialogOpen: false })}
|
||||
/>
|
||||
);
|
||||
|
@ -469,6 +450,25 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
);
|
||||
}
|
||||
|
||||
renderWrapQueryLimitSelector() {
|
||||
const { wrapQueryLimit, queryString } = this.state;
|
||||
if (QueryView.isJsonLike(queryString)) return;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content="Automatically wrap the query with a limit to protect against queries with very large result sets"
|
||||
hoverOpenDelay={800}
|
||||
>
|
||||
<Switch
|
||||
className="smart-query-limit"
|
||||
checked={Boolean(wrapQueryLimit)}
|
||||
label="Smart query limit"
|
||||
onChange={() => this.handleWrapQueryLimitChange(wrapQueryLimit ? undefined : 100)}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
renderMainArea() {
|
||||
const {
|
||||
queryString,
|
||||
|
@ -478,7 +478,6 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
error,
|
||||
columnMetadata,
|
||||
autoRun,
|
||||
wrapQuery,
|
||||
} = this.state;
|
||||
const emptyQuery = QueryView.isEmptyQuery(queryString);
|
||||
|
||||
|
@ -521,9 +520,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
<div className="control-bar">
|
||||
<RunButton
|
||||
autoRun={autoRun}
|
||||
setAutoRun={this.setAutoRun}
|
||||
wrapQuery={wrapQuery}
|
||||
setWrapQuery={this.setWrapQuery}
|
||||
onAutoRunChange={this.handleAutoRunChange}
|
||||
onEditContext={() => this.setState({ editContextDialogOpen: true })}
|
||||
runeMode={runeMode}
|
||||
queryContext={queryContext}
|
||||
|
@ -532,6 +529,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
onExplain={emptyQuery ? undefined : this.handleExplain}
|
||||
onHistory={() => this.setState({ historyDialogOpen: true })}
|
||||
/>
|
||||
{this.renderWrapQueryLimitSelector()}
|
||||
{result && (
|
||||
<QueryExtraInfo
|
||||
queryExtraInfo={result.queryExtraInfo}
|
||||
|
@ -563,6 +561,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
): void => {
|
||||
const { queryAst } = this.state;
|
||||
if (!queryAst) return;
|
||||
|
||||
const modifiedAst = queryAst.addFunctionToGroupBy(functionName, spacing, argumentsArray, alias);
|
||||
this.handleQueryStringChange(modifiedAst.toString(), preferablyRun);
|
||||
};
|
||||
|
@ -570,6 +569,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
private addToGroupBy = (columnName: string, preferablyRun: boolean): void => {
|
||||
const { queryAst } = this.state;
|
||||
if (!queryAst) return;
|
||||
|
||||
const modifiedAst = queryAst.addToGroupBy(columnName);
|
||||
this.handleQueryStringChange(modifiedAst.toString(), preferablyRun);
|
||||
};
|
||||
|
@ -577,6 +577,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
private replaceFrom = (table: RefExpression, preferablyRun: boolean): void => {
|
||||
const { queryAst } = this.state;
|
||||
if (!queryAst) return;
|
||||
|
||||
const modifiedAst = queryAst.replaceFrom(table);
|
||||
this.handleQueryStringChange(modifiedAst.toString(), preferablyRun);
|
||||
};
|
||||
|
@ -591,6 +592,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
): void => {
|
||||
const { queryAst } = this.state;
|
||||
if (!queryAst) return;
|
||||
|
||||
const modifiedAst = queryAst.addAggregateColumn(
|
||||
columnName,
|
||||
functionName,
|
||||
|
@ -608,6 +610,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
): void => {
|
||||
const { queryAst } = this.state;
|
||||
if (!queryAst) return;
|
||||
|
||||
const modifiedAst = queryAst.orderBy(header, direction);
|
||||
this.handleQueryStringChange(modifiedAst.toString(), preferablyRun);
|
||||
};
|
||||
|
@ -615,6 +618,7 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
private sqlExcludeColumn = (header: string, preferablyRun: boolean): void => {
|
||||
const { queryAst } = this.state;
|
||||
if (!queryAst) return;
|
||||
|
||||
const modifiedAst = queryAst.excludeColumn(header);
|
||||
this.handleQueryStringChange(modifiedAst.toString(), preferablyRun);
|
||||
};
|
||||
|
@ -648,17 +652,17 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
this.setState({ queryContext });
|
||||
};
|
||||
|
||||
private setAutoRun = (autoRun: boolean) => {
|
||||
private handleAutoRunChange = (autoRun: boolean) => {
|
||||
this.setState({ autoRun });
|
||||
localStorageSet(LocalStorageKeys.AUTO_RUN, String(autoRun));
|
||||
};
|
||||
|
||||
private setWrapQuery = (wrapQuery: boolean) => {
|
||||
this.setState({ wrapQuery });
|
||||
private handleWrapQueryLimitChange = (wrapQueryLimit: number | undefined) => {
|
||||
this.setState({ wrapQueryLimit });
|
||||
};
|
||||
|
||||
private handleRun = () => {
|
||||
const { queryString, queryContext, wrapQuery, queryHistory } = this.state;
|
||||
const { queryString, queryContext, wrapQueryLimit, queryHistory } = this.state;
|
||||
if (QueryView.isJsonLike(queryString) && !QueryView.validRune(queryString)) return;
|
||||
|
||||
const newQueryHistory = QueryHistoryDialog.addQueryToHistory(queryHistory, queryString);
|
||||
|
@ -667,50 +671,25 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
localStorageSet(LocalStorageKeys.QUERY_KEY, queryString);
|
||||
|
||||
this.setState({ queryHistory: newQueryHistory });
|
||||
this.sqlQueryManager.runQuery({ queryString, queryContext, wrapQuery });
|
||||
this.sqlQueryManager.runQuery({ queryString, queryContext, wrapQueryLimit });
|
||||
};
|
||||
|
||||
private handleExplain = () => {
|
||||
const { queryString, queryContext, wrapQuery } = this.state;
|
||||
const { queryString, queryContext, wrapQueryLimit } = this.state;
|
||||
|
||||
this.setState({ explainDialogOpen: true });
|
||||
this.explainQueryManager.runQuery({ queryString, queryContext, wrapQuery });
|
||||
this.explainQueryManager.runQuery({ queryString, queryContext, wrapQueryLimit });
|
||||
};
|
||||
|
||||
private handleSecondaryPaneSizeChange = (secondaryPaneSize: number) => {
|
||||
localStorageSet(LocalStorageKeys.QUERY_VIEW_PANE_SIZE, String(secondaryPaneSize));
|
||||
};
|
||||
|
||||
private getGroupBySetting = () => {
|
||||
const { queryString, queryAst } = this.state;
|
||||
const ast = queryAst;
|
||||
let tempAst: SqlQuery | undefined;
|
||||
if (!ast) {
|
||||
tempAst = parser(queryString);
|
||||
}
|
||||
|
||||
let hasGroupBy = false;
|
||||
if (ast && ast instanceof SqlQuery) {
|
||||
hasGroupBy = !!ast.groupByClause;
|
||||
} else if (tempAst && tempAst instanceof SqlQuery) {
|
||||
hasGroupBy = !!tempAst.groupByClause;
|
||||
}
|
||||
return hasGroupBy;
|
||||
};
|
||||
|
||||
private getQueryAst = () => {
|
||||
const { queryAst } = this.state;
|
||||
return queryAst;
|
||||
};
|
||||
|
||||
private getCurrentFilters = () => {
|
||||
const { queryAst } = this.state;
|
||||
if (queryAst) {
|
||||
return queryAst.getCurrentFilters();
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
render(): JSX.Element {
|
||||
const { columnMetadata, columnMetadataLoading, columnMetadataError, queryAst } = this.state;
|
||||
|
||||
|
@ -732,14 +711,12 @@ export class QueryView extends React.PureComponent<QueryViewProps, QueryViewStat
|
|||
addFunctionToGroupBy={this.addFunctionToGroupBy}
|
||||
addAggregateColumn={this.addAggregateColumn}
|
||||
addToGroupBy={this.addToGroupBy}
|
||||
hasGroupBy={this.getGroupBySetting}
|
||||
queryAst={this.getQueryAst}
|
||||
columnMetadataLoading={columnMetadataLoading}
|
||||
columnMetadata={columnMetadata}
|
||||
onQueryStringChange={this.handleQueryStringChange}
|
||||
defaultSchema={defaultSchema ? defaultSchema : 'druid'}
|
||||
defaultTable={defaultTable}
|
||||
currentFilters={this.getCurrentFilters}
|
||||
replaceFrom={this.replaceFrom}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -11,7 +11,7 @@ exports[`run button matches snapshot 1`] = `
|
|||
class="bp3-popover-target"
|
||||
>
|
||||
<button
|
||||
class="bp3-button"
|
||||
class="bp3-button bp3-intent-primary"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
|
@ -37,7 +37,7 @@ exports[`run button matches snapshot 1`] = `
|
|||
<span
|
||||
class="bp3-button-text"
|
||||
>
|
||||
Run with limit
|
||||
Run
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
|
@ -49,7 +49,7 @@ exports[`run button matches snapshot 1`] = `
|
|||
class="bp3-popover-target"
|
||||
>
|
||||
<button
|
||||
class="bp3-button"
|
||||
class="bp3-button bp3-intent-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
|
|
|
@ -26,11 +26,9 @@ describe('run button', () => {
|
|||
const runButton = (
|
||||
<RunButton
|
||||
autoRun
|
||||
setAutoRun={() => {}}
|
||||
wrapQuery
|
||||
setWrapQuery={() => {}}
|
||||
onHistory={() => null}
|
||||
onEditContext={() => null}
|
||||
onAutoRunChange={() => {}}
|
||||
onHistory={() => {}}
|
||||
onEditContext={() => {}}
|
||||
runeMode={false}
|
||||
queryContext={{}}
|
||||
onQueryContextChange={() => {}}
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
Hotkey,
|
||||
Hotkeys,
|
||||
HotkeysTarget,
|
||||
Intent,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
|
@ -46,9 +47,7 @@ import { DRUID_DOCS_RUNE, DRUID_DOCS_SQL } from '../../../variables';
|
|||
export interface RunButtonProps {
|
||||
runeMode: boolean;
|
||||
autoRun: boolean;
|
||||
setAutoRun: (autoRun: boolean) => void;
|
||||
wrapQuery: boolean;
|
||||
setWrapQuery: (wrapQuery: boolean) => void;
|
||||
onAutoRunChange: (autoRun: boolean) => void;
|
||||
queryContext: QueryContext;
|
||||
onQueryContextChange: (newQueryContext: QueryContext) => void;
|
||||
onRun: (() => void) | undefined;
|
||||
|
@ -92,9 +91,7 @@ export class RunButton extends React.PureComponent<RunButtonProps> {
|
|||
onEditContext,
|
||||
onHistory,
|
||||
autoRun,
|
||||
setAutoRun,
|
||||
wrapQuery,
|
||||
setWrapQuery,
|
||||
onAutoRunChange,
|
||||
} = this.props;
|
||||
|
||||
const useCache = getUseCache(queryContext);
|
||||
|
@ -113,15 +110,10 @@ export class RunButton extends React.PureComponent<RunButtonProps> {
|
|||
<>
|
||||
{onExplain && <MenuItem icon={IconNames.CLEAN} text="Explain" onClick={onExplain} />}
|
||||
<MenuItem icon={IconNames.HISTORY} text="History" onClick={onHistory} />
|
||||
<MenuCheckbox
|
||||
checked={wrapQuery}
|
||||
label="Wrap query with limit"
|
||||
onChange={() => setWrapQuery(!wrapQuery)}
|
||||
/>
|
||||
<MenuCheckbox
|
||||
checked={autoRun}
|
||||
label="Auto run queries"
|
||||
onChange={() => setAutoRun(!autoRun)}
|
||||
onChange={() => onAutoRunChange(!autoRun)}
|
||||
/>
|
||||
<MenuCheckbox
|
||||
checked={useApproximateCountDistinct}
|
||||
|
@ -156,20 +148,25 @@ export class RunButton extends React.PureComponent<RunButtonProps> {
|
|||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const { runeMode, onRun, wrapQuery } = this.props;
|
||||
const runButtonText = runeMode ? 'Rune' : wrapQuery ? 'Run with limit' : 'Run as is';
|
||||
const { runeMode, onRun } = this.props;
|
||||
const runButtonText = 'Run' + (runeMode ? 'e' : '');
|
||||
|
||||
return (
|
||||
<ButtonGroup className="run-button">
|
||||
{onRun ? (
|
||||
<Tooltip content="Control + Enter" hoverOpenDelay={900}>
|
||||
<Button icon={IconNames.CARET_RIGHT} onClick={this.handleRun} text={runButtonText} />
|
||||
<Button
|
||||
icon={IconNames.CARET_RIGHT}
|
||||
onClick={this.handleRun}
|
||||
text={runButtonText}
|
||||
intent={Intent.PRIMARY}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button icon={IconNames.CARET_RIGHT} text={runButtonText} disabled />
|
||||
)}
|
||||
<Popover position={Position.BOTTOM_LEFT} content={this.renderExtraMenu()}>
|
||||
<Button icon={IconNames.MORE} />
|
||||
<Button icon={IconNames.MORE} intent={Intent.PRIMARY} />
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue