From 7f967d929abc8dd9249b95076014f8efb85caac5 Mon Sep 17 00:00:00 2001 From: s7monk <“15512826113@163.com”> Date: Mon, 27 May 2024 19:05:21 +0800 Subject: [PATCH 01/12] commit. --- paimon-web-ui/src/api/models/cluster/index.ts | 9 +- paimon-web-ui/src/api/models/cluster/types.ts | 6 ++ .../query/components/debugger/index.tsx | 84 ++++++++++++------- paimon-web-ui/vite.config.ts | 2 +- 4 files changed, 71 insertions(+), 30 deletions(-) diff --git a/paimon-web-ui/src/api/models/cluster/index.ts b/paimon-web-ui/src/api/models/cluster/index.ts index 92f880920..e0a4fa19f 100644 --- a/paimon-web-ui/src/api/models/cluster/index.ts +++ b/paimon-web-ui/src/api/models/cluster/index.ts @@ -16,7 +16,7 @@ specific language governing permissions and limitations under the License. */ import httpRequest from '../../request' -import type { Cluster, ClusterDTO, ClusterNameParams } from './types' +import type {Cluster, ClusterDTO, ClusterNameParams, ClusterTypeParams} from './types' import type { ResponseOptions } from '@/api/types' /** @@ -29,6 +29,13 @@ export function getClusterList() { }) } +export function getClusterListByType() { + return httpRequest.createHooks!, ClusterTypeParams>({ + url: '/cluster/list', + method: 'get', + }) +} + /** * # Create Cluster */ diff --git a/paimon-web-ui/src/api/models/cluster/types.ts b/paimon-web-ui/src/api/models/cluster/types.ts index 0a1dd559c..27f6054cc 100644 --- a/paimon-web-ui/src/api/models/cluster/types.ts +++ b/paimon-web-ui/src/api/models/cluster/types.ts @@ -32,6 +32,12 @@ export interface ClusterNameParams { pageSize: number } +export interface ClusterTypeParams { + type?: string + pageNum: number + pageSize: number +} + export interface ClusterDTO { id?: number clusterName: string diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index d647cb582..04994a1f6 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -16,6 +16,7 @@ specific language governing permissions and limitations under the License. */ import { ChevronDown, Play, ReaderOutline, Save } from '@vicons/ionicons5' +import { getClusterListByType } from '@/api/models/cluster' import styles from './index.module.scss' export default defineComponent({ @@ -24,38 +25,32 @@ export default defineComponent({ setup(props, { emit }) { const { t } = useLocaleHooks() - const debuggerVariables = reactive({ + const [clusterList, useClusterList, { loading }] = getClusterListByType(); + + const debuggerVariables = reactive<{ + operatingConditionOptions: { label: string; key: string }[] + conditionValue: string + bigDataOptions: { label: string; value: string }[] + conditionValue2: string + clusterOptions: { label: string; value: string }[] + conditionValue3: string + executionModeOptions: { label: string; value: string }[] + }>({ operatingConditionOptions: [ - { - label: 'Limit 100 items', - key: '100', - }, - { - label: 'Limit 1000 items', - key: '1000', - }, + { label: 'Limit 100 items', key: '100' }, + { label: 'Limit 1000 items', key: '1000' }, ], - conditionValue: 'Flink', + conditionValue: '', bigDataOptions: [ - { - label: 'Flink', - value: 'Flink', - }, - { - label: 'Spark', - value: 'Spark', - }, + { label: 'Flink', value: 'Flink' }, + { label: 'Spark', value: 'Spark' }, ], - conditionValue2: 'test1', - clusterOptions: [ - { - label: 'test1', - value: 'test1', - }, - { - label: 'test2', - value: 'test2', - }, + conditionValue2: '', + clusterOptions: [], + conditionValue3: '', + executionModeOptions: [ + { label: 'Streaming', value: 'Streaming' }, + { label: 'Batch', value: 'Batch' }, ], }) @@ -71,6 +66,38 @@ export default defineComponent({ emit('handleSave') } + function getClusterData() { + const params = { + type: debuggerVariables.conditionValue, + pageNum: 1, + pageSize: Number.MAX_SAFE_INTEGER + } + useClusterList({ params }) + } + + watch(() => debuggerVariables.conditionValue, (newValue) => { + getClusterData() + }); + + watch(() => clusterList.value?.data, (newList) => { + console.log(clusterList.value) + if (newList) { + debuggerVariables.clusterOptions = newList.map(cluster => ({ + label: cluster.clusterName, + value: cluster.id != null ? cluster.id.toString() : '' + })) + } + }, { immediate: true }) + + onMounted(() => { + debuggerVariables.conditionValue = debuggerVariables.bigDataOptions[0].value + if (debuggerVariables.clusterOptions.length > 0) { + debuggerVariables.conditionValue2 = debuggerVariables.clusterOptions[0].value; + } + debuggerVariables.conditionValue3 = debuggerVariables.executionModeOptions[0].value + getClusterData() + }) + return { t, ...toRefs(debuggerVariables), @@ -103,6 +130,7 @@ export default defineComponent({ +
diff --git a/paimon-web-ui/vite.config.ts b/paimon-web-ui/vite.config.ts index 3e34e2baf..ffb3e6a90 100644 --- a/paimon-web-ui/vite.config.ts +++ b/paimon-web-ui/vite.config.ts @@ -34,7 +34,7 @@ export default defineConfig({ changeOrigin: true, }, '/api': { - target: 'http://127.0.0.1:10088', + target: 'http://47.94.247.86:10088', changeOrigin: true, }, }, From 4caaf1c5b4813a91f2c1c8a6970700ebb1277117 Mon Sep 17 00:00:00 2001 From: s7monk <15512826113@163.com> Date: Tue, 28 May 2024 09:00:50 +0800 Subject: [PATCH 02/12] commit. --- paimon-web-ui/src/api/models/cluster/index.ts | 13 ++--- paimon-web-ui/src/api/models/cluster/types.ts | 6 --- .../query/components/debugger/index.tsx | 50 ++++++++----------- 3 files changed, 27 insertions(+), 42 deletions(-) diff --git a/paimon-web-ui/src/api/models/cluster/index.ts b/paimon-web-ui/src/api/models/cluster/index.ts index e0a4fa19f..72aaa8187 100644 --- a/paimon-web-ui/src/api/models/cluster/index.ts +++ b/paimon-web-ui/src/api/models/cluster/index.ts @@ -16,7 +16,7 @@ specific language governing permissions and limitations under the License. */ import httpRequest from '../../request' -import type {Cluster, ClusterDTO, ClusterNameParams, ClusterTypeParams} from './types' +import type {Cluster, ClusterDTO, ClusterNameParams} from './types' import type { ResponseOptions } from '@/api/types' /** @@ -29,11 +29,12 @@ export function getClusterList() { }) } -export function getClusterListByType() { - return httpRequest.createHooks!, ClusterTypeParams>({ - url: '/cluster/list', - method: 'get', - }) +export function getClusterListByType(type: string, pageNum: number, pageSize: number) { + return httpRequest.get('/cluster/list', { + type, + pageNum, + pageSize + }); } /** diff --git a/paimon-web-ui/src/api/models/cluster/types.ts b/paimon-web-ui/src/api/models/cluster/types.ts index 27f6054cc..0a1dd559c 100644 --- a/paimon-web-ui/src/api/models/cluster/types.ts +++ b/paimon-web-ui/src/api/models/cluster/types.ts @@ -32,12 +32,6 @@ export interface ClusterNameParams { pageSize: number } -export interface ClusterTypeParams { - type?: string - pageNum: number - pageSize: number -} - export interface ClusterDTO { id?: number clusterName: string diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index 04994a1f6..555887225 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -18,6 +18,7 @@ under the License. */ import { ChevronDown, Play, ReaderOutline, Save } from '@vicons/ionicons5' import { getClusterListByType } from '@/api/models/cluster' import styles from './index.module.scss' +import type { Cluster } from "@/api/models/cluster/types"; export default defineComponent({ name: 'EditorDebugger', @@ -25,8 +26,6 @@ export default defineComponent({ setup(props, { emit }) { const { t } = useLocaleHooks() - const [clusterList, useClusterList, { loading }] = getClusterListByType(); - const debuggerVariables = reactive<{ operatingConditionOptions: { label: string; key: string }[] conditionValue: string @@ -40,14 +39,14 @@ export default defineComponent({ { label: 'Limit 100 items', key: '100' }, { label: 'Limit 1000 items', key: '1000' }, ], - conditionValue: '', + conditionValue: 'Flink', bigDataOptions: [ { label: 'Flink', value: 'Flink' }, { label: 'Spark', value: 'Spark' }, ], conditionValue2: '', clusterOptions: [], - conditionValue3: '', + conditionValue3: 'Streaming', executionModeOptions: [ { label: 'Streaming', value: 'Streaming' }, { label: 'Batch', value: 'Batch' }, @@ -67,37 +66,28 @@ export default defineComponent({ } function getClusterData() { - const params = { - type: debuggerVariables.conditionValue, - pageNum: 1, - pageSize: Number.MAX_SAFE_INTEGER - } - useClusterList({ params }) + getClusterListByType(debuggerVariables.conditionValue, 1, Number.MAX_SAFE_INTEGER).then(response => { + if (response && response.data) { + const clusterList = response.data as Cluster[]; + debuggerVariables.clusterOptions = clusterList.map(cluster => ({ + label: cluster.clusterName, + value: cluster.id.toString() + })) + if (debuggerVariables.clusterOptions.length > 0) { + debuggerVariables.conditionValue2 = debuggerVariables.clusterOptions[0].value; + } + } + }).catch(error => { + console.error('Failed to fetch clusters:', error); + }) } watch(() => debuggerVariables.conditionValue, (newValue) => { - getClusterData() - }); - - watch(() => clusterList.value?.data, (newList) => { - console.log(clusterList.value) - if (newList) { - debuggerVariables.clusterOptions = newList.map(cluster => ({ - label: cluster.clusterName, - value: cluster.id != null ? cluster.id.toString() : '' - })) - } - }, { immediate: true }) - - onMounted(() => { - debuggerVariables.conditionValue = debuggerVariables.bigDataOptions[0].value - if (debuggerVariables.clusterOptions.length > 0) { - debuggerVariables.conditionValue2 = debuggerVariables.clusterOptions[0].value; - } - debuggerVariables.conditionValue3 = debuggerVariables.executionModeOptions[0].value - getClusterData() + getClusterData(); }) + onMounted(() => {getClusterData()}) + return { t, ...toRefs(debuggerVariables), From 4a2633a3c02f6c914374d0185fd685c6844e4f65 Mon Sep 17 00:00:00 2001 From: s7monk <“15512826113@163.com”> Date: Tue, 28 May 2024 13:33:44 +0800 Subject: [PATCH 03/12] commit. --- paimon-web-ui/src/api/models/cluster/index.ts | 7 +++- paimon-web-ui/src/api/models/job/index.ts | 30 +++++++++++++ paimon-web-ui/src/api/models/job/types/job.ts | 42 +++++++++++++++++++ .../query/components/tabs/index.tsx | 6 +-- 4 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 paimon-web-ui/src/api/models/job/index.ts create mode 100644 paimon-web-ui/src/api/models/job/types/job.ts diff --git a/paimon-web-ui/src/api/models/cluster/index.ts b/paimon-web-ui/src/api/models/cluster/index.ts index 72aaa8187..ec937e3cf 100644 --- a/paimon-web-ui/src/api/models/cluster/index.ts +++ b/paimon-web-ui/src/api/models/cluster/index.ts @@ -29,6 +29,9 @@ export function getClusterList() { }) } +/** + * # List Cluster by ClusterType + */ export function getClusterListByType(type: string, pageNum: number, pageSize: number) { return httpRequest.get('/cluster/list', { type, @@ -48,7 +51,7 @@ export function createCluster() { } /** - * # Update user + * # Update Cluster */ export function updateCluster() { return httpRequest.createHooks!({ @@ -58,7 +61,7 @@ export function updateCluster() { } /** - * # delete a Cluster + * # Delete a Cluster */ export function deleteCluster(userId: number) { return httpRequest.delete!(`/cluster/${userId}`) diff --git a/paimon-web-ui/src/api/models/job/index.ts b/paimon-web-ui/src/api/models/job/index.ts new file mode 100644 index 000000000..10a39f112 --- /dev/null +++ b/paimon-web-ui/src/api/models/job/index.ts @@ -0,0 +1,30 @@ +/* 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 httpRequest from '../../request' +import type {JobSubmitDTO, Job} from "@/api/models/job/types/job"; +import type {ResponseOptions} from "@/api/types"; + +/** + * # Submit a Job + */ +export function Submit() { + return httpRequest.createHooks!, JobSubmitDTO>({ + url: '/job/submit', + method: 'post', + }) +} \ No newline at end of file diff --git a/paimon-web-ui/src/api/models/job/types/job.ts b/paimon-web-ui/src/api/models/job/types/job.ts new file mode 100644 index 000000000..bf62264e3 --- /dev/null +++ b/paimon-web-ui/src/api/models/job/types/job.ts @@ -0,0 +1,42 @@ +/* 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 interface Job { + submitId: string + jobName: string + type: string + executeMode: string + clusterId: string + sessionId: string + uid: number + status?: string + shouldFetchResult: boolean + resultData?: Array<{ [key: string]: any }> + startTime: string + endTime: string +} + +export interface JobSubmitDTO { + jobName?: string + taskType: string + clusterId: string + config?: { + [key: string]: string + } + Statements: string + streaming: boolean +} \ No newline at end of file diff --git a/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx index 052483f33..3b267a17d 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx @@ -31,12 +31,12 @@ export default defineComponent({ const handleAdd = () => { tabVariables.panelsList.push({ - tableName: `test${tabVariables.panelsList.length + 1}`, - key: `test${tabVariables.panelsList.length + 1}`, + tableName: `Q${tabVariables.panelsList.length + 1}`, + key: `Q${tabVariables.panelsList.length + 1}`, isSaved: false, content: '', }) - tabVariables.chooseTab = `test${tabVariables.panelsList.length}` + tabVariables.chooseTab = `Q${tabVariables.panelsList.length}` } const handleClose = (key: any) => { From 53eb65f05803dd9e815650760769a1f49be6f085 Mon Sep 17 00:00:00 2001 From: s7monk <15512826113@163.com> Date: Wed, 29 May 2024 04:20:23 +0800 Subject: [PATCH 04/12] commit. --- paimon-web-ui/src/api/models/job/index.ts | 18 ++-- paimon-web-ui/src/api/models/job/types/job.ts | 20 ++++- .../src/locales/en/modules/playground.ts | 2 + .../src/locales/zh/modules/playground.ts | 2 + .../console/components/controls/index.tsx | 51 +++++++++++- .../console/components/table/index.tsx | 83 +++++++++++-------- .../query/components/debugger/index.tsx | 47 +++++++++++ 7 files changed, 175 insertions(+), 48 deletions(-) diff --git a/paimon-web-ui/src/api/models/job/index.ts b/paimon-web-ui/src/api/models/job/index.ts index 10a39f112..564bb7192 100644 --- a/paimon-web-ui/src/api/models/job/index.ts +++ b/paimon-web-ui/src/api/models/job/index.ts @@ -16,15 +16,19 @@ specific language governing permissions and limitations under the License. */ import httpRequest from '../../request' -import type {JobSubmitDTO, Job} from "@/api/models/job/types/job"; +import type {JobSubmitDTO, Job, ResultFetchDTO, JobResultData} from "@/api/models/job/types/job"; import type {ResponseOptions} from "@/api/types"; /** - * # Submit a Job + * # Submit a job */ -export function Submit() { - return httpRequest.createHooks!, JobSubmitDTO>({ - url: '/job/submit', - method: 'post', - }) +export function submitJob(jobData: JobSubmitDTO) { + return httpRequest.post>('/job/submit', jobData); +} + +/** + * # Fetch the result of a submitted job + */ +export function fetchResult(resultFetchDTO: ResultFetchDTO) { + return httpRequest.post>('/job/fetch', resultFetchDTO); } \ No newline at end of file diff --git a/paimon-web-ui/src/api/models/job/types/job.ts b/paimon-web-ui/src/api/models/job/types/job.ts index bf62264e3..aa3f917ff 100644 --- a/paimon-web-ui/src/api/models/job/types/job.ts +++ b/paimon-web-ui/src/api/models/job/types/job.ts @@ -26,6 +26,7 @@ export interface Job { status?: string shouldFetchResult: boolean resultData?: Array<{ [key: string]: any }> + token: number startTime: string endTime: string } @@ -37,6 +38,21 @@ export interface JobSubmitDTO { config?: { [key: string]: string } - Statements: string + statements: string streaming: boolean -} \ No newline at end of file +} + +export interface JobResultData { + resultData?: Array<{ [key: string]: any }> + columns: number + rows: number + token: number +} + +export interface ResultFetchDTO { + submitId: string + clusterId: string + sessionId: string + taskType: string + token: number +} diff --git a/paimon-web-ui/src/locales/en/modules/playground.ts b/paimon-web-ui/src/locales/en/modules/playground.ts index b5126316c..7b90fafce 100644 --- a/paimon-web-ui/src/locales/en/modules/playground.ts +++ b/paimon-web-ui/src/locales/en/modules/playground.ts @@ -49,4 +49,6 @@ export default { schedule_refresh: 'Schedule Refresh', download: 'Export Data', copy: 'Copy Data', + job_submission_successfully: 'Job Submitted Successfully', + job_submission_failed: 'Job Submitted Failed', } diff --git a/paimon-web-ui/src/locales/zh/modules/playground.ts b/paimon-web-ui/src/locales/zh/modules/playground.ts index 73d59e9eb..727449ab9 100644 --- a/paimon-web-ui/src/locales/zh/modules/playground.ts +++ b/paimon-web-ui/src/locales/zh/modules/playground.ts @@ -49,4 +49,6 @@ export default { schedule_refresh: '定时刷新', download: '导出数据', copy: '复制数据', + job_submission_successfully: 'SQL 任务提交成功!', + job_submission_failed: '任务提交失败!', } diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index dce63f462..ef0b559a4 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -20,11 +20,17 @@ import { StopOutline } from '@vicons/ionicons5' import { ClockCircleOutlined, DownloadOutlined, LineChartOutlined } from '@vicons/antd' import styles from './index.module.scss' import { useConfigStore } from '@/store/config' +import type {Job, JobResultData} from "@/api/models/job/types/job"; +import {fetchResult} from "@/api/models/job"; export default defineComponent({ name: 'TableActionBar', - setup() { - const { t } = useLocaleHooks() + setup: function () { + const {t} = useLocaleHooks() + + const currentJob = ref(null); + const tableData = ref(null); + const configStore = useConfigStore() const isDarkMode = computed(() => configStore.theme === 'dark') @@ -34,11 +40,47 @@ export default defineComponent({ activeButton.value = button } + const {mittBus} = getCurrentInstance()!.appContext.config.globalProperties + mittBus.on('jobResult', (jobData: any) => { + currentJob.value = jobData; + }) + + const refreshData = async () => { + if (currentJob.value) { + if (currentJob.value.shouldFetchResult) { + try { + const resultFetchDTO = { + submitId: currentJob.value.submitId, + clusterId: currentJob.value.clusterId, + sessionId: currentJob.value.sessionId, + taskType: currentJob.value.type, + token: currentJob.value.token + } + + const response: any = await fetchResult(resultFetchDTO); + console.log(response.data) + tableData.value = response.data; + mittBus.emit('refreshedResult', response.data) + } catch (error) { + tableData.value = null; + console.error('Error fetching result:', error) + } + } else { + console.log('No fetching needed or job data is not available.') + } + } else { + console.log('No fetching needed or job data is not available.') + } + } + + return { t, activeButton, setActiveButton, isDarkMode, + refreshData, + tableData } }, render() { @@ -95,6 +137,7 @@ export default defineComponent({ trigger: () => ( , @@ -146,7 +189,7 @@ export default defineComponent({ {this.t('playground.schedule_refresh')} - 4 Columns + {this.tableData?.columns} Columns
@@ -154,7 +197,7 @@ export default defineComponent({ Job: Running
- Rows: 3 + Rows: {this.tableData?.rows} 1m:06s string | number | JSX.Element + } + + const data = ref([]) + const columns = ref([]) + + const { mittBus } = getCurrentInstance()!.appContext.config.globalProperties + + const handleResult = (result: any) => { + if (result && result.resultData) { + data.value = result.resultData + if (data.value.length > 0) { + generateColumns(data.value[0]); + } + } + } + + const generateColumns = (sampleObject: any) => { + const indexColumn: TableColumn = { title: '#', - key: 'key', - render: (_: any, index: number) => { - return `${index + 1}` - }, - }, - { - title: 'id', - key: 'id', - resizable: true, - }, - { - title: 'name', - key: 'name', - resizable: true, - }, - { - title: 'age', - key: 'age', - resizable: true, - }, - { - title: 'address', - key: 'address', + key: 'index', + fixed: 'left', + width: 50, + render: (row, index) => `${index + 1}` + } + + const dynamicColumns = Object.keys(sampleObject).map(key => ({ + title: key, + key: key, resizable: true, - }, - ] + sortable: true + })) + + columns.value = [indexColumn, ...dynamicColumns] + } + + mittBus?.on('jobResult', handleResult); + mittBus?.on('refreshedResult', handleResult); + + onUnmounted(() => { + mittBus.off('jobResult', handleResult); + mittBus.off('refreshedResult', handleResult); + }); interface User { id: number @@ -57,13 +77,6 @@ export default defineComponent({ address: string } - const data: User[] = [ - { id: 1, name: 'jack', age: 36, address: 'beijing' }, - { id: 2, name: 'li hua', age: 38, address: 'shanghai' }, - { id: 3, name: 'zhao ming', age: 27, address: 'hangzhou' }, - { id: 3, name: 'zhao ming', age: 27, address: 'hangzhou' }, - ] - return { columns, data, @@ -76,7 +89,7 @@ export default defineComponent({ class={styles.table} columns={this.columns} data={this.data} - max-height={138} + max-height={90} />
) diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index 555887225..e6f6e733b 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -19,12 +19,18 @@ import { ChevronDown, Play, ReaderOutline, Save } from '@vicons/ionicons5' import { getClusterListByType } from '@/api/models/cluster' import styles from './index.module.scss' import type { Cluster } from "@/api/models/cluster/types"; +import type {JobSubmitDTO} from "@/api/models/job/types/job"; +import {submitJob} from "@/api/models/job"; +import {useMessage} from "naive-ui"; export default defineComponent({ name: 'EditorDebugger', emits: ['handleFormat', 'handleSave'], setup(props, { emit }) { const { t } = useLocaleHooks() + const message = useMessage() + + const tabData = ref({}) as any; const debuggerVariables = reactive<{ operatingConditionOptions: { label: string; key: string }[] @@ -88,12 +94,52 @@ export default defineComponent({ onMounted(() => {getClusterData()}) + const { mittBus } = getCurrentInstance()!.appContext.config.globalProperties + mittBus.on('initTabData', (data: any) => { + tabData.value = data + }); + + const handleSubmit = async () => { + const currentTab = tabData.value.panelsList.find((item: any) => item.key === tabData.value.chooseTab) + + if (!currentTab) { + return + } + + const currentSQL = currentTab.content + if (!currentSQL) { + return + } + + const jobDataDTO: JobSubmitDTO = { + jobName: currentTab.tableName, + taskType: debuggerVariables.conditionValue, + clusterId: debuggerVariables.conditionValue2, + statements: currentSQL, + streaming: debuggerVariables.conditionValue3 === 'Streaming' + }; + + try { + const response = await submitJob(jobDataDTO); + console.log(response) + if (response.data) { + message.success(t('playground.job_submission_successfully')) + mittBus.emit('jobResult', response.data); + } else { + message.error(`${t('playground.job_submission_failed')}`) + } + } catch (error) { + console.error('Failed to submit job:', error) + } + } + return { t, ...toRefs(debuggerVariables), handleSelect, handleFormat, handleSave, + handleSubmit } }, render() { @@ -102,6 +148,7 @@ export default defineComponent({ , default: () => { From 9f497b3dec287ec1ba367759df182423bff1351b Mon Sep 17 00:00:00 2001 From: s7monk <“15512826113@163.com”> Date: Wed, 29 May 2024 19:07:26 +0800 Subject: [PATCH 05/12] commit. --- paimon-web-ui/src/App.tsx | 17 ++++++++ paimon-web-ui/src/api/models/job/index.ts | 23 ++++++++++- paimon-web-ui/src/api/models/job/types/job.ts | 13 +++++++ .../components/controls/index.module.scss | 4 ++ .../console/components/controls/index.tsx | 39 ++++++++++++++----- 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/paimon-web-ui/src/App.tsx b/paimon-web-ui/src/App.tsx index 55bef678f..457b42695 100644 --- a/paimon-web-ui/src/App.tsx +++ b/paimon-web-ui/src/App.tsx @@ -23,6 +23,7 @@ import { zhCN, } from 'naive-ui' import { useConfigStore } from '@/store/config' +import {refreshJobStatus} from "@/api/models/job"; import themes from '@/themes' export default defineComponent({ @@ -33,6 +34,22 @@ export default defineComponent({ const themeOverrides = computed(() => themes[theme.value ? 'dark' : 'light']) const locale = computed(() => configStore.getCurrentLocale) + const isRefreshStatusActive = ref(false) + + let refreshStatusIntervalId: number + onMounted(() => { + refreshStatusIntervalId = setInterval(async () => { + if (isRefreshStatusActive.value) { + //await refreshJobStatus(); + } + }, 3000); + }) + + onUnmounted(() => { + isRefreshStatusActive.value = false; + clearInterval(refreshStatusIntervalId); + }) + return { theme, themeOverrides, diff --git a/paimon-web-ui/src/api/models/job/index.ts b/paimon-web-ui/src/api/models/job/index.ts index 564bb7192..1e8eca081 100644 --- a/paimon-web-ui/src/api/models/job/index.ts +++ b/paimon-web-ui/src/api/models/job/index.ts @@ -16,7 +16,7 @@ specific language governing permissions and limitations under the License. */ import httpRequest from '../../request' -import type {JobSubmitDTO, Job, ResultFetchDTO, JobResultData} from "@/api/models/job/types/job"; +import type {JobSubmitDTO, Job, ResultFetchDTO, JobResultData, JobStatus, StopJobDTO} from "@/api/models/job/types/job"; import type {ResponseOptions} from "@/api/types"; /** @@ -31,4 +31,25 @@ export function submitJob(jobData: JobSubmitDTO) { */ export function fetchResult(resultFetchDTO: ResultFetchDTO) { return httpRequest.post>('/job/fetch', resultFetchDTO); +} + +/** + * # Refresh the status of jobs + */ +export function refreshJobStatus() { + return httpRequest.post>('/job/refresh'); +} + +/** + * # Fetch the status of a specific job by its ID + */ +export function getJobStatus(jobId: string) { + return httpRequest.get>(`/job/status/get/${jobId}`); +} + +/** + * # Stop a job + */ +export function stopJob(stopJobDTO: StopJobDTO) { + return httpRequest.post>('/job/fetch', stopJobDTO); } \ No newline at end of file diff --git a/paimon-web-ui/src/api/models/job/types/job.ts b/paimon-web-ui/src/api/models/job/types/job.ts index aa3f917ff..641c75ca1 100644 --- a/paimon-web-ui/src/api/models/job/types/job.ts +++ b/paimon-web-ui/src/api/models/job/types/job.ts @@ -17,6 +17,7 @@ under the License. */ export interface Job { submitId: string + jobId: string jobName: string type: string executeMode: string @@ -56,3 +57,15 @@ export interface ResultFetchDTO { taskType: string token: number } + +export interface JobStatus { + jobId: string + status: string +} + +export interface StopJobDTO { + clusterId: string + jobId: string + taskType: string + withSavepoint: boolean +} diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.module.scss b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.module.scss index d36d54908..842942c81 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.module.scss +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.module.scss @@ -43,6 +43,10 @@ under the License. */ color: $color-gray; } + .stop-button-running { + color: #D94F4F + } + .table-action-bar-text { color: $color-gray; } diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index ef0b559a4..e4aed2634 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -16,21 +16,21 @@ specific language governing permissions and limitations under the License. */ import { Copy, DataTable, Renew } from '@vicons/carbon' -import { StopOutline } from '@vicons/ionicons5' +import { StopOutline, Stop } from '@vicons/ionicons5' import { ClockCircleOutlined, DownloadOutlined, LineChartOutlined } from '@vicons/antd' import styles from './index.module.scss' import { useConfigStore } from '@/store/config' import type {Job, JobResultData} from "@/api/models/job/types/job"; -import {fetchResult} from "@/api/models/job"; +import {fetchResult, getJobStatus} from "@/api/models/job"; export default defineComponent({ name: 'TableActionBar', setup: function () { const {t} = useLocaleHooks() - const currentJob = ref(null); - const tableData = ref(null); - + const currentJob = ref(null) + const tableData = ref(null) + const jobStatus = ref('') const configStore = useConfigStore() const isDarkMode = computed(() => configStore.theme === 'dark') @@ -73,6 +73,22 @@ export default defineComponent({ } } + onMounted(() => { + setInterval(async () => { + if (currentJob.value && currentJob.value.jobId) { + const response: any = await getJobStatus(currentJob.value.jobId); + if (response.data) { + jobStatus.value = response.data.status + } + } + }, 1000); + }); + + const currentStopIcon = computed(() => jobStatus.value === 'RUNNING' ? StopOutline : Stop); + + const isButtonDisabled = computed(() => { + return jobStatus.value !== 'RUNNING' + }) return { t, @@ -80,7 +96,10 @@ export default defineComponent({ setActiveButton, isDarkMode, refreshData, - tableData + tableData, + jobStatus, + currentStopIcon, + isButtonDisabled } }, render() { @@ -157,10 +176,10 @@ export default defineComponent({ trigger: () => ( , + icon: () => , }} > @@ -195,7 +214,7 @@ export default defineComponent({
Job: - Running + {this.jobStatus}
Rows: {this.tableData?.rows} 1m:06s From f3d5346bb916b1647db04364f9790cda773f24e7 Mon Sep 17 00:00:00 2001 From: s7monk <15512826113@163.com> Date: Thu, 30 May 2024 03:26:28 +0800 Subject: [PATCH 06/12] commit. --- paimon-web-ui/src/api/models/job/index.ts | 2 +- .../src/locales/en/modules/playground.ts | 4 + .../src/locales/zh/modules/playground.ts | 4 + .../console/components/controls/index.tsx | 147 +++++++++++++++--- .../console/components/table/index.tsx | 49 ++++-- .../query/components/debugger/index.tsx | 6 +- 6 files changed, 171 insertions(+), 41 deletions(-) diff --git a/paimon-web-ui/src/api/models/job/index.ts b/paimon-web-ui/src/api/models/job/index.ts index 1e8eca081..9adbb8ed9 100644 --- a/paimon-web-ui/src/api/models/job/index.ts +++ b/paimon-web-ui/src/api/models/job/index.ts @@ -51,5 +51,5 @@ export function getJobStatus(jobId: string) { * # Stop a job */ export function stopJob(stopJobDTO: StopJobDTO) { - return httpRequest.post>('/job/fetch', stopJobDTO); + return httpRequest.post>('/job/stop', stopJobDTO); } \ No newline at end of file diff --git a/paimon-web-ui/src/locales/en/modules/playground.ts b/paimon-web-ui/src/locales/en/modules/playground.ts index 7b90fafce..103a9c8bb 100644 --- a/paimon-web-ui/src/locales/en/modules/playground.ts +++ b/paimon-web-ui/src/locales/en/modules/playground.ts @@ -51,4 +51,8 @@ export default { copy: 'Copy Data', job_submission_successfully: 'Job Submitted Successfully', job_submission_failed: 'Job Submitted Failed', + no_data: 'No Data', + job_stopping_successfully: 'Job Stopped Successfully', + job_stopping_failed: 'Job Stopped Failed', + data_copied_successfully: 'Data Copied to Clipboard', } diff --git a/paimon-web-ui/src/locales/zh/modules/playground.ts b/paimon-web-ui/src/locales/zh/modules/playground.ts index 727449ab9..225fb6adb 100644 --- a/paimon-web-ui/src/locales/zh/modules/playground.ts +++ b/paimon-web-ui/src/locales/zh/modules/playground.ts @@ -51,4 +51,8 @@ export default { copy: '复制数据', job_submission_successfully: 'SQL 任务提交成功!', job_submission_failed: '任务提交失败!', + no_data: '暂无数据,请重新执行SQL!', + job_stopping_successfully: '停止任务成功!', + job_stopping_failed: '停止任务失败!', + data_copied_successfully: '数据已复制到剪贴板!', } diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index e4aed2634..70b2b7ac6 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -20,17 +20,21 @@ import { StopOutline, Stop } from '@vicons/ionicons5' import { ClockCircleOutlined, DownloadOutlined, LineChartOutlined } from '@vicons/antd' import styles from './index.module.scss' import { useConfigStore } from '@/store/config' -import type {Job, JobResultData} from "@/api/models/job/types/job"; -import {fetchResult, getJobStatus} from "@/api/models/job"; +import type {Job, JobResultData} from "@/api/models/job/types/job" +import {fetchResult, getJobStatus, stopJob} from "@/api/models/job" +import {useMessage} from "naive-ui" export default defineComponent({ name: 'TableActionBar', setup: function () { const {t} = useLocaleHooks() + const message = useMessage() const currentJob = ref(null) const tableData = ref(null) const jobStatus = ref('') + const selectedInterval = ref('Disabled') + const intervalId = ref(null); const configStore = useConfigStore() const isDarkMode = computed(() => configStore.theme === 'dark') @@ -45,7 +49,7 @@ export default defineComponent({ currentJob.value = jobData; }) - const refreshData = async () => { + const handleRefreshData = async () => { if (currentJob.value) { if (currentJob.value.shouldFetchResult) { try { @@ -66,17 +70,38 @@ export default defineComponent({ console.error('Error fetching result:', error) } } else { - console.log('No fetching needed or job data is not available.') + message.warning(t('playground.no_data')) } } else { - console.log('No fetching needed or job data is not available.') + message.warning(t('playground.no_data')) + } + } + + const handleStopJob = async () => { + if (currentJob.value) { + const stopJobDTO = { + clusterId: currentJob.value.clusterId, + jobId: currentJob.value.jobId, + taskType: currentJob.value.type, + withSavepoint: false + } + try { + const response: any = await stopJob(stopJobDTO); + if (response.code === 200) { + message.success(t('playground.job_stopping_successfully')) + } else { + message.warning(t('playground.job_stopping_failed')) + } + } catch (error) { + message.warning(t('playground.job_stopping_failed')) + } } } onMounted(() => { setInterval(async () => { if (currentJob.value && currentJob.value.jobId) { - const response: any = await getJobStatus(currentJob.value.jobId); + const response: any = await getJobStatus(currentJob.value.jobId) if (response.data) { jobStatus.value = response.data.status } @@ -90,16 +115,88 @@ export default defineComponent({ return jobStatus.value !== 'RUNNING' }) + const jobStatusColor = computed(() => { + switch (jobStatus.value.toUpperCase()) { + case 'RUNNING': + return '#33994A' + case 'CANCELED': + return '#f6b658' + case 'FINISHED': + return '#f5c1bd' + case 'FAILED': + return '#f9827c' + default: + return '#7ce998'; + } + }) + + const formattedJobStatus = computed(() => { + return jobStatus.value.charAt(0).toUpperCase() + jobStatus.value.slice(1).toLowerCase() + }) + + const dropdownOptions = [ + { label: 'Disabled', key: 'Disabled' }, + { label: '5s', key: '5s' }, + { label: '10s', key: '10s' }, + { label: '30s', key: '30s' }, + { label: '1m', key: '1m' }, + { label: '5m', key: '5m' } + ]; + + const clearRefreshInterval = () => { + if (intervalId.value) { + clearInterval(intervalId.value); + intervalId.value = null; + } + } + + const setRefreshInterval = (milliseconds: number) => { + clearRefreshInterval(); + intervalId.value = setInterval(handleRefreshData, milliseconds); + } + + const handleSelect = (key: any) => { + selectedInterval.value = key + switch (key) { + case '5s': + setRefreshInterval(5000); + break; + case '10s': + setRefreshInterval(10000); + break; + case '30s': + setRefreshInterval(30000); + break; + case '1m': + setRefreshInterval(60000); + break; + case '5m': + setRefreshInterval(300000); + break; + case 'Disabled': + default: + clearRefreshInterval(); + break; + } + } + return { t, + mittBus, activeButton, setActiveButton, isDarkMode, - refreshData, + handleRefreshData, tableData, jobStatus, currentStopIcon, - isButtonDisabled + isButtonDisabled, + handleStopJob, + formattedJobStatus, + jobStatusColor, + dropdownOptions, + selectedInterval, + handleSelect } }, render() { @@ -156,7 +253,7 @@ export default defineComponent({ trigger: () => ( , @@ -176,6 +273,7 @@ export default defineComponent({ trigger: () => ( {this.t('playground.stop_job')} - ( - , - }} - > + ), + default: () => ( + + ) }} - > - {this.t('playground.schedule_refresh')} - + /> {this.tableData?.columns} Columns
@@ -214,7 +313,7 @@ export default defineComponent({
Job: - {this.jobStatus} + {this.formattedJobStatus}
Rows: {this.tableData?.rows} 1m:06s @@ -226,6 +325,7 @@ export default defineComponent({ trigger: () => ( this.mittBus.emit('triggerDownloadCsv')} class={styles['table-action-bar-button']} v-slots={{ icon: () => , @@ -245,6 +345,7 @@ export default defineComponent({ trigger: () => ( this.mittBus.emit('triggerCopyData')} class={styles['table-action-bar-button']} v-slots={{ icon: () => , diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/table/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/table/index.tsx index 5802b9de3..71dde237e 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/table/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/table/index.tsx @@ -16,11 +16,17 @@ specific language governing permissions and limitations under the License. */ import styles from './index.module.scss' -import type {Job} from "@/api/models/job/types/job"; +import type { DataTableInst } from 'naive-ui' +import {useMessage} from "naive-ui"; export default defineComponent({ name: 'TableResult', - setup() { + setup(props, { emit }) { + const {t} = useLocaleHooks() + const message = useMessage() + + const tableRef = ref(null); + interface TableColumn { title: string key: string @@ -38,7 +44,7 @@ export default defineComponent({ if (result && result.resultData) { data.value = result.resultData if (data.value.length > 0) { - generateColumns(data.value[0]); + generateColumns(data.value[0]) } } } @@ -62,34 +68,49 @@ export default defineComponent({ columns.value = [indexColumn, ...dynamicColumns] } - mittBus?.on('jobResult', handleResult); - mittBus?.on('refreshedResult', handleResult); + mittBus?.on('jobResult', handleResult) + mittBus?.on('refreshedResult', handleResult) + + mittBus.on('triggerDownloadCsv', () => { + if (tableRef.value) { + tableRef.value.downloadCsv({ fileName: 'data-table' }) + } + }) + + mittBus.on('triggerCopyData', () => { + if (data.value && data.value.length > 0) { + const jsonData = JSON.stringify(data.value, null, 2) + navigator.clipboard.writeText(jsonData) + .then(() => message.success(t('playground.data_copied_successfully'))) + .catch(err => console.error('Failed to copy data: ', err)) + } + }) onUnmounted(() => { - mittBus.off('jobResult', handleResult); - mittBus.off('refreshedResult', handleResult); + mittBus.off('jobResult', handleResult) + mittBus.off('refreshedResult', handleResult) + mittBus.off('triggerDownloadCsv') + mittBus.off('triggerCopyData') }); - interface User { - id: number - name: string - age: number - address: string - } - return { columns, data, + tableRef, } }, render() { return (
{ this.tableRef = el }} class={styles.table} columns={this.columns} data={this.data} max-height={90} + v-slots={{ + empty: () => '', + }} />
) diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index e6f6e733b..946db69fe 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -74,17 +74,17 @@ export default defineComponent({ function getClusterData() { getClusterListByType(debuggerVariables.conditionValue, 1, Number.MAX_SAFE_INTEGER).then(response => { if (response && response.data) { - const clusterList = response.data as Cluster[]; + const clusterList = response.data as Cluster[] debuggerVariables.clusterOptions = clusterList.map(cluster => ({ label: cluster.clusterName, value: cluster.id.toString() })) if (debuggerVariables.clusterOptions.length > 0) { - debuggerVariables.conditionValue2 = debuggerVariables.clusterOptions[0].value; + debuggerVariables.conditionValue2 = debuggerVariables.clusterOptions[0].value } } }).catch(error => { - console.error('Failed to fetch clusters:', error); + console.error('Failed to fetch clusters:', error) }) } From 8e3f7a4dd329d54dc1512587b4cc4ec8378db2a1 Mon Sep 17 00:00:00 2001 From: s7monk <“15512826113@163.com”> Date: Thu, 30 May 2024 11:06:40 +0800 Subject: [PATCH 07/12] support job submit frontend. --- paimon-web-ui/src/App.tsx | 17 --- paimon-web-ui/src/api/models/job/index.ts | 14 +-- .../console/components/controls/index.tsx | 100 ++++++++++++------ .../console/components/table/index.tsx | 4 +- .../query/components/debugger/index.tsx | 4 +- paimon-web-ui/vite.config.ts | 2 +- 6 files changed, 82 insertions(+), 59 deletions(-) diff --git a/paimon-web-ui/src/App.tsx b/paimon-web-ui/src/App.tsx index 457b42695..55bef678f 100644 --- a/paimon-web-ui/src/App.tsx +++ b/paimon-web-ui/src/App.tsx @@ -23,7 +23,6 @@ import { zhCN, } from 'naive-ui' import { useConfigStore } from '@/store/config' -import {refreshJobStatus} from "@/api/models/job"; import themes from '@/themes' export default defineComponent({ @@ -34,22 +33,6 @@ export default defineComponent({ const themeOverrides = computed(() => themes[theme.value ? 'dark' : 'light']) const locale = computed(() => configStore.getCurrentLocale) - const isRefreshStatusActive = ref(false) - - let refreshStatusIntervalId: number - onMounted(() => { - refreshStatusIntervalId = setInterval(async () => { - if (isRefreshStatusActive.value) { - //await refreshJobStatus(); - } - }, 3000); - }) - - onUnmounted(() => { - isRefreshStatusActive.value = false; - clearInterval(refreshStatusIntervalId); - }) - return { theme, themeOverrides, diff --git a/paimon-web-ui/src/api/models/job/index.ts b/paimon-web-ui/src/api/models/job/index.ts index 9adbb8ed9..66b53cfd8 100644 --- a/paimon-web-ui/src/api/models/job/index.ts +++ b/paimon-web-ui/src/api/models/job/index.ts @@ -16,40 +16,40 @@ specific language governing permissions and limitations under the License. */ import httpRequest from '../../request' -import type {JobSubmitDTO, Job, ResultFetchDTO, JobResultData, JobStatus, StopJobDTO} from "@/api/models/job/types/job"; -import type {ResponseOptions} from "@/api/types"; +import type {JobSubmitDTO, Job, ResultFetchDTO, JobResultData, JobStatus, StopJobDTO} from "@/api/models/job/types/job" +import type {ResponseOptions} from "@/api/types" /** * # Submit a job */ export function submitJob(jobData: JobSubmitDTO) { - return httpRequest.post>('/job/submit', jobData); + return httpRequest.post>('/job/submit', jobData) } /** * # Fetch the result of a submitted job */ export function fetchResult(resultFetchDTO: ResultFetchDTO) { - return httpRequest.post>('/job/fetch', resultFetchDTO); + return httpRequest.post>('/job/fetch', resultFetchDTO) } /** * # Refresh the status of jobs */ export function refreshJobStatus() { - return httpRequest.post>('/job/refresh'); + return httpRequest.post>('/job/refresh') } /** * # Fetch the status of a specific job by its ID */ export function getJobStatus(jobId: string) { - return httpRequest.get>(`/job/status/get/${jobId}`); + return httpRequest.get>(`/job/status/get/${jobId}`) } /** * # Stop a job */ export function stopJob(stopJobDTO: StopJobDTO) { - return httpRequest.post>('/job/stop', stopJobDTO); + return httpRequest.post>('/job/stop', stopJobDTO) } \ No newline at end of file diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index 70b2b7ac6..65630df52 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -19,7 +19,6 @@ import { Copy, DataTable, Renew } from '@vicons/carbon' import { StopOutline, Stop } from '@vicons/ionicons5' import { ClockCircleOutlined, DownloadOutlined, LineChartOutlined } from '@vicons/antd' import styles from './index.module.scss' -import { useConfigStore } from '@/store/config' import type {Job, JobResultData} from "@/api/models/job/types/job" import {fetchResult, getJobStatus, stopJob} from "@/api/models/job" import {useMessage} from "naive-ui" @@ -30,23 +29,23 @@ export default defineComponent({ const {t} = useLocaleHooks() const message = useMessage() + const {mittBus} = getCurrentInstance()!.appContext.config.globalProperties + const currentJob = ref(null) const tableData = ref(null) const jobStatus = ref('') const selectedInterval = ref('Disabled') - const intervalId = ref(null); - - const configStore = useConfigStore() - const isDarkMode = computed(() => configStore.theme === 'dark') - + const refreshIntervalId = ref(null); const activeButton = ref('table') + const startTime = ref(0); + const elapsedTime = ref(0); + const setActiveButton = (button: any) => { activeButton.value = button } - const {mittBus} = getCurrentInstance()!.appContext.config.globalProperties mittBus.on('jobResult', (jobData: any) => { - currentJob.value = jobData; + currentJob.value = jobData }) const handleRefreshData = async () => { @@ -98,16 +97,17 @@ export default defineComponent({ } } + let getJobStatusIntervalId: number onMounted(() => { - setInterval(async () => { + getJobStatusIntervalId = setInterval(async () => { if (currentJob.value && currentJob.value.jobId) { const response: any = await getJobStatus(currentJob.value.jobId) if (response.data) { jobStatus.value = response.data.status } } - }, 1000); - }); + }, 1000) + }) const currentStopIcon = computed(() => jobStatus.value === 'RUNNING' ? StopOutline : Stop); @@ -144,48 +144,87 @@ export default defineComponent({ ]; const clearRefreshInterval = () => { - if (intervalId.value) { - clearInterval(intervalId.value); - intervalId.value = null; + if (refreshIntervalId.value) { + clearInterval(refreshIntervalId.value) + refreshIntervalId.value = null } } const setRefreshInterval = (milliseconds: number) => { clearRefreshInterval(); - intervalId.value = setInterval(handleRefreshData, milliseconds); + refreshIntervalId.value = setInterval(handleRefreshData, milliseconds) } const handleSelect = (key: any) => { selectedInterval.value = key switch (key) { case '5s': - setRefreshInterval(5000); - break; + setRefreshInterval(5000) + break case '10s': - setRefreshInterval(10000); - break; + setRefreshInterval(10000) + break case '30s': - setRefreshInterval(30000); - break; + setRefreshInterval(30000) + break case '1m': - setRefreshInterval(60000); - break; + setRefreshInterval(60000) + break case '5m': - setRefreshInterval(300000); - break; + setRefreshInterval(300000) + break case 'Disabled': default: - clearRefreshInterval(); - break; + clearRefreshInterval() + break + } + } + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}m:${secs}s`; + } + + let computeExecutionTimeIntervalId: number + const startTimer = () => { + if (computeExecutionTimeIntervalId) { + clearInterval(computeExecutionTimeIntervalId); + } + elapsedTime.value = 0; + startTime.value = Date.now(); + computeExecutionTimeIntervalId = setInterval(() => { + elapsedTime.value = Math.floor((Date.now() - startTime.value) / 1000); + }, 3000); + } + + const stopTimer = ()=> { + if (computeExecutionTimeIntervalId) { + clearInterval(computeExecutionTimeIntervalId); } } + watch(jobStatus, (newStatus, oldStatus) => { + if (newStatus === 'RUNNING' && oldStatus !== 'RUNNING') { + startTimer(); + } else if (newStatus !== 'RUNNING' && oldStatus === 'RUNNING') { + stopTimer(); + elapsedTime.value = Math.floor((Date.now() - startTime.value) / 1000); + } + }); + + const formattedTime = computed(() => formatTime(elapsedTime.value)); + + onUnmounted(() => { + clearInterval(getJobStatusIntervalId) + stopTimer(); + }) + return { t, mittBus, activeButton, setActiveButton, - isDarkMode, handleRefreshData, tableData, jobStatus, @@ -196,7 +235,8 @@ export default defineComponent({ jobStatusColor, dropdownOptions, selectedInterval, - handleSelect + handleSelect, + formattedTime } }, render() { @@ -316,7 +356,7 @@ export default defineComponent({ {this.formattedJobStatus} Rows: {this.tableData?.rows} - 1m:06s + { this.formattedTime } (null); + const tableRef = ref(null) interface TableColumn { title: string diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index 946db69fe..a6888ce32 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -30,7 +30,7 @@ export default defineComponent({ const { t } = useLocaleHooks() const message = useMessage() - const tabData = ref({}) as any; + const tabData = ref({}) as any const debuggerVariables = reactive<{ operatingConditionOptions: { label: string; key: string }[] @@ -89,7 +89,7 @@ export default defineComponent({ } watch(() => debuggerVariables.conditionValue, (newValue) => { - getClusterData(); + getClusterData() }) onMounted(() => {getClusterData()}) diff --git a/paimon-web-ui/vite.config.ts b/paimon-web-ui/vite.config.ts index ffb3e6a90..3e34e2baf 100644 --- a/paimon-web-ui/vite.config.ts +++ b/paimon-web-ui/vite.config.ts @@ -34,7 +34,7 @@ export default defineConfig({ changeOrigin: true, }, '/api': { - target: 'http://47.94.247.86:10088', + target: 'http://127.0.0.1:10088', changeOrigin: true, }, }, From 0fb9d649347a84c65732b847b5758dc25681a572 Mon Sep 17 00:00:00 2001 From: s7monk <“15512826113@163.com”> Date: Thu, 30 May 2024 19:15:42 +0800 Subject: [PATCH 08/12] commit. --- paimon-web-ui/src/api/models/job/index.ts | 6 ++--- .../console/components/controls/index.tsx | 26 ++++++++++--------- paimon-web-ui/vite.config.ts | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/paimon-web-ui/src/api/models/job/index.ts b/paimon-web-ui/src/api/models/job/index.ts index 66b53cfd8..ccdae3f23 100644 --- a/paimon-web-ui/src/api/models/job/index.ts +++ b/paimon-web-ui/src/api/models/job/index.ts @@ -30,14 +30,14 @@ export function submitJob(jobData: JobSubmitDTO) { * # Fetch the result of a submitted job */ export function fetchResult(resultFetchDTO: ResultFetchDTO) { - return httpRequest.post>('/job/fetch', resultFetchDTO) + return httpRequest.post('/job/fetch', resultFetchDTO) } /** * # Refresh the status of jobs */ export function refreshJobStatus() { - return httpRequest.post>('/job/refresh') + return httpRequest.post('/job/refresh') } /** @@ -51,5 +51,5 @@ export function getJobStatus(jobId: string) { * # Stop a job */ export function stopJob(stopJobDTO: StopJobDTO) { - return httpRequest.post>('/job/stop', stopJobDTO) + return httpRequest.post('/job/stop', stopJobDTO) } \ No newline at end of file diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index 65630df52..77baad9cd 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -52,16 +52,16 @@ export default defineComponent({ if (currentJob.value) { if (currentJob.value.shouldFetchResult) { try { + const job = toRaw(currentJob.value); + const { submitId, clusterId, sessionId, type: taskType, token } = job const resultFetchDTO = { - submitId: currentJob.value.submitId, - clusterId: currentJob.value.clusterId, - sessionId: currentJob.value.sessionId, - taskType: currentJob.value.type, - token: currentJob.value.token + submitId, + clusterId, + sessionId, + taskType, + token } - - const response: any = await fetchResult(resultFetchDTO); - console.log(response.data) + const response = await fetchResult(resultFetchDTO) tableData.value = response.data; mittBus.emit('refreshedResult', response.data) } catch (error) { @@ -78,14 +78,16 @@ export default defineComponent({ const handleStopJob = async () => { if (currentJob.value) { + const job = toRaw(currentJob.value); + const { clusterId, jobId, type: taskType} = job const stopJobDTO = { - clusterId: currentJob.value.clusterId, - jobId: currentJob.value.jobId, - taskType: currentJob.value.type, + clusterId, + jobId, + taskType, withSavepoint: false } try { - const response: any = await stopJob(stopJobDTO); + const response = await stopJob(stopJobDTO); if (response.code === 200) { message.success(t('playground.job_stopping_successfully')) } else { diff --git a/paimon-web-ui/vite.config.ts b/paimon-web-ui/vite.config.ts index 3e34e2baf..ffb3e6a90 100644 --- a/paimon-web-ui/vite.config.ts +++ b/paimon-web-ui/vite.config.ts @@ -34,7 +34,7 @@ export default defineConfig({ changeOrigin: true, }, '/api': { - target: 'http://127.0.0.1:10088', + target: 'http://47.94.247.86:10088', changeOrigin: true, }, }, From 55775d58db73b75877caa6faab8b7cde4e8a6de8 Mon Sep 17 00:00:00 2001 From: s7monk <15512826113@163.com> Date: Thu, 30 May 2024 22:28:23 +0800 Subject: [PATCH 09/12] address comments. --- paimon-web-ui/src/api/models/job/index.ts | 10 +++++----- .../console/components/controls/index.tsx | 14 ++++++++------ .../components/query/components/debugger/index.tsx | 3 +-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/paimon-web-ui/src/api/models/job/index.ts b/paimon-web-ui/src/api/models/job/index.ts index ccdae3f23..e83fdfb46 100644 --- a/paimon-web-ui/src/api/models/job/index.ts +++ b/paimon-web-ui/src/api/models/job/index.ts @@ -22,15 +22,15 @@ import type {ResponseOptions} from "@/api/types" /** * # Submit a job */ -export function submitJob(jobData: JobSubmitDTO) { - return httpRequest.post>('/job/submit', jobData) +export function submitJob(jobSubmitDTO: JobSubmitDTO) { + return httpRequest.post>('/job/submit', jobSubmitDTO) } /** * # Fetch the result of a submitted job */ export function fetchResult(resultFetchDTO: ResultFetchDTO) { - return httpRequest.post('/job/fetch', resultFetchDTO) + return httpRequest.post>('/job/fetch', resultFetchDTO) } /** @@ -44,12 +44,12 @@ export function refreshJobStatus() { * # Fetch the status of a specific job by its ID */ export function getJobStatus(jobId: string) { - return httpRequest.get>(`/job/status/get/${jobId}`) + return httpRequest.get>(`/job/status/get/${jobId}`) } /** * # Stop a job */ export function stopJob(stopJobDTO: StopJobDTO) { - return httpRequest.post('/job/stop', stopJobDTO) + return httpRequest.post>('/job/stop', stopJobDTO) } \ No newline at end of file diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index 77baad9cd..46e2a1c00 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -22,6 +22,8 @@ import styles from './index.module.scss' import type {Job, JobResultData} from "@/api/models/job/types/job" import {fetchResult, getJobStatus, stopJob} from "@/api/models/job" import {useMessage} from "naive-ui" +import dayjs from 'dayjs' +import duration from 'dayjs/plugin/duration' export default defineComponent({ name: 'TableActionBar', @@ -103,7 +105,7 @@ export default defineComponent({ onMounted(() => { getJobStatusIntervalId = setInterval(async () => { if (currentJob.value && currentJob.value.jobId) { - const response: any = await getJobStatus(currentJob.value.jobId) + const response = await getJobStatus(currentJob.value.jobId) if (response.data) { jobStatus.value = response.data.status } @@ -161,19 +163,19 @@ export default defineComponent({ selectedInterval.value = key switch (key) { case '5s': - setRefreshInterval(5000) + setRefreshInterval(dayjs.duration(5, 'seconds').asMilliseconds()) break case '10s': - setRefreshInterval(10000) + setRefreshInterval(dayjs.duration(10, 'seconds').asMilliseconds()) break case '30s': - setRefreshInterval(30000) + setRefreshInterval(dayjs.duration(30, 'seconds').asMilliseconds()) break case '1m': - setRefreshInterval(60000) + setRefreshInterval(dayjs.duration(1, 'minute').asMilliseconds()) break case '5m': - setRefreshInterval(300000) + setRefreshInterval(dayjs.duration(5, 'minutes').asMilliseconds()) break case 'Disabled': default: diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index a6888ce32..e489dfcbf 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -121,8 +121,7 @@ export default defineComponent({ try { const response = await submitJob(jobDataDTO); - console.log(response) - if (response.data) { + if (response.code === 200) { message.success(t('playground.job_submission_successfully')) mittBus.emit('jobResult', response.data); } else { From 0d6193cdf13e165455ee3cf5a65e5e777e7048e9 Mon Sep 17 00:00:00 2001 From: s7monk <15512826113@163.com> Date: Thu, 30 May 2024 22:30:33 +0800 Subject: [PATCH 10/12] address comments. --- paimon-web-ui/src/locales/zh/modules/playground.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/paimon-web-ui/src/locales/zh/modules/playground.ts b/paimon-web-ui/src/locales/zh/modules/playground.ts index 225fb6adb..c37e079c8 100644 --- a/paimon-web-ui/src/locales/zh/modules/playground.ts +++ b/paimon-web-ui/src/locales/zh/modules/playground.ts @@ -49,10 +49,10 @@ export default { schedule_refresh: '定时刷新', download: '导出数据', copy: '复制数据', - job_submission_successfully: 'SQL 任务提交成功!', - job_submission_failed: '任务提交失败!', - no_data: '暂无数据,请重新执行SQL!', - job_stopping_successfully: '停止任务成功!', - job_stopping_failed: '停止任务失败!', - data_copied_successfully: '数据已复制到剪贴板!', + job_submission_successfully: 'SQL 任务提交成功', + job_submission_failed: '任务提交失败', + no_data: '暂无数据,请重新执行SQL', + job_stopping_successfully: '停止任务成功', + job_stopping_failed: '停止任务失败', + data_copied_successfully: '数据已复制到剪贴板', } From 3b5de89e97700b37bf691bc5652a4ea868485be3 Mon Sep 17 00:00:00 2001 From: s7monk <15512826113@163.com> Date: Fri, 31 May 2024 02:35:10 +0800 Subject: [PATCH 11/12] fix some issues. --- paimon-web-ui/package.json | 2 +- paimon-web-ui/src/store/job/index.ts | 40 +++++++++++++++++ paimon-web-ui/src/store/job/type.ts | 18 ++++++++ .../console/components/controls/index.tsx | 43 ++++++++++++++----- .../query/components/debugger/index.tsx | 13 ++++-- .../query/components/tabs/index.tsx | 8 ++-- paimon-web-ui/vite.config.ts | 2 +- 7 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 paimon-web-ui/src/store/job/index.ts create mode 100644 paimon-web-ui/src/store/job/type.ts diff --git a/paimon-web-ui/package.json b/paimon-web-ui/package.json index 67e65d035..5e1320812 100644 --- a/paimon-web-ui/package.json +++ b/paimon-web-ui/package.json @@ -19,7 +19,7 @@ "@antv/x6-plugin-dnd": "^2.1.1", "@antv/x6-vue-shape": "^2.1.1", "dart-sass": "^1.25.0", - "dayjs": "^1.11.10", + "dayjs": "^1.11.11", "lodash": "^4.17.21", "mitt": "^3.0.1", "monaco-editor": "^0.43.0", diff --git a/paimon-web-ui/src/store/job/index.ts b/paimon-web-ui/src/store/job/index.ts new file mode 100644 index 000000000..53e615ffc --- /dev/null +++ b/paimon-web-ui/src/store/job/index.ts @@ -0,0 +1,40 @@ +/* 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 type { ExecutionMode } from './type' + +export interface JobState { + executionMode: ExecutionMode +} + +export const useJobStore = defineStore({ + id: 'job', + state: (): JobState => ({ + executionMode: 'Streaming', + }), + persist: true, + getters: { + getExecutionMode(): ExecutionMode { + return this.executionMode + } + }, + actions: { + setExecutionMode(executionMode: ExecutionMode) { + this.executionMode = executionMode + } + } +}) \ No newline at end of file diff --git a/paimon-web-ui/src/store/job/type.ts b/paimon-web-ui/src/store/job/type.ts new file mode 100644 index 000000000..3eeeb0030 --- /dev/null +++ b/paimon-web-ui/src/store/job/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 ExecutionMode = 'Streaming' | 'Batch' \ No newline at end of file diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index 46e2a1c00..dc4c5c133 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -24,12 +24,14 @@ import {fetchResult, getJobStatus, stopJob} from "@/api/models/job" import {useMessage} from "naive-ui" import dayjs from 'dayjs' import duration from 'dayjs/plugin/duration' +import { useJobStore } from '@/store/job' export default defineComponent({ name: 'TableActionBar', setup: function () { const {t} = useLocaleHooks() const message = useMessage() + const jobStore = useJobStore() const {mittBus} = getCurrentInstance()!.appContext.config.globalProperties @@ -37,10 +39,10 @@ export default defineComponent({ const tableData = ref(null) const jobStatus = ref('') const selectedInterval = ref('Disabled') - const refreshIntervalId = ref(null); + const refreshIntervalId = ref(null) const activeButton = ref('table') - const startTime = ref(0); - const elapsedTime = ref(0); + const startTime = ref(0) + const elapsedTime = ref(0) const setActiveButton = (button: any) => { activeButton.value = button @@ -54,7 +56,7 @@ export default defineComponent({ if (currentJob.value) { if (currentJob.value.shouldFetchResult) { try { - const job = toRaw(currentJob.value); + const job = toRaw(currentJob.value) const { submitId, clusterId, sessionId, type: taskType, token } = job const resultFetchDTO = { submitId, @@ -64,7 +66,8 @@ export default defineComponent({ token } const response = await fetchResult(resultFetchDTO) - tableData.value = response.data; + tableData.value = response.data + console.log(response.data) mittBus.emit('refreshedResult', response.data) } catch (error) { tableData.value = null; @@ -119,6 +122,10 @@ export default defineComponent({ return jobStatus.value !== 'RUNNING' }) + const isScheduleButtonDisabled = computed(() => { + return jobStore.getExecutionMode === 'Batch' + }) + const jobStatusColor = computed(() => { switch (jobStatus.value.toUpperCase()) { case 'RUNNING': @@ -155,10 +162,19 @@ export default defineComponent({ } const setRefreshInterval = (milliseconds: number) => { - clearRefreshInterval(); + clearRefreshInterval() refreshIntervalId.value = setInterval(handleRefreshData, milliseconds) } + watch(jobStatus, () => { + if (jobStatus.value !== 'RUNNING') { + if (refreshIntervalId.value) { + clearRefreshInterval() + } + } + }) + + dayjs.extend(duration); const handleSelect = (key: any) => { selectedInterval.value = key switch (key) { @@ -185,9 +201,11 @@ export default defineComponent({ } const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60); - const secs = seconds % 60; - return `${mins}m:${secs}s`; + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const mins = Math.floor((seconds % 3600) / 60) + const secs = seconds % 60 + return `${days > 0 ? `${days}d:` : ''}${hours > 0 || days > 0 ? `${hours}h:` : ''}${mins}m:${secs}s` } let computeExecutionTimeIntervalId: number @@ -234,6 +252,7 @@ export default defineComponent({ jobStatus, currentStopIcon, isButtonDisabled, + isScheduleButtonDisabled, handleStopJob, formattedJobStatus, jobStatusColor, @@ -335,11 +354,15 @@ export default defineComponent({ size="small" placement="bottom-start" options={this.dropdownOptions} + disabled={this.isScheduleButtonDisabled} v-model:value={this.selectedInterval} on-select={this.handleSelect} v-slots={{ trigger: () => ( - + ), default: () => ( diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index e489dfcbf..46fbb06b5 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -18,10 +18,12 @@ under the License. */ import { ChevronDown, Play, ReaderOutline, Save } from '@vicons/ionicons5' import { getClusterListByType } from '@/api/models/cluster' import styles from './index.module.scss' -import type { Cluster } from "@/api/models/cluster/types"; -import type {JobSubmitDTO} from "@/api/models/job/types/job"; -import {submitJob} from "@/api/models/job"; -import {useMessage} from "naive-ui"; +import type { Cluster } from "@/api/models/cluster/types" +import type {JobSubmitDTO} from "@/api/models/job/types/job" +import { submitJob } from "@/api/models/job" +import { useMessage } from "naive-ui" +import { useJobStore } from '@/store/job' +import type {ExecutionMode} from "@/store/job/type"; export default defineComponent({ name: 'EditorDebugger', @@ -29,6 +31,7 @@ export default defineComponent({ setup(props, { emit }) { const { t } = useLocaleHooks() const message = useMessage() + const jobStore = useJobStore() const tabData = ref({}) as any @@ -106,6 +109,8 @@ export default defineComponent({ return } + jobStore.setExecutionMode(debuggerVariables.conditionValue3 as ExecutionMode) + const currentSQL = currentTab.content if (!currentSQL) { return diff --git a/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx index 3b267a17d..62ea32a42 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/tabs/index.tsx @@ -17,6 +17,7 @@ under the License. */ import styles from './index.module.scss' import ContextMenu from '@/components/context-menu' +import dayjs from 'dayjs' export default defineComponent({ name: 'EditorTabs', @@ -30,13 +31,14 @@ export default defineComponent({ }) const handleAdd = () => { + const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss') tabVariables.panelsList.push({ - tableName: `Q${tabVariables.panelsList.length + 1}`, - key: `Q${tabVariables.panelsList.length + 1}`, + tableName: timestamp, + key: timestamp, isSaved: false, content: '', }) - tabVariables.chooseTab = `Q${tabVariables.panelsList.length}` + tabVariables.chooseTab = timestamp } const handleClose = (key: any) => { diff --git a/paimon-web-ui/vite.config.ts b/paimon-web-ui/vite.config.ts index ffb3e6a90..3e34e2baf 100644 --- a/paimon-web-ui/vite.config.ts +++ b/paimon-web-ui/vite.config.ts @@ -34,7 +34,7 @@ export default defineConfig({ changeOrigin: true, }, '/api': { - target: 'http://47.94.247.86:10088', + target: 'http://127.0.0.1:10088', changeOrigin: true, }, }, From 17703dd96c6535663cc1456586a2a719ce7831da Mon Sep 17 00:00:00 2001 From: s7monk <“15512826113@163.com”> Date: Fri, 31 May 2024 17:30:06 +0800 Subject: [PATCH 12/12] fix some issues. --- paimon-web-ui/src/api/models/job/types/job.ts | 10 ++- paimon-web-ui/src/store/job/index.ts | 61 ++++++++++++- .../console/components/controls/index.tsx | 86 +++---------------- .../console/components/table/index.tsx | 36 ++++---- .../query/components/debugger/index.tsx | 4 +- .../playground/components/query/index.tsx | 60 +++++++++++++ 6 files changed, 158 insertions(+), 99 deletions(-) diff --git a/paimon-web-ui/src/api/models/job/types/job.ts b/paimon-web-ui/src/api/models/job/types/job.ts index 641c75ca1..c327a7714 100644 --- a/paimon-web-ui/src/api/models/job/types/job.ts +++ b/paimon-web-ui/src/api/models/job/types/job.ts @@ -26,14 +26,14 @@ export interface Job { uid: number status?: string shouldFetchResult: boolean - resultData?: Array<{ [key: string]: any }> + resultData: ResultDataItem[] token: number startTime: string endTime: string } export interface JobSubmitDTO { - jobName?: string + jobName: string taskType: string clusterId: string config?: { @@ -44,12 +44,16 @@ export interface JobSubmitDTO { } export interface JobResultData { - resultData?: Array<{ [key: string]: any }> + resultData: ResultDataItem[] columns: number rows: number token: number } +export interface ResultDataItem { + [key: string]: any +} + export interface ResultFetchDTO { submitId: string clusterId: string diff --git a/paimon-web-ui/src/store/job/index.ts b/paimon-web-ui/src/store/job/index.ts index 53e615ffc..ad2d264b1 100644 --- a/paimon-web-ui/src/store/job/index.ts +++ b/paimon-web-ui/src/store/job/index.ts @@ -16,25 +16,82 @@ specific language governing permissions and limitations under the License. */ import type { ExecutionMode } from './type' +import type { Job, JobResultData } from '@/api/models/job/types/job' export interface JobState { executionMode: ExecutionMode + currentJob: Job | null + jobResultData: JobResultData | null + jobStatus: string, + executionTime: string } export const useJobStore = defineStore({ id: 'job', state: (): JobState => ({ executionMode: 'Streaming', + currentJob: null, + jobResultData: null, + jobStatus: '', + executionTime: '0m:0s' }), - persist: true, + persist: false, getters: { getExecutionMode(): ExecutionMode { return this.executionMode + }, + getCurrentJob(): Job | null { + return this.currentJob + }, + getJobResultData(): JobResultData | null { + return this.jobResultData + }, + getColumns(): number { + if (this.currentJob && this.currentJob.resultData && this.currentJob.resultData.length > 0) { + return Object.keys(this.currentJob.resultData[0]).length + }else if (this.jobResultData && this.jobResultData.resultData && this.jobResultData.resultData.length > 0) { + return Object.keys(this.jobResultData.resultData[0]).length + } else { + return 0 + } + }, + getRows(): number { + if (this.currentJob && this.currentJob.resultData && this.currentJob.resultData.length > 0) { + return this.currentJob.resultData.length + }else if (this.jobResultData && this.jobResultData.resultData && this.jobResultData.resultData.length > 0) { + return this.jobResultData.resultData.length + } else { + return 0 + } + }, + getJobStatus(): string { + return this.jobStatus + }, + getExecutionTime(): string { + return this.executionTime } }, actions: { setExecutionMode(executionMode: ExecutionMode) { this.executionMode = executionMode - } + }, + setCurrentJob(currentJob: Job) { + this.currentJob = currentJob + }, + setJobResultData(jobResultData: JobResultData) { + this.jobResultData = jobResultData + }, + setJobStatus(jobStatus: string) { + this.jobStatus = jobStatus + }, + setExecutionTime(executionTime: string) { + this.executionTime = executionTime + }, + resetCurrentResult() { + this.currentJob = null + this.jobResultData = null + this.jobStatus = '' + this.executionTime = '0m:0s' + }, } }) \ No newline at end of file diff --git a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx index dc4c5c133..be3e50587 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/console/components/controls/index.tsx @@ -19,8 +19,7 @@ import { Copy, DataTable, Renew } from '@vicons/carbon' import { StopOutline, Stop } from '@vicons/ionicons5' import { ClockCircleOutlined, DownloadOutlined, LineChartOutlined } from '@vicons/antd' import styles from './index.module.scss' -import type {Job, JobResultData} from "@/api/models/job/types/job" -import {fetchResult, getJobStatus, stopJob} from "@/api/models/job" +import {fetchResult, stopJob} from "@/api/models/job" import {useMessage} from "naive-ui" import dayjs from 'dayjs' import duration from 'dayjs/plugin/duration' @@ -35,23 +34,17 @@ export default defineComponent({ const {mittBus} = getCurrentInstance()!.appContext.config.globalProperties - const currentJob = ref(null) - const tableData = ref(null) - const jobStatus = ref('') + const currentJob = computed(() => jobStore.getCurrentJob) + const jobStatus = computed(() => jobStore.getJobStatus) + const executionTime = computed(() => jobStore.getExecutionTime) const selectedInterval = ref('Disabled') const refreshIntervalId = ref(null) const activeButton = ref('table') - const startTime = ref(0) - const elapsedTime = ref(0) const setActiveButton = (button: any) => { activeButton.value = button } - mittBus.on('jobResult', (jobData: any) => { - currentJob.value = jobData - }) - const handleRefreshData = async () => { if (currentJob.value) { if (currentJob.value.shouldFetchResult) { @@ -66,11 +59,8 @@ export default defineComponent({ token } const response = await fetchResult(resultFetchDTO) - tableData.value = response.data - console.log(response.data) - mittBus.emit('refreshedResult', response.data) + jobStore.setJobResultData(response.data) } catch (error) { - tableData.value = null; console.error('Error fetching result:', error) } } else { @@ -104,18 +94,6 @@ export default defineComponent({ } } - let getJobStatusIntervalId: number - onMounted(() => { - getJobStatusIntervalId = setInterval(async () => { - if (currentJob.value && currentJob.value.jobId) { - const response = await getJobStatus(currentJob.value.jobId) - if (response.data) { - jobStatus.value = response.data.status - } - } - }, 1000) - }) - const currentStopIcon = computed(() => jobStatus.value === 'RUNNING' ? StopOutline : Stop); const isButtonDisabled = computed(() => { @@ -200,47 +178,8 @@ export default defineComponent({ } } - const formatTime = (seconds: number) => { - const days = Math.floor(seconds / 86400) - const hours = Math.floor((seconds % 86400) / 3600) - const mins = Math.floor((seconds % 3600) / 60) - const secs = seconds % 60 - return `${days > 0 ? `${days}d:` : ''}${hours > 0 || days > 0 ? `${hours}h:` : ''}${mins}m:${secs}s` - } - - let computeExecutionTimeIntervalId: number - const startTimer = () => { - if (computeExecutionTimeIntervalId) { - clearInterval(computeExecutionTimeIntervalId); - } - elapsedTime.value = 0; - startTime.value = Date.now(); - computeExecutionTimeIntervalId = setInterval(() => { - elapsedTime.value = Math.floor((Date.now() - startTime.value) / 1000); - }, 3000); - } - - const stopTimer = ()=> { - if (computeExecutionTimeIntervalId) { - clearInterval(computeExecutionTimeIntervalId); - } - } - - watch(jobStatus, (newStatus, oldStatus) => { - if (newStatus === 'RUNNING' && oldStatus !== 'RUNNING') { - startTimer(); - } else if (newStatus !== 'RUNNING' && oldStatus === 'RUNNING') { - stopTimer(); - elapsedTime.value = Math.floor((Date.now() - startTime.value) / 1000); - } - }); - - const formattedTime = computed(() => formatTime(elapsedTime.value)); - - onUnmounted(() => { - clearInterval(getJobStatusIntervalId) - stopTimer(); - }) + const rowCount = computed(() => jobStore.getRows) + const columnCount = computed(() => jobStore.getColumns) return { t, @@ -248,7 +187,6 @@ export default defineComponent({ activeButton, setActiveButton, handleRefreshData, - tableData, jobStatus, currentStopIcon, isButtonDisabled, @@ -259,7 +197,9 @@ export default defineComponent({ dropdownOptions, selectedInterval, handleSelect, - formattedTime + columnCount, + rowCount, + executionTime } }, render() { @@ -374,7 +314,7 @@ export default defineComponent({ }} /> - {this.tableData?.columns} Columns + {this.columnCount} Columns
@@ -382,8 +322,8 @@ export default defineComponent({ Job: {this.formattedJobStatus}
- Rows: {this.tableData?.rows} - { this.formattedTime } + Rows: {this.rowCount} + { this.executionTime } (null) @@ -35,19 +37,18 @@ export default defineComponent({ render?: (row: any, index: number) => string | number | JSX.Element } - const data = ref([]) - const columns = ref([]) + const initialData = computed(() => jobStore.getCurrentJob?.resultData || []); + const refreshedData = computed(() => jobStore.getJobResultData?.resultData || []); + const data = computed(() => refreshedData.value.length > 0 ? refreshedData.value : initialData.value); - const { mittBus } = getCurrentInstance()!.appContext.config.globalProperties - - const handleResult = (result: any) => { - if (result && result.resultData) { - data.value = result.resultData - if (data.value.length > 0) { - generateColumns(data.value[0]) - } + const columns = computed(() => { + if (data.value.length > 0) { + return generateColumns(data.value[0]); } - } + return []; + }); + + const {mittBus} = getCurrentInstance()!.appContext.config.globalProperties const generateColumns = (sampleObject: any) => { const indexColumn: TableColumn = { @@ -65,15 +66,12 @@ export default defineComponent({ sortable: true })) - columns.value = [indexColumn, ...dynamicColumns] + return [indexColumn, ...dynamicColumns]; } - mittBus?.on('jobResult', handleResult) - mittBus?.on('refreshedResult', handleResult) - mittBus.on('triggerDownloadCsv', () => { if (tableRef.value) { - tableRef.value.downloadCsv({ fileName: 'data-table' }) + tableRef.value.downloadCsv({fileName: 'data-table'}) } }) @@ -87,8 +85,6 @@ export default defineComponent({ }) onUnmounted(() => { - mittBus.off('jobResult', handleResult) - mittBus.off('refreshedResult', handleResult) mittBus.off('triggerDownloadCsv') mittBus.off('triggerCopyData') }); diff --git a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx index 46fbb06b5..1386c6b18 100644 --- a/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/components/debugger/index.tsx @@ -23,7 +23,7 @@ import type {JobSubmitDTO} from "@/api/models/job/types/job" import { submitJob } from "@/api/models/job" import { useMessage } from "naive-ui" import { useJobStore } from '@/store/job' -import type {ExecutionMode} from "@/store/job/type"; +import type {ExecutionMode} from "@/store/job/type" export default defineComponent({ name: 'EditorDebugger', @@ -110,6 +110,7 @@ export default defineComponent({ } jobStore.setExecutionMode(debuggerVariables.conditionValue3 as ExecutionMode) + jobStore.resetCurrentResult() const currentSQL = currentTab.content if (!currentSQL) { @@ -128,6 +129,7 @@ export default defineComponent({ const response = await submitJob(jobDataDTO); if (response.code === 200) { message.success(t('playground.job_submission_successfully')) + jobStore.setCurrentJob(response.data) mittBus.emit('jobResult', response.data); } else { message.error(`${t('playground.job_submission_failed')}`) 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 3125eca76..8c7ab8089 100644 --- a/paimon-web-ui/src/views/playground/components/query/index.tsx +++ b/paimon-web-ui/src/views/playground/components/query/index.tsx @@ -24,11 +24,19 @@ import EditorTabs from './components/tabs' import EditorDebugger from './components/debugger' import EditorConsole from './components/console' import MonacoEditor from '@/components/monaco-editor' +import { useJobStore } from '@/store/job' +import {getJobStatus} from "@/api/models/job" export default defineComponent({ name: 'QueryPage', setup() { const message = useMessage() + const jobStore = useJobStore() + + const startTime = ref(0) + const elapsedTime = ref(0) + const currentJob = computed(() => jobStore.getCurrentJob) + const jobStatus = computed(() => jobStore.getJobStatus) const editorVariables = reactive({ editor: {} as any, @@ -85,6 +93,58 @@ export default defineComponent({ tabData.value = data }) + let getJobStatusIntervalId: number + onMounted(() => { + getJobStatusIntervalId = setInterval(async () => { + if (currentJob.value && currentJob.value.jobId) { + const response = await getJobStatus(currentJob.value.jobId) + if (response.data) { + jobStore.setJobStatus(response.data.status) + } + } + }, 1000) + }) + + let computeExecutionTimeIntervalId: number + const startTimer = () => { + if (computeExecutionTimeIntervalId) { + clearInterval(computeExecutionTimeIntervalId) + } + elapsedTime.value = 0 + startTime.value = Date.now() + computeExecutionTimeIntervalId = setInterval(() => { + elapsedTime.value = Math.floor((Date.now() - startTime.value) / 1000) + }, 3000) + } + + const stopTimer = ()=> { + if (computeExecutionTimeIntervalId) { + clearInterval(computeExecutionTimeIntervalId) + } + } + + watch(jobStatus, (newStatus, oldStatus) => { + if (newStatus === 'RUNNING' && oldStatus !== 'RUNNING') { + startTimer(); + } else if (newStatus !== 'RUNNING' && oldStatus === 'RUNNING') { + stopTimer(); + elapsedTime.value = Math.floor((Date.now() - startTime.value) / 1000) + } + }) + + const formatTime = (seconds: number): string => { + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const mins = Math.floor((seconds % 3600) / 60) + const secs = seconds % 60 + return `${days > 0 ? `${days}d:` : ''}${hours > 0 || days > 0 ? `${hours}h:` : ''}${mins}m:${secs}s` + } + + const formattedTime = computed(() => formatTime(elapsedTime.value)) + watch(formattedTime, (formattedTime) => jobStore.setExecutionTime(formattedTime)) + + onUnmounted(() => jobStore.resetCurrentResult()) + return { ...toRefs(editorVariables), editorMounted,