From 7ca02f5d3e4b0b036887652ffd010489c62793f5 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Mon, 29 Apr 2024 17:09:01 -0600 Subject: [PATCH 1/5] Move druid-sql completions to dsql mode --- web-console/jest.unit.config.js | 2 +- .../__snapshots__/make-doc-html.spec.js.snap | 8 + web-console/src/ace-modes/dsql.js | 63 ++++++-- web-console/src/ace-modes/make-doc-html.js | 26 +++ .../src/ace-modes/make-doc-html.spec.js | 31 ++++ .../__snapshots__/json-input.spec.tsx.snap | 4 +- .../__snapshots__/spec-dialog.spec.tsx.snap | 4 +- web-console/src/setup-tests.ts | 1 + .../flexible-query-input.spec.tsx.snap | 9 +- .../flexible-query-input.spec.tsx | 10 -- .../flexible-query-input.tsx | 151 ++++-------------- 11 files changed, 153 insertions(+), 156 deletions(-) create mode 100644 web-console/src/ace-modes/__snapshots__/make-doc-html.spec.js.snap create mode 100644 web-console/src/ace-modes/make-doc-html.js create mode 100644 web-console/src/ace-modes/make-doc-html.spec.js diff --git a/web-console/jest.unit.config.js b/web-console/jest.unit.config.js index 2819ba86ca0a..5d2b3f491235 100644 --- a/web-console/jest.unit.config.js +++ b/web-console/jest.unit.config.js @@ -23,5 +23,5 @@ module.exports = Object.assign(common, { '\\.s?css$': 'identity-obj-proxy', }, setupFilesAfterEnv: ['src/setup-tests.ts'], - testMatch: ['**/src/**/?(*.)+(spec).(ts|tsx)'], + testMatch: ['**/src/**/?(*.)+(spec).(ts|tsx)', '**/src/ace-modes/?(*.)+(spec).js'], }); diff --git a/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.js.snap b/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.js.snap new file mode 100644 index 000000000000..21695129d99f --- /dev/null +++ b/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.js.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`makeDocHtml correctly formats helper HTML 1`] = ` +" +
COUNT
+
COUNT(*)
+
Counts the number of things
" +`; diff --git a/web-console/src/ace-modes/dsql.js b/web-console/src/ace-modes/dsql.js index f2349ee98fcf..b6735b4e1161 100644 --- a/web-console/src/ace-modes/dsql.js +++ b/web-console/src/ace-modes/dsql.js @@ -24,10 +24,12 @@ var druidKeywords = require('../../lib/keywords'); var druidFunctions = require('../../lib/sql-docs'); +var makeDocHtml = require('./make-doc-html'); + ace.define( 'ace/mode/dsql_highlight_rules', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'], - function (acequire, exports, module) { + function (acequire, exports, _module) { 'use strict'; var oop = acequire('../lib/oop'); @@ -122,24 +124,67 @@ ace.define( ace.define( 'ace/mode/dsql', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text', 'ace/mode/dsql_highlight_rules'], - function (acequire, exports, module) { + function (acequire, exports, _module) { 'use strict'; var oop = acequire('../lib/oop'); var TextMode = acequire('./text').Mode; var SqlHighlightRules = acequire('./dsql_highlight_rules').SqlHighlightRules; - var Mode = function () { + var completions = [].concat( + druidKeywords.SQL_KEYWORDS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })), + druidKeywords.SQL_EXPRESSION_PARTS.map(v => ({ + name: v, + value: v, + score: 0, + meta: 'keyword', + })), + druidKeywords.SQL_CONSTANTS.map(v => ({ name: v, value: v, score: 0, meta: 'constant' })), + druidKeywords.SQL_DYNAMICS.map(v => ({ name: v, value: v, score: 0, meta: 'dynamic' })), + Object.entries(druidFunctions.SQL_DATA_TYPES).map(([name, [runtime, description]]) => { + const item = { + name, + description, + syntax: `Druid runtime type: ${runtime}`, + }; + return { + name, + value: name, + score: 0, + meta: 'type', + docHTML: makeDocHtml(item), + docText: description, + }; + }), + Object.entries(druidFunctions.SQL_FUNCTIONS).flatMap(([name, versions]) => { + return versions.map(([args, description]) => { + const item = { name, description, syntax: `${name}(${args})` }; + return { + name, + value: versions.length > 1 ? `${name}(${args})` : name, + score: 1100, // Use a high score to appear over the 'local' suggestions that have a score of 1000 + meta: 'function', + docHTML: makeDocHtml(item), + docText: description, + completer: { + insertMatch: (editor, data) => { + editor.completer.insertMatch({ value: data.name }); + }, + }, + }; + }); + }), + ); + + const Mode = function () { this.HighlightRules = SqlHighlightRules; this.$behaviour = this.$defaultBehaviour; - }; - oop.inherits(Mode, TextMode); + this.$id = 'ace/mode/dsql'; - (function () { this.lineCommentStart = '--'; - - this.$id = 'ace/mode/dsql'; - }).call(Mode.prototype); + this.getCompletions = () => completions; + }; + oop.inherits(Mode, TextMode); exports.Mode = Mode; }, diff --git a/web-console/src/ace-modes/make-doc-html.js b/web-console/src/ace-modes/make-doc-html.js new file mode 100644 index 000000000000..63f976c91840 --- /dev/null +++ b/web-console/src/ace-modes/make-doc-html.js @@ -0,0 +1,26 @@ +/* + * 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. + */ + +var escape = require('lodash.escape'); + +module.exports = function makeDocHtml(item) { + return ` +
${item.name}
+
${escape(item.syntax)}
+
${item.description}
`; +}; diff --git a/web-console/src/ace-modes/make-doc-html.spec.js b/web-console/src/ace-modes/make-doc-html.spec.js new file mode 100644 index 000000000000..62b1c32cb19b --- /dev/null +++ b/web-console/src/ace-modes/make-doc-html.spec.js @@ -0,0 +1,31 @@ +/* + * 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. + */ + +var makeDocHtml = require('./make-doc-html'); + +describe('makeDocHtml', () => { + it('correctly formats helper HTML', () => { + expect( + makeDocHtml({ + name: 'COUNT', + syntax: 'COUNT(*)', + description: 'Counts the number of things', + }), + ).toMatchSnapshot(); + }); +}); diff --git a/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap b/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap index e96b7f6c4ec5..b71b692ee008 100644 --- a/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap +++ b/web-console/src/components/json-input/__snapshots__/json-input.spec.tsx.snap @@ -5,7 +5,7 @@ exports[`JsonInput matches snapshot (null) 1`] = ` class="json-input" >
@@ -104,7 +104,7 @@ exports[`JsonInput matches snapshot (value) 1`] = ` class="json-input" >
diff --git a/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap b/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap index cc8b602739d8..cc0971648e63 100644 --- a/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap +++ b/web-console/src/dialogs/spec-dialog/__snapshots__/spec-dialog.spec.tsx.snap @@ -58,7 +58,7 @@ exports[`SpecDialog matches snapshot no initSpec 1`] = `
@@ -252,7 +252,7 @@ exports[`SpecDialog matches snapshot with initSpec 1`] = `
diff --git a/web-console/src/setup-tests.ts b/web-console/src/setup-tests.ts index e75cb3bffb09..518045d6b6da 100644 --- a/web-console/src/setup-tests.ts +++ b/web-console/src/setup-tests.ts @@ -17,6 +17,7 @@ */ import 'core-js/stable'; +import './bootstrap/ace'; import { UrlBaser } from './singletons'; diff --git a/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap b/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap index 0efa8f7f7a71..812bb5436867 100644 --- a/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap +++ b/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap @@ -1,12 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FlexibleQueryInput correctly formats helper HTML 1`] = ` -" -
COUNT
-
COUNT(*)
-
Counts the number of things
" -`; - exports[`FlexibleQueryInput matches snapshot 1`] = `
diff --git a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx index 9a2b5b2fb500..06b712422208 100644 --- a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx +++ b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.spec.tsx @@ -30,14 +30,4 @@ describe('FlexibleQueryInput', () => { const { container } = render(sqlControl); expect(container.firstChild).toMatchSnapshot(); }); - - it('correctly formats helper HTML', () => { - expect( - FlexibleQueryInput.makeDocHtml({ - name: 'COUNT', - syntax: 'COUNT(*)', - description: 'Counts the number of things', - }), - ).toMatchSnapshot(); - }); }); diff --git a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx index e9eb36882952..79346a4e5af0 100644 --- a/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx +++ b/web-console/src/views/workbench-view/flexible-query-input/flexible-query-input.tsx @@ -24,17 +24,9 @@ import type { Ace } from 'ace-builds'; import ace from 'ace-builds'; import classNames from 'classnames'; import debounce from 'lodash.debounce'; -import escape from 'lodash.escape'; import React from 'react'; import AceEditor from 'react-ace'; -import { - SQL_CONSTANTS, - SQL_DYNAMICS, - SQL_EXPRESSION_PARTS, - SQL_KEYWORDS, -} from '../../../../lib/keywords'; -import { SQL_DATA_TYPES, SQL_FUNCTIONS } from '../../../../lib/sql-docs'; import { AppToaster } from '../../../singletons'; import { AceEditorStateCache } from '../../../singletons/ace-editor-state-cache'; import type { ColumnMetadata, QuerySlice, RowColumn } from '../../../utils'; @@ -46,18 +38,6 @@ const langTools = ace.require('ace/ext/language_tools'); const V_PADDING = 10; -const COMPLETER = { - insertMatch: (editor: any, data: Ace.Completion) => { - editor.completer.insertMatch({ value: data.name }); - }, -}; - -interface ItemDescription { - name: string; - syntax: string; - description: string; -} - export interface FlexibleQueryInputProps { queryString: string; onQueryStringChange?: (newQueryString: string) => void; @@ -87,86 +67,12 @@ export class FlexibleQueryInput extends React.PureComponent< FlexibleQueryInputProps, FlexibleQueryInputState > { + static aceTheme = 'solarized_dark'; + private aceEditor: Ace.Editor | undefined; private lastFoundQueries: QuerySlice[] = []; private highlightFoundQuery: { row: number; marker: number } | undefined; - static replaceDefaultAutoCompleter(): void { - if (!langTools) return; - - const keywordList = ([] as Ace.Completion[]).concat( - SQL_KEYWORDS.map(v => ({ name: v, value: v, score: 0, meta: 'keyword' })), - 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' })), - Object.entries(SQL_DATA_TYPES).map(([name, [runtime, description]]) => ({ - name, - value: name, - score: 0, - meta: 'type', - syntax: `Druid runtime type: ${runtime}`, - description, - })), - ); - - langTools.setCompleters([ - langTools.snippetCompleter, - langTools.textCompleter, - { - getCompletions: ( - _state: string, - _session: Ace.EditSession, - _pos: Ace.Point, - _prefix: string, - callback: any, - ) => { - return callback(null, keywordList); - }, - getDocTooltip: (item: any) => { - if (item.meta === 'type') { - item.docHTML = FlexibleQueryInput.makeDocHtml(item); - } - }, - }, - ]); - } - - static addFunctionAutoCompleter(): void { - if (!langTools) return; - - const functionList: Ace.Completion[] = Object.entries(SQL_FUNCTIONS).flatMap( - ([name, versions]) => { - return versions.map(([args, description]) => ({ - name: name, - value: versions.length > 1 ? `${name}(${args})` : name, - score: 1100, // Use a high score to appear over the 'local' suggestions that have a score of 1000 - meta: 'function', - syntax: `${name}(${args})`, - description, - completer: COMPLETER, - })); - }, - ); - - langTools.addCompleter({ - getCompletions: (_editor: any, _session: any, _pos: any, _prefix: any, callback: any) => { - callback(null, functionList); - }, - getDocTooltip: (item: any) => { - if (item.meta === 'function') { - item.docHTML = FlexibleQueryInput.makeDocHtml(item); - } - }, - }); - } - - static makeDocHtml(item: ItemDescription) { - return ` -
${item.name}
-
${escape(item.syntax)}
-
${item.description}
`; - } - static getCompletions( columnMetadata: readonly ColumnMetadata[], currentSchema: string | undefined, @@ -186,7 +92,7 @@ export class FlexibleQueryInput extends React.PureComponent< ).map(v => ({ value: quote ? String(T(v)) : v, score: 49, - meta: 'datasource', + meta: 'table', })), uniq( columnMetadata @@ -244,28 +150,6 @@ export class FlexibleQueryInput extends React.PureComponent< } componentDidMount(): void { - FlexibleQueryInput.replaceDefaultAutoCompleter(); - FlexibleQueryInput.addFunctionAutoCompleter(); - if (langTools) { - langTools.addCompleter({ - getCompletions: ( - _state: string, - session: Ace.EditSession, - pos: Ace.Point, - prefix: string, - callback: any, - ) => { - const charBeforePrefix = session.getLine(pos.row)[pos.column - prefix.length - 1]; - callback( - null, - charBeforePrefix === '"' - ? this.state.unquotedCompletions - : this.state.quotedCompletions, - ); - }, - }); - } - this.markQueries(); } @@ -283,6 +167,25 @@ export class FlexibleQueryInput extends React.PureComponent< delete this.aceEditor; } + private getAceCompleters(): Ace.Completer[] { + return [ + langTools.snippetCompleter, + langTools.keyWordCompleter, + langTools.textCompleter, + { + getCompletions: (_state, session, pos, prefix, callback) => { + const charBeforePrefix = session.getLine(pos.row)[pos.column - prefix.length - 1]; + callback( + null, + charBeforePrefix === '"' + ? this.state.unquotedCompletions + : this.state.quotedCompletions, + ); + }, + }, + ]; + } + private findAllQueriesByLine() { const { queryString } = this.props; const found = dedupe(findAllSqlQueriesInText(queryString), ({ startRowColumn }) => @@ -345,11 +248,14 @@ export class FlexibleQueryInput extends React.PureComponent< return ( { editor.renderer.setPadding(V_PADDING); From 30aeac315f296c77172d4b4dcea5cb911bd0c317 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Mon, 29 Apr 2024 17:11:03 -0600 Subject: [PATCH 2/5] Use font-size 12 --- .../__snapshots__/explain-dialog.spec.tsx.snap | 6 +++--- .../views/workbench-view/explain-dialog/explain-dialog.tsx | 2 +- .../__snapshots__/flexible-query-input.spec.tsx.snap | 2 +- .../flexible-query-input/flexible-query-input.tsx | 2 +- .../workbench-history-dialog/workbench-history-dialog.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap b/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap index bbbca4bf4d58..c0332ad0b015 100644 --- a/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap +++ b/web-console/src/views/workbench-view/explain-dialog/__snapshots__/explain-dialog.spec.tsx.snap @@ -122,7 +122,7 @@ exports[`ExplainDialog matches snapshot on some data (many queries) 1`] = ` enableLiveAutocompletion={false} enableSnippets={false} focus={false} - fontSize={13} + fontSize={12} height="100%" highlightActiveLine={true} maxLines={null} @@ -220,7 +220,7 @@ exports[`ExplainDialog matches snapshot on some data (many queries) 1`] = ` enableLiveAutocompletion={false} enableSnippets={false} focus={false} - fontSize={13} + fontSize={12} height="100%" highlightActiveLine={true} maxLines={null} @@ -348,7 +348,7 @@ exports[`ExplainDialog matches snapshot on some data (one query) 1`] = ` enableLiveAutocompletion={false} enableSnippets={false} focus={false} - fontSize={13} + fontSize={12} height="100%" highlightActiveLine={true} maxLines={null} diff --git a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx index 3c535ed5449a..4bab7e7bfb05 100644 --- a/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx +++ b/web-console/src/views/workbench-view/explain-dialog/explain-dialog.tsx @@ -131,7 +131,7 @@ export const ExplainDialog = React.memo(function ExplainDialog(props: ExplainDia theme="solarized_dark" className="query-string" name="ace-editor" - fontSize={13} + fontSize={12} width="100%" height="100%" showGutter diff --git a/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap b/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap index 812bb5436867..902b465fd23b 100644 --- a/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap +++ b/web-console/src/views/workbench-view/flexible-query-input/__snapshots__/flexible-query-input.spec.tsx.snap @@ -10,7 +10,7 @@ exports[`FlexibleQueryInput matches snapshot 1`] = `