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

cwylie 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 5406aaa  Add SQL auto complete in druid console (#7244)
5406aaa is described below

commit 5406aaa49d5a7a7071bb013e5be1d265405cf940
Author: Qi Shu <shuqi...@gmail.com>
AuthorDate: Sat Mar 16 01:45:53 2019 -0700

    Add SQL auto complete in druid console (#7244)
    
    * Add SQL auto complete in druid console
    
    * Add comment in sql.md to alert user to change create-sql-function-doc if 
sql.md format gets changed
---
 docs/content/querying/sql.md                |   7 ++
 web-console/.gitignore                      |   2 +
 web-console/script/build                    |   3 +
 web-console/script/create-sql-function-doc  |  50 +++++++++++
 web-console/src/components/sql-control.scss |  61 +++++++++++++
 web-console/src/components/sql-control.tsx  | 133 +++++++++++++++++++++++++++-
 web-console/tsconfig.json                   |   5 +-
 7 files changed, 255 insertions(+), 6 deletions(-)

diff --git a/docs/content/querying/sql.md b/docs/content/querying/sql.md
index 66ecc43..78b3768 100644
--- a/docs/content/querying/sql.md
+++ b/docs/content/querying/sql.md
@@ -22,6 +22,13 @@ title: "SQL"
   ~ under the License.
   -->
 
+  <!-- 
+    The format of the tables that describe the functions and operators 
+    should not be changed without updating the script create-sql-function-doc 
+    in web-console/script/create-sql-function-doc, because the script detects
+    patterns in this markdown file and parse it to TypeScript file for web 
console
+   -->
+
 # SQL
 
 <div class="note caution">
diff --git a/web-console/.gitignore b/web-console/.gitignore
index defba19..be63fe3 100644
--- a/web-console/.gitignore
+++ b/web-console/.gitignore
@@ -9,6 +9,8 @@ coordinator-console/
 pages/
 index.html
 
+lib/sql-function-doc.ts
+
 .tscache
 tscommand-*.tmp.txt
 
diff --git a/web-console/script/build b/web-console/script/build
index 7537122..cbc9092 100755
--- a/web-console/script/build
+++ b/web-console/script/build
@@ -27,6 +27,9 @@ echo "Copying blueprint assets in..."
 sed 's|url("assets|url("/assets|g' 
"./node_modules/@blueprintjs/core/dist/blueprint.css" > lib/blueprint.css
 cp -r "./node_modules/@blueprintjs/core/dist/assets" .
 
+echo "Adding SQL function doc..."
+PATH="./target/node:$PATH" ./script/create-sql-function-doc
+
 echo "Transpiling ReactTable CSS..."
 PATH="./target/node:$PATH" ./node_modules/.bin/stylus lib/react-table.styl -o 
lib/react-table.css
 
diff --git a/web-console/script/create-sql-function-doc 
b/web-console/script/create-sql-function-doc
new file mode 100755
index 0000000..146a39e
--- /dev/null
+++ b/web-console/script/create-sql-function-doc
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+# 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.
+
+readfile='../docs/content/querying/sql.md'
+writefile='lib/sql-function-doc.ts'
+
+> "$writefile"
+
+echo -e "// This file is auto generated and should not be modified\n" > 
"$writefile"
+echo -e 'export const SQLFunctionDoc: any[] = [' >> "$writefile"
+
+isFunction=false
+
+while read -r line; do
+    if [[ $line =~ ^###.*functions$ ]]; then
+        isFunction=true
+    elif [[ $line =~ ^## ]] ; then
+        isFunction=false
+    elif [[ $isFunction == true ]]; then
+        if [[ $line =~ \|\`.*\`\|.*\| ]]; then
+            syntax=$(echo $line | grep -o '|`.*`|')
+            syntax=${syntax:2:${#syntax}-4}
+            syntax=${syntax//\\/}
+            description=$(echo $line | grep -o '`|.*.|')
+            description=${description//\"/\'}
+            description=${description:2:${#description}-4}
+            echo -e "   {" >> "$writefile"
+            echo -e "       syntax: \"$syntax\"," >> "$writefile"
+            echo -e "       description: \"$description\"" >> "$writefile"
+            echo -e "   }," >> "$writefile"
+        fi
+    fi
+done < "$readfile"
+
+echo -e ']' >> "$writefile"
\ No newline at end of file
diff --git a/web-console/src/components/sql-control.scss 
b/web-console/src/components/sql-control.scss
new file mode 100644
index 0000000..9e55c5a
--- /dev/null
+++ b/web-console/src/components/sql-control.scss
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+
+.sql-control {
+
+  .ace_scroller {
+    background-color: #232C35;
+  }
+
+  .ace_gutter-layer {
+    background-color: #27313c;
+  }
+
+  .buttons {
+
+    button{
+      margin-right: 15px;
+    }
+
+  }
+
+}
+
+.auto-complete-checkbox {
+  margin:10px;
+  min-width: 120px;
+}
+
+.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;
+  }
+}
\ No newline at end of file
diff --git a/web-console/src/components/sql-control.tsx 
b/web-console/src/components/sql-control.tsx
index 96dd780..a2d21b5 100644
--- a/web-console/src/components/sql-control.tsx
+++ b/web-console/src/components/sql-control.tsx
@@ -17,6 +17,8 @@
  */
 
 import * as React from 'react';
+import * as ReactDOMServer from 'react-dom/server';
+import axios from "axios";
 import * as classNames from 'classnames';
 import * as ace from 'brace'
 import AceEditor from "react-ace";
@@ -24,8 +26,13 @@ import 'brace/mode/sql';
 import 'brace/mode/hjson';
 import 'brace/theme/solarized_dark';
 import 'brace/ext/language_tools';
-import { Intent, Button } from "@blueprintjs/core";
+import {Intent, Button, Popover, Checkbox, Classes, Position} from 
"@blueprintjs/core";
+import { SQLFunctionDoc } from "../../lib/sql-function-doc";
 import { IconNames } from './filler';
+import './sql-control.scss'
+import {AppToaster} from "../singletons/toaster";
+
+const langTools = ace.acequire('ace/ext/language_tools');
 
 export interface SqlControlProps extends React.Props<any> {
   initSql: string | null;
@@ -35,6 +42,7 @@ export interface SqlControlProps extends React.Props<any> {
 export interface SqlControlState {
   query: string;
   autoCompleteOn: boolean;
+  autoCompleteLoading: boolean;
 }
 
 export class SqlControl extends React.Component<SqlControlProps, 
SqlControlState> {
@@ -42,8 +50,111 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
     super(props, context);
     this.state = {
       query: props.initSql || '',
-      autoCompleteOn: true
+      autoCompleteOn: true,
+      autoCompleteLoading: false
+    };
+  }
+
+
+  private addDatasourceAutoCompleter = async (): Promise<any> =>{
+    const datasourceResp = await axios.post("/druid/v2/sql", { query: `SELECT 
datasource FROM sys.segments GROUP BY 1`})
+    const datasourceList: any[] = datasourceResp.data.map((d: any) => {
+      const datasourceName: string = d.datasource;
+      return {
+        value: datasourceName,
+        score: 50,
+        meta: "datasource"
+      };
+    });
+
+    const completer = {
+      getCompletions: (editor:any , session: any, pos: any, prefix: any, 
callback: any) => {
+        callback(null, datasourceList);
+      }
+    };
+
+    langTools.addCompleter(completer);
+  }
+
+  private addColumnNameAutoCompleter = async (): Promise<any> => {
+    const columnNameResp = await axios.post("/druid/v2/sql", {query: `SELECT 
COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'druid'`})
+    const columnNameList: any[] = columnNameResp.data.map((d: any) => {
+      const columnName: string = d.COLUMN_NAME;
+      return {
+        value: columnName,
+        score: 50,
+        meta: "column"
+      };
+    });
+
+    const completer = {
+      getCompletions: (editor:any , session: any, pos: any, prefix: any, 
callback: any) => {
+        callback(null, columnNameList);
+      }
     };
+
+    langTools.addCompleter(completer);
+  }
+
+  private addFunctionAutoCompleter = (): void => {
+    const functionList: any[]= SQLFunctionDoc.map((entry: any) => {
+      let funcName: string = entry.syntax.replace(/\(.*\)/,"()");
+      if (!funcName.includes("(")) funcName = funcName.substr(0,10);
+      return {
+        value: funcName,
+        score: 80,
+        meta: "function",
+        syntax: entry.syntax,
+        description: entry.description,
+        completer: {
+          insertMatch: (editor:any, data:any) => {
+            editor.completer.insertMatch({value: data.caption});
+            const pos = editor.getCursorPosition();
+            editor.gotoLine(pos.row+1, pos.column-1);
+          }
+        }
+      };
+    });
+
+    const completer = {
+      getCompletions: (editor:any , session: any, pos: any, prefix: any, 
callback: any) => {
+        callback(null, functionList);
+      },
+      getDocTooltip: (item: any) => {
+        if (item.meta === "function") {
+          const functionName = item.caption.slice(0,-2);
+          item.docHTML = ReactDOMServer.renderToStaticMarkup((
+            <div className={"function-doc"}>
+              <div className={"function-doc-name"}><b>{functionName}</b></div>
+              <hr/>
+              <div><b>Syntax:</b></div>
+              <div>{item.syntax}</div>
+              <br/>
+              <div><b>Description:</b></div>
+              <div>{item.description}</div>
+            </div>
+          ))
+        }
+      }
+    };
+    langTools.addCompleter(completer);
+  }
+
+  private addCompleters = async () => {
+    try {
+      this.addFunctionAutoCompleter();
+      await this.addDatasourceAutoCompleter();
+      await this.addColumnNameAutoCompleter();
+    } catch (e) {
+      AppToaster.show({
+        message: "Failed to load SQL auto completer",
+        intent: Intent.DANGER
+      });
+    }
+  }
+
+  componentDidMount(): void {
+    this.addCompleters();
   }
 
   private handleChange = (newValue: string): void => {
@@ -58,6 +169,15 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
 
     const isRune = query.trim().startsWith('{');
 
+    const autoCompletePopover = <div className={"auto-complete-checkbox"}>
+      <Checkbox
+        checked={isRune ? false : autoCompleteOn}
+        disabled={isRune}
+        label={"Auto complete"}
+        onChange={() => this.setState({autoCompleteOn: !autoCompleteOn})}
+      />
+    </div>;
+
     // Set the key in the AceEditor to force a rebind and prevent an error 
that happens otherwise
     return <div className="sql-control">
       <AceEditor
@@ -83,9 +203,14 @@ export class SqlControl extends 
React.Component<SqlControlProps, SqlControlState
         }}
       />
       <div className="buttons">
-        <Button rightIconName={IconNames.CARET_RIGHT} onClick={() => 
onRun(query)}>{isRune ? 'Rune' : 'Run'}</Button>
+        <Button rightIconName={IconNames.CARET_RIGHT} onClick={() => 
onRun(query)}>
+          {isRune ? 'Rune' : 'Run'}
+        </Button>
+        <Popover position={Position.BOTTOM_LEFT} content={autoCompletePopover} 
inline>
+          <Button className={`${Classes.MINIMAL} pt-icon-more`}/>
+        </Popover>
       </div>
-    </div>
+    </div>;
   }
 }
 
diff --git a/web-console/tsconfig.json b/web-console/tsconfig.json
index 56fdba0..a445cfa 100644
--- a/web-console/tsconfig.json
+++ b/web-console/tsconfig.json
@@ -15,14 +15,15 @@
     "moduleResolution": "node",
     "lib": ["dom", "es2016"],
     "jsx": "react",
-    "rootDir": "src",
+    "rootDirs": ["lib","src"],
 
     "outDir": "build"
   },
 
   "include": [
     "src/**/*.ts",
-    "src/**/*.tsx"
+    "src/**/*.tsx",
+    "lib/sql-function-doc.ts"
   ],
   "exclude": [
     "**/*.test.ts"


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

Reply via email to