You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by fj...@apache.org on 2019/04/12 15:51:43 UTC

[incubator-druid] branch master updated: Wrap query with limit within the web console (#7449)

This is an automated email from the ASF dual-hosted git repository.

fjy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 60dd75d  Wrap query with limit within the web console (#7449)
60dd75d is described below

commit 60dd75d3d957a99c7eb28f59336f596310cf48e7
Author: Vadim Ogievetsky <va...@gmail.com>
AuthorDate: Fri Apr 12 08:51:37 2019 -0700

    Wrap query with limit within the web console (#7449)
    
    * wrap with limit
    
    * make actual menu checkbox component
---
 .../{sql-control.scss => menu-checkbox.scss}       |  62 +-----------
 .../{sql-control.scss => menu-checkbox.tsx}        |  63 ++----------
 web-console/src/components/sql-control.scss        |  19 +---
 web-console/src/components/sql-control.tsx         | 109 +++++++++++++++------
 .../src/components/table-column-selection.tsx      |  20 ++--
 web-console/src/views/sql-view.tsx                 |  52 +++++++---
 6 files changed, 145 insertions(+), 180 deletions(-)

diff --git a/web-console/src/components/sql-control.scss b/web-console/src/components/menu-checkbox.scss
similarity index 52%
copy from web-console/src/components/sql-control.scss
copy to web-console/src/components/menu-checkbox.scss
index d5e894d..8d4963a 100644
--- a/web-console/src/components/sql-control.scss
+++ b/web-console/src/components/menu-checkbox.scss
@@ -16,63 +16,11 @@
  * limitations under the License.
  */
 
+.menu-checkbox {
+  height: 30px;
+  padding: 7px 4px;
 
-.sql-control {
-
-  .ace_scroller {
-    background-color: #232C35;
-  }
-
-  .ace_gutter-layer {
-    background-color: #27313c;
-  }
-
-  .buttons {
-    position: relative;
-
-    button{
-      margin-right: 15px;
-    }
-
-    .query-elapsed {
-      position: absolute;
-      right: 10px;
-    }
-  }
-
-}
-
-.sql-control-popover {
-  padding:10px;
-  min-width: 120px;
-
-  .bp3-form-group {
-    margin-bottom: 0;
-  }
-
-  button {
-    span {
-      position: relative;
-      left: -7px;
-    }
-    padding-right: 35px;
-  }
-}
-
-.ace_tooltip {
-  padding: 10px;
-  background-color: #333D47;
-  color: #C1CCD5;
-  width: 500px;
-  display: block;
-  height: auto;
-  white-space: initial;
-
-  hr {
-    margin: 10px 0;
-  }
-  
-  .function-doc-name {
-    font-size: 18px;
+  label {
+    margin: 0;
   }
 }
diff --git a/web-console/src/components/sql-control.scss b/web-console/src/components/menu-checkbox.tsx
similarity index 52%
copy from web-console/src/components/sql-control.scss
copy to web-console/src/components/menu-checkbox.tsx
index d5e894d..21b438b 100644
--- a/web-console/src/components/sql-control.scss
+++ b/web-console/src/components/menu-checkbox.tsx
@@ -16,63 +16,16 @@
  * limitations under the License.
  */
 
+import { Checkbox, ICheckboxProps } from '@blueprintjs/core';
+import * as React from 'react';
 
-.sql-control {
+import './menu-checkbox.scss';
 
-  .ace_scroller {
-    background-color: #232C35;
-  }
-
-  .ace_gutter-layer {
-    background-color: #27313c;
-  }
-
-  .buttons {
-    position: relative;
-
-    button{
-      margin-right: 15px;
-    }
-
-    .query-elapsed {
-      position: absolute;
-      right: 10px;
-    }
-  }
-
-}
+export class MenuCheckbox extends React.Component<ICheckboxProps, {}> {
 
-.sql-control-popover {
-  padding:10px;
-  min-width: 120px;
-
-  .bp3-form-group {
-    margin-bottom: 0;
-  }
-
-  button {
-    span {
-      position: relative;
-      left: -7px;
-    }
-    padding-right: 35px;
-  }
-}
-
-.ace_tooltip {
-  padding: 10px;
-  background-color: #333D47;
-  color: #C1CCD5;
-  width: 500px;
-  display: block;
-  height: auto;
-  white-space: initial;
-
-  hr {
-    margin: 10px 0;
-  }
-  
-  .function-doc-name {
-    font-size: 18px;
+  render() {
+    return <li className="menu-checkbox">
+      <Checkbox {...this.props}/>
+    </li>;
   }
 }
diff --git a/web-console/src/components/sql-control.scss b/web-console/src/components/sql-control.scss
index d5e894d..2fe4036 100644
--- a/web-console/src/components/sql-control.scss
+++ b/web-console/src/components/sql-control.scss
@@ -42,23 +42,6 @@
 
 }
 
-.sql-control-popover {
-  padding:10px;
-  min-width: 120px;
-
-  .bp3-form-group {
-    margin-bottom: 0;
-  }
-
-  button {
-    span {
-      position: relative;
-      left: -7px;
-    }
-    padding-right: 35px;
-  }
-}
-
 .ace_tooltip {
   padding: 10px;
   background-color: #333D47;
@@ -71,7 +54,7 @@
   hr {
     margin: 10px 0;
   }
-  
+
   .function-doc-name {
     font-size: 18px;
   }
diff --git a/web-console/src/components/sql-control.tsx b/web-console/src/components/sql-control.tsx
index 6b4de1d..1aa9089 100644
--- a/web-console/src/components/sql-control.tsx
+++ b/web-console/src/components/sql-control.tsx
@@ -16,7 +16,15 @@
  * limitations under the License.
  */
 
-import { Button, Checkbox, Classes, FormGroup, Intent, Menu, Popover, Position } from '@blueprintjs/core';
+import {
+  Button,
+  ButtonGroup,
+  Intent,
+  Menu,
+  MenuItem,
+  Popover,
+  Position
+} from '@blueprintjs/core';
 import { IconNames } from '@blueprintjs/icons';
 import axios from 'axios';
 import * as ace from 'brace';
@@ -25,6 +33,7 @@ import 'brace/mode/hjson';
 import 'brace/mode/sql';
 import 'brace/theme/solarized_dark';
 import * as classNames from 'classnames';
+import * as Hjson from 'hjson';
 import * as React from 'react';
 import AceEditor from 'react-ace';
 import * as ReactDOMServer from 'react-dom/server';
@@ -32,21 +41,34 @@ import * as ReactDOMServer from 'react-dom/server';
 import { SQLFunctionDoc } from '../../lib/sql-function-doc';
 import { AppToaster } from '../singletons/toaster';
 
+import { MenuCheckbox } from './menu-checkbox';
+
 import './sql-control.scss';
 
+function validHjson(query: string) {
+  try {
+    Hjson.parse(query);
+    return true;
+  } catch {
+    return false;
+  }
+}
+
 const langTools = ace.acequire('ace/ext/language_tools');
 
 export interface SqlControlProps extends React.Props<any> {
   initSql: string | null;
-  onRun: (query: string) => void;
-  onExplain: (query: string) => void;
+  onRun: (query: string, bypassCache: boolean, wrapQuery: boolean) => void;
+  onExplain: (sqlQuery: string) => void;
   queryElapsed: number | null;
 }
 
 export interface SqlControlState {
   query: string;
-  autoCompleteOn: boolean;
+  autoComplete: boolean;
   autoCompleteLoading: boolean;
+  bypassCache: boolean;
+  wrapQuery: boolean;
 }
 
 export class SqlControl extends React.Component<SqlControlProps, SqlControlState> {
@@ -54,8 +76,10 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
     super(props, context);
     this.state = {
       query: props.initSql || '',
-      autoCompleteOn: true,
-      autoCompleteLoading: false
+      autoComplete: true,
+      autoCompleteLoading: false,
+      bypassCache: false,
+      wrapQuery: true
     };
   }
 
@@ -166,29 +190,49 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
     });
   }
 
-  render() {
-    const { onRun, onExplain, queryElapsed } = this.props;
-    const { query, autoCompleteOn } = this.state;
+  private onRunClick = () => {
+    const { onRun } = this.props;
+    const { query, bypassCache, wrapQuery } = this.state;
+    onRun(query, bypassCache, wrapQuery);
+  }
 
-    const isRune = query.trim().startsWith('{');
+  renderExtraMenu(isRune: boolean) {
+    const { onExplain } = this.props;
+    const { query, autoComplete, bypassCache, wrapQuery } = this.state;
 
-    const SqlControlPopover = <Popover position={Position.BOTTOM_LEFT}>
-        <Button minimal icon={IconNames.MORE}/>
-        <div className="sql-control-popover">
-          <Checkbox
-            checked={isRune ? false : autoCompleteOn}
-            label="Auto complete"
-            onChange={() => this.setState({autoCompleteOn: !autoCompleteOn})}
-          />
-          <Button
+    return <Menu>
+      {
+        !isRune &&
+        <>
+          <MenuItem
             icon={IconNames.CLEAN}
-            className={Classes.POPOVER_DISMISS}
             text="Explain"
             onClick={() => onExplain(query)}
-            minimal
           />
-        </div>
-      </Popover>;
+          <MenuCheckbox
+            checked={autoComplete}
+            label="Auto complete"
+            onChange={() => this.setState({autoComplete: !autoComplete})}
+          />
+          <MenuCheckbox
+            checked={wrapQuery}
+            label="Wrap query with limit"
+            onChange={() => this.setState({wrapQuery: !wrapQuery})}
+          />
+        </>
+      }
+      <MenuCheckbox
+        checked={bypassCache}
+        label="Bypass cache"
+        onChange={() => this.setState({bypassCache: !bypassCache})}
+      />
+    </Menu>;
+  }
+
+  render() {
+    const { queryElapsed } = this.props;
+    const { query, autoComplete, wrapQuery } = this.state;
+    const isRune = query.trim().startsWith('{');
 
     // Set the key in the AceEditor to force a rebind and prevent an error that happens otherwise
     return <div className="sql-control">
@@ -208,17 +252,24 @@ export class SqlControl extends React.Component<SqlControlProps, SqlControlState
           $blockScrolling: Infinity
         }}
         setOptions={{
-          enableBasicAutocompletion: isRune ? false : autoCompleteOn,
-          enableLiveAutocompletion: isRune ? false : autoCompleteOn,
+          enableBasicAutocompletion: isRune ? false : autoComplete,
+          enableLiveAutocompletion: isRune ? false : autoComplete,
           showLineNumbers: true,
           tabSize: 2
         }}
       />
       <div className="buttons">
-        <Button rightIcon={IconNames.CARET_RIGHT} onClick={() => onRun(query)}>
-          {isRune ? 'Rune' : 'Run'}
-        </Button>
-        {!isRune && SqlControlPopover}
+        <ButtonGroup>
+          <Button
+            icon={IconNames.CARET_RIGHT}
+            onClick={this.onRunClick}
+            text={isRune ? 'Rune' : (wrapQuery ? 'Run with limit' : 'Run as is')}
+            disabled={isRune && !validHjson(query)}
+          />
+          <Popover position={Position.BOTTOM_LEFT} content={this.renderExtraMenu(isRune)}>
+            <Button icon={IconNames.MORE}/>
+          </Popover>
+        </ButtonGroup>
         {
           queryElapsed &&
           <span className={'query-elapsed'}> Last query took {(queryElapsed / 1000).toFixed(2)} seconds</span>
diff --git a/web-console/src/components/table-column-selection.tsx b/web-console/src/components/table-column-selection.tsx
index 641f3bb..3635d51 100644
--- a/web-console/src/components/table-column-selection.tsx
+++ b/web-console/src/components/table-column-selection.tsx
@@ -20,6 +20,8 @@ import { Button, Checkbox, FormGroup, Menu, Popover, Position } from '@blueprint
 import { IconNames } from '@blueprintjs/icons';
 import * as React from 'react';
 
+import { MenuCheckbox } from './menu-checkbox';
+
 import './table-column-selection.scss';
 
 interface TableColumnSelectionProps extends React.Props<any> {
@@ -44,16 +46,14 @@ export class TableColumnSelection extends React.Component<TableColumnSelectionPr
   render() {
     const { columns, onChange, tableColumnsHidden } = this.props;
     const checkboxes = <Menu className="table-column-selection-menu">
-      <FormGroup>
-        {columns.map(column => (
-          <Checkbox
-            label={column}
-            key={column}
-            checked={!tableColumnsHidden.includes(column)}
-            onChange={() => onChange(column)}
-          />
-        ))}
-      </FormGroup>
+      {columns.map(column => (
+        <MenuCheckbox
+          label={column}
+          key={column}
+          checked={!tableColumnsHidden.includes(column)}
+          onChange={() => onChange(column)}
+        />
+      ))}
     </Menu>;
 
     return <Popover
diff --git a/web-console/src/views/sql-view.tsx b/web-console/src/views/sql-view.tsx
index 53712d9..1713066 100644
--- a/web-console/src/views/sql-view.tsx
+++ b/web-console/src/views/sql-view.tsx
@@ -35,6 +35,12 @@ import {
 
 import './sql-view.scss';
 
+interface QueryWithFlags {
+  queryString: string;
+  bypassCache?: boolean;
+  wrapQuery?: boolean;
+}
+
 export interface SqlViewProps extends React.Props<any> {
   initSql: string | null;
 }
@@ -57,7 +63,7 @@ interface SqlQueryResult {
 
 export class SqlView extends React.Component<SqlViewProps, SqlViewState> {
 
-  private sqlQueryManager: QueryManager<string, SqlQueryResult>;
+  private sqlQueryManager: QueryManager<QueryWithFlags, SqlQueryResult>;
   private explainQueryManager: QueryManager<string, any>;
 
   constructor(props: SqlViewProps, context: any) {
@@ -76,22 +82,46 @@ export class SqlView extends React.Component<SqlViewProps, SqlViewState> {
 
   componentDidMount(): void {
     this.sqlQueryManager = new QueryManager({
-      processQuery: async (query: string) => {
+      processQuery: async (queryWithFlags: QueryWithFlags) => {
+        const { queryString, bypassCache, wrapQuery } = queryWithFlags;
         const startTime = new Date();
-        if (query.trim().startsWith('{')) {
+
+        if (queryString.trim().startsWith('{')) {
           // Secret way to issue a native JSON "rune" query
-          const runeQuery = Hjson.parse(query);
+          const runeQuery = Hjson.parse(queryString);
+
+          if (bypassCache) {
+            runeQuery.context = runeQuery.context || {};
+            runeQuery.context.useCache = false;
+            runeQuery.context.populateCache = false;
+          }
+
           const result = await queryDruidRune(runeQuery);
           return {
             queryResult: decodeRune(runeQuery, result),
             queryElapsed: new Date().valueOf() - startTime.valueOf()
           };
+
         } else {
-          const result = await queryDruidSql({
-            query,
+          const actualQuery = wrapQuery ?
+            `SELECT * FROM (${queryString.trim().replace(/;+$/, '')}) LIMIT 5000` :
+            queryString;
+
+          const queryPayload: Record<string, any> = {
+            query: actualQuery,
             resultFormat: 'array',
             header: true
-          });
+          };
+
+          if (wrapQuery) {
+            queryPayload.context = {
+              useCache: false,
+              populateCache: false
+            };
+          }
+
+          const result = await queryDruidSql(queryPayload);
+
           return {
             queryResult: {
               header: (result && result.length) ? result[0] : [],
@@ -178,11 +208,11 @@ export class SqlView extends React.Component<SqlViewProps, SqlViewState> {
     return <div className="sql-view app-view">
       <SqlControl
         initSql={initSql || localStorageGet(LocalStorageKeys.QUERY_KEY)}
-        onRun={q => {
-          localStorageSet(LocalStorageKeys.QUERY_KEY, q);
-          this.sqlQueryManager.runQuery(q);
+        onRun={(queryString, bypassCache, wrapQuery) => {
+          localStorageSet(LocalStorageKeys.QUERY_KEY, queryString);
+          this.sqlQueryManager.runQuery({ queryString, bypassCache, wrapQuery });
         }}
-        onExplain={(q: string) => this.getExplain(q)}
+        onExplain={this.getExplain}
         queryElapsed={queryElapsed}
       />
       {this.renderResultTable()}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org