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 768b8ff7c [KYUUBI #3648][UI] Add Session Detail Page
768b8ff7c is described below
commit 768b8ff7cabe229418621f059de1ca0249bd71c4
Author: zwangsheng <[email protected]>
AuthorDate: Wed Jun 14 14:56:53 2023 +0800
[KYUUBI #3648][UI] Add Session Detail Page
### _Why are the changes needed?_
Close #3648
### _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
Closes #4793 from zwangsheng/KYUUBI_3648.
Closes #3648
e5fe04f7a [zwangsheng] debug
a14c6d325 [zwangsheng] debug
eb96cf0dc [zwangsheng] retest
5cfbb64c5 [zwangsheng] Add UT
7a83a01b0 [zwangsheng] [KYUUBI #3648][UI] Add Session Detail Page
Authored-by: zwangsheng <[email protected]>
Signed-off-by: Cheng Pan <[email protected]>
---
.../kyuubi/server/api/v1/SessionsResource.scala | 30 ++++-
.../server/api/v1/SessionsResourceSuite.scala | 22 ++++
kyuubi-server/web-ui/src/api/session/index.ts | 14 ++
kyuubi-server/web-ui/src/locales/en_US/index.ts | 2 +
kyuubi-server/web-ui/src/locales/zh_CN/index.ts | 2 +
.../src/{api/session => router/detail}/index.ts | 22 ++--
kyuubi-server/web-ui/src/router/index.ts | 2 +
.../web-ui/src/views/detail/session/index.vue | 146 +++++++++++++++++++++
.../web-ui/src/views/management/session/index.vue | 24 +++-
9 files changed, 244 insertions(+), 20 deletions(-)
diff --git
a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
index 7866744dc..d735b87d8 100644
---
a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
+++
b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/api/v1/SessionsResource.scala
@@ -27,13 +27,14 @@ import scala.util.control.NonFatal
import io.swagger.v3.oas.annotations.media.{ArraySchema, Content, Schema}
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag
+import org.apache.commons.lang3.StringUtils
import org.apache.hive.service.rpc.thrift.{TGetInfoType, TProtocolVersion}
import org.apache.kyuubi.Logging
import org.apache.kyuubi.client.api.v1.dto
import org.apache.kyuubi.client.api.v1.dto._
import org.apache.kyuubi.config.KyuubiReservedKeys._
-import org.apache.kyuubi.operation.OperationHandle
+import org.apache.kyuubi.operation.{KyuubiOperation, OperationHandle}
import org.apache.kyuubi.server.api.{ApiRequestContext, ApiUtils}
import org.apache.kyuubi.session.{KyuubiSession, SessionHandle}
@@ -414,6 +415,33 @@ private[v1] class SessionsResource extends
ApiRequestContext with Logging {
throw new NotFoundException(errorMsg)
}
}
+
+ @ApiResponse(
+ responseCode = "200",
+ content = Array(new Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ array = new ArraySchema(schema = new Schema(implementation =
+ classOf[OperationData])))),
+ description =
+ "get the list of all type operations belong to session")
+ @GET
+ @Path("{sessionHandle}/operations")
+ def getOperation(@PathParam("sessionHandle") sessionHandleStr: String):
Seq[OperationData] = {
+ try {
+ fe.be.sessionManager.operationManager.allOperations().map { operation =>
+ if (StringUtils.equalsIgnoreCase(
+ operation.getSession.handle.identifier.toString,
+ sessionHandleStr)) {
+ ApiUtils.operationData(operation.asInstanceOf[KyuubiOperation])
+ }
+ }.toSeq.asInstanceOf[Seq[OperationData]]
+ } catch {
+ case NonFatal(e) =>
+ val errorMsg = "Error getting the list of all type operations belong
to session"
+ error(errorMsg, e)
+ throw new NotFoundException(errorMsg)
+ }
+ }
}
object SessionsResource {
diff --git
a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala
b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala
index b197a489c..b58e87bc8 100644
---
a/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala
+++
b/kyuubi-server/src/test/scala/org/apache/kyuubi/server/api/v1/SessionsResourceSuite.scala
@@ -364,4 +364,26 @@ class SessionsResourceSuite extends KyuubiFunSuite with
RestFrontendTestHelper {
assert(sessionDataList.isEmpty)
}
}
+
+ test("list all type operations under session") {
+ val sessionOpenRequest = new SessionOpenRequest(Map("testConfig" ->
"testValue").asJava)
+ val user = "kyuubi".getBytes()
+ val sessionOpenResp = webTarget.path("api/v1/sessions")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .header(
+ AUTHORIZATION_HEADER,
+ s"Basic ${new String(Base64.getEncoder.encode(user),
StandardCharsets.UTF_8)}")
+ .post(Entity.entity(sessionOpenRequest, MediaType.APPLICATION_JSON_TYPE))
+
+ val sessionHandle =
sessionOpenResp.readEntity(classOf[SessionHandle]).getIdentifier
+
+ // get operations belongs to specified session
+ val response = webTarget
+ .path(s"api/v1/sessions/${sessionHandle.toString}/operations")
+ .request().get()
+ assert(200 == response.getStatus)
+ val operations = response.readEntity(new GenericType[Seq[OperationData]]()
{})
+ assert(operations.size == 1)
+ assert(sessionHandle.toString.equals(operations.head.getSessionId))
+ }
}
diff --git a/kyuubi-server/web-ui/src/api/session/index.ts
b/kyuubi-server/web-ui/src/api/session/index.ts
index 5f3c74fef..fa4759b36 100644
--- a/kyuubi-server/web-ui/src/api/session/index.ts
+++ b/kyuubi-server/web-ui/src/api/session/index.ts
@@ -30,3 +30,17 @@ export function deleteSession(sessionId: string) {
method: 'delete'
})
}
+
+export function getSession(sessionId: string) {
+ return request({
+ url: `api/v1/sessions/${sessionId}`,
+ method: 'get'
+ })
+}
+
+export function getAllTypeOperation(sessionId: string) {
+ return request({
+ url: `api/v1/sessions/${sessionId}/operations`,
+ method: 'get'
+ })
+}
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 e291b62af..8606c74da 100644
--- a/kyuubi-server/web-ui/src/locales/en_US/index.ts
+++ b/kyuubi-server/web-ui/src/locales/en_US/index.ts
@@ -35,6 +35,8 @@ export default {
share_level: 'Share Level',
version: 'Version',
engine_ui: 'Engine UI',
+ failure_reason: 'Failure Reason',
+ session_properties: 'Session Properties',
operation: {
text: 'Operation',
delete_confirm: 'Delete Confirm',
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 e6dd8fe62..0c4cb66db 100644
--- a/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
+++ b/kyuubi-server/web-ui/src/locales/zh_CN/index.ts
@@ -35,6 +35,8 @@ export default {
share_level: '共享级别',
version: '版本',
engine_ui: 'Engine UI',
+ failure_reason: '失败原因',
+ session_properties: 'Session 参数',
operation: {
text: '操作',
delete_confirm: '确认删除',
diff --git a/kyuubi-server/web-ui/src/api/session/index.ts
b/kyuubi-server/web-ui/src/router/detail/index.ts
similarity index 73%
copy from kyuubi-server/web-ui/src/api/session/index.ts
copy to kyuubi-server/web-ui/src/router/detail/index.ts
index 5f3c74fef..5b5508460 100644
--- a/kyuubi-server/web-ui/src/api/session/index.ts
+++ b/kyuubi-server/web-ui/src/router/detail/index.ts
@@ -15,18 +15,12 @@
* limitations under the License.
*/
-import request from '@/utils/request'
+const router = [
+ {
+ path: '/detail/session',
+ name: 'session_detail',
+ component: () => import('@/views/detail/session/index.vue')
+ }
+]
-export function getAllSessions() {
- return request({
- url: 'api/v1/admin/sessions',
- method: 'get'
- })
-}
-
-export function deleteSession(sessionId: string) {
- return request({
- url: `api/v1/admin/sessions/${sessionId}`,
- method: 'delete'
- })
-}
+export default router
diff --git a/kyuubi-server/web-ui/src/router/index.ts
b/kyuubi-server/web-ui/src/router/index.ts
index cad831705..0b80aea17 100644
--- a/kyuubi-server/web-ui/src/router/index.ts
+++ b/kyuubi-server/web-ui/src/router/index.ts
@@ -21,6 +21,7 @@ import workloadRoutes from './workload'
import operationRoutes from './operation'
import contactRoutes from './contact'
import managementRoutes from './management'
+import detailRoutes from './detail'
const routes = [
{
@@ -40,6 +41,7 @@ const routes = [
...workloadRoutes,
...operationRoutes,
...managementRoutes,
+ ...detailRoutes,
...contactRoutes
]
}
diff --git a/kyuubi-server/web-ui/src/views/detail/session/index.vue
b/kyuubi-server/web-ui/src/views/detail/session/index.vue
new file mode 100644
index 000000000..4a77b2a66
--- /dev/null
+++ b/kyuubi-server/web-ui/src/views/detail/session/index.vue
@@ -0,0 +1,146 @@
+<!--
+* 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 :body-style="{ padding: '10px 14px' }">
+ <header>
+ <el-breadcrumb separator="/">
+ <el-breadcrumb-item :to="{ path: '/management/session' }">{{
+ 'Sessions'
+ }}</el-breadcrumb-item>
+ <el-breadcrumb-item>{{ route.query.sessionId }}</el-breadcrumb-item>
+ </el-breadcrumb>
+ </header>
+ </el-card>
+ <el-collapse class="session-properties-container">
+ <el-collapse-item name="1">
+ <template #title>
+ <div class="title">
+ <span>{{ $t('session_properties') }}</span>
+ </div>
+ </template>
+ <el-descriptions :column="1" border>
+ <div v-for="(p, key) in sessionProperties" :key="key">
+ <el-descriptions-item :label="key">
+ {{ p }}
+ </el-descriptions-item></div
+ ></el-descriptions
+ >
+ </el-collapse-item>
+ </el-collapse>
+ <el-card class="table-container">
+ <template #header>
+ <div class="card-header">
+ <span>{{ 'Operations' }}</span>
+ </div>
+ </template>
+ <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 :label="$t('create_time')" width="160">
+ <template #default="scope">
+ {{
+ scope.row.createTime != null && scope.row.createTime > -1
+ ? format(scope.row.createTime, '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 > -1
+ ? format(scope.row.completeTime, 'yyyy-MM-dd HH:mm:ss')
+ : '-'
+ }}
+ </template>
+ </el-table-column>
+ <el-table-column :label="$t('duration')" width="130">
+ <template #default="scope">{{
+ scope.row.createTime != null &&
+ scope.row.completeTime != null &&
+ scope.row.createTime > -1 &&
+ scope.row.completeTime > -1
+ ? secondTransfer(
+ (scope.row.completeTime - scope.row.createTime) / 1000
+ )
+ : '-'
+ }}</template>
+ </el-table-column>
+ <el-table-column prop="statement" :label="$t('statement')" width="160" />
+ <el-table-column prop="exception" :label="$t('failure_reason')">
+ <template #default="scope">
+ {{ scope.row.exception == '' ? '-' : scope.row.exception }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+</template>
+
+<script lang="ts" setup>
+ import { Ref, ref } from 'vue'
+ import { getSession, getAllTypeOperation } from '@/api/session'
+ import { useRoute } from 'vue-router'
+ import { format } from 'date-fns'
+ import { secondTransfer } from '@/utils/unit'
+ import { useTable } from '@/views/common/use-table'
+ const route = useRoute()
+ const sessionProperties: Ref<any> = ref({})
+ const sessionPropertiesLoading = ref(false)
+ const { tableData, loading, getList: _getList } = useTable()
+
+ const getSessionById = () => {
+ const sessionId = route.query.sessionId
+ if (sessionId) {
+ sessionPropertiesLoading.value = true
+ getSession(sessionId as string)
+ .then((res: any) => {
+ sessionProperties.value = res?.conf || {}
+ })
+ .finally(() => {
+ sessionPropertiesLoading.value = false
+ })
+ }
+ }
+ const getList = () => {
+ const sessionId = route.query.sessionId
+ if (sessionId) {
+ _getList(getAllTypeOperation, sessionId)
+ }
+ }
+ getSessionById()
+ getList()
+</script>
+<style lang="scss" scoped>
+ header {
+ display: flex;
+ justify-content: space-between;
+ .el-breadcrumb {
+ line-height: 32px;
+ }
+ }
+ .session-properties-container {
+ margin-bottom: 20px;
+ border-radius: 20px;
+ .title {
+ margin-left: 20px;
+ }
+ }
+</style>
diff --git a/kyuubi-server/web-ui/src/views/management/session/index.vue
b/kyuubi-server/web-ui/src/views/management/session/index.vue
index 327664dd1..0465c1a4a 100644
--- a/kyuubi-server/web-ui/src/views/management/session/index.vue
+++ b/kyuubi-server/web-ui/src/views/management/session/index.vue
@@ -26,17 +26,20 @@
style="width: 100%">
<el-table-column prop="user" :label="$t('user')" width="160px" />
<!-- TODO need jump to engine page -->
- <el-table-column prop="engineId" :label="$t('engine_ip')" width="160px"
/>
+ <el-table-column prop="engineId" :label="$t('engine_id')" width="160px"
/>
<el-table-column prop="ipAddr" :label="$t('client_ip')" width="160px" />
<el-table-column
prop="kyuubiInstance"
:label="$t('kyuubi_instance')"
width="180px" />
<!-- TODO need jump to session page -->
- <el-table-column
- prop="identifier"
- :label="$t('session_id')"
- width="300px" />
+ <el-table-column :label="$t('session_id')" width="300px">
+ <template #default="scope">
+ <el-link @click="handleSessionDetailJump(scope.row.identifier)">{{
+ scope.row.identifier
+ }}</el-link>
+ </template>
+ </el-table-column>
<el-table-column :label="$t('create_time')" width="200">
<template #default="scope">
{{
@@ -76,6 +79,7 @@
import { ElMessage } from 'element-plus'
import { useI18n } from 'vue-i18n'
import { useTable } from '@/views/common/use-table'
+ import { Router, useRouter } from 'vue-router'
const { t } = useI18n()
const { tableData, loading, getList: _getList } = useTable()
const handleDeleteSession = (sessionId: string) => {
@@ -100,5 +104,15 @@
const getList = () => {
_getList(getAllSessions)
}
+ const router: Router = useRouter()
+
+ function handleSessionDetailJump(sessionId: string) {
+ router.push({
+ path: '/detail/session',
+ query: {
+ sessionId
+ }
+ })
+ }
getList()
</script>