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

chengpan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kyuubi.git


The following commit(s) were added to refs/heads/master by this push:
     new 2b20f7b62 [KYUUBI #5517] [UI] Initial implement the SQL Lab page
2b20f7b62 is described below

commit 2b20f7b6268e30b4d4e719a1135cd521098cb7a0
Author: labbomb <[email protected]>
AuthorDate: Wed Oct 25 21:17:44 2023 +0800

    [KYUUBI #5517] [UI] Initial implement the SQL Lab page
    
    ### _Why are the changes needed?_
    
    1. New SQL Lab Page
    2. New core editor functionality
    <img width="1674" alt="image" 
src="https://github.com/apache/kyuubi/assets/15062456/00cc9194-f43b-422f-a3dd-7b4456d9eb5b";>
    
    ### _How was this patch tested?_
    - [ ] Add some test cases that check the changes thoroughly including 
negative and positive cases if possible
    
    - [x] Add screenshots for manual tests if appropriate
    
    - [ ] [Run 
test](https://kyuubi.readthedocs.io/en/master/contributing/code/testing.html#running-tests)
 locally before make a pull request
    
    ### _Was this patch authored or co-authored using generative AI tooling?_
    
    No.
    
    Closes #5517 from labbomb/dev.
    
    Closes #5517
    
    7b9f6dcea [labbomb] fix: adjust keyword case
    edd71d232 [labbomb] fix: delete some language configs
    45d999b88 [labbomb] fix: update license-binary
    70edf9f47 [labbomb] fix: fix some errors
    becb4e3a6 [labbomb] fix: fix some errors
    d47e7f5c7 [labbomb] feat: add editor component
    
    Authored-by: labbomb <[email protected]>
    Signed-off-by: Cheng Pan <[email protected]>
---
 LICENSE-binary                                     |  22 +++
 kyuubi-server/web-ui/package.json                  |   2 +
 kyuubi-server/web-ui/pnpm-lock.yaml                |  72 +++++++++-
 .../web-ui/src/components/monaco-editor/index.vue  | 149 +++++++++++++++++++++
 .../web-ui/src/components/monaco-editor/type.ts    |  81 +++++++++++
 .../web-ui/src/layout/components/aside/types.ts    |   5 +
 kyuubi-server/web-ui/src/pinia/editor/index.ts     |  37 +++++
 kyuubi-server/web-ui/src/pinia/editor/type.ts      |  18 +++
 kyuubi-server/web-ui/src/router/index.ts           |   4 +-
 kyuubi-server/web-ui/src/router/lab/index.ts       |  26 ++++
 kyuubi-server/web-ui/src/views/lab/index.vue       |  64 +++++++++
 11 files changed, 474 insertions(+), 6 deletions(-)

diff --git a/LICENSE-binary b/LICENSE-binary
index b449a8344..748842a61 100644
--- a/LICENSE-binary
+++ b/LICENSE-binary
@@ -374,12 +374,16 @@ is auto-generated by `pnpm licenses list --prod`.
 ├────────────────────────────────────┼──────────────┤
 │ typescript                         │ Apache-2.0   │
 ├────────────────────────────────────┼──────────────┤
+│ moo                                │ BSD-3-Clause │
+├────────────────────────────────────┼──────────────┤
 │ normalize-wheel-es                 │ BSD-3-Clause │
 ├────────────────────────────────────┼──────────────┤
 │ source-map                         │ BSD-3-Clause │
 ├────────────────────────────────────┼──────────────┤
 │ source-map-js                      │ BSD-3-Clause │
 ├────────────────────────────────────┼──────────────┤
+│ railroad-diagrams                  │ CC0-1.0      │
+├────────────────────────────────────┼──────────────┤
 │ picocolors                         │ ISC          │
 ├────────────────────────────────────┼──────────────┤
 │ @babel/helper-string-parser        │ MIT          │
@@ -452,6 +456,8 @@ is auto-generated by `pnpm licenses list --prod`.
 ├────────────────────────────────────┼──────────────┤
 │ combined-stream                    │ MIT          │
 ├────────────────────────────────────┼──────────────┤
+│ commander                          │ MIT          │
+├────────────────────────────────────┼──────────────┤
 │ csstype                            │ MIT          │
 ├────────────────────────────────────┼──────────────┤
 │ date-fns                           │ MIT          │
@@ -460,6 +466,8 @@ is auto-generated by `pnpm licenses list --prod`.
 ├────────────────────────────────────┼──────────────┤
 │ delayed-stream                     │ MIT          │
 ├────────────────────────────────────┼──────────────┤
+│ discontinuous-range                │ MIT          │
+├────────────────────────────────────┼──────────────┤
 │ element-plus                       │ MIT          │
 ├────────────────────────────────────┼──────────────┤
 │ escape-html                        │ MIT          │
@@ -470,6 +478,8 @@ is auto-generated by `pnpm licenses list --prod`.
 ├────────────────────────────────────┼──────────────┤
 │ form-data                          │ MIT          │
 ├────────────────────────────────────┼──────────────┤
+│ get-stdin                          │ MIT          │
+├────────────────────────────────────┼──────────────┤
 │ lodash                             │ MIT          │
 ├────────────────────────────────────┼──────────────┤
 │ lodash-es                          │ MIT          │
@@ -484,16 +494,26 @@ is auto-generated by `pnpm licenses list --prod`.
 ├────────────────────────────────────┼──────────────┤
 │ mime-types                         │ MIT          │
 ├────────────────────────────────────┼──────────────┤
+│ monaco-editor                      │ MIT          │
+├────────────────────────────────────┼──────────────┤
 │ nanoid                             │ MIT          │
 ├────────────────────────────────────┼──────────────┤
+│ nearley                            │ MIT          │
+├────────────────────────────────────┼──────────────┤
 │ pinia                              │ MIT          │
 ├────────────────────────────────────┼──────────────┤
 │ pinia-plugin-persistedstate        │ MIT          │
 ├────────────────────────────────────┼──────────────┤
 │ postcss                            │ MIT          │
 ├────────────────────────────────────┼──────────────┤
+│ randexp                            │ MIT          │
+├────────────────────────────────────┼──────────────┤
+│ ret                                │ MIT          │
+├────────────────────────────────────┼──────────────┤
 │ sourcemap-codec                    │ MIT          │
 ├────────────────────────────────────┼──────────────┤
+│ sql-formatter                      │ MIT          │
+├────────────────────────────────────┼──────────────┤
 │ to-fast-properties                 │ MIT          │
 ├────────────────────────────────────┼──────────────┤
 │ vue                                │ MIT          │
@@ -503,4 +523,6 @@ is auto-generated by `pnpm licenses list --prod`.
 │ vue-i18n                           │ MIT          │
 ├────────────────────────────────────┼──────────────┤
 │ vue-router                         │ MIT          │
+├────────────────────────────────────┼──────────────┤
+│ argparse                           │ Python-2.0   │
 └────────────────────────────────────┴──────────────┘
diff --git a/kyuubi-server/web-ui/package.json 
b/kyuubi-server/web-ui/package.json
index f31b836dc..239c62706 100644
--- a/kyuubi-server/web-ui/package.json
+++ b/kyuubi-server/web-ui/package.json
@@ -19,8 +19,10 @@
     "axios": "^0.27.2",
     "date-fns": "^2.29.3",
     "element-plus": "^2.2.12",
+    "monaco-editor": "^0.44.0",
     "pinia": "^2.0.18",
     "pinia-plugin-persistedstate": "^2.1.1",
+    "sql-formatter": "^13.0.1",
     "swagger-ui-dist": "^4.9.1",
     "vue": "^3.2.37",
     "vue-i18n": "^9.2.2",
diff --git a/kyuubi-server/web-ui/pnpm-lock.yaml 
b/kyuubi-server/web-ui/pnpm-lock.yaml
index ffed6c6bd..a83d162ab 100644
--- a/kyuubi-server/web-ui/pnpm-lock.yaml
+++ b/kyuubi-server/web-ui/pnpm-lock.yaml
@@ -1,9 +1,5 @@
 lockfileVersion: '6.0'
 
-settings:
-  autoInstallPeers: true
-  excludeLinksFromLockfile: false
-
 dependencies:
   '@element-plus/icons-vue':
     specifier: ^2.0.9
@@ -17,12 +13,18 @@ dependencies:
   element-plus:
     specifier: ^2.2.12
     version: 2.2.13([email protected])
+  monaco-editor:
+    specifier: ^0.44.0
+    version: 0.44.0
   pinia:
     specifier: ^2.0.18
     version: 2.0.18([email protected])([email protected])
   pinia-plugin-persistedstate:
     specifier: ^2.1.1
     version: 2.1.1([email protected])
+  sql-formatter:
+    specifier: ^13.0.1
+    version: 13.0.1
   swagger-ui-dist:
     specifier: ^4.9.1
     version: 4.19.1
@@ -1019,7 +1021,6 @@ packages:
 
   /[email protected]:
     resolution: {integrity: 
sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
-    dev: true
 
   /[email protected]:
     resolution: {integrity: 
sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
@@ -1148,6 +1149,10 @@ packages:
     dependencies:
       delayed-stream: 1.0.0
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
     dev: true
@@ -1268,6 +1273,10 @@ packages:
       path-type: 4.0.0
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==}
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
     engines: {node: '>=6.0.0'}
@@ -1648,6 +1657,11 @@ packages:
     resolution: {integrity: 
sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==}
+    engines: {node: '>=10'}
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
     engines: {node: '>= 6'}
@@ -2055,6 +2069,14 @@ packages:
       ufo: 1.1.2
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q==}
+    dev: false
+
+  /[email protected]:
+    resolution: {integrity: 
sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==}
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
     dev: true
@@ -2068,6 +2090,16 @@ packages:
     resolution: {integrity: 
sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==}
+    hasBin: true
+    dependencies:
+      commander: 2.20.3
+      moo: 0.5.2
+      railroad-diagrams: 1.0.0
+      randexp: 0.4.6
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
@@ -2293,6 +2325,18 @@ packages:
     resolution: {integrity: 
sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==}
+    dev: false
+
+  /[email protected]:
+    resolution: {integrity: 
sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==}
+    engines: {node: '>=0.12'}
+    dependencies:
+      discontinuous-range: 1.0.0
+      ret: 0.1.15
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
     dev: true
@@ -2327,6 +2371,11 @@ packages:
       supports-preserve-symlinks-flag: 1.0.0
     dev: true
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
+    engines: {node: '>=0.12'}
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
     engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -2423,6 +2472,15 @@ packages:
   /[email protected]:
     resolution: {integrity: 
sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
 
+  /[email protected]:
+    resolution: {integrity: 
sha512-/tM0H1O2kto6DKv1YuaZa75PMr1J9vTHj5aMu3GF0coKskWUNNcHCvtE1YBHpoFEuKnGUqxAwEpeAH+BAcZcEQ==}
+    hasBin: true
+    dependencies:
+      argparse: 2.0.1
+      get-stdin: 8.0.0
+      nearley: 2.20.1
+    dev: false
+
   /[email protected]:
     resolution: {integrity: 
sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
     dev: true
@@ -2911,3 +2969,7 @@ packages:
     resolution: {integrity: 
sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
     engines: {node: '>=12.20'}
     dev: true
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
diff --git a/kyuubi-server/web-ui/src/components/monaco-editor/index.vue 
b/kyuubi-server/web-ui/src/components/monaco-editor/index.vue
new file mode 100644
index 000000000..4c387172a
--- /dev/null
+++ b/kyuubi-server/web-ui/src/components/monaco-editor/index.vue
@@ -0,0 +1,149 @@
+<!--
+* 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.
+-->
+
+<template>
+  <div ref="codeEditBox" style="height: 100%" />
+</template>
+
+<script lang="ts" setup>
+  import * as monaco from 'monaco-editor'
+  import { format } from 'sql-formatter'
+  import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
+  import { editorProps } from './type'
+  import { useEditorStore } from '@/pinia/editor'
+  import { ref, toRaw, watch, onBeforeUnmount, onMounted } from 'vue'
+
+  // @ts-ignore: worker
+  self.MonacoEnvironment = {
+    getWorker() {
+      return new EditorWorker()
+    }
+  }
+
+  const props = defineProps(editorProps)
+  const emit = defineEmits([
+    'update:modelValue',
+    'change',
+    'editorMounted',
+    'editorSave'
+  ])
+
+  const editorStore = useEditorStore()
+  const monacoEditorThemeRef = ref(
+    editorStore.getCurrentTheme === 'dark' ? 'vs-dark' : 'vs'
+  )
+  let editor: monaco.editor.IStandaloneCodeEditor
+  const codeEditBox = ref()
+  const init = () => {
+    monaco.languages.registerCompletionItemProvider('sql', {
+      provideCompletionItems: function (model: any, position: any) {
+        const word = model.getWordUntilPosition(position)
+        const range = {
+          startLineNumber: position.lineNumber,
+          endLineNumber: position.lineNumber,
+          startColumn: word.startColumn,
+          endColumn: word.endColumn
+        }
+        const suggestions = []
+        const keywords = [
+          'SELECT',
+          'FROM',
+          'WHERE',
+          'AND',
+          'OR',
+          'LIMIT',
+          'ORDER BY',
+          'GROUP BY'
+        ]
+        for (const i in keywords) {
+          suggestions.push({
+            label: keywords[i],
+            kind: monaco.languages.CompletionItemKind['Function'],
+            insertText: keywords[i],
+            detail: '',
+            range: range
+          })
+        }
+        return {
+          suggestions: suggestions
+        }
+      }
+    })
+
+    editor = monaco.editor.create(codeEditBox.value, {
+      value: props.modelValue,
+      language: props.language,
+      theme: monacoEditorThemeRef.value,
+      ...props.options
+    })
+
+    editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () 
{
+      emit('editorSave')
+    })
+
+    editor.setValue(format(toRaw(editor).getValue()))
+
+    editor.onDidChangeModelContent(() => {
+      const value = editor.getValue()
+      emit('update:modelValue', value)
+      emit('change', value)
+    })
+    emit('editorMounted', editor)
+  }
+  watch(
+    () => props.modelValue,
+    (newValue) => {
+      if (editor) {
+        const value = editor.getValue()
+        if (newValue !== value) {
+          editor.setValue(newValue)
+          editor.setValue(format(toRaw(editor).getValue()))
+        }
+      }
+    }
+  )
+  watch(
+    () => props.options,
+    (newValue) => {
+      editor.updateOptions(newValue)
+    },
+    { deep: true }
+  )
+  watch(
+    () => props.language,
+    (newValue) => {
+      monaco.editor.setModelLanguage(editor.getModel()!, newValue)
+    }
+  )
+  watch(
+    () => editorStore.getCurrentTheme,
+    () => {
+      editor?.dispose()
+      monacoEditorThemeRef.value =
+        editorStore.getCurrentTheme === 'dark' ? 'vs-dark' : 'vs'
+      init()
+    }
+  )
+
+  onBeforeUnmount(() => {
+    editor.dispose()
+  })
+  onMounted(() => {
+    init()
+  })
+</script>
diff --git a/kyuubi-server/web-ui/src/components/monaco-editor/type.ts 
b/kyuubi-server/web-ui/src/components/monaco-editor/type.ts
new file mode 100644
index 000000000..80400565e
--- /dev/null
+++ b/kyuubi-server/web-ui/src/components/monaco-editor/type.ts
@@ -0,0 +1,81 @@
+/*
+ * 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 { PropType } from 'vue'
+
+export type Theme = 'vs' | 'vs-dark'
+export type FoldingStrategy = 'auto' | 'indentation'
+export type RenderLineHighlight = 'all' | 'line' | 'none' | 'gutter'
+export interface Options {
+  automaticLayout?: boolean
+  foldingStrategy?: FoldingStrategy
+  renderLineHighlight?: RenderLineHighlight
+  selectOnLineNumbers?: boolean
+  minimap?: {
+    enabled: boolean
+  }
+  readOnly: boolean
+  contextmenu: boolean
+  fontSize?: number
+  scrollBeyondLastLine?: boolean
+  overviewRulerBorder?: boolean
+}
+
+export const editorProps = {
+  modelValue: {
+    type: String as PropType<string>,
+    default: null
+  },
+  width: {
+    type: [String, Number] as PropType<string | number>,
+    default: '100%'
+  },
+  height: {
+    type: [String, Number] as PropType<string | number>,
+    default: '100%'
+  },
+  language: {
+    type: String as PropType<string>,
+    default: 'sql'
+  },
+  theme: {
+    type: String as PropType<Theme>,
+    validator(value: string): boolean {
+      return ['vs', 'vs-dark'].includes(value)
+    },
+    default: 'vs'
+  },
+  options: {
+    type: Object as PropType<Options>,
+    default() {
+      return {
+        automaticLayout: true,
+        foldingStrategy: 'indentation',
+        renderLineHighlight: 'line',
+        selectOnLineNumbers: true,
+        minimap: {
+          enabled: false
+        },
+        readOnly: false,
+        contextmenu: true,
+        fontSize: 16,
+        scrollBeyondLastLine: true,
+        overviewRulerBorder: false
+      }
+    }
+  }
+}
diff --git a/kyuubi-server/web-ui/src/layout/components/aside/types.ts 
b/kyuubi-server/web-ui/src/layout/components/aside/types.ts
index 728ee326d..a7e495e18 100644
--- a/kyuubi-server/web-ui/src/layout/components/aside/types.ts
+++ b/kyuubi-server/web-ui/src/layout/components/aside/types.ts
@@ -94,5 +94,10 @@ export const MENUS = [
     label: 'Contact Us',
     icon: 'PhoneFilled',
     router: '/contact'
+  },
+  {
+    label: 'SQL Lab',
+    icon: 'Cpu',
+    router: '/lab'
   }
 ]
diff --git a/kyuubi-server/web-ui/src/pinia/editor/index.ts 
b/kyuubi-server/web-ui/src/pinia/editor/index.ts
new file mode 100644
index 000000000..8902d172c
--- /dev/null
+++ b/kyuubi-server/web-ui/src/pinia/editor/index.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { defineStore } from 'pinia'
+import { Theme } from './type'
+
+export const useEditorStore = defineStore({
+  id: 'editor',
+  state: (): { theme: Theme } => ({
+    theme: 'light'
+  }),
+  persist: true,
+  getters: {
+    getCurrentTheme(): Theme {
+      return this.theme
+    }
+  },
+  actions: {
+    setCurrentTheme(theme: Theme): void {
+      this.theme = theme
+    }
+  }
+})
diff --git a/kyuubi-server/web-ui/src/pinia/editor/type.ts 
b/kyuubi-server/web-ui/src/pinia/editor/type.ts
new file mode 100644
index 000000000..4155d60e0
--- /dev/null
+++ b/kyuubi-server/web-ui/src/pinia/editor/type.ts
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+export type Theme = 'dark' | 'light'
diff --git a/kyuubi-server/web-ui/src/router/index.ts 
b/kyuubi-server/web-ui/src/router/index.ts
index 423715661..c59c5f28c 100644
--- a/kyuubi-server/web-ui/src/router/index.ts
+++ b/kyuubi-server/web-ui/src/router/index.ts
@@ -23,6 +23,7 @@ import contactRoutes from './contact'
 import managementRoutes from './management'
 import detailRoutes from './detail'
 import swaggerRoutes from './swagger'
+import labRoutes from './lab'
 
 const routes = [
   {
@@ -44,7 +45,8 @@ const routes = [
       ...managementRoutes,
       ...detailRoutes,
       ...swaggerRoutes,
-      ...contactRoutes
+      ...contactRoutes,
+      ...labRoutes
     ]
   }
 ]
diff --git a/kyuubi-server/web-ui/src/router/lab/index.ts 
b/kyuubi-server/web-ui/src/router/lab/index.ts
new file mode 100644
index 000000000..d78838079
--- /dev/null
+++ b/kyuubi-server/web-ui/src/router/lab/index.ts
@@ -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.
+ */
+
+const routes = [
+  {
+    path: '/lab',
+    name: 'lab',
+    component: () => import('@/views/lab/index.vue')
+  }
+]
+
+export default routes
diff --git a/kyuubi-server/web-ui/src/views/lab/index.vue 
b/kyuubi-server/web-ui/src/views/lab/index.vue
new file mode 100644
index 000000000..26ecfac0d
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/lab/index.vue
@@ -0,0 +1,64 @@
+<!--
+* 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.
+-->
+
+<template>
+  <div class="container">
+    <MonacoEditor
+      v-model="editorVariables.content"
+      :language="editorVariables.language"
+      @editor-mounted="editorMounted"
+      @change="handleContentChange"
+      @editor-save="editorSave" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import MonacoEditor from '@/components/monaco-editor/index.vue'
+  import { reactive, toRaw } from 'vue'
+  import * as monaco from 'monaco-editor'
+  import { format } from 'sql-formatter'
+
+  const editorVariables = reactive({
+    editor: {} as any,
+    language: 'sql',
+    content: ''
+  })
+
+  const editorMounted = (editor: monaco.editor.IStandaloneCodeEditor) => {
+    editorVariables.editor = editor
+  }
+  const handleFormat = () => {
+    toRaw(editorVariables.editor).setValue(
+      format(toRaw(editorVariables.editor).getValue())
+    )
+  }
+
+  const editorSave = () => {
+    handleFormat()
+  }
+
+  const handleContentChange = (value: string) => {
+    editorVariables.content = value
+  }
+</script>
+
+<style lang="scss" scoped>
+  .container {
+    height: 70%;
+  }
+</style>

Reply via email to