This is an automated email from the ASF dual-hosted git repository.
nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-webui.git
The following commit(s) were added to refs/heads/main by this push:
new 42a4ebff [Feature] Introduce job log console (#417)
42a4ebff is described below
commit 42a4ebff84e7c6e1bec47f4030b192446dd78b42
Author: s7monk <[email protected]>
AuthorDate: Tue Jun 18 19:06:44 2024 +0800
[Feature] Introduce job log console (#417)
---
paimon-web-ui/package.json | 1 +
paimon-web-ui/src/App.tsx | 20 +++++
paimon-web-ui/src/api/models/job/index.ts | 7 ++
paimon-web-ui/src/store/job/index.ts | 8 ++
.../console/{ => components/log}/index.module.scss | 17 ++---
.../components/console/components/log/index.tsx | 87 ++++++++++++++++++++++
.../query/components/console/index.module.scss | 2 +-
.../components/query/components/console/index.tsx | 5 +-
.../views/playground/components/query/index.tsx | 84 +++++++++++++--------
9 files changed, 186 insertions(+), 45 deletions(-)
diff --git a/paimon-web-ui/package.json b/paimon-web-ui/package.json
index a279baba..fc115b3d 100644
--- a/paimon-web-ui/package.json
+++ b/paimon-web-ui/package.json
@@ -27,6 +27,7 @@
"@antv/x6-vue-shape": "^2.1.1",
"dart-sass": "^1.25.0",
"dayjs": "^1.11.11",
+ "highlight.js": "^11.9.0",
"lodash": "^4.17.21",
"mitt": "^3.0.1",
"monaco-editor": "^0.43.0",
diff --git a/paimon-web-ui/src/App.tsx b/paimon-web-ui/src/App.tsx
index 39f05866..85cbbc12 100644
--- a/paimon-web-ui/src/App.tsx
+++ b/paimon-web-ui/src/App.tsx
@@ -22,9 +22,27 @@ import {
enUS,
zhCN,
} from 'naive-ui'
+import hljs from 'highlight.js/lib/core'
+import csharp from 'highlight.js/lib/languages/csharp'
import { useConfigStore } from '@/store/config'
import themes from '@/themes'
+hljs.registerLanguage('csharp', csharp)
+
+const log = function (hljs: any) {
+ return {
+ contains: [
+ ...hljs.getLanguage('csharp').contains,
+ {
+ begin: /Job ID: [^\]]+/,
+ relevance: 0,
+ },
+ ],
+ }
+}
+
+hljs.registerLanguage('log', log)
+
export default defineComponent({
name: 'App',
setup() {
@@ -37,6 +55,7 @@ export default defineComponent({
theme,
themeOverrides,
locale,
+ hljs,
}
},
render() {
@@ -47,6 +66,7 @@ export default defineComponent({
locale={this.locale === 'en' ? enUS : zhCN}
date-locale={this.locale === 'en' ? dateEnUS : dateZhCN}
style={{ width: '100%', height: '100vh' }}
+ hljs={this.hljs}
>
<n-message-provider>
<n-dialog-provider>
diff --git a/paimon-web-ui/src/api/models/job/index.ts
b/paimon-web-ui/src/api/models/job/index.ts
index 1a6f5da7..1bc7f7d9 100644
--- a/paimon-web-ui/src/api/models/job/index.ts
+++ b/paimon-web-ui/src/api/models/job/index.ts
@@ -76,3 +76,10 @@ export function getRecordList(params: RecordNameParams) {
export function createRecord(params: RecordDTO) {
return httpRequest.post<RecordDTO, ResponseOptions<unknown>>('/statement',
params)
}
+
+/**
+ * # Get job logs
+ */
+export function getLogs() {
+ return httpRequest.get<unknown, ResponseOptions<string>>('/job/logs/get')
+}
diff --git a/paimon-web-ui/src/store/job/index.ts
b/paimon-web-ui/src/store/job/index.ts
index 098e940e..3a313abb 100644
--- a/paimon-web-ui/src/store/job/index.ts
+++ b/paimon-web-ui/src/store/job/index.ts
@@ -24,6 +24,7 @@ export interface JobState {
jobResultData: JobResultData | null
jobStatus: string
executionTime: string
+ jobLog?: string
}
export const useJobStore = defineStore({
@@ -34,6 +35,7 @@ export const useJobStore = defineStore({
jobResultData: null,
jobStatus: '',
executionTime: '0m:0s',
+ jobLog: '',
}),
persist: false,
getters: {
@@ -68,6 +70,9 @@ export const useJobStore = defineStore({
getExecutionTime(): string {
return this.executionTime
},
+ getJobLog(): string | undefined {
+ return this.jobLog
+ },
},
actions: {
setExecutionMode(executionMode: ExecutionMode) {
@@ -85,6 +90,9 @@ export const useJobStore = defineStore({
setExecutionTime(executionTime: string) {
this.executionTime = executionTime
},
+ setJobLog(jobLog: string) {
+ this.jobLog = jobLog
+ },
resetCurrentResult() {
this.currentJob = null
this.jobResultData = null
diff --git
a/paimon-web-ui/src/views/playground/components/query/components/console/index.module.scss
b/paimon-web-ui/src/views/playground/components/query/components/console/components/log/index.module.scss
similarity index 85%
copy from
paimon-web-ui/src/views/playground/components/query/components/console/index.module.scss
copy to
paimon-web-ui/src/views/playground/components/query/components/console/components/log/index.module.scss
index 626d6f8b..e8ae6749 100644
---
a/paimon-web-ui/src/views/playground/components/query/components/console/index.module.scss
+++
b/paimon-web-ui/src/views/playground/components/query/components/console/components/log/index.module.scss
@@ -15,15 +15,14 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License. */
-
-.editor-console {
- width: 100%;
+.container {
+ display: flex;
+ align-items: center;
height: 100%;
- position: relative;
- .operations {
- position: absolute;
- top: 17px;
- right: 20px;
+ .log{
+ width: 100%;
+ height: 100%;
+ padding: 14px 0 12px 14px;
}
-}
+}
\ No newline at end of file
diff --git
a/paimon-web-ui/src/views/playground/components/query/components/console/components/log/index.tsx
b/paimon-web-ui/src/views/playground/components/query/components/console/components/log/index.tsx
new file mode 100644
index 00000000..40278c2e
--- /dev/null
+++
b/paimon-web-ui/src/views/playground/components/query/components/console/components/log/index.tsx
@@ -0,0 +1,87 @@
+/* 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 styles from './index.module.scss'
+import { useJobStore } from '@/store/job'
+
+export default defineComponent({
+ name: 'LogConsole',
+ props: {
+ maxHeight: {
+ type: Number as PropType<number>,
+ default: 150,
+ },
+ },
+ setup(props) {
+ const jobStore = useJobStore()
+ const { mittBus } =
getCurrentInstance()!.appContext.config.globalProperties
+ const logContent = computed(() => jobStore.getJobLog)
+ const maxLogHeight = ref(0)
+ const logRef = ref<any>(null)
+ const containerRef = ref<HTMLElement | null>(null)
+
+ function updateLogHeight() {
+ const viewportHeight = window.innerHeight
+ const calculatedHeight = (viewportHeight - 181) * 0.4 - 54
+ maxLogHeight.value = Math.max(calculatedHeight, props.maxHeight + 52)
+ }
+
+ mittBus.on('resizeLog', () => {
+ updateLogHeight()
+ })
+
+ watchEffect(() => {
+ if (logContent.value) {
+ nextTick(() => {
+ logRef.value?.scrollTo({ position: 'bottom', silent: true })
+ })
+ }
+ })
+
+ onMounted(() => {
+ nextTick(() => {
+ updateLogHeight()
+ })
+ })
+
+ onUnmounted(() => {
+ mittBus.off('resizeLog')
+ })
+
+ return {
+ logContent,
+ maxLogHeight,
+ logRef,
+ containerRef,
+ }
+ },
+ render() {
+ return (
+ <div class={styles.container}>
+ <n-log
+ ref="logRef"
+ class={styles.log}
+ style={{ maxHeight: `${this.maxLogHeight}px` }}
+ line-height={1.8}
+ log={this.logContent}
+ language="log"
+ rows={40}
+ />
+ </div>
+ )
+ },
+})
diff --git
a/paimon-web-ui/src/views/playground/components/query/components/console/index.module.scss
b/paimon-web-ui/src/views/playground/components/query/components/console/index.module.scss
index 626d6f8b..5c2faf2b 100644
---
a/paimon-web-ui/src/views/playground/components/query/components/console/index.module.scss
+++
b/paimon-web-ui/src/views/playground/components/query/components/console/index.module.scss
@@ -16,7 +16,7 @@ specific language governing permissions and limitations
under the License. */
-.editor-console {
+.container {
width: 100%;
height: 100%;
position: relative;
diff --git
a/paimon-web-ui/src/views/playground/components/query/components/console/index.tsx
b/paimon-web-ui/src/views/playground/components/query/components/console/index.tsx
index df165af2..c3ad0284 100644
---
a/paimon-web-ui/src/views/playground/components/query/components/console/index.tsx
+++
b/paimon-web-ui/src/views/playground/components/query/components/console/index.tsx
@@ -19,6 +19,7 @@ import { CloseSharp, KeyboardDoubleArrowDownSharp,
KeyboardDoubleArrowUpSharp }
import { throttle } from 'lodash'
import TableActionBar from './components/controls'
import TableResult from './components/table'
+import LogConsole from './components/log'
import styles from './index.module.scss'
export default defineComponent({
@@ -77,7 +78,7 @@ export default defineComponent({
},
render() {
return (
- <div class={styles['editor-console']} ref="editorConsoleRef">
+ <div class={styles.container} ref="editorConsoleRef">
<n-tabs
type="line"
size="large"
@@ -86,7 +87,7 @@ export default defineComponent({
pane-style="padding: 0px;box-sizing: border-box;"
>
<n-tab-pane name="logs" tab={this.t('playground.logs')}>
- {/* {this.t('playground.logs')} */}
+ <LogConsole maxHeight={this.adjustedHeight} />
</n-tab-pane>
<n-tab-pane name="result" tab={this.t('playground.result')}>
{
diff --git a/paimon-web-ui/src/views/playground/components/query/index.tsx
b/paimon-web-ui/src/views/playground/components/query/index.tsx
index 151253cc..5b9a15a2 100644
--- a/paimon-web-ui/src/views/playground/components/query/index.tsx
+++ b/paimon-web-ui/src/views/playground/components/query/index.tsx
@@ -18,6 +18,7 @@ under the License. */
import type * as monaco from 'monaco-editor'
import { format } from 'sql-formatter'
import { useMessage } from 'naive-ui'
+import { onMounted } from 'vue'
import styles from './index.module.scss'
import MenuTree from './components/menu-tree'
import EditorTabs from './components/tabs'
@@ -25,7 +26,7 @@ import EditorDebugger from './components/debugger'
import EditorConsole from './components/console'
import MonacoEditor from '@/components/monaco-editor'
import { useJobStore } from '@/store/job'
-import { getJobStatus, refreshJobStatus } from '@/api/models/job'
+import { getJobStatus, getLogs, refreshJobStatus } from '@/api/models/job'
import { createSession } from '@/api/models/session'
export default defineComponent({
@@ -90,6 +91,7 @@ export default defineComponent({
const handleDragEnd = () => {
mittBus.emit('editorResized')
+ mittBus.emit('resizeLog')
}
// mitt - handle tab choose
@@ -97,6 +99,16 @@ export default defineComponent({
tabData.value = data
})
+ let getJobLogsIntervalId: number | undefined
+ const getJobLog = () => {
+ getJobLogsIntervalId = setInterval(async () => {
+ const response = await getLogs()
+ jobStore.setJobLog(response.data)
+ }, 1000)
+ }
+
+ onMounted(getJobLog)
+
let createSessionIntervalId: number | undefined
watch(currentJob, (newJob) => {
if (newJob && createSessionIntervalId === undefined) {
@@ -201,6 +213,10 @@ export default defineComponent({
clearInterval(createSessionIntervalId)
createSessionIntervalId = undefined
}
+ if (getJobLogsIntervalId !== undefined) {
+ clearInterval(getJobLogsIntervalId)
+ getJobLogsIntervalId = undefined
+ }
})
return {
@@ -243,44 +259,46 @@ export default defineComponent({
)
}
</div>
- <n-split direction="vertical" max={0.6} min={0.00}
resize-trigger-size={0} v-model:size={this.editorSize}
on-drag-end={this.handleDragEnd}>
- {{
- '1': () => (
- <div class={styles.editor}>
- {
- this.tabData.panelsList?.length > 0
- && (
- <n-card content-style="height: 100%;padding: 0;">
- <MonacoEditor
- v-model={this.tabData.panelsList.find((item:
any) => item.key === this.tabData.chooseTab).content}
- language={this.language}
- onEditorMounted={this.editorMounted}
- onEditorSave={this.editorSave}
- onChange={this.handleContentChange}
- />
- </n-card>
- )
- }
- </div>
- ),
- '2': () => (this.showConsole && (
- <div class={styles.console}>
- {
+ <div style={{ display: 'flex', flex: 1, flexDirection:
'column', maxHeight: 'calc(100vh - 181px)' }}>
+ <n-split direction="vertical" max={0.6} min={0.00}
resize-trigger-size={0} v-model:size={this.editorSize}
on-drag-end={this.handleDragEnd}>
+ {{
+ '1': () => (
+ <div class={styles.editor}>
+ {
this.tabData.panelsList?.length > 0
&& (
<n-card content-style="height: 100%;padding:
0;">
- <EditorConsole
onConsoleDown={this.handleConsoleDown} onConsoleUp={this.handleConsoleUp}
onConsoleClose={this.handleConsoleClose} />
+ <MonacoEditor
+
v-model={this.tabData.panelsList.find((item: any) => item.key ===
this.tabData.chooseTab).content}
+ language={this.language}
+ onEditorMounted={this.editorMounted}
+ onEditorSave={this.editorSave}
+ onChange={this.handleContentChange}
+ />
</n-card>
)
}
- </div>
- )
- ),
- 'resize-trigger': () => (
- <div class={styles['console-splitter']} />
- ),
- }}
- </n-split>
+ </div>
+ ),
+ '2': () => (this.showConsole && (
+ <div class={styles.console}>
+ {
+ this.tabData.panelsList?.length > 0
+ && (
+ <n-card content-style="height: 100%;padding:
0;">
+ <EditorConsole
onConsoleDown={this.handleConsoleDown} onConsoleUp={this.handleConsoleUp}
onConsoleClose={this.handleConsoleClose} />
+ </n-card>
+ )
+ }
+ </div>
+ )
+ ),
+ 'resize-trigger': () => (
+ <div class={styles['console-splitter']} />
+ ),
+ }}
+ </n-split>
+ </div>
</n-card>
</div>
),