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

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


The following commit(s) were added to refs/heads/master by this push:
     new c8f9537c99 [FEATURE] add kanban in linkisweb (#5089)
c8f9537c99 is described below

commit c8f9537c990bc19e0ef3e5e320197c6d5c66bd41
Author: LiuGuoHua <[email protected]>
AuthorDate: Wed Mar 13 14:29:48 2024 +0800

    [FEATURE] add kanban in linkisweb (#5089)
    
    * Add new features to the Kanban view
    
    * Add new features to the Kanban view
    
    * Add query parameters to the interface
    
    * Add code to the front-end section of the Kanban view
    
    * 1. 增加筛选项目
    2. 拆分mysql/pgsql
    
    * 1. 修复resultMap重复的bug
    2. 去除无用字段
    
    * 1. 优化mapper sql
    
    * 1. 调整显示格式
    
    * 1. formatted code
    
    * 1. fix error name
    
    * 1. formatted code
    
    * fotmatted code
    
    * matted code
---
 .../linkis/jobhistory/dao/JobHistoryMapper.java    |  22 +
 .../linkis/jobhistory/dao/JobStatisticsMapper.java |  63 ++
 .../linkis/jobhistory/entity/JobStatistics.java    |  76 ++
 .../jobhistory/restful/api/QueryRestfulApi.java    |  89 +++
 .../restful/api/StatisticsRestfulApi.java          | 195 +++++
 .../mapper/common/JobStatisticsMapper.xml          |  52 ++
 .../resources/mapper/mysql/JobHistoryMapper.xml    |  70 +-
 .../resources/mapper/mysql/JobStatisticsMapper.xml |  86 +++
 .../mapper/postgresql/JobHistoryMapper.xml         |  71 +-
 .../mapper/postgresql/JobStatisticsMapper.xml      |  86 +++
 .../jobhistory/service/JobHistoryQueryService.java |   8 +-
 .../service/JobStatisticsQueryService.java         |  30 +
 .../service/impl/JobHistoryQueryServiceImpl.scala  |  40 ++
 .../impl/JobStatisticsQueryServiceImpl.scala       | 108 +++
 linkis-web/package.json                            |   3 +-
 linkis-web/src/apps/linkis/i18n/common/en.json     |   8 +-
 linkis-web/src/apps/linkis/i18n/common/zh.json     |   6 +-
 .../linkis/module/statisticsDashboard/index.js     |  25 +
 .../linkis/module/statisticsDashboard/index.scss   | 157 ++++
 .../linkis/module/statisticsDashboard/index.vue    | 786 +++++++++++++++++++++
 .../statisticsDashboard/statisticsDashboard.vue    | 441 ++++++++++++
 linkis-web/src/apps/linkis/router.js               |  20 +
 linkis-web/src/apps/linkis/view/linkis/index.vue   |   7 +
 23 files changed, 2410 insertions(+), 39 deletions(-)

diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/dao/JobHistoryMapper.java
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/dao/JobHistoryMapper.java
index 300d00b989..c01720a5f3 100644
--- 
a/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/dao/JobHistoryMapper.java
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/dao/JobHistoryMapper.java
@@ -130,4 +130,26 @@ public interface JobHistoryMapper {
       @Param("statusList") List<String> statusList,
       @Param("startTimestamp") Long startTimestamp,
       @Param("limit") Integer limit);
+
+  List<JobHistory> taskDurationTopN(
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("umUser") String username,
+      @Param("engineType") String engineType);
+
+  List<JobHistory> taskDurationTopNWithUserCreator(
+      @Param("umUser") String username,
+      @Param("userCreatorKey") String userCreatorKey,
+      @Param("userCreatorValue") String userCreator,
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("engineType") String engineType);
+
+  List<JobHistory> taskDurationTopNWithCreatorOnly(
+      @Param("umUser") String username,
+      @Param("userCreatorKey") String userCreatorKey,
+      @Param("creator") String userCreator,
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("engineType") String engineType);
 }
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/dao/JobStatisticsMapper.java
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/dao/JobStatisticsMapper.java
new file mode 100644
index 0000000000..9c0ee86d9e
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/dao/JobStatisticsMapper.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package org.apache.linkis.jobhistory.dao;
+
+import org.apache.linkis.jobhistory.entity.JobStatistics;
+
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+
+public interface JobStatisticsMapper {
+
+  JobStatistics taskExecutionStatistics(
+      @Param("umUser") String username,
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("engineType") String engineType);
+
+  JobStatistics taskExecutionStatisticsWithUserCreator(
+      @Param("umUser") String username,
+      @Param("userCreatorKey") String userCreatorKey,
+      @Param("userCreatorValue") String userCreator,
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("engineType") String engineType);
+
+  JobStatistics taskExecutionStatisticsWithCreatorOnly(
+      @Param("umUser") String username,
+      @Param("userCreatorKey") String userCreatorKey,
+      @Param("creator") String userCreator,
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("engineType") String engineType);
+
+  JobStatistics engineExecutionStatisticsWithUserCreator(
+      @Param("umUser") String username,
+      @Param("userCreatorValue") String userCreator,
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("engineType") String engineType);
+
+  JobStatistics engineExecutionStatistics(
+      @Param("umUser") String username,
+      @Param("creator") String userCreator,
+      @Param("startDate") Date startDate,
+      @Param("endDate") Date endDate,
+      @Param("engineType") String engineType);
+}
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/entity/JobStatistics.java
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/entity/JobStatistics.java
new file mode 100644
index 0000000000..23621708f5
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/entity/JobStatistics.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package org.apache.linkis.jobhistory.entity;
+
+public class JobStatistics {
+
+  private Long id;
+
+  private Integer sumCount;
+
+  private Integer succeedCount;
+
+  private Integer failedCount;
+
+  private Integer cancelledCount;
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public Integer getSumCount() {
+    return sumCount;
+  }
+
+  public void setSumCount(Integer sumCount) {
+    this.sumCount = sumCount;
+  }
+
+  public Integer getSucceedCount() {
+    return succeedCount;
+  }
+
+  public void setSucceedCount(Integer succeedCount) {
+    this.succeedCount = succeedCount;
+  }
+
+  public Integer getFailedCount() {
+    return failedCount;
+  }
+
+  public void setFailedCount(Integer failedCount) {
+    this.failedCount = failedCount;
+  }
+
+  public Integer getCancelledCount() {
+    return cancelledCount;
+  }
+
+  public void setCancelledCount(Integer cancelledCount) {
+    this.cancelledCount = cancelledCount;
+  }
+
+  @Override
+  public String toString() {
+    return "JobHistory{" + "id=" + id + '}';
+  }
+}
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/restful/api/QueryRestfulApi.java
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/restful/api/QueryRestfulApi.java
index a18da3a042..0ae72b84b3 100644
--- 
a/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/restful/api/QueryRestfulApi.java
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/restful/api/QueryRestfulApi.java
@@ -412,4 +412,93 @@ public class QueryRestfulApi {
 
     return Message.ok().data(JobRequestConstants.TOTAL_PAGE(), total);
   }
+
+  /** Method list should not contain subjob, which may cause performance 
problems. */
+  @ApiOperation(value = "listDurationTop", notes = "listDurationTop", response 
= Message.class)
+  @ApiImplicitParams({
+    @ApiImplicitParam(name = "startDate", dataType = "long", example = 
"1658937600001"),
+    @ApiImplicitParam(name = "endDate", dataType = "long", example = 
"1658937600000"),
+    @ApiImplicitParam(name = "executeApplicationName", dataType = "String"),
+    @ApiImplicitParam(name = "creator", required = false, dataType = "String", 
value = "creator"),
+    @ApiImplicitParam(
+        name = "proxyUser",
+        required = false,
+        dataType = "String",
+        value = "proxyUser"),
+    @ApiImplicitParam(name = "pageNow", required = false, dataType = 
"Integer", value = "page now"),
+    @ApiImplicitParam(name = "pageSize", dataType = "Integer"),
+  })
+  @RequestMapping(path = "/listDurationTop", method = RequestMethod.GET)
+  public Message listDurationTop(
+      HttpServletRequest req,
+      @RequestParam(value = "startDate", required = false) Long startDate,
+      @RequestParam(value = "endDate", required = false) Long endDate,
+      @RequestParam(value = "executeApplicationName", required = false)
+          String executeApplicationName,
+      @RequestParam(value = "creator", required = false) String creator,
+      @RequestParam(value = "proxyUser", required = false) String proxyUser,
+      @RequestParam(value = "pageNow", required = false) Integer pageNow,
+      @RequestParam(value = "pageSize", required = false) Integer pageSize)
+      throws QueryException {
+    if (StringUtils.isEmpty(pageNow)) {
+      pageNow = 1;
+    }
+    if (StringUtils.isEmpty(pageSize)) {
+      pageSize = 20;
+    }
+    if (StringUtils.isEmpty(proxyUser)) {
+      proxyUser = null;
+    } else {
+      if (!QueryUtils.checkNameValid(proxyUser)) {
+        return Message.error("Invalid proxyUser : " + proxyUser);
+      }
+    }
+    if (StringUtils.isEmpty(creator)) {
+      creator = null;
+    } else {
+      if (!QueryUtils.checkNameValid(creator)) {
+        return Message.error("Invalid creator : " + creator);
+      }
+    }
+    if (!StringUtils.isEmpty(executeApplicationName)) {
+      if (!QueryUtils.checkNameValid(executeApplicationName)) {
+        return Message.error("Invalid applicationName : " + 
executeApplicationName);
+      }
+    } else {
+      executeApplicationName = null;
+    }
+
+    if (endDate == null) {
+      endDate = System.currentTimeMillis();
+    }
+    if (startDate == null) {
+      startDate = 0L;
+    }
+
+    Date sDate = new Date(startDate);
+    Date eDate = new Date(endDate);
+    if (sDate.getTime() == eDate.getTime()) {
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTimeInMillis(endDate);
+      calendar.add(Calendar.DAY_OF_MONTH, 1);
+      eDate = new Date(calendar.getTime().getTime()); // todo check
+    }
+    List<JobHistory> queryTasks = null;
+    PageHelper.startPage(pageNow, pageSize);
+    try {
+      queryTasks =
+          jobHistoryQueryService.taskDurationTopN(
+              sDate, eDate, proxyUser, creator, executeApplicationName);
+    } finally {
+      PageHelper.clearPage();
+    }
+
+    List<QueryTaskVO> vos = new ArrayList<>();
+    for (JobHistory jobHistory : queryTasks) {
+      QueryUtils.exchangeExecutionCode(jobHistory);
+      QueryTaskVO taskVO = TaskConversions.jobHistory2TaskVO(jobHistory, null);
+      vos.add(taskVO);
+    }
+    return Message.ok().data(TaskConstant.TASKS, vos);
+  }
 }
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/restful/api/StatisticsRestfulApi.java
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/restful/api/StatisticsRestfulApi.java
new file mode 100644
index 0000000000..23a12c2b94
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/java/org/apache/linkis/jobhistory/restful/api/StatisticsRestfulApi.java
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+package org.apache.linkis.jobhistory.restful.api;
+
+import org.apache.linkis.governance.common.entity.job.QueryException;
+import org.apache.linkis.jobhistory.entity.JobStatistics;
+import org.apache.linkis.jobhistory.service.JobStatisticsQueryService;
+import org.apache.linkis.jobhistory.util.QueryUtils;
+import org.apache.linkis.server.Message;
+
+import org.apache.commons.lang3.time.DateUtils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Api(tags = "jobstatistics api")
+@RestController
+@RequestMapping(path = "/jobhistory/jobstatistics")
+public class StatisticsRestfulApi {
+
+  private Logger log = LoggerFactory.getLogger(this.getClass());
+
+  @Autowired private JobStatisticsQueryService jobStatisticsQueryService;
+
+  @ApiOperation(value = "taskCount", notes = "taskCount", response = 
Message.class)
+  @ApiImplicitParams({
+    @ApiImplicitParam(name = "startDate", dataType = "long"),
+    @ApiImplicitParam(name = "endDate", required = false, dataType = "long", 
value = "end date"),
+    @ApiImplicitParam(name = "executeApplicationName", dataType = "String"),
+    @ApiImplicitParam(name = "creator", required = false, dataType = "String", 
value = "creator"),
+    @ApiImplicitParam(
+        name = "proxyUser",
+        required = false,
+        dataType = "String",
+        value = "proxyUser"),
+  })
+  @RequestMapping(path = "/taskCount", method = RequestMethod.GET)
+  public Message taskCount(
+      HttpServletRequest req,
+      @RequestParam(value = "startDate", required = false) Long startDate,
+      @RequestParam(value = "endDate", required = false) Long endDate,
+      @RequestParam(value = "executeApplicationName", required = false)
+          String executeApplicationName,
+      @RequestParam(value = "creator", required = false) String creator,
+      @RequestParam(value = "proxyUser", required = false) String proxyUser)
+      throws IOException, QueryException {
+    if (endDate == null) {
+      endDate = System.currentTimeMillis();
+    }
+    if (startDate == null) {
+      startDate = 0L;
+    }
+    Date sDate = new Date(startDate);
+    Date eDate = new Date(endDate);
+    if (startDate == 0L) {
+      sDate = DateUtils.addDays(eDate, -1);
+    }
+    if (sDate.getTime() == eDate.getTime()) {
+      Calendar instance = Calendar.getInstance();
+      instance.setTimeInMillis(endDate);
+      instance.add(Calendar.DAY_OF_MONTH, 1);
+      eDate = new Date(instance.getTime().getTime());
+    }
+    if (StringUtils.isEmpty(proxyUser)) {
+      proxyUser = null;
+    } else {
+      if (!QueryUtils.checkNameValid(proxyUser)) {
+        return Message.error("Invalid proxyUser : " + proxyUser);
+      }
+    }
+    if (StringUtils.isEmpty(creator)) {
+      creator = null;
+    } else {
+      if (!QueryUtils.checkNameValid(creator)) {
+        return Message.error("Invalid creator : " + creator);
+      }
+    }
+    if (!StringUtils.isEmpty(executeApplicationName)) {
+      if (!QueryUtils.checkNameValid(executeApplicationName)) {
+        return Message.error("Invalid applicationName : " + 
executeApplicationName);
+      }
+    } else {
+      executeApplicationName = null;
+    }
+    JobStatistics jobStatistics =
+        jobStatisticsQueryService.taskExecutionStatistics(
+            sDate, eDate, proxyUser, creator, executeApplicationName);
+
+    return Message.ok()
+        .data("sumCount", jobStatistics.getSumCount())
+        .data("succeedCount", jobStatistics.getSucceedCount())
+        .data("failedCount", jobStatistics.getFailedCount())
+        .data("cancelledCount", jobStatistics.getCancelledCount());
+  }
+
+  @ApiOperation(value = "engineCount", notes = "engineCount", response = 
Message.class)
+  @ApiImplicitParams({
+    @ApiImplicitParam(name = "startDate", dataType = "long"),
+    @ApiImplicitParam(name = "endDate", required = false, dataType = "long", 
value = "end date"),
+    @ApiImplicitParam(name = "executeApplicationName", dataType = "String"),
+    @ApiImplicitParam(name = "creator", required = false, dataType = "String", 
value = "creator"),
+    @ApiImplicitParam(
+        name = "proxyUser",
+        required = false,
+        dataType = "String",
+        value = "proxyUser"),
+  })
+  @RequestMapping(path = "/engineCount", method = RequestMethod.GET)
+  public Message engineCount(
+      HttpServletRequest req,
+      @RequestParam(value = "startDate", required = false) Long startDate,
+      @RequestParam(value = "endDate", required = false) Long endDate,
+      @RequestParam(value = "executeApplicationName", required = false)
+          String executeApplicationName,
+      @RequestParam(value = "creator", required = false) String creator,
+      @RequestParam(value = "proxyUser", required = false) String proxyUser)
+      throws IOException, QueryException {
+    if (endDate == null) {
+      endDate = System.currentTimeMillis();
+    }
+    if (startDate == null) {
+      startDate = 0L;
+    }
+    Date sDate = new Date(startDate);
+    Date eDate = new Date(endDate);
+    if (startDate == 0L) {
+      sDate = DateUtils.addDays(eDate, -1);
+    }
+    if (sDate.getTime() == eDate.getTime()) {
+      Calendar instance = Calendar.getInstance();
+      instance.setTimeInMillis(endDate);
+      instance.add(Calendar.DAY_OF_MONTH, 1);
+      eDate = new Date(instance.getTime().getTime());
+    }
+    if (StringUtils.isEmpty(proxyUser)) {
+      proxyUser = null;
+    } else {
+      if (!QueryUtils.checkNameValid(proxyUser)) {
+        return Message.error("Invalid proxyUser : " + proxyUser);
+      }
+    }
+    if (StringUtils.isEmpty(creator)) {
+      creator = null;
+    } else {
+      if (!QueryUtils.checkNameValid(creator)) {
+        return Message.error("Invalid creator : " + creator);
+      }
+    }
+    if (!StringUtils.isEmpty(executeApplicationName)) {
+      if (!QueryUtils.checkNameValid(executeApplicationName)) {
+        return Message.error("Invalid applicationName : " + 
executeApplicationName);
+      }
+    } else {
+      executeApplicationName = null;
+    }
+    JobStatistics jobStatistics =
+        jobStatisticsQueryService.engineExecutionStatistics(
+            sDate, eDate, proxyUser, creator, executeApplicationName);
+
+    return Message.ok()
+        .data("countEngine", jobStatistics.getSumCount())
+        .data("countEngineSucceed", jobStatistics.getSucceedCount())
+        .data("countEngineFailed", jobStatistics.getFailedCount())
+        .data("countEngineShutting", jobStatistics.getCancelledCount());
+  }
+}
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/common/JobStatisticsMapper.xml
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/common/JobStatisticsMapper.xml
new file mode 100644
index 0000000000..809f82e3fc
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/common/JobStatisticsMapper.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ 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.
+  -->
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"; >
+
+
+<mapper namespace="org.apache.linkis.jobhistory.dao.JobStatisticsMapper">
+
+    <resultMap id="jobStatisticsMap" 
type="org.apache.linkis.jobhistory.entity.JobStatistics" autoMapping="false" >
+        <id property="id" column="id" />
+        <result property = "sumCount" column = "sumcount" />
+        <result property = "succeedCount" column = "succeedcount" />
+        <result property = "failedCount" column = "failedcount" />
+        <result property = "cancelledCount" column = "cancelledcount" />
+    </resultMap>
+
+
+    <select id="taskExecutionStatistics" useCache="true" 
resultMap="jobStatisticsMap" >
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Succeed' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='Cancelled' then 1 END) cancelledcount
+        FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">
+                and submit_user = #{umUser}
+            </if>
+            <if test="engineType != null">
+                and engine_type = #{engineType}
+            </if>
+            <if test="startDate != null">
+                and created_time >= #{startDate} AND created_time 
<![CDATA[<=]]> #{endDate}
+            </if>
+        </where>
+    </select>
+
+
+</mapper>
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/mysql/JobHistoryMapper.xml
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/mysql/JobHistoryMapper.xml
index a5b769f2d3..6644f6b54b 100644
--- 
a/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/mysql/JobHistoryMapper.xml
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/mysql/JobHistoryMapper.xml
@@ -64,7 +64,7 @@
     </insert>
 
 
-  <select id="selectJobHistory" useCache="false" resultMap="jobHistoryMap" 
parameterType="org.apache.linkis.jobhistory.entity.JobHistory">
+    <select id="selectJobHistory" useCache="false" resultMap="jobHistoryMap" 
parameterType="org.apache.linkis.jobhistory.entity.JobHistory">
         SELECT * FROM linkis_ps_job_history_group_history
         <where>
             <if test="id != null">id = #{id}</if>
@@ -170,11 +170,11 @@
 
     <select id="selectJobHistoryStatusForUpdate" flushCache="true" 
resultType="java.lang.String">
         SELECT
-        bdt.status
+            bdt.status
         FROM
-        linkis_ps_job_history_group_history bdt
+            linkis_ps_job_history_group_history bdt
         WHERE
-        id = #{jobId} FOR UPDATE
+            id = #{jobId} FOR UPDATE
     </select>
 
     <select id="countUndoneTaskNoCreator" useCache="true" 
resultType="java.lang.Integer" >
@@ -232,18 +232,18 @@
     <select id="selectFailoverJobHistory" resultMap="jobHistoryMap" >
         SELECT a.* FROM linkis_ps_job_history_group_history a
         WHERE (
-            a.instances = ''
-            OR a.instances IS NULL
-            OR a.instances NOT IN <foreach collection="instancesMap.keys" 
open="(" separator="," close=")" item="key">#{key}</foreach>
-            OR EXISTS (
-                SELECT 1 FROM
-                (
-                    <foreach collection="instancesMap.entrySet()" 
separator="union all" index="key" item="val">
-                        SELECT #{key} AS instances, #{val} AS registryTime
-                    </foreach>
-                ) b
-                WHERE a.instances = b.instances AND a.created_time 
<![CDATA[<]]> FROM_UNIXTIME(b.registryTime/1000)
-            )
+        a.instances = ''
+        OR a.instances IS NULL
+        OR a.instances NOT IN <foreach collection="instancesMap.keys" open="(" 
separator="," close=")" item="key">#{key}</foreach>
+        OR EXISTS (
+        SELECT 1 FROM
+        (
+        <foreach collection="instancesMap.entrySet()" separator="union all" 
index="key" item="val">
+            SELECT #{key} AS instances, #{val} AS registryTime
+        </foreach>
+        ) b
+        WHERE a.instances = b.instances AND a.created_time <![CDATA[<]]> 
FROM_UNIXTIME(b.registryTime/1000)
+        )
         )
         AND
         status IN <foreach collection="statusList" open="(" separator="," 
close=")" item="status">#{status}</foreach>
@@ -252,4 +252,42 @@
 
     </select>
 
+    <select id="taskDurationTopN" useCache="true" resultMap="jobHistoryMap" >
+        /*slave*/ SELECT * FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]> #{endDate}</if>
+        </where>
+        ORDER BY FROM_UNIXTIME(TIMEDIFF(cast(updated_time as datetime), 
cast(created_time as datetime))) desc
+    </select>
+
+    <select id="taskDurationTopNWithCreatorOnly" useCache="true" 
resultMap="jobHistoryMap" >
+        /*slave*/ SELECT * FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]>#{endDate}
+            </if>
+            <if test="userCreatorKey != null and creator != null">
+                and labels like CONCAT('%"', #{userCreatorKey}, '":"%-', 
#{creator}, '%')
+            </if>
+        </where>
+        ORDER BY FROM_UNIXTIME(TIMEDIFF(cast(updated_time as datetime), 
cast(created_time as datetime))) desc
+    </select>
+
+    <select id="taskDurationTopNWithUserCreator" useCache="true" 
resultMap="jobHistoryMap" >
+        /*slave*/ SELECT * FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]>#{endDate}
+            </if>
+            <if test="userCreatorKey != null and userCreatorValue != null">
+                and LOCATE(CONCAT('"', #{userCreatorKey}, '":"', 
#{userCreatorValue}), labels) > 0
+            </if>
+        </where>
+        ORDER BY FROM_UNIXTIME(TIMEDIFF(cast(updated_time as datetime), 
cast(created_time as datetime))) desc
+    </select>
+
 </mapper>
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/mysql/JobStatisticsMapper.xml
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/mysql/JobStatisticsMapper.xml
new file mode 100644
index 0000000000..d05728faac
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/mysql/JobStatisticsMapper.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ 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.
+  -->
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"; >
+
+
+<mapper namespace="org.apache.linkis.jobhistory.dao.JobStatisticsMapper">
+
+    <select id="taskExecutionStatisticsWithCreatorOnly" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Succeed' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='Cancelled' then 1 END) cancelledcount
+        FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]> #{endDate}</if>
+            <if test="userCreatorKey != null and creator != null">
+                and labels like CONCAT('%"', #{userCreatorKey}, '":"%-', 
#{creator}, '%')
+            </if>
+        </where>
+    </select>
+
+    <select id="taskExecutionStatisticsWithUserCreator" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Succeed' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='Cancelled' then 1 END) cancelledcount
+        FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]>#{endDate}
+            </if>
+            <if test="userCreatorKey != null and userCreatorValue != null">
+                and LOCATE(CONCAT('"', #{userCreatorKey}, '":"', 
#{userCreatorValue}), labels) > 0
+            </if>
+        </where>
+    </select>
+
+    <select id="engineExecutionStatistics" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Success' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='ShuttingDown' then 1 END) cancelledcount FROM
+        linkis_cg_ec_resource_info_record
+        <where>
+            <if test="umUser != null">and create_user = #{umUser}</if>
+            <if test="engineType != null">and label_value like CONCAT('%', 
#{engineType},'%')</if>
+            <if test="startDate != null">and create_time >= #{startDate} AND 
create_time <![CDATA[<=]]> #{endDate}</if>
+            <if test="creator != null">
+                and label_value like CONCAT('%-', #{creator}, '%')
+            </if>
+        </where>
+    </select>
+
+    <select id="engineExecutionStatisticsWithUserCreator" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Success' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='ShuttingDown' then 1 END) cancelledcount FROM
+        linkis_cg_ec_resource_info_record
+        <where>
+            <if test="umUser != null">and create_user = #{umUser}</if>
+            <if test="engineType != null">and label_value like CONCAT('%', 
#{engineType},'%')</if>
+            <if test="startDate != null">and create_time >= #{startDate} AND 
create_time <![CDATA[<=]]> #{endDate}</if>
+            <if test="userCreatorValue != null">
+                and label_value like CONCAT('%',#{userCreatorValue}, '%')
+            </if>
+        </where>
+    </select>
+
+
+</mapper>
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/postgresql/JobHistoryMapper.xml
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/postgresql/JobHistoryMapper.xml
index f7e75dea0e..0289141e89 100644
--- 
a/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/postgresql/JobHistoryMapper.xml
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/postgresql/JobHistoryMapper.xml
@@ -172,11 +172,11 @@
 
     <select id="selectJobHistoryStatusForUpdate" flushCache="true" 
resultType="java.lang.String">
         SELECT
-        bdt."status"
+            bdt."status"
         FROM
-        "linkis_ps_job_history_group_history" bdt
+            "linkis_ps_job_history_group_history" bdt
         WHERE
-        id = #{jobId} FOR UPDATE
+            id = #{jobId} FOR UPDATE
     </select>
 
     <select id="countUndoneTaskNoCreator" useCache="true" 
resultType="java.lang.Integer" >
@@ -223,7 +223,7 @@
     </update>
 
     <update id="updateJobHistoryCancelById" >
-            update linkis_ps_job_history_group_history set status = 
'Cancelled' ,error_desc = #{errorDesc}
+        update linkis_ps_job_history_group_history set status = 'Cancelled' 
,error_desc = #{errorDesc}
         WHERE id IN
         <foreach collection="idList" item="id" separator="," open="(" 
close=")">
             #{id}
@@ -232,18 +232,18 @@
     <select id="selectFailoverJobHistory" resultMap="jobHistoryMap" >
         SELECT a.* FROM linkis_ps_job_history_group_history a
         WHERE (
-            a.instances = ''
-            OR a.instances IS NULL
-            OR a.instances NOT IN <foreach collection="instancesMap.keys" 
open="(" separator="," close=")" item="key">#{key}</foreach>
-            OR EXISTS (
-                SELECT 1 FROM
-                (
-                    <foreach collection="instancesMap.entrySet()" 
separator="union all" index="key" item="val">
-                        SELECT #{key} AS instances, #{val} AS registryTime
-                    </foreach>
-                ) b
-                WHERE a.instances = b.instances AND a.created_time 
<![CDATA[<]]> FROM_UNIXTIME(b.registryTime/1000)
-            )
+        a.instances = ''
+        OR a.instances IS NULL
+        OR a.instances NOT IN <foreach collection="instancesMap.keys" open="(" 
separator="," close=")" item="key">#{key}</foreach>
+        OR EXISTS (
+        SELECT 1 FROM
+        (
+        <foreach collection="instancesMap.entrySet()" separator="union all" 
index="key" item="val">
+            SELECT #{key} AS instances, #{val} AS registryTime
+        </foreach>
+        ) b
+        WHERE a.instances = b.instances AND a.created_time <![CDATA[<]]> 
FROM_UNIXTIME(b.registryTime/1000)
+        )
         )
         AND
         status IN <foreach collection="statusList" open="(" separator="," 
close=")" item="status">#{status}</foreach>
@@ -251,4 +251,43 @@
         <if test="limit != null and limit > 0">LIMIT #{limit}</if>
 
     </select>
+
+    <select id="taskDurationTopN" useCache="true" resultMap="jobHistoryMap" >
+        /*slave*/ SELECT * FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]> #{endDate}</if>
+        </where>
+        ORDER BY EXTRACT(EPOCH FROM AGE(cast(updated_time as timestamp), 
cast(created_time as timestamp))) desc
+    </select>
+
+    <select id="taskDurationTopNWithCreatorOnly" useCache="true" 
resultMap="jobHistoryMap" >
+        /*slave*/ SELECT * FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]>#{endDate}
+            </if>
+            <if test="userCreatorKey != null and creator != null">
+                and labels like CONCAT('%"', #{userCreatorKey}::text, '":"%-', 
#{creator}::text, '%')
+            </if>
+        </where>
+        ORDER BY EXTRACT(EPOCH FROM AGE(cast(updated_time as timestamp), 
cast(created_time as timestamp))) desc
+    </select>
+
+    <select id="taskDurationTopNWithUserCreator" useCache="true" 
resultMap="jobHistoryMap" >
+        /*slave*/ SELECT * FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]>#{endDate}
+            </if>
+            <if test="userCreatorKey != null and userCreatorValue != null">
+                and  STRPOS(labels, CONCAT('"', #{userCreatorKey}, '":"', 
#{userCreatorValue})) > 0
+            </if>
+        </where>
+        ORDER BY EXTRACT(EPOCH FROM AGE(cast(updated_time as timestamp), 
cast(created_time as timestamp))) desc
+    </select>
+
 </mapper>
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/postgresql/JobStatisticsMapper.xml
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/postgresql/JobStatisticsMapper.xml
new file mode 100644
index 0000000000..f1dd6e0887
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/resources/mapper/postgresql/JobStatisticsMapper.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ 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.
+  -->
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"; >
+
+
+<mapper namespace="org.apache.linkis.jobhistory.dao.JobStatisticsMapper">
+
+    <select id="taskExecutionStatisticsWithCreatorOnly" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Succeed' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='Cancelled' then 1 END) cancelledcount
+        FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]> #{endDate}</if>
+            <if test="userCreatorKey != null and creator != null">
+                and labels like CONCAT('%"', #{userCreatorKey}::text, '":"%-', 
#{creator}::text, '%')
+            </if>
+        </where>
+    </select>
+
+    <select id="taskExecutionStatisticsWithUserCreator" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Succeed' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='Cancelled' then 1 END) cancelledcount
+        FROM linkis_ps_job_history_group_history
+        <where>
+            <if test="umUser != null">and submit_user = #{umUser}</if>
+            <if test="engineType != null">and engine_type = #{engineType}</if>
+            <if test="startDate != null">and created_time >= #{startDate} AND 
created_time <![CDATA[<=]]>#{endDate}
+            </if>
+            <if test="userCreatorKey != null and userCreatorValue != null">
+                and  STRPOS(labels, CONCAT('"', #{userCreatorKey}, '":"', 
#{userCreatorValue})) > 0
+            </if>
+        </where>
+    </select>
+
+    <select id="engineExecutionStatistics" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Success' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='ShuttingDown' then 1 END) cancelledcount FROM
+        linkis_cg_ec_resource_info_record
+        <where>
+            <if test="umUser != null">and create_user = #{umUser}</if>
+            <if test="engineType != null">and label_value like CONCAT('%', 
#{engineType},'%')</if>
+            <if test="startDate != null">and create_time >= #{startDate} AND 
create_time <![CDATA[<=]]> #{endDate}</if>
+            <if test="creator != null">
+                and label_value like CONCAT('%-', #{creator}::text, '%')
+            </if>
+        </where>
+    </select>
+
+    <select id="engineExecutionStatisticsWithUserCreator" useCache="true" 
resultMap="jobStatisticsMap">
+        SELECT COUNT(1) sumcount, COUNT(case WHEN status ='Success' then 1 
END) succeedcount,
+        COUNT(case WHEN status ='Failed' then 1 END) failedcount,
+        COUNT(case WHEN status ='ShuttingDown' then 1 END) cancelledcount FROM
+        linkis_cg_ec_resource_info_record
+        <where>
+            <if test="umUser != null">and create_user = #{umUser}</if>
+            <if test="engineType != null">and label_value like CONCAT('%', 
#{engineType},'%')</if>
+            <if test="startDate != null">and create_time >= #{startDate} AND 
create_time <![CDATA[<=]]> #{endDate}</if>
+            <if test="userCreatorValue != null">
+                and label_value like CONCAT('%',#{userCreatorValue}::text, '%')
+            </if>
+        </where>
+    </select>
+
+
+</mapper>
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/JobHistoryQueryService.java
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/JobHistoryQueryService.java
index b8731554d4..335ea7cdee 100644
--- 
a/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/JobHistoryQueryService.java
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/JobHistoryQueryService.java
@@ -5,16 +5,16 @@
  * 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.
  */
- 
+
 package org.apache.linkis.jobhistory.service;
 
 import org.apache.linkis.governance.common.entity.job.JobRequest;
@@ -53,4 +53,6 @@ public interface JobHistoryQueryService {
     void changeObserveInfoById(JobHistory jobHistory);
 
     void clearUndoneTasksByEntranceInstance(EntranceInstanceConfRequest 
request, Sender sender);
+
+    List<JobHistory> taskDurationTopN(Date sDate, Date eDate, String username, 
String creator, String engineType);
 }
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/JobStatisticsQueryService.java
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/JobStatisticsQueryService.java
new file mode 100644
index 0000000000..5d11b623ad
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/JobStatisticsQueryService.java
@@ -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.
+ */
+
+package org.apache.linkis.jobhistory.service;
+
+import org.apache.linkis.jobhistory.entity.JobStatistics;
+
+import java.util.Date;
+
+
+public interface JobStatisticsQueryService {
+
+    JobStatistics taskExecutionStatistics(Date startDate, Date endDate, String 
username, String creator, String engineType);
+
+    JobStatistics engineExecutionStatistics(Date startDate, Date endDate, 
String username, String creator, String engineType);
+}
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/impl/JobHistoryQueryServiceImpl.scala
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/impl/JobHistoryQueryServiceImpl.scala
index 5c49be89b7..84b73d6a23 100644
--- 
a/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/impl/JobHistoryQueryServiceImpl.scala
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/impl/JobHistoryQueryServiceImpl.scala
@@ -486,4 +486,44 @@ class JobHistoryQueryServiceImpl extends 
JobHistoryQueryService with Logging {
     }
   }
 
+  override def taskDurationTopN(
+      sDate: Date,
+      eDate: Date,
+      username: String,
+      creator: String,
+      engineType: String
+  ): util.List[JobHistory] = {
+    val result = if (StringUtils.isBlank(creator)) {
+      jobHistoryMapper.taskDurationTopN(sDate, eDate, username, engineType)
+    } else if (StringUtils.isBlank(username)) {
+      val fakeLabel = new UserCreatorLabel
+      jobHistoryMapper.taskDurationTopNWithCreatorOnly(
+        username,
+        fakeLabel.getLabelKey,
+        creator,
+        sDate,
+        eDate,
+        engineType
+      )
+    } else {
+      val fakeLabel = new UserCreatorLabel
+      fakeLabel.setUser(username)
+      fakeLabel.setCreator(creator)
+      val userCreator = fakeLabel.getStringValue
+      Utils.tryCatch(fakeLabel.valueCheck(userCreator)) { t =>
+        logger.info("input user or creator is not correct", t)
+        throw t
+      }
+      jobHistoryMapper.taskDurationTopNWithUserCreator(
+        username,
+        fakeLabel.getLabelKey,
+        userCreator,
+        sDate,
+        eDate,
+        engineType
+      )
+    }
+    result
+  }
+
 }
diff --git 
a/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/impl/JobStatisticsQueryServiceImpl.scala
 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/impl/JobStatisticsQueryServiceImpl.scala
new file mode 100644
index 0000000000..27b8173d05
--- /dev/null
+++ 
b/linkis-public-enhancements/linkis-jobhistory/src/main/scala/org/apache/linkis/jobhistory/service/impl/JobStatisticsQueryServiceImpl.scala
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+package org.apache.linkis.jobhistory.service.impl
+
+import org.apache.linkis.common.utils.{Logging, Utils}
+import org.apache.linkis.jobhistory.dao.JobStatisticsMapper
+import org.apache.linkis.jobhistory.entity.JobStatistics
+import org.apache.linkis.jobhistory.service.JobStatisticsQueryService
+import org.apache.linkis.manager.label.entity.engine.UserCreatorLabel
+
+import org.apache.commons.lang3.StringUtils
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+
+import java.util.Date
+
+@Service
+class JobStatisticsQueryServiceImpl extends JobStatisticsQueryService with 
Logging {
+
+  @Autowired
+  private var jobStatisticsMapper: JobStatisticsMapper = _
+
+  override def taskExecutionStatistics(
+      sDate: Date,
+      eDate: Date,
+      username: String,
+      creator: String,
+      engineType: String
+  ): JobStatistics = {
+    val result = if (StringUtils.isBlank(creator)) {
+      jobStatisticsMapper.taskExecutionStatistics(username, sDate, eDate, 
engineType)
+    } else if (StringUtils.isBlank(username)) {
+      val fakeLabel = new UserCreatorLabel
+      jobStatisticsMapper.taskExecutionStatisticsWithCreatorOnly(
+        username,
+        fakeLabel.getLabelKey,
+        creator,
+        sDate,
+        eDate,
+        engineType
+      )
+    } else {
+      val fakeLabel = new UserCreatorLabel
+      fakeLabel.setUser(username)
+      fakeLabel.setCreator(creator)
+      val userCreator = fakeLabel.getStringValue
+      Utils.tryCatch(fakeLabel.valueCheck(userCreator)) { t =>
+        logger.info("input user or creator is not correct", t)
+        throw t
+      }
+      jobStatisticsMapper.taskExecutionStatisticsWithUserCreator(
+        username,
+        fakeLabel.getLabelKey,
+        userCreator,
+        sDate,
+        eDate,
+        engineType
+      )
+    }
+    result
+  }
+
+  override def engineExecutionStatistics(
+      sDate: Date,
+      eDate: Date,
+      username: String,
+      creator: String,
+      engineType: String
+  ): JobStatistics = {
+    val result = if (StringUtils.isBlank(username) || 
StringUtils.isBlank(creator)) {
+      jobStatisticsMapper.engineExecutionStatistics(username, creator, sDate, 
eDate, engineType)
+    } else {
+      val fakeLabel = new UserCreatorLabel
+      fakeLabel.setUser(username)
+      fakeLabel.setCreator(creator)
+      val userCreator = fakeLabel.getStringValue
+      Utils.tryCatch(fakeLabel.valueCheck(userCreator)) { t =>
+        logger.info("input user or creator is not correct", t)
+        throw t
+      }
+      jobStatisticsMapper.engineExecutionStatisticsWithUserCreator(
+        username,
+        userCreator,
+        sDate,
+        eDate,
+        engineType
+      )
+    }
+    result
+  }
+
+}
diff --git a/linkis-web/package.json b/linkis-web/package.json
index b43a05d183..fbc9566ec7 100644
--- a/linkis-web/package.json
+++ b/linkis-web/package.json
@@ -51,7 +51,8 @@
         "vue-router": "3.4.8",
         "vuedraggable": "2.24.3",
         "vuescroll": "4.16.1",
-        "worker-loader": "3.0.8"
+        "worker-loader": "3.0.8",
+        "echarts": "^5.1.1"
     },
     "devDependencies": {
         "@intlify/vue-i18n-loader": "1.0.0",
diff --git a/linkis-web/src/apps/linkis/i18n/common/en.json 
b/linkis-web/src/apps/linkis/i18n/common/en.json
index eaec2aa06f..ce5c0da805 100644
--- a/linkis-web/src/apps/linkis/i18n/common/en.json
+++ b/linkis-web/src/apps/linkis/i18n/common/en.json
@@ -90,6 +90,9 @@
       "tenant": "Tenant",
       "inputTenant": "Please Input Tenant",
       "globalSettings": "GlobalSettings",
+      "taskOverTimeTop": "TaskDuration-TOP",
+      "taskTotalNum": "TaskTotalNum",
+      "engineTotalNum":"engineTotalNum",
       "resultSet": {
         "prefixText": "Because your result set is large, for a better 
experience, ",
         "linkText": "View result set",
@@ -107,7 +110,7 @@
       "resourceManagement": {
         "resourceUsage": "Resource usage",
         "applicationList": "Applications"
-    },
+      },
       "time": {
         "second": "Second",
         "minute": "Minute",
@@ -199,7 +202,8 @@
             "EngineConnList": "Engine Conn List",
             "opsTool": "OPS Tool",
             "userConfig": "User Configuration",
-            "configManagement": "Configuration Management"
+            "configManagement": "Configuration Management",
+            "statisticsDashboard": "Statistics Dashboard"
           }
         }
       },
diff --git a/linkis-web/src/apps/linkis/i18n/common/zh.json 
b/linkis-web/src/apps/linkis/i18n/common/zh.json
index ba1a8919b8..50f6d2ecf0 100644
--- a/linkis-web/src/apps/linkis/i18n/common/zh.json
+++ b/linkis-web/src/apps/linkis/i18n/common/zh.json
@@ -90,6 +90,9 @@
           "tenant": "租户标签",
           "inputTenant": "请输入租户标签",
           "globalSettings": "全局设置",
+          "taskOverTimeTop": "任务耗时-TOP",
+          "taskTotalNum": "任务总数",
+          "engineTotalNum":"引擎总数",
           "resultSet": {
             "prefixText": "因为您的结果集较大,为了更好的体验,",
             "linkText": "点击查看结果集",
@@ -199,7 +202,8 @@
                       "EngineConnList": "引擎列表",
                       "opsTool": "运维工具",
                       "userConfig": "用户配置",
-                      "configManagement": "配置项管理"
+                      "configManagement": "配置项管理",
+                      "statisticsDashboard": "统计看板"
                   }
               }
           },
diff --git a/linkis-web/src/apps/linkis/module/statisticsDashboard/index.js 
b/linkis-web/src/apps/linkis/module/statisticsDashboard/index.js
new file mode 100644
index 0000000000..1bec7a05b4
--- /dev/null
+++ b/linkis-web/src/apps/linkis/module/statisticsDashboard/index.js
@@ -0,0 +1,25 @@
+/*
+ * 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 default {
+  name: 'StatisticsDashboard',
+  dispatchs: {
+    Workbench: ['add'],
+  },
+  component: () => import('./index.vue'),
+};
diff --git a/linkis-web/src/apps/linkis/module/statisticsDashboard/index.scss 
b/linkis-web/src/apps/linkis/module/statisticsDashboard/index.scss
new file mode 100644
index 0000000000..3a042ec069
--- /dev/null
+++ b/linkis-web/src/apps/linkis/module/statisticsDashboard/index.scss
@@ -0,0 +1,157 @@
+/*
+ * 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 '@/common/style/variables.scss';
+.progress-wrap {
+    position: $relative;
+    height: 20px;
+    display: flex;
+    align-items: center;
+    font-size: 10px;
+    background: $background-color-base;
+    border-radius: 10px;
+    .progress-busy {
+        background: $success-color;
+        height: 100%;
+        border-radius: 10px;
+        position: $absolute;
+        z-index: 0;
+    }
+    .progress-label {
+        width: 100%;
+        z-index: 1;
+    }
+}
+.global-history {
+    position: $relative;
+    height: 100%;
+    overflow-x: hidden;
+    .global-history-searchbar {
+        position: $relative;
+        .float-right {
+            position: $absolute;
+            right: 10px;
+        }
+    }
+    .ivu-form {
+        display: flex;
+        .ivu-form-item {
+            margin-right: 4px;
+            margin-bottom: 10px;
+            .ivu-form-item-content {
+                display: flex !important;
+            }
+        }
+        .ivu-input-with-suffix {
+            padding-right: 7px;
+        }
+    }
+    .global-history-table {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        margin-top: 16px;
+        .ivu-table th {
+            background-color: $table-thead-blue-bg;
+            color: $body-background;
+        }
+    }
+    .global-history-loading {
+        animation: ani-demo-spin 1s linear infinite;
+    }
+    .divider {
+        margin-top: 7px;
+        height: 20px;
+    }
+    .global-history-page {
+        margin-top: 10px;
+        width: 100%;
+        text-align: center;
+    }
+}
+.datepicker {
+    .ivu-picker-panel-body {
+        background-color: $background-color-white;
+    }
+    .ivu-icon {
+        display: none;
+    }
+}
+.workbench-log-view {
+    height: calc(100% - 76px) !important;
+    .log-tools {
+        height: 36px;
+        line-height: 36px;
+        padding-left: 10px;
+        background: $background-color-base;
+        position: $relative;
+        border-bottom: 2px solid $border-color-base;
+        overflow: hidden;
+        margin-bottom: -2px;
+        .log-tools-control {
+            display: inline-block;
+            position: $absolute;
+            top: 2px;
+            .log-tabs {
+                display: inline-block;
+                position: $absolute;
+            }
+            .log-search {
+                width: 100px;
+                position: $absolute;
+                left: 350px;
+                top: 5px;
+                font-size: $font-size-small;
+            }
+            .err-badge {
+                background: $error-color !important;
+            }
+            .warn-badge {
+                background: $yellow-color !important;
+            }
+        }
+    }
+}
+
+.render-btn {
+    padding: 1px 0 2px 0 !important;
+}
+.pie-content{
+  width: 100%;
+  height: 280px;
+  display: flex;
+  justify-content: space-around;
+  overflow:hidden;
+  min-width: 1000px;
+  padding:10px 0;
+  .pie-chart{
+    width: 42%;
+    min-width: 500px;
+    max-width: 700px;
+    height: 100%;
+  }
+}
+.global-history-title{
+  font-size: 16px;
+  color: #6379bb;
+}
+::v-deep .we-table-wrap{
+  border: 1px solid #dcdee2;
+}
+::v-deep .we-table-wrap .we-table{
+  border:none
+}
\ No newline at end of file
diff --git a/linkis-web/src/apps/linkis/module/statisticsDashboard/index.vue 
b/linkis-web/src/apps/linkis/module/statisticsDashboard/index.vue
new file mode 100644
index 0000000000..16f2c09a6e
--- /dev/null
+++ b/linkis-web/src/apps/linkis/module/statisticsDashboard/index.vue
@@ -0,0 +1,786 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<template>
+  <div class="statistics-dashboard">
+    <Form
+      class="statistics-dashboard-searchbar"
+      :model="searchBar"
+      inline
+    >
+      <FormItem
+        prop="shortcut"
+        :label="$t('message.linkis.formItems.date.label')"
+      >
+        <DatePicker
+          :transfer="true"
+          class="datepicker"
+          :options="shortcutOpt"
+          v-model="searchBar.shortcut"
+          type="daterange"
+          placement="bottom-start"
+          format="yyyy-MM-dd"
+          :placeholder="$t('message.linkis.formItems.date.placeholder')"
+          style="width: 175px"
+          :editable="false"
+        />
+      </FormItem>
+      <FormItem prop="creator" 
:label="$t('message.linkis.formItems.creator.label')">
+        <Input
+          :maxlength="50"
+          v-model="searchBar.creator"
+          :placeholder="$t('message.linkis.formItems.creator.placeholder')"
+          style="width: 120px"
+        />
+      </FormItem>
+      <FormItem prop="proxyUser" :label="$t('message.linkis.userName')">
+        <Input
+          :maxlength="50"
+          v-model="searchBar.proxyUser"
+          :placeholder="$t('message.linkis.searchName')"
+          style="width: 120px"
+        />
+      </FormItem>
+      <FormItem prop="engine" 
:label="$t('message.linkis.formItems.engine.label')">
+        <Select v-model="searchBar.engine" style="width: 120px">
+          <Option v-for="(item) in getEngineTypes" :label="item === 'all' ? 
$t('message.linkis.engineTypes.all'): item" :value="item" :key="item" />
+        </Select>
+      </FormItem>
+      <FormItem>
+        <Button
+          type="primary"
+          @click="search"
+          style="margin-right: 10px; margin-top: 32px;"
+        >{{ $t('message.linkis.search') }}</Button>
+        <Button
+          type="warning"
+          @click="reset"
+          style="margin-right: 10px; margin-top: 32px;"
+        >{{ $t('message.linkis.clearSearch') }}</Button>
+      </FormItem>
+    </Form>
+    <div class="pie-content">
+      <div
+        id="task"
+        class="pie-chart"
+      ></div>
+      <div
+        id="engine"
+        class="pie-chart"
+      ></div>
+    </div>
+    <div>
+      <div class="statistics-dashboard-title">{{ 
$t('message.linkis.taskOverTimeTop') }}
+        <InputNumber
+          v-model="pageSetting.pageSize"
+          style="width:80px;"
+          :min="1"
+          :max="100"
+          :activeChange="false"
+          @on-blur="handleBlur"
+        ></InputNumber>
+      </div>
+      <div
+        class="statistics-dashboard-table"
+        :style="{width: '100%', 'height': moduleHeight+'px'}"
+      >
+        <Icon
+          v-show="isLoading"
+          type="ios-loading"
+          size="30"
+          class="statistics-dashboard-loading"
+        />
+        <history-table
+          v-if="!isLoading"
+          :columns="column"
+          :data="list"
+          :height="moduleHeight"
+          :no-data-text="$t('message.linkis.noDataText')"
+          border
+          stripe
+          @checkall="checkChange"
+          @select-change="selectChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import storage from '@/common/helper/storage'
+import table from '@/components/virtualTable'
+import mixin from '@/common/service/mixin'
+import api from '@/common/service/api'
+var echarts = require('echarts')
+export default {
+  name: 'StatisticsDashboard',
+  components: {
+    historyTable: table.historyTable,
+  },
+  mixins: [mixin],
+  data() {
+    const today = new Date(new Date().toLocaleDateString())
+    return {
+      list: [],
+      column: [],
+      getEngineTypes: [],
+      isLoading: false,
+      pageSetting: {
+        total: 0,
+        pageSize: 10,
+        current: 1,
+      },
+      searchBar: {
+        creator: '',
+        proxyUser: '',
+        engine: 'all',
+        status: '',
+        shortcut: [today, today],
+      },
+      inputType: 'number',
+      shortcutOpt: {
+        shortcuts: [
+          {
+            text: this.$t('message.linkis.shortcuts.week'),
+            value() {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+              return [start, end]
+            },
+          },
+          {
+            text: this.$t('message.linkis.shortcuts.month'),
+            value() {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+              return [start, end]
+            },
+          },
+          {
+            text: this.$t('message.linkis.shortcuts.threeMonths'),
+            value() {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
+              return [start, end]
+            },
+          },
+        ],
+      },
+      engineTypes: [
+        {
+          label: this.$t('message.linkis.engineTypes.all'),
+          value: 'all'
+        },
+        {
+          label: 'spark',
+          value: 'spark'
+        },
+        {
+          label: 'hive',
+          value: 'hive'
+        },
+        {
+          label: 'pipeline',
+          value: 'pipeline'
+        },
+        {
+          label: 'python',
+          value: 'python'
+        },
+        {
+          label: 'flowexecution',
+          value: 'flowexecution'
+        },
+        {
+          label: 'appjoint',
+          value: 'appjoint'
+        },
+        {
+          label: 'shell',
+          value: 'shell'
+        }
+      ],
+      isLogAdmin: false,
+      isHistoryAdmin: false,
+      isAdminModel: false,
+      moduleHeight: 200,
+      taskChart: [],
+      engineChart: [],
+    }
+  },
+  created() {
+    // Get whether it is a historical administrator(获取是否是历史管理员权限)
+    api.fetch('/jobhistory/governanceStationAdmin', 'get').then((res) => {
+      this.isLogAdmin = res.admin
+      this.isHistoryAdmin = res.historyAdmin
+    })
+    api.fetch('/configuration/engineType', 'get').then((res) => {
+      this.getEngineTypes = ['all', ...res.engineType]
+    })
+  },
+  mounted() {
+    this.init()
+    this.moduleHeight = this.$parent.$el.clientHeight - 480
+    // Monitor window changes and get browser width and height(监听窗口变化,获取浏览器宽高)
+    window.addEventListener('resize', this.getHeight)
+  },
+  beforeDestroy() {
+    // Monitor window changes and get browser width and height(监听窗口变化,获取浏览器宽高)
+    storage.set('last-admin-model', this.isAdminModel)
+    // storage.set('last-searchbar-status', this.searchBar)
+    window.removeEventListener('resize', this.getHeight)
+  },
+  beforeRouteEnter(to, from, next) {
+    if (from.name !== 'viewHistory') {
+      sessionStorage.removeItem('last-admin-model')
+      sessionStorage.removeItem('last-searchbar-status')
+      sessionStorage.removeItem('last-pageSetting-status')
+    }
+    next()
+  },
+  activated() {
+    this.init()
+  },
+  methods: {
+    getHeight() {
+      this.moduleHeight = this.$parent.$el.clientHeight - 480
+      this.taskChart.resize()
+      this.engineChart.resize()
+    },
+    init() {
+      let isAdminModel = storage.get('last-admin-model')
+      const lastSearch = storage.get('last-searchbar-status')
+      const lastPage = storage.get('last-pageSetting-status')
+      if (lastSearch) {
+        if (lastSearch.shortcut[0] && lastSearch.shortcut[1]) {
+          lastSearch.shortcut = [
+            new Date(lastSearch.shortcut[0]),
+            new Date(lastSearch.shortcut[1]),
+          ]
+        } else {
+          const today = new Date(new Date().toLocaleDateString())
+          lastSearch.shortcut = [today, today]
+        }
+        this.searchBar = lastSearch
+      }
+      if (lastPage) {
+        this.pageSetting = lastPage
+      }
+      if (isAdminModel) {
+        this.switchAdmin()
+      } else {
+        this.search()
+      }
+      storage.remove('last-pageSetting-status')
+    },
+    // Click to view historical details log and return results(点击查看历史详情日志和返回结果)
+    async viewHistory(params) {
+      let sourceJson = params.row.sourceJson
+      if (typeof sourceJson === 'string') {
+        try {
+          sourceJson = JSON.parse(sourceJson)
+        } catch (error) {
+          window.console.log(sourceJson)
+        }
+      }
+      let fileName = ''
+      if (sourceJson && sourceJson.scriptPath) {
+        fileName = sourceJson.scriptPath.split('/').pop()
+      }
+      if (sourceJson && sourceJson.nodeName) {
+        fileName = sourceJson.nodeName
+      }
+      const query = {
+        taskID: params.row.taskID,
+        execID: params.row.strongerExecId,
+        status: params.row.status,
+        fileName,
+      }
+      if (this.isAdminModel) {
+        query.proxyUser = params.row.executeUser
+      }
+      storage.set('last-searchbar-status', this.searchBar)
+      storage.set('last-pageSetting-status', this.pageSetting)
+      // Jump to view the history details page(跳转查看历史详情页面)
+      this.$router.push({
+        path: '/console/statisticsDashboardHistory',
+        query,
+      })
+    },
+
+    getParams() {
+      const startDate = this.searchBar.shortcut[0]
+        ? new Date(this.searchBar.shortcut[0].setHours(0, 0, 0, 0))
+        : this.searchBar.shortcut[0]
+      const endDate = this.searchBar.shortcut[1]
+        ? new Date(this.searchBar.shortcut[1].setHours(23, 59, 59, 0))
+        : this.searchBar.shortcut[1]
+      const params = {
+        creator: this.searchBar.creator,
+        proxyUser: this.searchBar.proxyUser,
+        executeApplicationName: this.searchBar.engine,
+        startDate: startDate && startDate.getTime(),
+        endDate: endDate && endDate.getTime(),
+        pageNow: 1,
+        pageSize: this.pageSetting.pageSize
+
+      }
+
+      let { engine, shortcut } = this.searchBar
+      if (engine === 'all') {
+        delete params.executeApplicationName
+      }
+
+      if (!shortcut[0]) {
+        delete params.startDate
+      }
+      if (!shortcut[1]) {
+        delete params.endDate
+      }
+
+      return params
+    },
+    search() {
+      this.isLoading = true
+      let taskTotal = 0
+      let taskSuccess = 0
+      let taskFalied = 0
+      let taskCancelled = 0
+      let countEngine = 0
+      let countEngineSucceed = 0
+      let countEngineFailed = 0
+      let countEngineShutting = 0
+      const params = this.getParams()
+      this.column = this.getColumns()
+      api
+        .fetch('/jobhistory/listDurationTop', params, 'get')
+        .then((rst) => {
+          this.pageSetting.total = rst.totalPage
+          this.isLoading = false
+          this.list = this.getList(rst.tasks)
+        })
+        .catch(() => {
+          this.list = []
+          this.isLoading = false
+        })
+      api
+        .fetch('/jobhistory/jobstatistics/taskCount', params, 'get')
+        .then((rst) => {
+          this.isLoading = false
+          taskTotal = rst.sumCount
+          taskSuccess = rst.succeedCount
+          taskFalied = rst.failedCount
+          taskCancelled = rst.cancelledCount
+          this.taskChart = echarts.init(document.getElementById('task'))
+          this.taskChart.setOption({
+            title: {
+              text: this.$t('message.linkis.taskTotalNum')+'(' + taskTotal + 
')',
+              left: 'center',
+            },
+            tooltip: {
+              trigger: 'item',
+              formatter: '{b}:{c}',
+            },
+            legend: {
+              orient: 'vertical',
+              left: 'right',
+              top: 'bottom',
+              itemWidth: 30,
+              formatter: '{name}',
+              textStyle: {
+                color: '#000000',
+              },
+              data: [
+                { name: this.$t('message.linkis.statusType.succeed'), icon: 
'rect' },
+                { name: this.$t('message.linkis.statusType.failed'), icon: 
'rect' },
+                { name: this.$t('message.linkis.statusType.cancelled'), icon: 
'rect' },
+              ],
+            },
+            calculable: true,
+            series: [
+              {
+                name: '',
+                type: 'pie',
+                radius: '55%',
+                center: ['50%', '50%'],
+                //roseType:'angle',
+                label: {
+                  normal: {
+                    show: true,
+                    // position: 'inner',
+                    textStyle: {
+                      fontWeight: 300,
+                      fontSize: 14,
+                      color: '#000000',
+                    },
+                    formatter: '{b}:{c}',
+                  },
+                  labelLine: { show: true },
+                },
+                data: [
+                  { value: taskSuccess, name: 
this.$t('message.linkis.statusType.succeed') },
+                  { value: taskFalied, name: 
this.$t('message.linkis.statusType.failed') },
+                  { value: taskCancelled, name: 
this.$t('message.linkis.statusType.cancelled') },
+                ],
+              },
+            ],
+          })
+        })
+        .catch(() => {
+          this.isLoading = false
+        })
+      api
+        .fetch('/jobhistory/jobstatistics/engineCount', params, 'get')
+        .then((rst) => {
+          this.isLoading = false
+          countEngine = rst.countEngine
+          countEngineSucceed = rst.countEngineSucceed
+          countEngineFailed = rst.countEngineFailed
+          countEngineShutting = rst.countEngineShutting
+          this.engineChart = echarts.init(document.getElementById('engine'))
+          this.engineChart.setOption({
+            title: {
+              text: this.$t('message.linkis.engineTotalNum')+'(' + countEngine 
+ ')',
+              left: 'center',
+            },
+            tooltip: {
+              trigger: 'item',
+              formatter: '{b}:{c}',
+            },
+            legend: {
+              orient: 'vertical',
+              left: 'right',
+              top: 'bottom',
+              itemWidth: 30,
+              formatter: '{name}',
+              textStyle: {
+                color: '#000000',
+              },
+              data: [
+                { name: this.$t('message.linkis.statusType.succeed'), icon: 
'rect' },
+                { name: this.$t('message.linkis.statusType.failed') , icon: 
'rect' },
+                { name: this.$t('message.linkis.statusType.cancelled') , icon: 
'rect' },
+              ],
+            },
+            calculable: true,
+            series: [
+              {
+                name: '',
+                type: 'pie',
+                radius: '55%',
+                center: ['50%', '50%'],
+                //roseType:'angle',
+                label: {
+                  normal: {
+                    show: true,
+                    // position: 'inner',
+                    textStyle: {
+                      fontWeight: 300,
+                      fontSize: 14,
+                      color: '#000000',
+                    },
+                    formatter: '{b}:{c}',
+                  },
+                  labelLine: { show: true },
+                },
+                data: [
+                  { value: countEngineSucceed, name: 
this.$t('message.linkis.statusType.succeed') },
+                  { value: countEngineFailed, name: 
this.$t('message.linkis.statusType.failed') },
+                  { value: countEngineShutting, name: 
this.$t('message.linkis.statusType.cancelled') },
+                ],
+              },
+            ],
+          })
+        })
+        .catch(() => {
+          this.isLoading = false
+        })
+    },
+    getList(list) {
+      const getFailedReason = (item) => {
+        return item.errCode && item.errDesc
+          ? item.errCode + item.errDesc
+          : item.errCode || item.errDesc || ''
+      }
+      if (!this.isAdminModel) {
+        return list.map((item) => {
+          const paramsJson = JSON.parse(item.paramsJson)
+          let jobName = ''
+          try {
+            if (paramsJson.configuration.startup.etl_job_name) {
+              jobName = paramsJson.configuration.startup.etl_job_name
+            }
+          } catch (e) {
+            jobName = ''
+          }
+          return {
+            disabled:
+              ['Submitted', 'Inited', 'Scheduled', 'Running'].indexOf(
+                item.status
+              ) === -1,
+            taskID: item.taskID,
+            jobName: jobName,
+            strongerExecId: item.strongerExecId,
+            source: item.sourceTailor,
+            executionCode: item.executionCode,
+            status: item.status,
+            costTime: item.costTime,
+            instance: item.instance,
+            engineInstance: item.engineInstance,
+            requestApplicationName: item.requestApplicationName,
+            executeApplicationName: item.executeApplicationName,
+            executeApplicationName2: item.labels
+              .filter((item) => {
+                return item.startsWith('engineType:')
+              })[0]
+              .substring(11),
+            createdTime: item.createdTime,
+            progress: item.progress,
+            failedReason: getFailedReason(item),
+            runType: item.runType,
+            // instance: item.instance
+          }
+        })
+      }
+      return list.map((item) => {
+        const paramsJson = JSON.parse(item.paramsJson)
+        let jobName = ''
+        try {
+          if (paramsJson.configuration.startup.etl_job_name) {
+            jobName = paramsJson.configuration.startup.etl_job_name
+          }
+        } catch (e) {
+          jobName = ''
+        }
+        return Object.assign(item, {
+          disabled:
+            ['Submitted', 'Inited', 'Scheduled', 'Running'].indexOf(
+              item.status
+            ) === -1,
+          executeApplicationName2: item.labels
+            .filter((item) => {
+              return item.startsWith('engineType:')
+            })[0]
+            .substring(11),
+          jobName: jobName,
+          failedReason: getFailedReason(item),
+          source: item.sourceTailor,
+        })
+      })
+    },
+    checkChange(v) {
+      this.list = this.list.map((it) => {
+        it.checked = !it.disabled && v
+        return it
+      })
+    },
+    selectChange() {
+      this.list = this.list.slice(0)
+    },
+    handleBlur(){
+      this.isLoading = true
+      const params = this.getParams()
+      this.column = this.getColumns()
+      api
+        .fetch('/jobhistory/listDurationTop', params, 'get')
+        .then((rst) => {
+          this.pageSetting.total = rst.totalPage
+          this.isLoading = false
+          this.list = this.getList(rst.tasks)
+        })
+        .catch(() => {
+          this.list = []
+          this.isLoading = false
+        })
+    },
+    getColumns() {
+      const column = [
+        {
+          title: '',
+          key: 'checked',
+          align: 'center',
+          width: 60,
+          renderType: 'checkbox',
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.control.title'),
+          key: 'control',
+          fixed: 'right',
+          align: 'center',
+          width: 60,
+          renderType: 'button',
+          renderParams: [
+            {
+              label: this.$t('message.linkis.tableColumns.control.label'),
+              action: this.viewHistory,
+            },
+          ],
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.taskID'),
+          key: 'taskID',
+          align: 'center',
+          width: 80,
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.fileName'),
+          key: 'source',
+          align: 'center',
+          ellipsis: true,
+          width: 160,
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.executionCode'),
+          key: 'executionCode',
+          align: 'center',
+          width: 280,
+          // overflow to show(溢出以...显示)
+          ellipsis: true,
+          // renderType: 'tooltip',
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.status'),
+          key: 'status',
+          align: 'center',
+          width: 164,
+          renderType: 'if',
+          renderParams: {
+            action: this.setRenderType,
+          },
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.costTime'),
+          key: 'costTime',
+          align: 'center',
+          width: 100,
+          renderType: 'convertTime',
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.failedReason'),
+          key: 'failedReason',
+          align: 'center',
+          className: 'history-failed',
+          width: 180,
+          renderType: 'a',
+          renderParams: {
+            hasDoc: this.checkIfHasDoc,
+            action: this.linkTo,
+          },
+        },
+        {
+          title: `${this.$t(
+            'message.linkis.tableColumns.requestApplicationName'
+          )}/${this.$t('message.linkis.tableColumns.executeApplicationName')}`,
+          key: 'requestApplicationName',
+          align: 'center',
+          width: 140,
+          renderType: 'concat',
+          renderParams: {
+            concatKey: 'executeApplicationName2',
+          },
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.user'),
+          key: 'umUser',
+          align: 'center',
+          width: 80,
+        },
+        {
+          title: 'entrance',
+          key: 'instance',
+          align: 'center',
+          width: 150,
+        },
+        {
+          title: 'engineplugin',
+          key: 'engineInstance',
+          align: 'center',
+          width: 100,
+        },
+        {
+          title: this.$t('message.linkis.tableColumns.createdTime'),
+          key: 'createdTime',
+          align: 'center',
+          width: 150,
+          renderType: 'formatTime',
+        },
+      ]
+      if (!this.isAdminModel) {
+        const index = column.findIndex((item) => item.key === 'umUser')
+        column.splice(index, 1)
+      }
+      return column
+    },
+    reset() {
+      const today = new Date(new Date().toLocaleDateString())
+      this.searchBar = {
+        creator: '',
+        proxyUser: '',
+        engine: 'all',
+        shortcut: [today, today],
+      }
+      this.pageSetting={
+        pageSize: 10,
+      }
+      this.search()
+    },
+    switchAdmin() {
+      if (!this.isLoading) {
+        if (this.isAdminModel) {
+          this.searchBar.id = null
+          this.searchBar.proxyUser = ''
+        }
+        this.isAdminModel = !this.isAdminModel
+        this.search()
+      }
+    },
+    linkTo(params) {
+      this.$router.push({
+        path: '/console/FAQ',
+        query: {
+          errCode: parseInt(params.row.failedReason),
+          isSkip: true,
+        },
+      })
+    },
+    checkIfHasDoc(params) {
+      const errCodeList = [11011, 11012, 11013, 11014, 11015, 11016, 11017]
+      const errCode = parseInt(params.row.failedReason)
+      if (errCodeList.indexOf(errCode) !== -1) {
+        return true
+      }
+      return false
+    },
+    setRenderType(params) {
+      if (params.row.status === 'Running' && params.row.progress !== 0) {
+        return {
+          type: 'Progress',
+          value: params.row.progress,
+        }
+      } else {
+        return {
+          type: 'Tag',
+          value: params.row.status,
+        }
+      }
+    },
+  },
+}
+</script>
+<style src="./index.scss" lang="scss" scoped></style>
diff --git 
a/linkis-web/src/apps/linkis/module/statisticsDashboard/statisticsDashboard.vue 
b/linkis-web/src/apps/linkis/module/statisticsDashboard/statisticsDashboard.vue
new file mode 100644
index 0000000000..2600cbf868
--- /dev/null
+++ 
b/linkis-web/src/apps/linkis/module/statisticsDashboard/statisticsDashboard.vue
@@ -0,0 +1,441 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<template>
+  <div class="statistics-dashboard">
+    <Tabs @on-click="onClickTabs">
+      <TabPane name="log" :label="$t('message.linkis.log')"></TabPane>
+      <!-- <TabPane name="detail" :label="$t('message.linkis.detail')" 
disabled></TabPane> -->
+      <TabPane name="result" :label="$t('message.linkis.result')"></TabPane>
+    </Tabs>
+    <Button v-if="!isHistoryDetail" class="backButton" type="primary" 
@click="back">{{$t('message.linkis.back')}}</Button>
+    <Icon v-show="isLoading" type="ios-loading" size="30" 
class="statistics-dashboard-loading" />
+    <log v-if="tabName === 'log'" :logs="logs" :from-line="fromLine" 
:script-view-state="scriptViewState" />
+    <result
+      v-if="tabName === 'result'"
+      ref="result"
+      :script="script"
+      :dispatch="dispatch"
+      :visualShow="visualShow"
+      :script-view-state="scriptViewState"
+      :dataWranglerParams="dataWranglerParams"
+      getResultUrl="filesystem"
+      @on-set-change="changeResultSet"
+      @on-analysis="openAnalysisTab"
+      :visualParams="visualParams"
+    />
+  </div>
+</template>
+<script>
+import result from '@/components/consoleComponent/result.vue'
+import log from '@/components/consoleComponent/log.vue'
+import api from '@/common/service/api'
+import mixin from '@/common/service/mixin'
+import util from '@/common/util'
+import { isUndefined } from 'lodash'
+export default {
+  name: 'statisticsDashboardHistory',
+  components: {
+    log,
+    result
+  },
+  mixins: [mixin],
+  props: {},
+  data() {
+    return {
+      hasResultData: false,
+      isLoading: true,
+      tabName: 'log',
+      visualShow: 'table',
+      dataWranglerParams: {},
+      script: {
+        data: '',
+        oldData: '',
+        result: {},
+        steps: [],
+        progress: {},
+        resultList: null,
+        resultSet: 0,
+        params: {},
+        readOnly: false
+      },
+      visualParams: {},
+      scriptViewState: {
+        // topPanelHeight: '250px',
+        bottomContentHeight: 800,
+        topPanelFull: false,
+        showPanel: 'log',
+        bottomPanelFull: false,
+        cacheLogScroll: 0
+      },
+      logs: {
+        all: '',
+        error: '',
+        warning: '',
+        info: ''
+      },
+      fromLine: 1,
+      isAdminModel: false,
+      jobhistoryTask: null
+    }
+  },
+  created() {
+    this.hasResultData = false
+  },
+  mounted() {
+    let taskID = this.$route.query.taskID
+    this.initHistory(taskID)
+    const node = document.getElementsByClassName('statistics-dashboard')[0];
+    this.scriptViewState.bottomContentHeight = node.clientHeight - 85
+  },
+  computed: {
+    isHistoryDetail() {
+      return this.$route.path === '/console/viewHistoryDetail'
+    }
+  },
+  methods: {
+    // The request is triggered when the tab is clicked, and the log is 
requested at the beginning, and no judgment is made.(点击tab时触发请求,log初始就请求了,不做判断)
+    onClickTabs(name) {
+      this.tabName = name
+      if (name === 'result') {
+        // Determine whether it is a result set(判断是否为结果集)
+        if (this.hasResultData) return // Determine whether the data has been 
obtained, and return directly if it is obtained(判断是否已经获取过数据,获取过则直接返回)
+        if (this.jobhistoryTask && this.jobhistoryTask.resultLocation) {
+          //Determine if there is a resource address, if not, do not send a 
request(判断是否有资源地址,如果没有则不发请求)
+          this.getResult(this.jobhistoryTask)
+        } else {
+          this.$Notice.warning({
+            title: this.$t('message.linkis.tip'),
+            desc: this.$t('message.linkis.serverTip')
+          })
+        }
+      }
+    },
+    changeResultSet(data, cb) {
+      const resultSet = isUndefined(data.currentSet)
+        ? this.script.resultSet
+        : data.currentSet
+      const findResult = this.script.resultList[resultSet]
+      const resultPath = findResult && findResult.path
+      const hasResult = Object.prototype.hasOwnProperty.call(
+        this.script.resultList[resultSet],
+        'result'
+      )
+      if (!hasResult) {
+        const pageSize = 5000
+        const url = '/filesystem/openFile'
+        api
+          .fetch(
+            url,
+            {
+              path: resultPath,
+              pageSize
+            },
+            'get'
+          )
+          .then(ret => {
+            let result = {}
+            if (ret.metadata && ret.metadata.length >= 500) {
+              result = {
+                headRows: [],
+                bodyRows: [],
+                // If totalLine is null, it will be displayed as 
0(如果totalLine是null,就显示为0)
+                total: ret.totalLine ? ret.totalLine : 0,
+                // (If the content is null, it will display no 
data)如果内容为null,就显示暂无数据
+                type: ret.fileContent ? ret.type : 0,
+                path: resultPath,
+                current: 1,
+                size: 20,
+                hugeData: true
+              }
+            } else {
+              result = {
+                headRows: ret.metadata,
+                bodyRows: ret.fileContent,
+                // If totalLine is null, it will be displayed as 
0(如果totalLine是null,就显示为0)
+                total: ret.totalLine ? ret.totalLine : 0,
+                // If the content is null, it will display no 
data(如果内容为null,就显示暂无数据)
+                type: ret.fileContent ? ret.type : 0,
+                path: resultPath,
+                current: 1,
+                size: 20
+              }
+            }
+
+            this.script.resultList[resultSet].result = result
+            this.script.resultSet = resultSet
+            this.script = {
+              ...this.script
+            }
+            cb()
+          })
+          .catch(() => {
+            cb()
+          })
+      } else {
+        this.script.resultSet = resultSet
+        this.script = {
+          ...this.script
+        }
+        cb()
+      }
+    },
+    // Format the array into json form.(将数组格式化成json形式。)
+    openAnalysisTab(type) {
+      this.visualShow = type
+      if (type === 'visual') {
+        this.biLoading = true
+        let rows = this.scriptResult.headRows
+        let model = {}
+        let dates = ['DATE', 'DATETIME', 'TIMESTAMP', 'TIME', 'YEAR']
+        let numbers = [
+          'TINYINT',
+          'SMALLINT',
+          'MEDIUMINT',
+          'INT',
+          'INTEGER',
+          'BIGINT',
+          'FLOAT',
+          'DOUBLE',
+          'DOUBLE PRECISION',
+          'REAL',
+          'DECIMAL',
+          'BIT',
+          'SERIAL',
+          'BOOL',
+          'BOOLEAN',
+          'DEC',
+          'FIXED',
+          'NUMERIC'
+        ]
+        rows.forEach(item => {
+          let sqlType = item.dataType.toUpperCase()
+          let visualType = 'string'
+          if (numbers.indexOf(sqlType) > -1) {
+            visualType = 'number'
+          } else if (dates.indexOf(sqlType) > -1) {
+            visualType = 'date'
+          }
+          model[item.columnName] = {
+            sqlType,
+            visualType,
+            modelType: visualType === 'number' ? 'value' : 'category'
+          }
+        })
+        this.visualParams = {
+          // viewId: id,
+          // projectId,
+          json: {
+            name: `${this.script.fileName.replace(/\./g, '')}${
+              this.script.resultSet
+            }`,
+            model,
+            source: {
+              engineType: 'spark', //engine type(引擎类型)
+              dataSourceType: 'resultset', //Data source types, result sets, 
scripts, library tables(数据源类型,结果集、脚本、库表)
+              dataSourceContent: {
+                resultLocation: this.scriptResult.path
+              },
+              creator: 'IDE'
+            }
+          }
+        }
+      } else if (type === 'dataWrangler') {
+        this.dataWranglerParams = {
+          simpleMode: true,
+          showBottomBar: false,
+          importConfig: {
+            dataSourceConfig: {
+              dataSourceType: 'linkis',
+              dataSourceOptions: {
+                taskID: this.work.taskID || this.work.data.history[0].taskID
+              }
+            },
+            config: {
+              myConfig: {
+                resultSetPath: [this.scriptResult.path]
+              },
+              importConfig: {
+                mergeTables: true,
+                limitRows: 5000,
+                pivotTable: false,
+                tableHeaderRows: 1
+              }
+            }
+          }
+        }
+      }
+    },
+    // Get historical details(获取历史详情)
+    async initHistory(jobId) {
+      try {
+        let jobhistory = await api.fetch(`/jobhistory/${jobId}/get`, 'get')
+        const option = jobhistory.task
+        this.jobhistoryTask = option
+        this.script.runType = option.runType
+        if (!jobhistory.task.logPath) {
+          const errCode = jobhistory.task.errCode
+            ? `\n${this.$t('message.linkis.errorCode')}:${
+              jobhistory.task.errCode
+            }`
+            : ''
+          const errDesc = jobhistory.task.errDesc
+            ? `\n${this.$t('message.linkis.errorDescription')}:${
+              jobhistory.task.errDesc
+            }`
+            : ''
+          const info = this.$t('message.linkis.notLog') + errCode + errDesc
+          this.logs = { all: info, error: '', warning: '', info: '' }
+          this.fromLine = 1
+          return
+        }
+        const params = {
+          path: jobhistory.task.logPath
+        }
+        if (this.$route.query.proxyUser) {
+          params.proxyUser = this.$route.query.proxyUser
+        }
+        let openLog = {}
+        if (this.$route.query.status === 'Scheduled' || 
this.$route.query.status === 'Running') {
+          const tempParams = {
+            fromLine: this.fromLine,
+            size: -1,
+          }
+          openLog = await 
api.fetch(`/entrance/${this.$route.query.execID}/log`, tempParams, 'get')
+        } else {
+          openLog = await api.fetch('/filesystem/openLog', params, 'get')
+        }
+        if (openLog) {
+          const log = { all: '', error: '', warning: '', info: '' }
+          const convertLogs = util.convertLog(openLog.log)
+          Object.keys(convertLogs).forEach(key => {
+            if (convertLogs[key]) {
+              log[key] += convertLogs[key] + '\n'
+            }
+          })
+          this.logs = log
+          this.fromLine = log['all'].split('\n').length
+        }
+        this.isLoading = false
+      } catch (errorMsg) {
+        window.console.error(errorMsg)
+        this.isLoading = false
+      }
+    },
+    // Get result set content(获取结果集内容)
+    async getResult(option) {
+      this.isLoading = true
+      try {
+        const url1 = `/filesystem/getDirFileTrees`
+        const rst = await api.fetch(
+          url1,
+          {
+            path: option.resultLocation
+          },
+          'get'
+        )
+        if (rst.dirFileTrees) {
+          // The order of the result set in the background is sorted by string 
according to the name of the result set. When displaying, there will be a 
problem that the result set cannot be matched, so add 
sorting(后台的结果集顺序是根据结果集名称按字符串排序的,展示时会出现结果集对应不上的问题,所以加上排序)
+          let scriptResultList = rst.dirFileTrees.children.sort(
+            (a, b) => parseInt(a.name, 10) - parseInt(b.name, 10)
+          )
+          if (scriptResultList.length) {
+            const currentResultPath = rst.dirFileTrees.children[0].path
+            const url2 = `/filesystem/openFile`
+            api
+              .fetch(
+                url2,
+                {
+                  path: currentResultPath,
+                  page: 1,
+                  pageSize: 5000
+                },
+                'get'
+              )
+              .then(ret => {
+                let tmpResult = {}
+                if (ret.metadata && ret.metadata.length >= 500) {
+                  tmpResult = {
+                    headRows: [],
+                    bodyRows: [],
+                    total: ret.totalLine,
+                    type: ret.type,
+                    path: currentResultPath,
+                    hugeData: true
+                  }
+                } else {
+                  tmpResult = {
+                    headRows: ret.metadata,
+                    bodyRows: ret.fileContent,
+                    total: ret.totalLine,
+                    type: ret.type,
+                    path: currentResultPath
+                  }
+                }
+                this.script.resultSet = 0
+                this.script.resultList = scriptResultList
+                this.$set(this.script.resultList[0], 'result', {})
+                Object.assign(this.script.resultList[0].result, tmpResult)
+                this.scriptViewState.showPanel = 'result'
+                this.isLoading = false
+              })
+          }
+          this.hasResultData = true
+        } else {
+          // If not returned, set an initialization data(没有返回则设置一个初始化数据)
+          let tmpResult = {
+            headRows: [],
+            bodyRows: [],
+            total: 0,
+            type: '2',
+            path: ''
+          }
+          this.script.resultSet = 0
+          this.script.resultList = [{}]
+          this.$set(this.script.resultList[0], 'result', {})
+          Object.assign(this.script.resultList[0].result, tmpResult)
+          this.scriptViewState.showPanel = 'result'
+          this.isLoading = false
+        }
+      } catch (error) {
+        this.isLoading = false
+        window.console.error(error)
+      }
+    },
+    // go back to the last page(返回上一页)
+    back() {
+      if (this.isLoading) {
+        return this.$Message.warning(this.$t('message.linkis.logLoading'))
+      }
+      this.$router.go(-1)
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+
+
+.backButton {
+  position: absolute;
+  top: 0;
+  right: 20px;
+}
+/deep/ .table-div {
+  height: 100% !important;
+}
+</style>
+
diff --git a/linkis-web/src/apps/linkis/router.js 
b/linkis-web/src/apps/linkis/router.js
index 9aa3b05a32..70ea0b0454 100644
--- a/linkis-web/src/apps/linkis/router.js
+++ b/linkis-web/src/apps/linkis/router.js
@@ -319,6 +319,26 @@ export default [
         publicPage: true,
       },
     },
+    {
+      name: 'statisticsDashboard',
+      path: 'statisticsDashboard',
+      component: () =>
+        import('./module/statisticsDashboard/index.vue'),
+      meta: {
+        title: 'statisticsDashboard',
+        publicPage: true,
+      },
+    },
+    {
+      name: 'statisticsDashboardDetail',
+      path: 'statisticsDashboardDetail',
+      component: () =>
+        import('./module/statisticsDashboard/statisticsDashboard.vue'),
+      meta: {
+        title: 'statisticsDashboardHistory',
+        publicPage: true,
+      },
+    },
     ],
   },
 ]
diff --git a/linkis-web/src/apps/linkis/view/linkis/index.vue 
b/linkis-web/src/apps/linkis/view/linkis/index.vue
index 07b792801c..6530358eea 100644
--- a/linkis-web/src/apps/linkis/view/linkis/index.vue
+++ b/linkis-web/src/apps/linkis/view/linkis/index.vue
@@ -187,6 +187,13 @@ export default {
             ),
             showSubMenu: true,
           },
+          {
+            key: '1-13',
+            name: this.$t(
+              
'message.linkis.sideNavList.function.children.statisticsDashboard'
+            ),
+            path: '/console/statisticsDashboard',
+          },
         ],
       },
       datasourceNavList: {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to