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