You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ch...@apache.org on 2023/04/05 09:31:21 UTC

[kyuubi] branch master updated: [KYUUBI #3650][UI] Add Operation Statistics Page

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 015b80015 [KYUUBI #3650][UI] Add Operation Statistics Page
015b80015 is described below

commit 015b800156d7e0e56ba1246fe6a3781950620e3a
Author: He Zhao <he...@cisco.com>
AuthorDate: Wed Apr 5 17:31:10 2023 +0800

    [KYUUBI #3650][UI] Add Operation Statistics Page
    
    ### _Why are the changes needed?_
    
    Close #3650
    
    ### _How was this patch tested?_
    - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
    
    - [ ] Add screenshots for manual tests if appropriate
    
    - [ ] [Run test](https://kyuubi.readthedocs.io/en/master/develop_tools/testing.html#running-tests) locally before make a pull request
    
    ![popo_2023-04-04  15-50-15](https://user-images.githubusercontent.com/52876270/229724723-c6ddc892-0a1c-4d38-acf6-f2c6c8bf20b2.jpg)
    
    Closes #4663 from zwangsheng/KYUUBI_3650.
    
    Closes #3650
    
    277e544e6 [Cheng Pan] Update kyuubi-server/web-ui/src/views/operation/operation-statistics/index.vue
    688cfb44e [zwangsheng] i18n
    ed314d7c8 [zwangsheng] [KYUUBI #3650][UI] Add Operation Statistics Page
    c1965031a [zwangsheng] [KYUUBI #3650][UI] Add Operation Statistics Page
    65779b878 [zwangsheng] [KYUUBI #3650][UI] Add Operation Statistics Page
    
    Lead-authored-by: He Zhao <he...@cisco.com>
    Co-authored-by: zwangsheng <22...@qq.com>
    Co-authored-by: Cheng Pan <pa...@gmail.com>
    Signed-off-by: Cheng Pan <ch...@apache.org>
---
 .../src/{locales/en_US => api/operation}/index.ts  |  31 +++--
 kyuubi-server/web-ui/src/locales/en_US/index.ts    |  14 +-
 kyuubi-server/web-ui/src/locales/zh_CN/index.ts    |  14 +-
 kyuubi-server/web-ui/src/router/operation/index.ts |   5 +
 .../src/{locales/en_US/index.ts => utils/unit.ts}  |  34 +++--
 .../src/views/layout/components/aside/types.ts     |   5 +
 .../views/operation/operation-statistics/index.vue | 144 +++++++++++++++++++++
 7 files changed, 220 insertions(+), 27 deletions(-)

diff --git a/kyuubi-server/web-ui/src/locales/en_US/index.ts b/kyuubi-server/web-ui/src/api/operation/index.ts
similarity index 69%
copy from kyuubi-server/web-ui/src/locales/en_US/index.ts
copy to kyuubi-server/web-ui/src/api/operation/index.ts
index 99e851651..51a3b5394 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/api/operation/index.ts
@@ -15,17 +15,24 @@
  * limitations under the License.
  */
 
-export default {
-  test: 'test',
-  user: 'User',
-  client_ip: 'Client IP',
-  kyuubi_instance: 'Kyuubi Instance',
-  session_id: 'Session ID',
-  create_time: 'Create Time',
-  operation: 'Operation',
-  delete_confirm: 'Delete Confirm',
-  message: {
-    delete_succeeded: 'Delete {name} Succeeded',
-    delete_failed: 'Delete {name} Failed'
+import request from '@/utils/request'
+
+export function getAllOperations() {
+  return request({
+    url: 'api/v1/admin/operations',
+    method: 'get'
+  })
+}
+
+export function actionOnOperation(
+  operationId: string,
+  data: {
+    action: 'CANCEL' | 'CLOSE'
   }
+) {
+  return request({
+    url: `api/v1/operations/${operationId}`,
+    method: 'put',
+    data
+  })
 }
diff --git a/kyuubi-server/web-ui/src/locales/en_US/index.ts b/kyuubi-server/web-ui/src/locales/en_US/index.ts
index 99e851651..d50f22915 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/locales/en_US/index.ts
@@ -21,11 +21,23 @@ export default {
   client_ip: 'Client IP',
   kyuubi_instance: 'Kyuubi Instance',
   session_id: 'Session ID',
+  operation_id: 'Operation ID',
   create_time: 'Create Time',
   operation: 'Operation',
   delete_confirm: 'Delete Confirm',
+  close_confirm: 'Close Confirm',
+  cancel_confirm: 'Cancel Confirm',
+  start_time: 'State Time',
+  complete_time: 'Completed Time',
+  state: 'State',
+  duration: 'Duration',
+  statement: 'Statement',
   message: {
     delete_succeeded: 'Delete {name} Succeeded',
-    delete_failed: 'Delete {name} Failed'
+    delete_failed: 'Delete {name} Failed',
+    close_succeeded: 'Close {name} Succeeded',
+    close_failed: 'Close {name} Failed',
+    cancel_succeeded: 'Cancel {name} Succeeded',
+    cancel_failed: 'Cancel {name} Failed'
   }
 }
diff --git a/kyuubi-server/web-ui/src/locales/zh_CN/index.ts b/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
index 016aaa8e7..443d129cc 100644
--- a/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
+++ b/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
@@ -21,11 +21,23 @@ export default {
   client_ip: '客户端地址',
   kyuubi_instance: '服务端地址',
   session_id: 'Session ID',
+  operation_id: 'Operation ID',
   create_time: '创建时间',
   operation: '操作',
   delete_confirm: '确认删除',
+  close_confirm: '确认关闭',
+  cancel_confirm: '确认取消',
+  start_time: '开始时间',
+  complete_time: '完成时间',
+  state: '状态',
+  duration: '运行时间',
+  statement: 'Statement',
   message: {
     delete_succeeded: '删除 {name} 成功',
-    delete_failed: '删除 {name} 失败'
+    delete_failed: '删除 {name} 失败',
+    close_succeeded: '关闭 {name} 成功',
+    close_failed: '关闭 {name} 失败',
+    cancel_succeeded: '取消 {name} 成功',
+    cancel_failed: '取消 {name} 失败'
   }
 }
diff --git a/kyuubi-server/web-ui/src/router/operation/index.ts b/kyuubi-server/web-ui/src/router/operation/index.ts
index 03ba4c285..8d6dfbd91 100644
--- a/kyuubi-server/web-ui/src/router/operation/index.ts
+++ b/kyuubi-server/web-ui/src/router/operation/index.ts
@@ -25,6 +25,11 @@ const routes = [
     path: '/operation/completedJobs',
     name: 'operation-completedJobs',
     component: () => import('@/views/operation/completedJobs/index.vue')
+  },
+  {
+    path: '/operation/operation-statistics',
+    name: 'operation-statistics',
+    component: () => import('@/views/operation/operation-statistics/index.vue')
   }
 ]
 
diff --git a/kyuubi-server/web-ui/src/locales/en_US/index.ts b/kyuubi-server/web-ui/src/utils/unit.ts
similarity index 59%
copy from kyuubi-server/web-ui/src/locales/en_US/index.ts
copy to kyuubi-server/web-ui/src/utils/unit.ts
index 99e851651..7e43e48f9 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/utils/unit.ts
@@ -15,17 +15,25 @@
  * limitations under the License.
  */
 
-export default {
-  test: 'test',
-  user: 'User',
-  client_ip: 'Client IP',
-  kyuubi_instance: 'Kyuubi Instance',
-  session_id: 'Session ID',
-  create_time: 'Create Time',
-  operation: 'Operation',
-  delete_confirm: 'Delete Confirm',
-  message: {
-    delete_succeeded: 'Delete {name} Succeeded',
-    delete_failed: 'Delete {name} Failed'
-  }
+function millTransfer(val: number) {
+  return secondTransfer(val / 1000)
 }
+
+function secondTransfer(val: number) {
+  const h = Math.floor(val / 3600)
+  const min = Math.floor((val - 3600 * h) / 60)
+  const sec = Math.round(val - 3600 * h - 60 * min)
+  return h === 0
+    ? min == 0
+      ? `${sec} sec`
+      : sec === 0
+      ? `${min} min`
+      : `${min} min ${sec} sec`
+    : sec === 0
+    ? min !== 0
+      ? `${h} hour ${min} min`
+      : `${h} hour`
+    : `${h} hour ${min} min ${sec} sec`
+}
+
+export { millTransfer, secondTransfer }
diff --git a/kyuubi-server/web-ui/src/views/layout/components/aside/types.ts b/kyuubi-server/web-ui/src/views/layout/components/aside/types.ts
index 71d1d0128..4772c1a4e 100644
--- a/kyuubi-server/web-ui/src/views/layout/components/aside/types.ts
+++ b/kyuubi-server/web-ui/src/views/layout/components/aside/types.ts
@@ -61,6 +61,11 @@ export const MENUS = [
     label: 'Operation',
     icon: 'List',
     children: [
+      {
+        label: 'Operation Statistics',
+        icon: 'VideoPlay',
+        router: '/operation/operation-statistics'
+      },
       {
         label: 'Running Jobs',
         icon: 'VideoPlay',
diff --git a/kyuubi-server/web-ui/src/views/operation/operation-statistics/index.vue b/kyuubi-server/web-ui/src/views/operation/operation-statistics/index.vue
new file mode 100644
index 000000000..ff6706c72
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/operation/operation-statistics/index.vue
@@ -0,0 +1,144 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+<template>
+  <el-card class="table-container">
+    <el-table v-loading="loading" :data="tableData" style="width: 100%">
+      <el-table-column prop="sessionUser" :label="$t('user')" width="160" />
+      <el-table-column
+        prop="identifier"
+        :label="$t('operation_id')"
+        width="300" />
+      <el-table-column prop="statement" :label="$t('statement')" width="160" />
+      <el-table-column prop="state" :label="$t('state')" width="160" />
+      <el-table-column :label="$t('start_time')" width="160">
+        <template #default="scope">
+          {{
+            scope.row.startTime != null && scope.row.startTime > 0
+              ? format(scope.row.startTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('complete_time')" width="160">
+        <template #default="scope">
+          {{
+            scope.row.completeTime != null && scope.row.completeTime > 0
+              ? format(scope.row.completeTime, 'yyyy-MM-dd HH:mm:ss')
+              : '-'
+          }}
+        </template>
+      </el-table-column>
+      <el-table-column :label="$t('duration')" width="140">
+        <template #default="scope">{{
+          scope.row.startTime != null &&
+          scope.row.completeTime != null &&
+          scope.row.startTime > 0 &&
+          scope.row.completeTime > 0
+            ? millTransfer(scope.row.completeTime - scope.row.startTime)
+            : '-'
+        }}</template>
+      </el-table-column>
+      <el-table-column fixed="right" :label="$t('operation')" width="110">
+        <template #default="scope">
+          <el-space wrap>
+            <el-popconfirm
+              v-if="!isTerminalState(scope.row.state)"
+              :title="$t('cancel_confirm')"
+              @confirm="handleOperate(scope.row.identifier, 'CANCEL')">
+              <template #reference>
+                <span>
+                  <el-tooltip
+                    effect="dark"
+                    :content="$t('cancel')"
+                    placement="top">
+                    <template #default>
+                      <el-button type="danger" icon="Remove" circle />
+                    </template>
+                  </el-tooltip>
+                </span>
+              </template>
+            </el-popconfirm>
+            <el-popconfirm
+              :title="$t('close_confirm')"
+              @confirm="handleOperate(scope.row.identifier, 'CLOSE')">
+              <template #reference>
+                <span>
+                  <el-tooltip
+                    effect="dark"
+                    :content="$t('close')"
+                    placement="top">
+                    <template #default>
+                      <el-button type="danger" icon="CircleClose" circle />
+                    </template>
+                  </el-tooltip>
+                </span>
+              </template>
+            </el-popconfirm>
+          </el-space>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+  import { getAllOperations, actionOnOperation } from '@/api/operation'
+  import { millTransfer } from '@/utils/unit'
+  import { format } from 'date-fns'
+  import { useI18n } from 'vue-i18n'
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/views/common/use-table'
+
+  const { t } = useI18n()
+  const { tableData, loading, getList: _getList } = useTable()
+  const handleOperate = (operationId: string, action: 'CANCEL' | 'CLOSE') => {
+    actionOnOperation(operationId, { action: action }).then(() => {
+      // TODO add delete success or failed logic after api support
+      ElMessage({
+        message: t(`${action.toLowerCase()}_succeeded`, {
+          operationId: operationId
+        }),
+        type: 'success'
+      })
+      getList()
+    })
+  }
+  const getList = () => {
+    _getList(getAllOperations)
+  }
+
+  const terminalStates = new Set([
+    'FINISHED_STATE',
+    'CLOSED_STATE',
+    'CANCELED_STATE',
+    'TIMEOUT_STATE',
+    'ERROR_STATE'
+  ])
+
+  function isTerminalState(state: string): Boolean {
+    return terminalStates.has(state)
+  }
+  getList()
+</script>
+<style lang="scss" scoped>
+  header {
+    display: flex;
+    justify-content: flex-end;
+  }
+</style>