From 834b0eddeb84e3ad2bd916455884482dd5376658 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Tue, 30 Apr 2024 12:53:39 -0600 Subject: [PATCH] web-console: ACE editor refactoring (#16359) * Move druid-sql completions to dsql mode * Use font-size 12 * Convert ace-modes to typescript * Move aceCompleters to class member * Use namespace imports --- web-console/lib/sql-docs.d.ts | 2 +- .../__snapshots__/make-doc-html.spec.ts.snap | 8 + .../src/ace-modes/{dsql.js => dsql.ts} | 95 ++++++++--- .../src/ace-modes/{hjson.js => hjson.ts} | 30 ++-- .../src/ace-modes/make-doc-html.spec.ts | 31 ++++ web-console/src/ace-modes/make-doc-html.ts | 32 ++++ .../__snapshots__/json-input.spec.tsx.snap | 4 +- .../__snapshots__/spec-dialog.spec.tsx.snap | 4 +- web-console/src/setup-tests.ts | 1 + .../explain-dialog.spec.tsx.snap | 6 +- .../explain-dialog/explain-dialog.tsx | 2 +- .../flexible-query-input.spec.tsx.snap | 11 +- .../flexible-query-input.spec.tsx | 10 -- .../flexible-query-input.tsx | 148 +++--------------- .../workbench-history-dialog.tsx | 2 +- 15 files changed, 197 insertions(+), 189 deletions(-) create mode 100644 web-console/src/ace-modes/__snapshots__/make-doc-html.spec.ts.snap rename web-console/src/ace-modes/{dsql.js => dsql.ts} (56%) rename web-console/src/ace-modes/{hjson.js => hjson.ts} (89%) create mode 100644 web-console/src/ace-modes/make-doc-html.spec.ts create mode 100644 web-console/src/ace-modes/make-doc-html.ts diff --git a/web-console/lib/sql-docs.d.ts b/web-console/lib/sql-docs.d.ts index a5af23211d70..5948206a2208 100644 --- a/web-console/lib/sql-docs.d.ts +++ b/web-console/lib/sql-docs.d.ts @@ -16,5 +16,5 @@ * limitations under the License. */ -export const SQL_DATA_TYPES: Record; +export const SQL_DATA_TYPES: Record; export const SQL_FUNCTIONS: Record; diff --git a/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.ts.snap b/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.ts.snap new file mode 100644 index 000000000000..21695129d99f --- /dev/null +++ b/web-console/src/ace-modes/__snapshots__/make-doc-html.spec.ts.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.ts similarity index 56% rename from web-console/src/ace-modes/dsql.js rename to web-console/src/ace-modes/dsql.ts index f2349ee98fcf..e57d17d51e7c 100644 --- a/web-console/src/ace-modes/dsql.js +++ b/web-console/src/ace-modes/dsql.ts @@ -21,36 +21,42 @@ // 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-docs'); +import type { Ace } from 'ace-builds'; +import ace from 'ace-builds/src-noconflict/ace'; + +import * as druidKeywords from '../../lib/keywords'; +import * as druidFunctions from '../../lib/sql-docs'; + +import type { ItemDescription } from './make-doc-html'; +import { makeDocHtml } from './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: any, exports: any) { 'use strict'; - var oop = acequire('../lib/oop'); - var TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules; + const oop = acequire('../lib/oop'); + const TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules; - var SqlHighlightRules = function () { + const SqlHighlightRules = function (this: any) { // 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) + const keywords = druidKeywords.SQL_KEYWORDS.concat(druidKeywords.SQL_EXPRESSION_PARTS) .join('|') .replace(/\s/g, '|'); // Stuff like: 'true|false' - var builtinConstants = druidKeywords.SQL_CONSTANTS.join('|'); + const builtinConstants = druidKeywords.SQL_CONSTANTS.join('|'); // Stuff like: 'avg|count|first|last|max|min' - var builtinFunctions = druidKeywords.SQL_DYNAMICS.concat( + const builtinFunctions = druidKeywords.SQL_DYNAMICS.concat( Object.keys(druidFunctions.SQL_FUNCTIONS), ).join('|'); // Stuff like: 'int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp' - var dataTypes = Object.keys(druidFunctions.SQL_DATA_TYPES).join('|'); + const dataTypes = Object.keys(druidFunctions.SQL_DATA_TYPES).join('|'); - var keywordMapper = this.createKeywordMapper( + const keywordMapper = this.createKeywordMapper( { 'support.function': builtinFunctions, 'keyword': keywords, @@ -122,24 +128,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: any, exports: any) { 'use strict'; - var oop = acequire('../lib/oop'); - var TextMode = acequire('./text').Mode; - var SqlHighlightRules = acequire('./dsql_highlight_rules').SqlHighlightRules; - - var Mode = function () { + const oop = acequire('../lib/oop'); + const TextMode = acequire('./text').Mode; + const SqlHighlightRules = acequire('./dsql_highlight_rules').SqlHighlightRules; + + const completions = ([] as Ace.Completion[]).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: ItemDescription = { + 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: any, data: any) => { + editor.completer.insertMatch({ value: data.name }); + }, + }, + } as Ace.Completion; + }); + }), + ); + + const Mode = function (this: any) { 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/hjson.js b/web-console/src/ace-modes/hjson.ts similarity index 89% rename from web-console/src/ace-modes/hjson.js rename to web-console/src/ace-modes/hjson.ts index 316ce9870e90..1c58a5c79450 100644 --- a/web-console/src/ace-modes/hjson.js +++ b/web-console/src/ace-modes/hjson.ts @@ -22,16 +22,18 @@ // This file was modified to remove the folding functionality that did not play nice when loaded along side the // sql mode (which does not have any folding function) +import ace from 'ace-builds/src-noconflict/ace'; + ace.define( 'ace/mode/hjson_highlight_rules', ['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'], - function (acequire, exports, module) { + function (acequire: any, exports: any) { 'use strict'; - var oop = acequire('../lib/oop'); - var TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules; + const oop = acequire('../lib/oop'); + const TextHighlightRules = acequire('./text_highlight_rules').TextHighlightRules; - var HjsonHighlightRules = function () { + const HjsonHighlightRules = function (this: any) { this.$rules = { 'start': [ { @@ -107,7 +109,7 @@ ace.define( '#keyname': [ { token: 'keyword', - regex: /(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*(?=:)/, + regex: /(?:[^,{[}\]\s]+|"(?:[^"\\]|\\.)*")\s*(?=:)/, }, ], '#mstring': [ @@ -166,7 +168,7 @@ ace.define( '#rootObject': [ { token: 'paren', - regex: /(?=\s*(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*:)/, + regex: /(?=\s*(?:[^,{[}\]\s]+|"(?:[^"\\]|\\.)*")\s*:)/, push: [ { token: 'paren.rparen', @@ -205,7 +207,7 @@ ace.define( }, { token: 'constant.language.escape', - regex: /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/, + regex: /\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/, }, { token: 'invalid.illegal', @@ -220,7 +222,7 @@ ace.define( '#ustring': [ { token: 'string', - regex: /\b[^:,0-9\-\{\[\}\]\s].*$/, + regex: /\b[^:,0-9\-{[}\]\s].*$/, }, ], '#value': [ @@ -277,19 +279,19 @@ ace.define( 'ace/mode/text', 'ace/mode/hjson_highlight_rules', ], - function (acequire, exports, module) { + function (acequire: any, exports: any) { 'use strict'; - var oop = acequire('../lib/oop'); - var TextMode = acequire('./text').Mode; - var HjsonHighlightRules = acequire('./hjson_highlight_rules').HjsonHighlightRules; + const oop = acequire('../lib/oop'); + const TextMode = acequire('./text').Mode; + const HjsonHighlightRules = acequire('./hjson_highlight_rules').HjsonHighlightRules; - var Mode = function () { + const Mode = function (this: any) { this.HighlightRules = HjsonHighlightRules; }; oop.inherits(Mode, TextMode); - (function () { + (function (this: any) { this.lineCommentStart = '//'; this.blockComment = { start: '/*', end: '*/' }; this.$id = 'ace/mode/hjson'; diff --git a/web-console/src/ace-modes/make-doc-html.spec.ts b/web-console/src/ace-modes/make-doc-html.spec.ts new file mode 100644 index 000000000000..35172d0f5b95 --- /dev/null +++ b/web-console/src/ace-modes/make-doc-html.spec.ts @@ -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. + */ + +import { makeDocHtml } from './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/ace-modes/make-doc-html.ts b/web-console/src/ace-modes/make-doc-html.ts new file mode 100644 index 000000000000..996541b3ec63 --- /dev/null +++ b/web-console/src/ace-modes/make-doc-html.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import escape from 'lodash.escape'; + +export interface ItemDescription { + name: string; + syntax: string; + description: string; +} + +export function makeDocHtml(item: ItemDescription) { + return ` +
${item.name}
+
${escape(item.syntax)}
+
${item.description}
`; +} 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/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 0efa8f7f7a71..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 @@ -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`] = `