feat( 报表统计): 报表统计从xpack挪到开源版,并增加保存、导出的权限。

报表统计从xpack挪到开源版,并增加保存、导出的权限。
This commit is contained in:
song-tianyang 2021-09-26 16:31:06 +08:00 committed by song-tianyang
parent 6504298c63
commit be6390d3bf
51 changed files with 4189 additions and 9 deletions

View File

@ -0,0 +1,14 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.reportstatistics.dto.TestAnalysisChartRequest;
import io.metersphere.reportstatistics.dto.TestAnalysisChartResult;
import java.util.List;
public interface ExtTestAnalysisMapper {
List<TestAnalysisChartResult> getCraeteCaseReport(TestAnalysisChartRequest request);
List<TestAnalysisChartResult> getUpdateCaseReport(TestAnalysisChartRequest request);
}

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.metersphere.base.mapper.ext.ExtTestAnalysisMapper">
<select id="getCraeteCaseReport" resultType="io.metersphere.reportstatistics.dto.TestAnalysisChartResult">
select dateStr, ifnull(tt.num,0) countNum
from
(
select adddate('2019-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) dateStr from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4
) v
left join
(select FROM_UNIXTIME(t.create_time/1000, '%Y-%m-%d') as create_times,t.project_id,t.node_id,t.priority,count(id) num ,t.maintainer from test_case t
WHERE project_id in
<foreach collection="projects" item="id" separator="," open="(" close=")">
#{id}
</foreach>
<if test="modules != null and modules.size() > 0">
and node_id in
<foreach collection="modules" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="prioritys != null and prioritys.size() > 0">
and priority in
<foreach collection="prioritys" item="p" separator="," open="(" close=")">
#{p}
</foreach>
</if>
<if test="users != null and users.size() > 0">
and maintainer in
<foreach collection="users" item="p" separator="," open="(" close=")">
#{p}
</foreach>
</if>
GROUP BY create_times
) tt
on dateStr = tt.create_times
where dateStr BETWEEN #{startTime} and #{endTime}
<if test="order == null or order == ''">
order by dateStr
</if>
<if test="order != null and order != ''">
order by countNum ${order}
</if>
</select>
<select id="getUpdateCaseReport" resultType="io.metersphere.reportstatistics.dto.TestAnalysisChartResult">
select dateStr, ifnull(tt.num,0) countNum
from
(
select adddate('2019-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) dateStr from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4
) v
left join
(select FROM_UNIXTIME(t.update_time/1000, '%Y-%m-%d') as update_times,t.project_id,t.node_id,t.priority,t.create_time,t.update_time,count(id) num ,t.maintainer from test_case t
WHERE create_time!= update_time
and project_id in
<foreach collection="projects" item="id" separator="," open="(" close=")">
#{id}
</foreach>
<if test="modules != null and modules.size() > 0">
and node_id in
<foreach collection="modules" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="prioritys != null and prioritys.size() > 0">
and priority in
<foreach collection="prioritys" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="users != null and users.size() > 0">
and maintainer in
<foreach collection="users" item="p" separator="," open="(" close=")">
#{p}
</foreach>
</if>
GROUP BY update_times
) tt
on dateStr = tt.update_times
where dateStr BETWEEN #{startTime} and #{endTime}
<if test="order == null or order == ''">
order by dateStr
</if>
<if test="order != null and order != ''">
order by countNum ${order}
</if>
</select>
</mapper>

View File

@ -0,0 +1,65 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.reportstatistics.dto.TestCaseCountChartResult;
import io.metersphere.reportstatistics.dto.TestCaseCountRequest;
import java.util.List;
public interface ExtTestCaseCountMapper {
/**
* 创建人 维护人 用例类型 用例状态 用例等级
*
* create_user
* maintainer
* '功能用例'
* status
* priority
*
* @ request
* @return
*/
List<TestCaseCountChartResult> getFunctionCaseCount(TestCaseCountRequest request);
/**
* 创建人 维护人 用例类型 用例状态 用例等级
*
* create_user_id
* ----不知道
* '接口用例'
* status
* ----不知道
*
* @param request
* @return
*/
List<TestCaseCountChartResult> getApiCaseCount(TestCaseCountRequest request);
/**
* 创建人 维护人 用例类型 用例状态 用例等级
*
* create_user
* principal
* '场景用例'
* status
* level
*
* @param request
* @return
*/
List<TestCaseCountChartResult> getScenarioCaseCount(TestCaseCountRequest request);
/**
* 创建人 维护人 用例类型 用例状态 用例等级
*
* create_user
* follow_people
* '性能用例'
* status
* 不知道
*
* @param request
* @return
*/
List<TestCaseCountChartResult> getLoadCaseCount(TestCaseCountRequest request);
}

View File

@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.metersphere.base.mapper.ext.ExtTestCaseCountMapper">
<select id="getFunctionCaseCount" resultType="io.metersphere.reportstatistics.dto.TestCaseCountChartResult">
select
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
${testCaseGroupColumn} AS groupName,
</if>
count(id) AS countNum
FROM test_case
<where>
project_id = #{projectId}
AND `status` != 'Trash'
<if test="startTime > 0">
AND create_time &gt;= #{startTime}
</if>
<if test="endTime > 0">
AND create_time &lt;= #{endTime}
</if>
<if test="filterSearchList != null and filterSearchList.size() > 0">
AND
<trim prefix="(" prefixOverrides="AND|OR" suffix=")">
<foreach collection="filterSearchList.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='caselevel'">
${filterType} priority in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='creator'">
${filterType} create_user in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='maintainer'">
${filterType} maintainer in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='casestatus'">
${filterType} UPPER(status) in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
</choose>
</if>
</foreach>
</trim>
</if>
</where>
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
GROUP BY ${testCaseGroupColumn}
</if>
</select>
<select id="getApiCaseCount" resultType="io.metersphere.reportstatistics.dto.TestCaseCountChartResult">
select
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
${apiCaseGroupColumn} AS groupName,
</if>
count(id) AS countNum
FROM api_test_case
<where>
project_id = #{projectId}
AND `status` != 'Trash'
<if test="startTime > 0">
AND (create_time &gt;= #{startTime})
</if>
<if test="endTime > 0">
AND ( create_time &lt;= #{endTime})
</if>
<if test="apiFilterSearchList != null and apiFilterSearchList.size() > 0">
AND
<trim prefix="(" prefixOverrides="AND|OR" suffix=")">
<foreach collection="apiFilterSearchList.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='caselevel'">
${filterType} priority in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='creator'">
${filterType} create_user_id in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='casestatus'">
${filterType}
(UPPER(status) in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
<if test="values.contains('RUNNING')">
OR status IS NULL
</if>
)
</when>
</choose>
</if>
</foreach>
</trim>
</if>
</where>
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
GROUP BY ${apiCaseGroupColumn}
</if>
</select>
<select id="getScenarioCaseCount" resultType="io.metersphere.reportstatistics.dto.TestCaseCountChartResult">
select
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
${scenarioCaseGroupColumn} AS groupName,
</if>
count(id) AS countNum
FROM api_scenario
<where>
project_id = #{projectId}
AND `status` != 'Trash'
<if test="startTime > 0">
AND (create_time &gt;= #{startTime})
</if>
<if test="endTime > 0">
AND ( create_time &lt;= #{endTime})
</if>
<if test="filterSearchList != null and filterSearchList.size() > 0">
AND
<trim prefix="(" prefixOverrides="AND|OR" suffix=")">
<foreach collection="filterSearchList.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='caselevel'">
${filterType} level in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='creator'">
${filterType} create_user in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='maintainer'">
${filterType} principal in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='casestatus'">
${filterType} UPPER(status) in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
</choose>
</if>
</foreach>
</trim>
</if>
</where>
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
GROUP BY ${scenarioCaseGroupColumn}
</if>
</select>
<select id="getLoadCaseCount" resultType="io.metersphere.reportstatistics.dto.TestCaseCountChartResult">
select
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
${loadCaseGroupColumn} AS groupName,
</if>
count(id) AS countNum
FROM load_test
<where>
project_id = #{projectId}
<if test="startTime > 0">
AND (create_time &gt;= #{startTime})
</if>
<if test="endTime > 0">
AND ( create_time &lt;= #{endTime})
</if>
<if test="loadFilterSearchList != null and loadFilterSearchList.size() > 0">
AND
<trim prefix="(" prefixOverrides="AND|OR" suffix=")">
<foreach collection="loadFilterSearchList.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">
<choose>
<when test="key=='creator'">
${filterType} create_user in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<when test="key=='casestatus'">
${filterType} UPPER(status) in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
</choose>
</if>
</foreach>
</trim>
</if>
</where>
<if test="testCaseGroupColumn != null and testCaseGroupColumn != ''">
GROUP BY ${loadCaseGroupColumn}
</if>
</select>
</mapper>

View File

@ -0,0 +1,56 @@
package io.metersphere.reportstatistics.controller;
import com.alibaba.fastjson.JSONArray;
import io.metersphere.base.domain.ReportStatistics;
import io.metersphere.base.domain.ReportStatisticsWithBLOBs;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.reportstatistics.dto.ReportStatisticsSaveRequest;
import io.metersphere.reportstatistics.service.ReportStatisticsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @author song.tianyang
* @Date 2021/9/14 2:58 下午
*/
@RestController
@RequestMapping(value = "/history/report")
public class HistoryReportController {
@Resource
private ReportStatisticsService reportStatisticsService;
@PostMapping("/selectByParams")
public List<ReportStatistics> selectByParams(@RequestBody ReportStatisticsSaveRequest request) {
List<ReportStatistics> returnList = reportStatisticsService.selectByProjectIdAndReportType(request.getProjectId(),request.getReportType());
LogUtil.info("报表查询结果:"+JSONArray.toJSONString(returnList));
return returnList;
}
@PostMapping("/saveReport")
public ReportStatisticsWithBLOBs saveReport(@RequestBody ReportStatisticsSaveRequest request){
ReportStatisticsWithBLOBs returnData = reportStatisticsService.saveByRequest(request);
return returnData;
}
@PostMapping("/updateReport")
public ReportStatisticsWithBLOBs updateReport(@RequestBody ReportStatisticsSaveRequest request){
ReportStatisticsWithBLOBs returnData = reportStatisticsService.updateByRequest(request);
return returnData;
}
@PostMapping("/deleteByParam")
public int deleteById(@RequestBody ReportStatisticsSaveRequest request) {
return reportStatisticsService.deleteById(request.getId());
}
@PostMapping("/selectById")
public ReportStatisticsWithBLOBs selectById(@RequestBody ReportStatisticsSaveRequest request) {
return reportStatisticsService.selectById(request.getId());
}
}

View File

@ -0,0 +1,24 @@
package io.metersphere.reportstatistics.controller;
import io.metersphere.reportstatistics.dto.TestAnalysisChartRequest;
import io.metersphere.reportstatistics.dto.TestAnalysisResult;
import io.metersphere.reportstatistics.service.TestAnalysisService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping(value = "/report/test/analysis")
public class TestAnalysisController {
@Resource
TestAnalysisService testAnalysisService;
@PostMapping("/getReport")
public TestAnalysisResult getReport(@RequestBody TestAnalysisChartRequest request) {
return testAnalysisService.getReport(request);
}
}

View File

@ -0,0 +1,34 @@
package io.metersphere.reportstatistics.controller;
import io.metersphere.reportstatistics.dto.TestCaseCountRequest;
import io.metersphere.reportstatistics.dto.TestCaseCountResponse;
import io.metersphere.reportstatistics.service.TestCaseCountService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping(value = "/report/test/case/count")
public class TestCaseCountController {
@Resource
TestCaseCountService testCaseCountService;
@PostMapping("/initDatas")
public Map<String, List<Map<String,String>>> initDatas(@RequestBody TestCaseCountRequest request) {
Map<String,List<Map<String, String>>> returnMap = testCaseCountService.getSelectFilterDatas(request.getProjectId());
return returnMap;
}
@PostMapping("/getReport")
public TestCaseCountResponse getReport(@RequestBody TestCaseCountRequest request) {
TestCaseCountResponse response = testCaseCountService.getReport(request);
return response;
}
}

View File

@ -0,0 +1,27 @@
package io.metersphere.reportstatistics.dto;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.reportstatistics.dto.charts.Series;
import io.metersphere.reportstatistics.dto.charts.Title;
import io.metersphere.reportstatistics.dto.charts.XAxis;
import io.metersphere.reportstatistics.dto.charts.YAxis;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class PieChartDTO {
private JSONObject dataset;
private JSONObject tooltip;
private XAxis xAxis;
private YAxis yAxis;
private List<Series> series;
private List<Title> title;
private int width;
public PieChartDTO() {
tooltip = new JSONObject();
}
}

View File

@ -0,0 +1,14 @@
package io.metersphere.reportstatistics.dto;
import io.metersphere.base.domain.ReportStatisticsWithBLOBs;
import lombok.Getter;
import lombok.Setter;
/**
* @author song.tianyang
* @Date 2021/9/14 4:51 下午
*/
@Getter
@Setter
public class ReportStatisticsSaveRequest extends ReportStatisticsWithBLOBs {
}

View File

@ -0,0 +1,5 @@
package io.metersphere.reportstatistics.dto;
public enum ReportStatisticsType {
TEST_CASE_COUNT,TEST_CASE_ANALYSIS
}

View File

@ -0,0 +1,29 @@
package io.metersphere.reportstatistics.dto;
import io.metersphere.reportstatistics.dto.charts.Legend;
import io.metersphere.reportstatistics.dto.charts.Series;
import io.metersphere.reportstatistics.dto.charts.XAxis;
import io.metersphere.reportstatistics.dto.charts.YAxis;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestAnalysisChartDTO {
private Legend legend;
private XAxis xAxis;
private YAxis yAxis;
private List<Series> series;
public TestAnalysisChartDTO() {
}
public TestAnalysisChartDTO(Legend legend, XAxis xAxis, YAxis yAxis, List<Series> series) {
this.legend = legend;
this.xAxis = xAxis;
this.yAxis = yAxis;
this.series = series;
}
}

View File

@ -0,0 +1,21 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestAnalysisChartRequest {
private boolean createCase;
private boolean updateCase;
private String order;
private List<Long> times;
private String startTime;
private String endTime;
private List<String> prioritys;
private List<String> projects;
private List<String> modules;
private List<String> users;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TestAnalysisChartResult {
private String dateStr;
private String countNum;
}

View File

@ -0,0 +1,22 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestAnalysisResult {
private TestAnalysisChartDTO chartDTO;
private List<TestAnalysisTableDTO> tableDTOs;
public TestAnalysisResult() {
}
public TestAnalysisResult(TestAnalysisChartDTO chartDTO, List<TestAnalysisTableDTO> tableDTOs) {
this.chartDTO = chartDTO;
this.tableDTOs = tableDTOs;
}
}

View File

@ -0,0 +1,29 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.UUID;
@Getter
@Setter
public class TestAnalysisTableDTO {
private String id;
private String name;
private String createCount;
private String updateCount;
private List<TestAnalysisTableDTO> children;
public TestAnalysisTableDTO() {
}
public TestAnalysisTableDTO(String name, String createCount, String updateCount, List<TestAnalysisTableDTO> children) {
this.id = UUID.randomUUID().toString();
this.name = name;
this.createCount = createCount;
this.updateCount = updateCount;
this.children = children;
}
}

View File

@ -0,0 +1,15 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TestCaseCountChartResult {
private String groupName;
private long countNum;
public String getCountNumStr(){
return String.valueOf(countNum);
}
}

View File

@ -0,0 +1,79 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Getter
@Setter
public class TestCaseCountRequest {
//x轴字段
private String xaxis;
//y轴字段
private List<String> yaxis;
//搜索条件
private String projectId;
private String timeType;
private TimeFilter timeFilter;
private List<Long> times;
private String order;
//起始时间
private long startTime = 0;
//结束时间
private long endTime = 0;
//其余条件
private String filterType;
private List<Map<String,Object>> filters;
/**
* 功能用例接口用例场景用例性能用例的分组字段
*/
private String testCaseGroupColumn;
private String apiCaseGroupColumn;
private String scenarioCaseGroupColumn;
private String loadCaseGroupColumn;
/**
* filter整理后的查询数据
* @return
*/
private Map<String,List<String>> filterSearchList;
private Map<String,List<String>> apiFilterSearchList;
private Map<String,List<String>> loadFilterSearchList;
public int getTimeRange(){
if(timeFilter != null){
return timeFilter.getTimeRange();
}else {
return 0;
}
}
public String getTimeRangeUnit(){
if(timeFilter != null){
return timeFilter.getTimeRangeUnit();
}else {
return null;
}
}
public void setFilterSearchList(String key,List<String> values){
if(this.filterSearchList == null){
this.filterSearchList = new HashMap<>();
}
filterSearchList.put(key,values);
}
}
@Getter
@Setter
class TimeFilter{
private int timeRange;
private String timeRangeUnit;
}

View File

@ -0,0 +1,24 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestCaseCountResponse {
private TestAnalysisChartDTO barChartDTO;
private PieChartDTO pieChartDTO;
private List<TestCaseCountTableDTO> tableDTOs;
public TestCaseCountResponse() {
}
public TestCaseCountResponse(TestAnalysisChartDTO chartDTO, PieChartDTO pieChartDTO, List<TestCaseCountTableDTO> tableDTOs) {
this.pieChartDTO = pieChartDTO;
this.barChartDTO = chartDTO;
this.tableDTOs = tableDTOs;
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.reportstatistics.dto;
/**
* @author song.tianyang
* @Date 2021/9/8 5:36 下午
*/
public class TestCaseCountSummary {
public String groupName;
public long testCaseCount = 0;
public long apiCaseCount = 0;
public long scenarioCaseCount = 0;
public long loadCaseCount = 0;
public TestCaseCountSummary(String groupName) {
this.groupName = groupName;
}
public long getAllCount() {
return this.testCaseCount + this.apiCaseCount + this.scenarioCaseCount + this.loadCaseCount;
}
}

View File

@ -0,0 +1,34 @@
package io.metersphere.reportstatistics.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Getter
@Setter
public class TestCaseCountTableDTO {
private String id;
private String name;
private String allCount;
private String testCaseCount;
private String apiCaseCount;
private String scenarioCaseCount;
private String loadCaseCount;
private List<TestCaseCountTableDTO> children;
public TestCaseCountTableDTO(String name, long testCaseCount, long apiCaseCount, long scenarioCaseCount, long loadCaseCount) {
this.id = UUID.randomUUID().toString();
this.name = name;
this.testCaseCount = String.valueOf(testCaseCount);
this.apiCaseCount = String.valueOf(apiCaseCount);
this.scenarioCaseCount = String.valueOf(scenarioCaseCount);
this.loadCaseCount = String.valueOf(loadCaseCount);
this.allCount = String.valueOf(testCaseCount+apiCaseCount+scenarioCaseCount+loadCaseCount);
children = new ArrayList<>();
}
}

View File

@ -0,0 +1,20 @@
package io.metersphere.reportstatistics.dto.charts;
import lombok.Getter;
import lombok.Setter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Getter
@Setter
public class Legend {
private final String x = "center";
private final String y = "bottom";
private final String type = "scroll";
private final List<Integer> padding = Arrays.asList(0, 40, 0, 0);
private Map<String, Boolean> selected;
private List<String> data;
}

View File

@ -0,0 +1,20 @@
package io.metersphere.reportstatistics.dto.charts;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class PieData {
private String name;
private long value;
private JSONObject itemStyle;
public void setColor(String color){
if(itemStyle == null){
itemStyle = new JSONObject();
}
itemStyle.put("color",color);
}
}

View File

@ -0,0 +1,20 @@
package io.metersphere.reportstatistics.dto.charts;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class Series {
private String name;
private List<Object> data;
private String color = "#783887";
private String type = "line";
private String radius = "50";
private String stack;
private JSONObject encode;
private List<String> center;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.reportstatistics.dto.charts;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Title {
private String name;
private String subtext;
private String left;
private String top = "75%";
private String textAlign = "center";
}

View File

@ -0,0 +1,17 @@
package io.metersphere.reportstatistics.dto.charts;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Getter
@Setter
public class XAxis {
private final String type = "category";
private List<String> data;
private String name;
private Map<String,Integer> axisLabel = new HashMap<String,Integer>(){ {this.put("interval",0);this.put("rotate",30);}};
}

View File

@ -0,0 +1,14 @@
package io.metersphere.reportstatistics.dto.charts;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class YAxis {
private String type;
private List<String> data;
private String name;
}

View File

@ -0,0 +1,77 @@
package io.metersphere.reportstatistics.service;
import io.metersphere.base.domain.ReportStatistics;
import io.metersphere.base.domain.ReportStatisticsExample;
import io.metersphere.base.domain.ReportStatisticsWithBLOBs;
import io.metersphere.base.mapper.ReportStatisticsMapper;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.reportstatistics.dto.ReportStatisticsSaveRequest;
import io.metersphere.reportstatistics.dto.ReportStatisticsType;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
/**
* @author song.tianyang
* @Date 2021/9/14 4:50 下午
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class ReportStatisticsService {
@Resource
private ReportStatisticsMapper reportStatisticsMapper;
public ReportStatisticsWithBLOBs saveByRequest(ReportStatisticsSaveRequest request) {
ReportStatisticsWithBLOBs model = new ReportStatisticsWithBLOBs();
model.setId(UUID.randomUUID().toString());
String name = "用例分析报表";
if(StringUtils.equalsIgnoreCase(ReportStatisticsType.TEST_CASE_COUNT.name(),request.getReportType())){
name = "用例统计报表";
model.setReportType(ReportStatisticsType.TEST_CASE_COUNT.name());
}else {
model.setReportType(ReportStatisticsType.TEST_CASE_ANALYSIS.name());
}
model.setName(name);
model.setDataOption(request.getDataOption());
model.setSelectOption(request.getSelectOption());
model.setCreateTime(System.currentTimeMillis());
model.setUpdateTime(System.currentTimeMillis());
model.setProjectId(request.getProjectId());
String userId = SessionUtils.getUserId();
model.setCreateUser(userId);
model.setUpdateUser(userId);
reportStatisticsMapper.insert(model);
return model;
}
public int deleteById(String id) {
return reportStatisticsMapper.deleteByPrimaryKey(id);
}
public List<ReportStatistics> selectByProjectIdAndReportType(String projectId, String reportType) {
ReportStatisticsExample example = new ReportStatisticsExample();
example.createCriteria().andProjectIdEqualTo(projectId).andReportTypeEqualTo(reportType);
example.setOrderByClause("create_time DESC");
return reportStatisticsMapper.selectByExample(example);
}
public ReportStatisticsWithBLOBs selectById(String id) {
return reportStatisticsMapper.selectByPrimaryKey(id);
}
public ReportStatisticsWithBLOBs updateByRequest(ReportStatisticsSaveRequest request) {
ReportStatisticsWithBLOBs updateModel = new ReportStatisticsWithBLOBs();
updateModel.setId(request.getId());
updateModel.setName(request.getName());
updateModel.setUpdateTime(request.getUpdateTime());
updateModel.setUpdateUser(SessionUtils.getUserId());
reportStatisticsMapper.updateByPrimaryKeySelective(updateModel);
return updateModel;
}
}

View File

@ -0,0 +1,171 @@
package io.metersphere.reportstatistics.service;
import io.metersphere.base.mapper.ext.ExtTestAnalysisMapper;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.ProjectRequest;
import io.metersphere.dto.ProjectDTO;
import io.metersphere.reportstatistics.dto.*;
import io.metersphere.reportstatistics.dto.charts.Legend;
import io.metersphere.reportstatistics.dto.charts.Series;
import io.metersphere.reportstatistics.dto.charts.XAxis;
import io.metersphere.reportstatistics.dto.charts.YAxis;
import io.metersphere.service.ProjectService;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestAnalysisService {
@Resource
private ExtTestAnalysisMapper extTestAnalysisMapper;
@Resource
private ProjectService projectService;
private final String ADD = "新增用例";
private final String UPDATE = "修改用例";
public TestAnalysisResult getReport(TestAnalysisChartRequest request) {
if (CollectionUtils.isEmpty(request.getTimes())) {
// 最近七天
request.setTimes(Arrays.asList(System.currentTimeMillis() - 7 * 24 * 3600 * 1000L, System.currentTimeMillis()));
}
request.setStartTime(DateUtils.getDataStr(request.getTimes().get(0)));
request.setEndTime(DateUtils.getDataStr(request.getTimes().get(1)));
if (CollectionUtils.isEmpty(request.getProjects())) {
// 获取当前组织空间下所有项目
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
ProjectRequest projectRequest = new ProjectRequest();
projectRequest.setWorkspaceId(currentWorkspaceId);
List<ProjectDTO> projectDTOS = projectService.getProjectList(projectRequest);
if (CollectionUtils.isNotEmpty(projectDTOS)) {
request.setProjects(projectDTOS.stream().map(ProjectDTO::getId).collect(Collectors.toList()));
} else {
request.setProjects(new LinkedList<String>(){{this.add(UUID.randomUUID().toString());}});
}
}
TestAnalysisChartDTO dto = new TestAnalysisChartDTO();
List<TestAnalysisTableDTO> dtos = new LinkedList<>();
List<Series> seriesList = new LinkedList<>();
XAxis xAxis = new XAxis();
if (CollectionUtils.isEmpty(request.getUsers())) {
// 组织charts格式数据
Legend legend = new Legend();
formatLegend(legend, null, request);
dto.setLegend(legend);
List<TestAnalysisChartResult> createResults = extTestAnalysisMapper.getCraeteCaseReport(request);
// 获取修改的用例统计报表
List<TestAnalysisChartResult> updateResults = extTestAnalysisMapper.getUpdateCaseReport(request);
formatXaxisSeries(xAxis, seriesList, "", dto, createResults, updateResults);
formatTable(dtos, createResults, updateResults);
} else {
List<String> users = request.getUsers();
Legend legend = new Legend();
formatLegend(legend, users, request);
dto.setLegend(legend);
// 按用户展示
boolean isFlag = true;
for (String item : users) {
request.setUsers(Arrays.asList(item));
List<TestAnalysisChartResult> createResults = extTestAnalysisMapper.getCraeteCaseReport(request);
// 获取修改的用例统计报表
List<TestAnalysisChartResult> updateResults = extTestAnalysisMapper.getUpdateCaseReport(request);
formatXaxisSeries(xAxis, seriesList, item + "-", dto, createResults, updateResults);
// 初始化列表总量按天统计总量
if (isFlag) {
formatTable(dtos, createResults, updateResults);
isFlag = false;
}
// 增加子项
for (int j = 0; j < dtos.size(); j++) {
TestAnalysisTableDTO childItem = new TestAnalysisTableDTO(item, createResults.get(j).getCountNum(), updateResults.get(j).getCountNum(), null);
dtos.get(j).getChildren().add(childItem);
}
}
}
// 每行总计
dtos.forEach(item -> {
if (CollectionUtils.isNotEmpty(item.getChildren())) {
// table 总和计算
List<Integer> collect = item.getChildren().stream().map(childItem -> Integer.valueOf(childItem.getCreateCount())).collect(Collectors.toList());
// reduce求和
Optional<Integer> createCount = collect.stream().reduce(Integer::sum);
List<Integer> upCollect = item.getChildren().stream().map(childItem -> Integer.valueOf(childItem.getUpdateCount())).collect(Collectors.toList());
// reduce求和
Optional<Integer> updateCount = upCollect.stream().reduce(Integer::sum);
item.setCreateCount(createCount.get().toString());
item.setUpdateCount(updateCount.get().toString());
}
});
// table 总和计算
List<Integer> collect = dtos.stream().map(item -> Integer.valueOf(item.getCreateCount())).collect(Collectors.toList());
// reduce求和
Optional<Integer> createCount = collect.stream().reduce(Integer::sum);
List<Integer> upCollect = dtos.stream().map(item -> Integer.valueOf(item.getUpdateCount())).collect(Collectors.toList());
// reduce求和
Optional<Integer> updateCount = upCollect.stream().reduce(Integer::sum);
dtos.add(new TestAnalysisTableDTO("总计", createCount.get().toString(), updateCount.get().toString(), new LinkedList<>()));
TestAnalysisResult testAnalysisResult = new TestAnalysisResult();
testAnalysisResult.setChartDTO(dto);
testAnalysisResult.setTableDTOs(dtos);
return testAnalysisResult;
}
private void formatXaxisSeries(XAxis xAxis, List<Series> seriesList, String name, TestAnalysisChartDTO dto, List<TestAnalysisChartResult> createResults, List<TestAnalysisChartResult> updateResults) {
if (CollectionUtils.isNotEmpty(createResults)) {
xAxis.setData(createResults.stream().map(TestAnalysisChartResult::getDateStr).collect(Collectors.toList()));
Series series = new Series();
series.setName(name + ADD);
series.setData(createResults.stream().map(TestAnalysisChartResult::getCountNum).collect(Collectors.toList()));
seriesList.add(series);
}
if (CollectionUtils.isNotEmpty(updateResults)) {
xAxis.setData(updateResults.stream().map(TestAnalysisChartResult::getDateStr).collect(Collectors.toList()));
Series series = new Series();
series.setName(name + UPDATE);
series.setColor("#B8741A");
series.setData(updateResults.stream().map(TestAnalysisChartResult::getCountNum).collect(Collectors.toList()));
seriesList.add(series);
}
dto.setXAxis(xAxis);
dto.setYAxis(new YAxis());
dto.setSeries(seriesList);
}
private void formatLegend(Legend legend, List<String> datas, TestAnalysisChartRequest request) {
Map<String, Boolean> selected = new LinkedHashMap<>();
List<String> list = new LinkedList<>();
if (CollectionUtils.isEmpty(datas)) {
selected.put(ADD, request.isCreateCase());
selected.put(UPDATE, request.isUpdateCase());
list.add(ADD);
list.add(UPDATE);
} else {
datas.forEach(item -> {
selected.put(item + "-" + ADD, request.isCreateCase());
selected.put(item + "-" + UPDATE, request.isUpdateCase());
list.add(item + "-" + ADD);
list.add(item + "-" + UPDATE);
});
}
legend.setSelected(selected);
legend.setData(list);
}
private void formatTable(List<TestAnalysisTableDTO> dtos, List<TestAnalysisChartResult> createResults, List<TestAnalysisChartResult> updateResults) {
for (int i = 0; i < createResults.size(); i++) {
TestAnalysisTableDTO dto = new TestAnalysisTableDTO(createResults.get(i).getDateStr(), createResults.get(i).getCountNum(), updateResults.get(i).getCountNum(), new LinkedList<>());
dtos.add(dto);
}
}
}

View File

@ -0,0 +1,775 @@
package io.metersphere.reportstatistics.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.CustomField;
import io.metersphere.base.domain.User;
import io.metersphere.base.mapper.ext.ExtTestCaseCountMapper;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.controller.request.member.QueryMemberRequest;
import io.metersphere.dto.TestCaseTemplateDao;
import io.metersphere.i18n.Translator;
import io.metersphere.reportstatistics.dto.*;
import io.metersphere.reportstatistics.dto.charts.*;
import io.metersphere.service.TestCaseTemplateService;
import io.metersphere.service.UserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.*;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestCaseCountService {
@Resource
private ExtTestCaseCountMapper extTestCaseCountMapper;
@Resource
UserService userService;
public TestCaseCountResponse getReport(TestCaseCountRequest request) {
request.setFilterType(request.getFilterType().toUpperCase(Locale.ROOT));
TestAnalysisChartDTO dto = new TestAnalysisChartDTO();
PieChartDTO pieChartDTO = new PieChartDTO();
List<TestCaseCountTableDTO> dtos = new LinkedList<>();
List<Series> seriesList = new LinkedList<>();
XAxis xAxis = new XAxis();
xAxis.setAxisLabel(new HashMap<String,Integer>(){ {this.put("interval",0);this.put("rotate",0);}});
// 组织charts格式数据
Legend legend = new Legend();
formatLegend(legend, request.getYaxis(), request);
dto.setLegend(legend);
//根据X轴分组计算字段来整理不同表对应的字段 注意x轴维护人/查询条件有维护人时 不查接口和性能 x轴为用例等级/查询条件有用例等级的不查性能
boolean yAxisSelectTestCase = false;
boolean yAxisSelectApi = false;
boolean yAxisSelectScenarioCase = false;
boolean yAxisSelectLoad = false;
boolean selectApi = true;
boolean selectLoad = true;
boolean parseUser = false;
boolean parseStatus = false;
switch (request.getXaxis()) {
case "creator":
request.setTestCaseGroupColumn("create_user");
request.setApiCaseGroupColumn("create_user_id");
request.setScenarioCaseGroupColumn("create_user");
request.setLoadCaseGroupColumn("create_user");
parseUser = true;
break;
case "maintainer":
request.setTestCaseGroupColumn("maintainer");
request.setApiCaseGroupColumn("'无维护人'");
request.setScenarioCaseGroupColumn("principal");
request.setLoadCaseGroupColumn("'无维护人'");
selectApi = false;
selectLoad = false;
parseUser = true;
break;
case "casetype":
Map<String, String> caseDescMap = this.getCaseDescMap();
request.setTestCaseGroupColumn("'" + caseDescMap.get("testCaseDesc") + "'");
request.setApiCaseGroupColumn("'" + caseDescMap.get("apiCaseDesc") + "'");
request.setScenarioCaseGroupColumn("'" + caseDescMap.get("scenarioCaseDesc") + "'");
request.setLoadCaseGroupColumn("'" + caseDescMap.get("loadCaseDesc") + "'");
break;
case "casestatus":
request.setTestCaseGroupColumn("status");
request.setApiCaseGroupColumn("status");
request.setScenarioCaseGroupColumn("status");
request.setLoadCaseGroupColumn("status");
selectApi = false;
parseStatus = true;
break;
case "caselevel":
request.setTestCaseGroupColumn("priority");
request.setApiCaseGroupColumn("priority");
request.setScenarioCaseGroupColumn("level");
request.setLoadCaseGroupColumn("'无用例等级'");
selectLoad = false;
break;
default:
return new TestCaseCountResponse();
}
//计算时间
if (StringUtils.equalsIgnoreCase(request.getTimeType(), "dynamicTime")) {
int dateCountType = 0;
if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "day")) {
dateCountType = Calendar.DAY_OF_MONTH;
} else if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "month")) {
dateCountType = Calendar.MONTH;
} else if (StringUtils.equalsIgnoreCase(request.getTimeRangeUnit(), "year")) {
dateCountType = Calendar.YEAR;
}
if (dateCountType != 0 && request.getTimeRange() != 0) {
long startTime = DateUtils.dateSum(new Date(), (0 - request.getTimeRange()), dateCountType).getTime();
request.setStartTime(startTime);
}
} else if (StringUtils.equalsIgnoreCase(request.getTimeType(), "fixedTime")) {
if (CollectionUtils.isNotEmpty(request.getTimes()) && request.getTimes().size() == 2) {
request.setStartTime(request.getTimes().get(0));
request.setEndTime(request.getTimes().get(1));
}
}
//计算更多属性
if (CollectionUtils.isNotEmpty(request.getFilters())) {
for (Map<String, Object> filterMap : request.getFilters()) {
String filterType = String.valueOf(filterMap.get("type"));
if (StringUtils.equalsAnyIgnoreCase(filterType, "casetype", "caselevel", "creator", "maintainer")) {
Object valueObj = filterMap.get("values");
if (valueObj instanceof List) {
List<String> searchList = (List) valueObj;
if(!searchList.isEmpty()){
request.setFilterSearchList(filterType, searchList);
}
}
if (StringUtils.equalsIgnoreCase(filterType, "caselevel")) {
selectLoad = false;
}else if (StringUtils.equalsIgnoreCase(filterType, "maintainer")) {
selectApi = false;
selectLoad = false;
}
}else if(StringUtils.equalsAnyIgnoreCase(filterType, "casestatus")){
List<String> searchList = new ArrayList<>();
Object valueObj = filterMap.get("values");
if (valueObj instanceof List) {
for (String statusStr : (List<String>) valueObj) {
searchList.add(statusStr.toUpperCase(Locale.ROOT));
}
}
//如果包含Running
if(searchList.contains("RUNNING")){
if(!searchList.contains("Starting")){
searchList.add("STARTING");
}
if(!searchList.contains("Underway")){
searchList.add("UNDERWAY");
}
}
if(searchList.contains("FINISHED")){
if(!searchList.contains("Completed")){
searchList.add("Completed");
}
}
if(!searchList.isEmpty()){
request.setFilterSearchList(filterType, searchList);
}
selectApi = false;
}
}
}
//获取测试用例接口用例场景用例性能用例的统计
List<TestCaseCountChartResult> functionCaseCountResult = new ArrayList<>();
List<TestCaseCountChartResult> apiCaseCountResult = new ArrayList<>();
List<TestCaseCountChartResult> scenarioCaseCount = new ArrayList<>();
List<TestCaseCountChartResult> loadCaseCount = new ArrayList<>();
List<String> moreOptionsAboutCaseType = new ArrayList<>();
if (StringUtils.equalsIgnoreCase(request.getFilterType(), "And") && MapUtils.isNotEmpty(request.getFilterSearchList())) {
if (request.getFilterSearchList().containsKey("maintainer")) {
selectApi = false;
}
if (request.getFilterSearchList().containsKey("caselevel")) {
selectLoad = false;
}
if (request.getFilterSearchList().containsKey("casetype")) {
//如果""查询同时针对案例类型做过筛选那么则分开批量查询
List<String> selectCaseTypeList = request.getFilterSearchList().get("casetype");
request.getFilterSearchList().remove("casetype");
if (CollectionUtils.isNotEmpty(selectCaseTypeList)) {
moreOptionsAboutCaseType.addAll(selectCaseTypeList);
}
}
}
//没有选择的话默认搜索条件是所有类型的案例
if(moreOptionsAboutCaseType.isEmpty()){
moreOptionsAboutCaseType.add("testCase");
moreOptionsAboutCaseType.add("apiCase");
moreOptionsAboutCaseType.add("scenarioCase");
moreOptionsAboutCaseType.add("loadCase");
}
//解析Y轴判断要查询的案例类型
if(CollectionUtils.isNotEmpty(request.getYaxis())){
for (String selectType:request.getYaxis()) {
if(moreOptionsAboutCaseType.contains(selectType)){
if (StringUtils.equalsIgnoreCase(selectType, "testCase")) {
yAxisSelectTestCase = true;
} else if (StringUtils.equalsIgnoreCase(selectType, "apiCase")) {
if(selectApi){
yAxisSelectApi = true;
}
} else if (StringUtils.equalsIgnoreCase(selectType, "scenarioCase")) {
yAxisSelectScenarioCase = true;
} else if (StringUtils.equalsIgnoreCase(selectType, "loadCase")) {
if(selectLoad){
yAxisSelectLoad = true;
}
}
}
}
}
if(yAxisSelectTestCase){
functionCaseCountResult = extTestCaseCountMapper.getFunctionCaseCount(request);
}
if (yAxisSelectApi) {
Map<String,List<String>> apiCaseFilterList = new HashMap<>();
if(MapUtils.isNotEmpty(request.getFilterSearchList())){
for (Map.Entry<String,List<String>> entry : request.getFilterSearchList().entrySet()) {
String type = entry.getKey();
if(!StringUtils.equalsAnyIgnoreCase(type,"maintainer","casestatus")){
apiCaseFilterList.put(entry.getKey(),entry.getValue());
}
}
}
request.setApiFilterSearchList(apiCaseFilterList);
apiCaseCountResult = extTestCaseCountMapper.getApiCaseCount(request);
}
if(yAxisSelectScenarioCase){
scenarioCaseCount = extTestCaseCountMapper.getScenarioCaseCount(request);
}
if (yAxisSelectLoad) {
Map<String,List<String>> loadCaseFilterMap = new HashMap<>();
if(MapUtils.isNotEmpty(request.getFilterSearchList())){
for (Map.Entry<String,List<String>> entry : request.getFilterSearchList().entrySet()) {
String type = entry.getKey();
if(!StringUtils.equalsAnyIgnoreCase(type,"maintainer","caselevel")){
loadCaseFilterMap.put(entry.getKey(),entry.getValue());
}
}
}
request.setLoadFilterSearchList(loadCaseFilterMap);
loadCaseCount = extTestCaseCountMapper.getLoadCaseCount(request);
}
Map<String, TestCaseCountSummary> summaryMap = this.summaryCountResult(parseUser, parseStatus,request.getProjectId(),request.getOrder(),
functionCaseCountResult, apiCaseCountResult, scenarioCaseCount, loadCaseCount);
formatXaxisSeries(xAxis, seriesList, dto, summaryMap);
formatTable(dtos, summaryMap);
formatPieChart(pieChartDTO, request.getXaxis(), summaryMap,yAxisSelectTestCase,yAxisSelectApi,yAxisSelectScenarioCase,yAxisSelectLoad);
TestCaseCountResponse testCaseCountResult = new TestCaseCountResponse();
testCaseCountResult.setBarChartDTO(dto);
testCaseCountResult.setTableDTOs(dtos);
testCaseCountResult.setPieChartDTO(pieChartDTO);
return testCaseCountResult;
}
private void formatPieChart(PieChartDTO pieChartDTO, String groupName, Map<String, TestCaseCountSummary> summaryMap,
boolean selectTestCase, boolean selectApi, boolean selectScenarioCase, boolean selectLoad) {
JSONArray titleArray = new JSONArray();
titleArray.add("type");
titleArray.add("count");
titleArray.add(groupName);
List<Series> seriesArr = new ArrayList<>();
List<Title> titles = new ArrayList<>();
int leftPx = 200;
Map<String, String> caseDescMap = this.getCaseDescMap();
for (TestCaseCountSummary summary : summaryMap.values()) {
String leftPxStr = String.valueOf(leftPx);
List<Object> dataList = new ArrayList<>();
if(selectTestCase && summary.testCaseCount > 0){
PieData pieData = new PieData();
pieData.setName(caseDescMap.get("testCaseDesc"));
pieData.setValue(summary.testCaseCount);
pieData.setColor("#F38F1F");
dataList.add(pieData);
}
if(selectApi && summary.apiCaseCount > 0){
PieData apicasePieData = new PieData();
apicasePieData.setName(caseDescMap.get("apiCaseDesc"));
apicasePieData.setValue(summary.apiCaseCount);
apicasePieData.setColor("#6FD999");
dataList.add(apicasePieData);
}
if(selectScenarioCase && summary.scenarioCaseCount > 0){
PieData scenarioPieData = new PieData();
scenarioPieData.setName(caseDescMap.get("scenarioCaseDesc"));
scenarioPieData.setValue(summary.scenarioCaseCount);
scenarioPieData.setColor("#2884F3");
dataList.add(scenarioPieData);
}
if(selectLoad && summary.loadCaseCount > 0){
PieData loadCasePieData = new PieData();
loadCasePieData.setName(caseDescMap.get("loadCaseDesc"));
loadCasePieData.setValue(summary.loadCaseCount);
loadCasePieData.setColor("#F45E53");
dataList.add(loadCasePieData);
}
Series series = new Series();
series.setType("pie");
series.setRadius("50");
series.setEncode(new JSONObject() {{
this.put("itemName", "groupname");
this.put("value", summary.groupName);
}});
seriesArr.add(series);
series.setData(dataList);
series.setCenter(new ArrayList<String>() {{
this.add(leftPxStr);
this.add("50%");
}});
Title title = new Title();
title.setSubtext(summary.groupName);
title.setLeft(leftPxStr);
titles.add(title);
leftPx = leftPx + 350;
}
pieChartDTO.setSeries(seriesArr);
pieChartDTO.setTitle(titles);
pieChartDTO.setWidth(leftPx);
}
private Map<String, TestCaseCountSummary> summaryCountResult(boolean parseGroupNameToUserName, boolean parseGrouNameToCaseStatus, String projectId, String order,
List<TestCaseCountChartResult> functionCaseCountResult, List<TestCaseCountChartResult> apiCaseCountResult, List<TestCaseCountChartResult> scenarioCaseCount, List<TestCaseCountChartResult> loadCaseCount) {
Map<String, TestCaseCountSummary> summaryMap = new LinkedHashMap<>();
//groupName 解析对象
Map<String, String> groupNameParseMap = new HashMap<>();
if (parseGroupNameToUserName) {
groupNameParseMap.putAll(this.getUserIdMap());
}
if (parseGrouNameToCaseStatus) {
groupNameParseMap.putAll(this.getCaseStatusMap(projectId));
}
if (CollectionUtils.isNotEmpty(functionCaseCountResult)) {
for (TestCaseCountChartResult result : functionCaseCountResult) {
if(result.getGroupName() == null){
result.setGroupName(groupNameParseMap.get("running"));
}else {
if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) {
result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT)));
}
}
String groupName = result.getGroupName();
if (StringUtils.isNotEmpty(groupName)) {
TestCaseCountSummary summary = summaryMap.get(groupName);
if (summary == null) {
summary = new TestCaseCountSummary(groupName);
}
summary.testCaseCount = result.getCountNum();
summaryMap.put(groupName, summary);
}
}
}
if (CollectionUtils.isNotEmpty(apiCaseCountResult)) {
for (TestCaseCountChartResult result : apiCaseCountResult) {
if(result.getGroupName() == null){
result.setGroupName(groupNameParseMap.get("running"));
}else {
if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) {
result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT)));
}
}
String groupName = result.getGroupName();
if (StringUtils.isNotEmpty(groupName)) {
TestCaseCountSummary summary = summaryMap.get(groupName);
if (summary == null) {
summary = new TestCaseCountSummary(groupName);
}
summary.apiCaseCount = result.getCountNum();
summaryMap.put(groupName, summary);
}
}
}
if (CollectionUtils.isNotEmpty(scenarioCaseCount)) {
for (TestCaseCountChartResult result : scenarioCaseCount) {
if(result.getGroupName() == null){
result.setGroupName(groupNameParseMap.get("running"));
}else {
if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) {
result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT)));
}
}
String groupName = result.getGroupName();
if (StringUtils.isNotEmpty(groupName)) {
TestCaseCountSummary summary = summaryMap.get(groupName);
if (summary == null) {
summary = new TestCaseCountSummary(groupName);
}
summary.scenarioCaseCount = result.getCountNum();
summaryMap.put(groupName, summary);
}
}
}
if (CollectionUtils.isNotEmpty(loadCaseCount)) {
for (TestCaseCountChartResult result : loadCaseCount) {
if(result.getGroupName() == null){
result.setGroupName(groupNameParseMap.get("running"));
}else {
if (groupNameParseMap.containsKey(result.getGroupName().toLowerCase(Locale.ROOT))) {
result.setGroupName(groupNameParseMap.get(result.getGroupName().toLowerCase(Locale.ROOT)));
}
}
String groupName = result.getGroupName();
if (StringUtils.isNotEmpty(groupName)) {
TestCaseCountSummary summary = summaryMap.get(groupName);
if (summary == null) {
summary = new TestCaseCountSummary(groupName);
}
summary.loadCaseCount = result.getCountNum();
summaryMap.put(groupName, summary);
}
}
}
Map<String, TestCaseCountSummary> returmMap = new LinkedHashMap<>();
if(StringUtils.equalsIgnoreCase(order,"desc")){
TreeMap<Long,List<TestCaseCountSummary>> treeMap = new TreeMap<>();
for (TestCaseCountSummary model : summaryMap.values()) {
if(treeMap.containsKey(model.getAllCount())){
treeMap.get(model.getAllCount()).add(model);
}else {
List<TestCaseCountSummary> list = new ArrayList<>();
list.add(model);
treeMap.put(model.getAllCount(),list);
}
}
ArrayList<TestCaseCountSummary> sortedList = new ArrayList<>();
for (List<TestCaseCountSummary> list : treeMap.values()) {
sortedList.addAll(list);
}
for (int i = sortedList.size(); i > 0; i --) {
TestCaseCountSummary model = sortedList.get(i-1);
returmMap.put(model.groupName,model);
}
}else if(StringUtils.equalsIgnoreCase(order,"asc")){
TreeMap<Long,List<TestCaseCountSummary>> treeMap = new TreeMap<>();
for (TestCaseCountSummary model : summaryMap.values()) {
if(treeMap.containsKey(model.getAllCount())){
treeMap.get(model.getAllCount()).add(model);
}else {
List<TestCaseCountSummary> list = new ArrayList<>();
list.add(model);
treeMap.put(model.getAllCount(),list);
}
}
for (List<TestCaseCountSummary> list : treeMap.values()) {
for (TestCaseCountSummary model : list ) {
returmMap.put(model.groupName,model);
}
}
}else {
returmMap = summaryMap;
}
return returmMap;
}
private Map<String, String> getUserIdMap() {
List<User> userList = userService.getUserList();
Map<String, String> userIdMap = new HashMap<>();
for (User model : userList) {
userIdMap.put(model.getId(), model.getId() + "\n(" + model.getName() + ")");
}
return userIdMap;
}
private Map<String, String> getCaseStatusMap(String projectId) {
Map<String, String> caseStatusMap = new HashMap<>();
TestCaseTemplateService testCaseTemplateService = CommonBeanFactory.getBean(TestCaseTemplateService.class);
TestCaseTemplateDao testCaseTemplate = testCaseTemplateService.getTemplate(projectId);
caseStatusMap.put("prepare", Translator.get("test_case_status_prepare"));
caseStatusMap.put("error", Translator.get("test_case_status_error"));
caseStatusMap.put("success", Translator.get("test_case_status_success"));
caseStatusMap.put("trash", Translator.get("test_case_status_trash"));
caseStatusMap.put("underway", Translator.get("test_case_status_running"));
caseStatusMap.put("starting", Translator.get("test_case_status_running"));
caseStatusMap.put("saved", Translator.get("test_case_status_saved"));
caseStatusMap.put("running", Translator.get("test_case_status_running"));
caseStatusMap.put("finished", Translator.get("test_case_status_finished"));
caseStatusMap.put("completed", Translator.get("test_case_status_finished"));
if (testCaseTemplate != null && CollectionUtils.isNotEmpty(testCaseTemplate.getCustomFields())) {
for (CustomField customField : testCaseTemplate.getCustomFields()) {
if (StringUtils.equals(customField.getName(), "用例状态")) {
JSONArray optionsArr = JSONArray.parseArray(customField.getOptions());
for (int i = 0; i < optionsArr.size(); i++) {
JSONObject jsonObject = optionsArr.getJSONObject(i);
if (jsonObject.containsKey("value") && jsonObject.containsKey("text") &&
!StringUtils.equalsAnyIgnoreCase(jsonObject.getString("value"), "Prepare", "Error", "Success", "Trash", "Underway", "Starting", "Saved")) {
caseStatusMap.put(jsonObject.getString("value"), jsonObject.getString("text"));
}
}
}
}
}
return caseStatusMap;
}
private void formatXaxisSeries(XAxis xAxis, List<Series> seriesList, TestAnalysisChartDTO dto,
Map<String, TestCaseCountSummary> summaryMap) {
List<String> xAxisDataList = new ArrayList<>();
List<Object> testCaseCountList = new ArrayList<>();
List<Object> apiCaseCountList = new ArrayList<>();
List<Object> scenarioCaseCountList = new ArrayList<>();
List<Object> loadCaseCountList = new ArrayList<>();
for (TestCaseCountSummary summary : summaryMap.values()) {
xAxisDataList.add(summary.groupName);
testCaseCountList.add(String.valueOf(summary.testCaseCount));
apiCaseCountList.add(String.valueOf(summary.apiCaseCount));
scenarioCaseCountList.add(String.valueOf(summary.scenarioCaseCount));
loadCaseCountList.add(String.valueOf(summary.loadCaseCount));
}
xAxis.setData(xAxisDataList);
Map<String, String> caseDescMap = this.getCaseDescMap();
Series tetcaseSeries = new Series();
tetcaseSeries.setName(caseDescMap.get("testCaseDesc"));
tetcaseSeries.setColor("#F38F1F");
tetcaseSeries.setRadius("20%");
tetcaseSeries.setType("bar");
tetcaseSeries.setStack("total");
tetcaseSeries.setData(testCaseCountList);
seriesList.add(tetcaseSeries);
Series apiSeries = new Series();
apiSeries.setName(caseDescMap.get("apiCaseDesc"));
apiSeries.setColor("#6FD999");
apiSeries.setType("bar");
apiSeries.setStack("total");
apiSeries.setData(apiCaseCountList);
seriesList.add(apiSeries);
Series scenarioSeries = new Series();
scenarioSeries.setName(caseDescMap.get("scenarioCaseDesc"));
scenarioSeries.setColor("#2884F3");
scenarioSeries.setType("bar");
scenarioSeries.setStack("total");
scenarioSeries.setData(scenarioCaseCountList);
seriesList.add(scenarioSeries);
Series loadSeries = new Series();
loadSeries.setName(caseDescMap.get("loadCaseDesc"));
loadSeries.setColor("#F45E53");
loadSeries.setType("bar");
loadSeries.setStack("total");
loadSeries.setData(loadCaseCountList);
seriesList.add(loadSeries);
dto.setXAxis(xAxis);
dto.setYAxis(new YAxis());
dto.setSeries(seriesList);
}
private void formatLegend(Legend legend, List<String> datas, TestCaseCountRequest yrequest) {
Map<String, Boolean> selected = new LinkedHashMap<>();
List<String> list = new LinkedList<>();
legend.setSelected(selected);
legend.setData(datas);
}
private void formatTable(List<TestCaseCountTableDTO> dtos, Map<String, TestCaseCountSummary> summaryMap) {
for (TestCaseCountSummary summary : summaryMap.values()) {
TestCaseCountTableDTO dto = new TestCaseCountTableDTO(summary.groupName, summary.testCaseCount, summary.apiCaseCount, summary.scenarioCaseCount, summary.loadCaseCount);
dtos.add(dto);
}
}
private Map<String, String> getCaseDescMap() {
Map<String, String> map = new HashMap<>();
map.put("testCaseDesc", Translator.get("test_case"));
map.put("apiCaseDesc", Translator.get("api_case"));
map.put("scenarioCaseDesc", Translator.get("scenario_case"));
map.put("loadCaseDesc", Translator.get("performance_case"));
return map;
}
public Map<String, List<Map<String, String>>> getSelectFilterDatas(String projectId) {
Map<String, List<Map<String, String>>> returnMap = new HashMap<>();
//组装用户
QueryMemberRequest memberRequest = new QueryMemberRequest();
memberRequest.setProjectId(projectId);
List<User> userList = userService.getUserList();
List<Map<String, String>> returnUserList = new ArrayList<>();
for (User user : userList) {
Map<String, String> map = new HashMap<>();
map.put("id", user.getId());
map.put("label", user.getId() + "(" + user.getName() + ")");
returnUserList.add(map);
}
//组装用例等级和用例状态
TestCaseTemplateService testCaseTemplateService = CommonBeanFactory.getBean(TestCaseTemplateService.class);
TestCaseTemplateDao testCaseTemplate = testCaseTemplateService.getTemplate(projectId);
List<Map<String, String>> caseLevelList = new ArrayList<>();
List<Map<String, String>> caseStatusList = new ArrayList<>();
Map<String, String> statusMap1 = new HashMap<>();
statusMap1.put("id", "Prepare");
statusMap1.put("label", Translator.get("test_case_status_prepare"));
// Map<String, String> statusMap2 = new HashMap<>();
// statusMap2.put("id", "Error");
// statusMap2.put("label", Translator.get("test_case_status_error"));
//
// Map<String, String> statusMap3 = new HashMap<>();
// statusMap3.put("id", "Success");
// statusMap3.put("label", Translator.get("test_case_status_success"));
// Map<String, String> statusMap4 = new HashMap<>();
// statusMap4.put("id", "Trash");
// statusMap4.put("label", Translator.get("test_case_status_trash"));
// Map<String, String> statusMap5 = new HashMap<>();
// statusMap5.put("id", "Underway");
// statusMap5.put("label", Translator.get("test_case_status_running"));
// Map<String, String> statusMap6 = new HashMap<>();
// statusMap6.put("id", "Starting");
// statusMap6.put("label", Translator.get("test_case_status_running"));
Map<String, String> statusMap7 = new HashMap<>();
statusMap7.put("id", "Saved");
statusMap7.put("label", Translator.get("test_case_status_saved"));
Map<String, String> statusMap8 = new HashMap<>();
statusMap8.put("id", "Running");
statusMap8.put("label", Translator.get("test_case_status_running"));
Map<String, String> statusMap9 = new HashMap<>();
statusMap9.put("id", "Finished");
statusMap9.put("label", Translator.get("test_case_status_finished"));
caseStatusList.add(statusMap1);
// caseStatusList.add(statusMap2);
// caseStatusList.add(statusMap3);
// caseStatusList.add(statusMap4);
// caseStatusList.add(statusMap5);
// caseStatusList.add(statusMap6);
caseStatusList.add(statusMap7);
caseStatusList.add(statusMap8);
caseStatusList.add(statusMap9);
Map<String, String> levelMap1 = new HashMap<>();
levelMap1.put("id", "P0");
levelMap1.put("label", "P0");
Map<String, String> levelMap2 = new HashMap<>();
levelMap2.put("id", "P1");
levelMap2.put("label", "P1");
Map<String, String> levelMap3 = new HashMap<>();
levelMap3.put("id", "P2");
levelMap3.put("label", "P2");
Map<String, String> levelMap4 = new HashMap<>();
levelMap4.put("id", "P3");
levelMap4.put("label", "P3");
caseLevelList.add(levelMap1);
caseLevelList.add(levelMap2);
caseLevelList.add(levelMap3);
caseLevelList.add(levelMap4);
if (testCaseTemplate != null && CollectionUtils.isNotEmpty(testCaseTemplate.getCustomFields())) {
for (CustomField customField : testCaseTemplate.getCustomFields()) {
if (StringUtils.equals(customField.getName(), "用例状态")) {
JSONArray optionsArr = JSONArray.parseArray(customField.getOptions());
for (int i = 0; i < optionsArr.size(); i++) {
JSONObject jsonObject = optionsArr.getJSONObject(i);
if (jsonObject.containsKey("value") && jsonObject.containsKey("text")) {
String value = jsonObject.getString("value");
if(!StringUtils.equalsAnyIgnoreCase(value, "Prepare", "Error", "Success", "Trash", "Underway", "Starting", "Saved",
"Completed","test_track.case.status_finished")){
Map<String, String> statusMap = new HashMap<>();
statusMap.put("id", jsonObject.getString("value"));
statusMap.put("label", jsonObject.getString("text"));
caseStatusList.add(statusMap);
}
}
}
} else if (StringUtils.equals(customField.getName(), "用例等级")) {
JSONArray optionsArr = JSONArray.parseArray(customField.getOptions());
for (int i = 0; i < optionsArr.size(); i++) {
JSONObject jsonObject = optionsArr.getJSONObject(i);
if (jsonObject.containsKey("value") && jsonObject.containsKey("text") &&
!StringUtils.equalsAnyIgnoreCase(jsonObject.getString("value"), "P0", "P1", "P2", "P3")) {
Map<String, String> levelMap = new HashMap<>();
levelMap.put("id", jsonObject.getString("value"));
levelMap.put("label", jsonObject.getString("text"));
caseLevelList.add(levelMap);
}
}
}
}
}
Map<String, String> caseDescMap = this.getCaseDescMap();
// 组装用例类型
List<Map<String, String>> caseTypeList = new ArrayList<>();
Map<String, String> caseTypeMap1 = new HashMap<>();
caseTypeMap1.put("id", "testCase");
caseTypeMap1.put("label", caseDescMap.get("testCaseDesc"));
Map<String, String> caseTypeMap2 = new HashMap<>();
caseTypeMap2.put("id", "apiCase");
caseTypeMap2.put("label", caseDescMap.get("apiCaseDesc"));
Map<String, String> caseTypeMap3 = new HashMap<>();
caseTypeMap3.put("id", "scenarioCase");
caseTypeMap3.put("label", caseDescMap.get("scenarioCaseDesc"));
Map<String, String> caseTypeMap4 = new HashMap<>();
caseTypeMap4.put("id", "loadCase");
caseTypeMap4.put("label", caseDescMap.get("loadCaseDesc"));
caseTypeList.add(caseTypeMap1);
caseTypeList.add(caseTypeMap2);
caseTypeList.add(caseTypeMap3);
caseTypeList.add(caseTypeMap4);
returnMap.put("casetype", caseTypeList);
returnMap.put("caselevel", caseLevelList);
returnMap.put("casestatus", caseStatusList);
returnMap.put("creator", returnUserList);
returnMap.put("maintainer", returnUserList);
return returnMap;
}
}

@ -1 +1 @@
Subproject commit bc8f05fdd7bfa0a13438d409394a2d150187f88b
Subproject commit 6edae7aeeb9d5ade65d64a115d00af96e4dc56d6

View File

@ -816,6 +816,18 @@
"name": "导出",
"resourceId": "PROJECT_REPORT_ANALYSIS",
"license": true
},
{
"id": "PROJECT_REPORT_ANALYSIS:READ+UPDATE",
"name": "保存",
"resourceId": "PROJECT_REPORT_ANALYSIS",
"license": true
},
{
"id": "PROJECT_REPORT_ANALYSIS:READ+CREATE",
"name": "另存为",
"resourceId": "PROJECT_REPORT_ANALYSIS",
"license": true
}
],
"resource": [

View File

@ -5,11 +5,12 @@ import Setting from "@/business/components/settings/router";
import API from "@/business/components/api/router";
import Performance from "@/business/components/performance/router";
import Track from "@/business/components/track/router";
import ReportStatistics from "@/business/components/reportstatistics/router";
import {getCurrentUserId} from "@/common/js/utils";
const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/);
const Report = requireContext.keys().map(key => requireContext(key).report);
const ReportObj = Report && Report != null && Report.length > 0 && Report[0] != undefined ? Report : [{path: "/sidebar"}];
// const requireContext = require.context('@/business/components/xpack/', true, /router\.js$/);
// const Report = requireContext.keys().map(key => requireContext(key).report);
// const ReportObj = Report && Report != null && Report.length > 0 && Report[0] != undefined ? Report : [{path: "/sidebar"}];
Vue.use(VueRouter);
@ -26,7 +27,8 @@ const router = new VueRouter({
API,
Performance,
Track,
...ReportObj
ReportStatistics,
// ...ReportStatistics
]
});

View File

@ -290,11 +290,14 @@ export default {
this.$refs.tree.setCheckedKeys(thisKeys);
this.returnDataKeys = thisKeys;
let t = [];
this.options = [];
thisKeys.map((item) => {//option
let node = this.$refs.tree.getNode(item); // node
t.push(node.data);
this.options.push({label: node.label, value: node.key});
return {label: node.label, value: node.key};
if(node){
t.push(node.data);
this.options.push({label: node.label, value: node.key});
return {label: node.label, value: node.key};
}
});
this.returnDatas = t;
this.popoverHide()
@ -398,6 +401,23 @@ export default {
},
deep: true
},
defaultKey:{
handler:function(){
this.init();
if(this.data && this.data.length > 0){
this.$refs.tree.setCheckedKeys(this.defaultKey);
}
},
deep:true
},
data:{
handler:function(){
if(this.defaultKey && this.defaultKey.length > 0){
this.$refs.tree.setCheckedKeys(this.defaultKey);
}
},
deep:true
},
filterText(val) {
this.$nextTick(() => {
this.$refs.tree.filter(val);

View File

@ -0,0 +1,101 @@
<template>
<div>
<el-row type="flex">
<p class="tip">
<span class="ms-span">{{$t('commons.report_statistics.name')}}</span>
<el-select v-model="reportType" class="ms-col-type" size="mini" style="width: 120px">
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in reportTypes"/>
</el-select>
</p>
</el-row>
<transition>
<keep-alive>
<report-card @openCard="openCard"/>
</keep-alive>
</transition>
<!-- 测试用例趋势页面 -->
<ms-drawer :visible="testCaseTrendDrawer" :size="100" @close="close" direction="right" :show-full-screen="false" :is-show-close="false" style="overflow: hidden">
<template v-slot:header>
<report-header :title="$t('commons.report_statistics.test_case_analysis')" :history-report-id="historyReportId"
@closePage="close" @saveReport="saveReport" @selectAndSaveReport="selectAndSaveReport"/>
</template>
<test-analysis-container @initHistoryReportId="initHistoryReportId" ref="testAnalysisContainer"/>
</ms-drawer>
<!-- 测试用例分析页面 -->
<ms-drawer :visible="testCaseCountDrawer" :size="100" @close="close" direction="right" :show-full-screen="false" :is-show-close="false" style="overflow: hidden">
<template v-slot:header>
<report-header :title="$t('commons.report_statistics.test_case_count')" :history-report-id="historyReportId"
@closePage="close" @saveReport="saveReport" @selectAndSaveReport="selectAndSaveReport"/>
</template>
<test-case-count-container @initHistoryReportId="initHistoryReportId" ref="testCaseCountContainer"/>
</ms-drawer>
</div>
</template>
<script>
import ReportCard from "./ReportCard";
import TestAnalysisContainer from "./track/TestAnalysisContainer";
import MsDrawer from "@/business/components/common/components/MsDrawer";
import ReportHeader from './base/ReportHeader';
import TestCaseCountContainer from "./testCaseCount/TestCaseCountContainer";
export default {
name: "ReportAnalysis",
components: {ReportCard, TestAnalysisContainer, MsDrawer, ReportHeader, TestCaseCountContainer},
data() {
return {
reportType: "track",
testCaseTrendDrawer: false,
testCaseCountDrawer: false,
historyReportId:"",
reportTypes: [{id: 'track', name: this.$t('test_track.test_track')}],
}
},
methods: {
openCard(type) {
if(type === 'trackTestCase'){
this.testCaseTrendDrawer = true;
}else if(type === 'countTestCase'){
this.testCaseCountDrawer = true;
}
},
close() {
this.testCaseTrendDrawer = false;
this.testCaseCountDrawer = false;
},
saveReport(){
if(this.testCaseTrendDrawer){
this.$refs.testAnalysisContainer.saveReport();
}else if(this.testCaseCountDrawer){
this.$refs.testCaseCountContainer.saveReport();
}
},
selectAndSaveReport(){
if(this.testCaseTrendDrawer){
this.$refs.testAnalysisContainer.selectAndSaveReport();
}else if(this.testCaseCountDrawer){
this.$refs.testCaseCountContainer.selectAndSaveReport();
}
},
initHistoryReportId(reportId){
this.historyReportId = reportId;
},
},
}
</script>
<style scoped>
.ms-span {
margin: 10px 10px 0px
}
.tip {
float: left;
font-size: 14px;
border-radius: 2px;
border-left: 2px solid #783887;
margin: 10px 20px 0px;
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<div class="ms-content">
<el-row>
<el-col :span="4">
<el-card :body-style="{ padding: '0px' }" class="ms-col" @click.native="openCard('trackTestCase')">
<img src="../../../assets/track.jpg" class="image">
<div style="padding: 10px;">
<span>{{$t('commons.report_statistics.test_case_analysis')}}</span>
<div class="bottom clearfix">
<time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time>
</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card :body-style="{ padding: '0px' }" class="ms-col" @click.native="openCard('countTestCase')">
<img src="../../../assets/track.jpg" class="image">
<div style="padding: 10px;">
<span>{{$t('commons.report_statistics.test_case_count')}}</span>
<div class="bottom clearfix">
<time class="time">{{$t('commons.report_statistics.test_case_count_activity')}}</time>
</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card :body-style="{ padding: '0px' }" class="ms-col">
<img src="../../../assets/other.png" class="image">
<div style="padding: 10px;">
<span>预留模块敬请期待</span>
<div class="bottom clearfix">
<time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time>
</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card :body-style="{ padding: '0px' }" class="ms-col">
<img src="../../../assets/other.png" class="image">
<div style="padding: 10px;">
<span>预留模块敬请期待</span>
<div class="bottom clearfix">
<time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time>
</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card :body-style="{ padding: '0px' }" class="ms-col">
<img src="../../../assets/other.png" class="image">
<div style="padding: 10px;">
<span>预留模块敬请期待</span>
<div class="bottom clearfix">
<time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time>
</div>
</div>
</el-card>
</el-col>
<el-col :span="4">
<el-card :body-style="{ padding: '0px' }" class="ms-col">
<img src="../../../assets/other.png" class="image">
<div style="padding: 10px;">
<span>预留模块敬请期待</span>
<div class="bottom clearfix">
<time class="time">{{$t('commons.report_statistics.test_case_activity')}}</time>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import {hasPermission} from "@/common/js/utils";
export default {
name: "ReportCard",
components: {},
data() {
return {}
},
methods: {
openCard(type) {
if (!hasPermission('PROJECT_REPORT_ANALYSIS:READ')) {
this.$warning("无查看权限!");
return;
}
this.$emit('openCard', type);
}
},
}
</script>
<style scoped>
.time {
font-size: 13px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 100%;
display: block;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.ms-col {
margin: 5px;
}
.ms-content {
padding: 15px 10px 15px 15px;
}
.ms-col:hover {
cursor: pointer;
border-color: #783887;
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<div>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('commons.report_statistics.report_data.all_report')" name="allReport">
<history-report-data-card :report-data="allReportData" :show-options-button="false" @deleteReport="deleteReport" @selectReport="selectReport"/>
</el-tab-pane>
<el-tab-pane :label="$t('commons.report_statistics.report_data.my_report')" name="myReport">
<history-report-data-card :report-data="myReportData" :show-options-button="true" @deleteReport="deleteReport" @selectReport="selectReport"/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import {getCurrentProjectID,getCurrentUserId} from "@/common/js/utils";
import HistoryReportDataCard from "./compose/HistoryReportDataCard";
export default {
name: "HistoryReportData",
components: {HistoryReportDataCard},
data() {
return {
activeName: 'allReport',
allReportData: [],
myReportData: [],
}
},
props:{
reportType:String
},
created(){
this.initReportData();
},
watch :{
activeName(){
this.initReportData();
}
},
methods: {
initReportData(){
let projectId = getCurrentProjectID();
let userId = getCurrentUserId();
this.allReportData = [];
this.myReportData = [];
let paramsObj = {
projectId:getCurrentProjectID(),
reportType:this.reportType,
};
this.$post('/history/report/selectByParams',paramsObj, response => {
let allData = response.data;
allData.forEach(item => {
if(item){
this.allReportData.push(item);
if(item.createUser === userId){
this.myReportData.push(item);
}
}
});
});
},
deleteReport(deleteId){
let paramObj = {
id:deleteId
}
this.$post('/history/report/deleteByParam',paramObj, response => {
this.initReportData();
});
this.$emit("removeHistoryReportId");
},
selectReport(id){
this.$emit("selectReport",id);
}
},
}
</script>
<style scoped>
.historyCard{
border: 0px;
}
/deep/ .el-card__header{
border: 0px;
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<div class="ms-header">
<el-row>
<div class="ms-div">{{title}}</div>
<div class="ms-header-right">
<el-button type="primary" v-if="isSaveAsButtonShow" size="mini" @click="handleSaveAs" :disabled="readOnly">{{ $t('commons.save_as') }}<i class="el-icon-files el-icon--right"></i></el-button>
<el-button type="primary" v-if="isSaveButtonShow" size="mini" @click="handleSave" :disabled="readOnly">{{ $t('commons.save') }}<i class="el-icon-files el-icon--right"></i></el-button>
<el-button type="" size="mini" @click="handleExport" :disabled="readOnly">{{ $t('report.export') }}<i class="el-icon-download el-icon--right"></i></el-button>
<span class="ms-span">|</span>
<i class="el-icon-close report-alt-ico" @click="close"/>
</div>
</el-row>
</div>
</template>
<script>
import {exportPdf, hasPermission} from "@/common/js/utils";
import html2canvas from 'html2canvas';
export default {
name: "ReportHeader",
components: {},
data() {
return {}
},
props:{
title:String,
historyReportId:String,
},
created() {
},
computed: {
readOnly() {
return !hasPermission('PROJECT_REPORT_ANALYSIS:READ+EXPORT');
},
isSaveAsButtonShow(){
if(!this.historyReportId || this.historyReportId === null || this.historyReportId === ''){
return false;
}else {
if(hasPermission('PROJECT_REPORT_ANALYSIS:READ+CREATE')){
return true;
}else {
return false;
}
}
},
isSaveButtonShow(){
if(hasPermission('PROJECT_REPORT_ANALYSIS:READ+UPDATE')){
return true;
}else {
return false;
}
}
},
methods: {
handleExport() {
let name = this.title;
this.$nextTick(function () {
setTimeout(() => {
html2canvas(document.getElementById('reportAnalysis'), {
scale: 2
}).then(function (canvas) {
exportPdf(name, [canvas]);
});
}, 1000);
});
},
handleSave(){
this.$emit("saveReport");
},
handleSaveAs(){
this.$emit("selectAndSaveReport");
},
close() {
this.$emit('closePage');
},
},
}
</script>
<style scoped>
.ms-header {
border-bottom: 1px solid #E6E6E6;
background-color: #FFF;
}
.ms-div {
float: left;
margin-left: 20px;
margin-top: 12px;
}
.ms-span {
margin: 0px 10px 10px;
}
.ms-header-right {
float: right;
/*width: 320px;*/
margin-bottom: 10px;
margin-top: 10px;
margin-right: 20px;
}
.report-alt-ico {
font-size: 17px;
top: auto;
}
.report-alt-ico:hover {
color: black;
cursor: pointer;
font-size: 18px;
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<div v-if="loadIsOver" v-loading="loading">
<el-card class="historyCard" v-for="item in reportData" :key="item.id">
<div slot="header">
<li style="color:var(--count_number); font-size: 18px">
<el-input v-if="item.isEdit === 'edit'" size="mini" @blur="updateReport(item)" v-model="item.name"/>
<el-link v-if="item.isEdit !== 'edit'" type="info" @click="selectReport(item.id)" target="_blank" style="color:#303133; font-size: 14px">
{{ item.name }}
</el-link>
<el-button v-if="showOptionsButton && item.isEdit !== 'edit'" style="float: right; padding: 3px 0; border: 0px;margin-left: 5px" icon="el-icon-delete" size="mini" @click="deleteReport(item.id)"></el-button>
<el-button v-if="showOptionsButton && item.isEdit !== 'edit'" style="float: right; padding: 3px 0; border: 0px" icon="el-icon-edit" size="mini" @click="renameReport(item)"></el-button>
</li>
</div>
<div class="text item">
<span>{{ item.createTime | timestampFormatDate }}</span>
</div>
</el-card>
</div>
</template>
<script>
export default {
name: "HistoryReportDataCard",
components: {},
data() {
return {
loadIsOver: true,
loading: false,
}
},
props:{
reportData:Array,
showOptionsButton:Boolean
},
watch:{
reportData:{
handler:function (){
this.loading = false;
},
deep:true
}
},
methods: {
reload(){
this.loadIsOver = false;
this.$nextTick(() => {
this.loadIsOver = true;
})
},
deleteReport(id){
this.loading = true;
this.$emit("deleteReport",id);
},
renameReport(item){
item.isEdit = 'edit';
this.reload();
},
selectReport(id){
this.$emit("selectReport",id);
},
updateReport(item){
let obj = {
name: item.name,
id: item.id
};
this.$post("/history/report/updateReport", obj, response => {
});
item.isEdit = "";
this.reload();
}
},
}
</script>
<style scoped>
.historyCard{
border: 0px;
}
/deep/ .el-card__header{
border: 0px;
padding-bottom: 0px;
padding-top: 5px;
}
</style>

View File

@ -0,0 +1,16 @@
const reportForm = () => import('./ReportAnalysis');
export default {
path: "/report",
name: "report",
redirect: "/report/home",
components: {
content: reportForm
},
children: [
{
path: 'home',
name: 'reportHome',
},
]
}

View File

@ -0,0 +1,217 @@
<template>
<div>
<el-container v-loading="loading" id="reportAnalysis" style="overflow: scroll">
<el-container class="ms-row">
<el-aside v-if="!isHide" :width="!isHide ?'235px':'0px'" :style="{ 'max-height': h-50 + 'px', 'margin-left': '5px'}" >
<history-report-data report-type="TEST_CASE_COUNT"
@selectReport="selectReport" @removeHistoryReportId="removeHistoryReportId"
ref="historyReport"/>
</el-aside>
<el-main class="ms-main" style="padding: 0px 5px 0px">
<div>
<test-case-count-chart @hidePage="hidePage" @orderCharts="orderCharts" ref="analysisChart"
:chart-width="chartWidth" :load-option="loadOption" :pie-option="pieOption"/>
</div>
<div class="ms-row" v-if="!isHide">
<test-case-count-table :group-name="getGroupNameStr(options.xaxis)" :show-coloums="options.yaxis" :tableData="tableData"/>
</div>
</el-main>
<el-aside v-if="!isHide" style="height: 100%" :width="!isHide ?'485px':'0px'">
<test-case-count-filter @filterCharts="filterCharts" ref="countFilter"/>
</el-aside>
</el-container>
</el-container>
</div>
</template>
<script>
import TestCaseCountChart from "./chart/TestCaseCountChart";
import TestCaseCountTable from "@/business/components/reportstatistics/testCaseCount/table/TestCaseCountTable";
import TestCaseCountFilter from "./filter/TestCaseCountFilter";
import {exportPdf,getCurrentProjectID} from "@/common/js/utils";
import html2canvas from 'html2canvas';
import HistoryReportData from "../base/HistoryReportData";
export default {
name: "TestCaseCountContainer",
components: {TestCaseCountChart, TestCaseCountTable, TestCaseCountFilter, HistoryReportData},
data() {
return {
isHide: false,
loading: false,
options: {},
chartWidth: 0,
tableHeight: 300,
loadOption: {
legend: {},
xAxis: {},
yAxis: {},
label: {},
tooltip: {},
series: []
},
pieOption: {
legend: {},
label: {},
tooltip: {},
series: [],
title: [],
},
tableData: [],
h: document.documentElement.clientHeight - 40,
};
},
methods: {
handleExport() {
let name = this.$t('commons.report_statistics.test_case_analysis');
this.$nextTick(function () {
setTimeout(() => {
html2canvas(document.getElementById('reportAnalysis'), {
scale: 2
}).then(function (canvas) {
exportPdf(name, [canvas]);
});
}, 1000);
});
},
hidePage(isHide) {
this.isHide = isHide;
},
close() {
this.$emit('closePage');
},
init(opt) {
this.loading = true;
this.options = opt;
this.$post(' /report/test/case/count/getReport', opt, response => {
let data = response.data.barChartDTO;
let pieData = response.data.pieChartDTO;
let selectTableData = response.data.tableDTOs;
this.initPic(data,pieData,selectTableData);
},error => {
this.loading = false;
});
},
initPic(barData,pieData,selectTableData){
this.loading = true;
if (barData) {
this.loadOption.legend = barData.legend;
this.loadOption.xAxis = barData.xaxis;
this.loadOption.xaxis = barData.xaxis;
this.loadOption.series = barData.series;
this.loadOption.grid = {
bottom: '75px',//
};
this.loadOption.series.forEach(item => {
item.type = this.$refs.analysisChart.chartType;
});
}
if (pieData) {
this.pieOption.legend = pieData.legend;
this.pieOption.series = pieData.series;
this.pieOption.title = pieData.title;
this.pieOption.grid = {
bottom: '75px',//
};
if (pieData.width) {
this.pieOption.width = pieData.width;
this.chartWidth = pieData.width;
}
this.pieOption.series.forEach(item => {
item.type = this.$refs.analysisChart.chartType;
});
}
if (selectTableData) {
this.tableData = selectTableData;
}
this.loading = false;
this.$refs.analysisChart.reload();
},
filterCharts(opt) {
this.init(opt);
},
orderCharts(order) {
this.options.order = order;
this.filterCharts(this.options);
},
saveReport() {
let obj = {};
obj.projectId = getCurrentProjectID();
obj.selectOption = JSON.stringify(this.options);
let dataOptionObj = {
loadOption: this.loadOption,
pieOption: this.pieOption,
tableData: this.tableData,
};
obj.dataOption = JSON.stringify(dataOptionObj);
obj.reportType = 'TEST_CASE_COUNT';
this.$post("/history/report/saveReport", obj, response => {
this.$alert(this.$t('commons.save_success'));
this.$refs.historyReport.initReportData();
});
},
selectReport(selectId){
if(selectId){
this.loading = true;
let paramObj = {
id:selectId
}
this.$post('/history/report/selectById',paramObj, response => {
let reportData = response.data;
if(reportData){
if(reportData.dataOption){
let dataOptionObj = JSON.parse(reportData.dataOption);
this.initPic(dataOptionObj.loadOption,dataOptionObj.pieOption,dataOptionObj.tableData);
}
if(reportData.selectOption){
let selectOptionObj = JSON.parse(reportData.selectOption);
this.$refs.countFilter.initSelectOption(selectOptionObj);
}
this.loading = false;
}
}, (error) => {
this.loading = false;
});
this.$emit('initHistoryReportId',selectId);
}
},
removeHistoryReportId(){
this.$emit('initHistoryReportId',"");
},
getGroupNameStr(groupName){
if(groupName === 'creator') {
return this.$t('commons.report_statistics.report_filter.select_options.creator');
}else if(groupName === 'maintainer'){
return this.$t('commons.report_statistics.report_filter.select_options.maintainer');
}else if(groupName === 'casetype'){
return this.$t('commons.report_statistics.report_filter.select_options.case_type');
}else if(groupName === 'casestatus'){
return this.$t('commons.report_statistics.report_filter.select_options.case_status');
}else if(groupName === 'caselevel'){
return this.$t('commons.report_statistics.report_filter.select_options.case_level');
}else {
return "";
}
},
selectAndSaveReport(){
let opt = this.$refs.countFilter.getOption();
this.options = opt;
this.saveReport();
}
},
};
</script>
<style scoped>
.ms-row {
padding-top: 5px;
}
/deep/ .el-main {
padding: 0px 20px 0px;
}
</style>

View File

@ -0,0 +1,284 @@
<template>
<div v-loading="loading">
<el-card class="ms-test-chart" :style="{ width: w+'px', height: h + 'px'}" ref="msDrawer">
<el-row class="ms-row">
<p class="tip"><span style="margin-left: 5px"></span> {{$t('commons.report_statistics.chart')}} </p>
<div class="ms-test-chart-header">
<el-dropdown @command="exportCommand" :hide-on-click="false">
<span class="el-dropdown-link">
{{ $t('commons.export') }}<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="jpg">JPG</el-dropdown-item>
<el-dropdown-item command="png">PNG</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span style="margin: 0px 10px 10px">|</span>
<el-select v-model="chartType" class="ms-col-type" size="mini" style="width: 100px" @change="generateOption">
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in charts"/>
</el-select>
<span style="margin: 0px 10px 10px">|</span>
<el-select v-model="order" class="ms-col-type" size="mini" style="width: 120px" @change="orderCharts">
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in orders"/>
</el-select>
<span style="margin: 0px 10px 10px">|</span>
<font-awesome-icon v-if="!isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'expand-alt']" size="lg" @click="fullScreen"/>
<font-awesome-icon v-if="isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'compress-alt']" size="lg" @click="unFullScreen"/>
</div>
</el-row>
<el-row style="overflow: auto">
<ms-chart ref="chart1" v-if="!loading" :options="dataOption" :style="{width: chartWidthNumber+'px', height: (h-50) + 'px'}" class="chart-config" :autoresize="true" id="picChart"/>
</el-row>
</el-card>
</div>
</template>
<script>
import echarts from "echarts";
import MsChart from "@/business/components/common/chart/MsChart";
export default {
name: "TestCaseCountChart",
components: {MsChart},
props: {
loadOption: {},
pieOption: {},
chartWidth:Number,
},
data() {
return {
dataOption:{},
x: 0,
y: 0,
w: document.documentElement.clientWidth - 760,
h: document.documentElement.clientHeight * 0.5 ,
chartWidthNumber:document.documentElement.clientWidth - 760,
isFullScreen: false,
originalW: 100,
originalH: 100,
showFullScreen: {
type: Boolean,
default() {
return true;
}
},
//
chartType: "bar",
charts: [
{id: 'bar', name: this.$t('commons.report_statistics.bar')},
{id: 'pie', name: this.$t('commons.report_statistics.pie')}
],
order: "",
orders: [{id: '', name: '默认排序'},{id: 'desc', name: this.$t('commons.report_statistics.desc')}, {id: 'asc', name: this.$t('commons.report_statistics.asc')}],
loading: false,
options: {},
pieItemOption:{
dataset: [{
source: [
['Product', 'Sales', 'Price', 'Year'],
['Cake', 123, 32, 2011],
['Cereal', 231, 14, 2011],
['Tofu', 235, 5, 2011],
['Dumpling', 341, 25, 2011],
['Biscuit', 122, 29, 2011],
['Cake', 143, 30, 2012],
['Cereal', 201, 19, 2012],
['Tofu', 255, 7, 2012],
['Dumpling', 241, 27, 2012],
['Biscuit', 102, 34, 2012],
['Cake', 153, 28, 2013],
['Cereal', 181, 21, 2013],
['Tofu', 395, 4, 2013],
['Dumpling', 281, 31, 2013],
['Biscuit', 92, 39, 2013],
['Cake', 223, 29, 2014],
['Cereal', 211, 17, 2014],
['Tofu', 345, 3, 2014],
['Dumpling', 211, 35, 2014],
['Biscuit', 72, 24, 2014],
],
}, {
transform: {
type: 'filter',
config: { dimension: 'Year', value: 2011 }
},
}, {
transform: {
type: 'filter',
config: { dimension: 'Year', value: 2012 }
}
}, {
transform: {
type: 'filter',
config: { dimension: 'Year', value: 2013 }
}
}],
series: [{
type: 'pie', radius: 50, center: ['50%', '25%'],
datasetIndex: 1
}, {
type: 'pie', radius: 50, center: ['50%', '50%'],
datasetIndex: 2
}, {
type: 'pie', radius: 50, center: ['50%', '75%'],
datasetIndex: 3
}],
media: [{
query: { minAspectRatio: 1 },
option: {
series: [
{ center: ['25%', '50%'] },
{ center: ['50%', '50%'] },
{ center: ['75%', '50%'] }
]
}
}, {
option: {
series: [
{ center: ['50%', '25%'] },
{ center: ['50%', '50%'] },
{ center: ['50%', '75%'] }
]
}
}]
},
}
},
created() {
this.dataOption = this.loadOption;
},
watch:{
chartWidth(){
this.countChartWidth();
},
chartType(){
this.countChartWidth();
}
},
methods: {
countChartWidth(){
if(this.chartWidth === 0 || this.chartType === 'bar'){
this.chartWidthNumber = this.w;
}else {
this.chartWidthNumber = this.chartWidth;
}
},
orderCharts() {
this.$emit('orderCharts', this.order);
},
generateOption() {
if(this.chartType === 'pie'){
this.dataOption = this.pieOption;
}else {
this.dataOption = this.loadOption;
}
this.dataOption.series.forEach(item => {
item.type = this.chartType;
})
this.reload();
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
fullScreen() {
this.originalW = this.w;
this.originalH = this.h;
this.w = document.body.clientWidth - 50;
this.h = document.body.clientHeight;
this.isFullScreen = true;
this.$emit('hidePage', true);
},
unFullScreen() {
this.w = this.originalW;
this.h = this.originalH;
this.isFullScreen = false;
this.$emit('hidePage', false);
},
exportCommand(command){
let fileName = 'report_pic.'+command;
if (document.getElementById('picChart')) {
let chartsCanvas = document.getElementById('picChart').querySelectorAll('canvas')[0]
let mime = 'image/png';
if(command === 'jpg'){
mime = 'image/jpg';
}
if (chartsCanvas) {
// toDataURL()canvascanvasbase64
let imageUrl = chartsCanvas && chartsCanvas.toDataURL(mime)
if (navigator.userAgent.indexOf('Trident') > -1) {
// IE11
let arr = imageUrl.split(',')
// atob() 使base64
let bstr = atob(arr[1])
let bstrLen = bstr.length
// Uint8Array, 8 0
let u8arr = new Uint8Array(bstrLen)
while (bstrLen--) {
// charCodeAt() Unicode
u8arr[bstrLen] = bstr.charCodeAt(bstrLen)
}
// msSaveOrOpenBlob Internet
window.navigator.msSaveOrOpenBlob(new Blob([u8arr], {type: mime}), fileName );
} else {
//
let $a = document.createElement('a')
$a.setAttribute('href', imageUrl)
$a.setAttribute('download', fileName)
$a.click()
}
}
}
},
},
}
</script>
<style scoped>
.ms-test-chart-header {
z-index: 999;
width: 380px;
float: right;
margin-right: 10px;
}
.report-alt-ico {
font-size: 15px;
margin: 0px 10px 0px;
color: #8c939d;
}
.report-alt-ico:hover {
color: black;
cursor: pointer;
font-size: 18px;
}
/deep/ .echarts {
height: calc(100vh / 1.95);
}
.tip {
float: left;
font-size: 14px;
border-radius: 2px;
border-left: 2px solid #783887;
margin: 0px 20px 0px;
}
.ms-row {
padding-top: 10px;
}
.chart-config {
width: 100%;
}
/deep/ .el-card__body {
padding: 0px;
}
</style>

View File

@ -0,0 +1,401 @@
<template>
<div v-loading="loading">
<el-card :style="{height: h + 'px'}" class="ms-card">
<el-row style="padding-top: 10px">
<p class="tip"><span style="margin-left: 5px"></span> {{ $t('commons.report_statistics.options') }}</p>
</el-row>
<el-row class="ms-row">
<p>{{ $t('commons.report_statistics.report_filter.xaxis') }}</p>
<el-select class="ms-http-select" size="small" v-model="option.xaxis" style="width: 100%">
<el-option v-for="item in xAxisOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</el-row>
<el-row class="ms-row">
<p>{{ $t('commons.report_statistics.report_filter.yaxis') }}</p>
<el-select class="ms-http-select" size="small" v-model="option.yaxis" multiple style="width: 100%">
<el-option v-for="item in yAxisOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</el-row>
<el-row class="ms-row">
<p>{{ $t('commons.create_time')}}</p>
<div style="width: 25%;float: left">
<el-select class="ms-http-select" size="small" v-model="option.timeType" >
<el-option v-for="item in timeTypeOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</div>
<div v-if="option.timeType === 'fixedTime'" style="width: 70%;margin-left: 20px;float: left">
<el-date-picker
size="small"
v-model="option.times"
type="datetimerange"
value-format="timestamp"
:range-separator="$t('api_monitor.to')"
:start-placeholder="$t('commons.date.start_date')"
:end-placeholder="$t('commons.date.end_date')"
:picker-options="datePickerOptions"
style="margin-left: 10px;width: 100%">
</el-date-picker>
</div>
<div v-if="option.timeType === 'dynamicTime'" style="width: 70%;margin-left: 20px;float: left">
<span style="width: 20%">{{ $t('commons.report_statistics.report_filter.recently') }}</span>
<el-select class="ms-http-select" size="small" v-model="option.timeFilter.timeRange" style="width: 30%;margin-left: 10px;width: 40%">
<el-option v-for="item in timeRangeNumberMax" :key="item" :label="item" :value="item"/>
</el-select>
<el-select class="ms-http-select" size="small" v-model="option.timeFilter.timeRangeUnit"
@change="timeRangeUnitChange"
style="width: 30%;margin-left: 10px;width: 40%">
<el-option v-for="item in timeRangeUnitOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</div>
</el-row>
<el-row class="ms-row" style="margin-left: 0px;margin-right: 0px; margin-top: 20px">
<el-collapse v-model="collapseActiveNames">
<el-collapse-item :title="$t('commons.report_statistics.report_filter.more_options')" name="1">
<el-container>
<el-aside width="73px" style="overflow: hidden">
<div v-if="option.filters.length > 1" style="height: 100%" id="moreOptionTypeDiv">
<div class="top-line-box" :style="{ height:lineDivHeight+'px'}">
</div>
<div>
<el-select class="ms-http-select" size="small" v-model="option.filterType" style="width: 70px">
<el-option v-for="item in filterTypes" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</div>
<div class="bottom-line-box" :style="{ height:lineDivHeight+'px'}">
</div>
</div>
</el-aside>
<el-main v-if="optionLoad" style="padding: 0px">
<el-row v-for="filterItem in option.filters" :key="filterItem.id" style="margin-bottom: 5px">
<el-col :span="24">
<el-select style="width: 100px" class="ms-http-select" size="small" v-model="filterItem.type">
<el-option v-for="item in getFilterOptionKey(filterItem.type)" :key="item.type" :label="item.name" :value="item.type"/>
</el-select>
<span style="margin-left:10px;margin-right:10px">{{ $t('commons.report_statistics.report_filter.belone') }}</span>
<el-select style="width:173px" :collapse-tags="true" class="ms-http-select" size="small" multiple filterable v-model="filterItem.values" v-if="getFilterOptions(filterItem.type).length > 0">
<el-option v-for="itemOption in getFilterOptions(filterItem.type)" :key="itemOption.id" :label="itemOption.label" :value="itemOption.id"/>
</el-select>
<el-input style="width:173px" v-model="filterItem.value" size="small" v-else ></el-input>
<el-button @click="addFilterOptions(filterItem.type)"
@keydown.enter.native.prevent
type="primary"
icon="el-icon-plus"
circle
style="color:white;padding: 0px 0.1px;width: 20px;height: 20px;margin-left:5px;"
size="mini"/>
<el-button @click="removeFilterOptions(filterItem.type)"
@keydown.enter.native.prevent
type="danger"
icon="el-icon-minus"
circle
style="color:white;padding: 0px 0.1px;width: 20px;height: 20px;margin-left:5px;"
size="mini"/>
</el-col>
</el-row>
</el-main>
</el-container>
</el-collapse-item>
</el-collapse>
</el-row>
<el-row type="flex">
<el-col style="height: 100%" :span="4" >
</el-col>
<el-col :span="20">
</el-col>
</el-row>
<el-row align="middle">
<el-button style="margin-left: 200px;margin-top: 20px" type="primary" size="mini" @click="init">{{ $t('commons.confirm') }}</el-button>
</el-row>
</el-card>
</div>
</template>
<script>
import {getCurrentProjectID, getUUID} from "@/common/js/utils";
import MsSelectTree from "@/business/components/common/select-tree/SelectTree";
export default {
name: "TestAnalysisTable",
components: {MsSelectTree},
data() {
return {
collapseActiveNames: "",
option: {
xaxis: "creator",
yaxis: ["testCase","apiCase","scenarioCase","loadCase"],
timeType: "dynamicTime",
projectId: getCurrentProjectID(),
filterType: "And",
timeFilter:{
timeRange: 7,
timeRangeUnit: "day",
},
times: [new Date().getTime() - 6 * 24 * 3600 * 1000, new Date().getTime()],
filters:[
{
type:"",
name:"",
compType:"input",
isShow:false,
},
],
},
h: document.documentElement.clientHeight + 80,
lineDivHeight: 0,
disabled: false,
loading: false,
optionLoad: true,
result: {},
items: [],
modules: [],
xAxisOptions: [
{id: 'creator', label: this.$t('commons.report_statistics.report_filter.select_options.creator')},
{id: 'maintainer', label: this.$t('commons.report_statistics.report_filter.select_options.maintainer')},
{id: 'casetype', label: this.$t('commons.report_statistics.report_filter.select_options.case_type')},
{id: 'casestatus', label: this.$t('commons.report_statistics.report_filter.select_options.case_status')},
{id: 'caselevel', label: this.$t('commons.report_statistics.report_filter.select_options.case_level')},
],
yAxisOptions: [
{id: 'testCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.functional')},
{id: 'apiCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.api')},
{id: 'scenarioCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.scene')},
{id: 'loadCase', label: this.$t('api_test.home_page.failed_case_list.table_value.case_type.load')},
],
filterTypes: [
{id: 'And', label: 'And'},
{id: 'Or', label: 'Or'},
],
timeTypeOptions: [
{id: 'fixedTime', label: this.$t('commons.report_statistics.report_filter.time_options.fixed_time')},
{id: 'dynamicTime', label: this.$t('commons.report_statistics.report_filter.time_options.dynamic_time')},
],
timeRangeNumberMax: 31,
timeRangeUnitOptions: [
{id: 'day', label: this.$t('commons.report_statistics.report_filter.time_options.day')},
{id: 'month', label: this.$t('commons.report_statistics.report_filter.time_options.month')},
{id: 'year', label: this.$t('commons.report_statistics.report_filter.time_options.year')},
],
priorityFilters: [
{id: 'P0', label: 'P0'},
{id: 'P1', label: 'P1'},
{id: 'P2', label: 'P2'},
{id: 'P3', label: 'P3'}
],
moduleObj: {
id: 'id',
label: 'name',
},
moreOptionsSelectorKeys:[
{
type:"casetype",
name:this.$t('commons.report_statistics.report_filter.select_options.case_type'),
},
{
type:"creator",
name:this.$t('commons.report_statistics.report_filter.select_options.creator'),
},
{
type:"maintainer",
name:this.$t('commons.report_statistics.report_filter.select_options.maintainer'),
},
{
type:"casestatus",
name:this.$t('commons.report_statistics.report_filter.select_options.case_status'),
},
{
type:"caselevel",
name:this.$t('commons.report_statistics.report_filter.select_options.case_level'),
},
],
moreOptionsSelectorValues: {
id: 'id',
label: 'label',
},
datePickerOptions: {
disabledDate: (time) => {
let nowDate = new Date();
let oneDay = 1000 * 60 * 60 * 24;
let oneYearLater = new Date(nowDate.getTime() + (oneDay * 365));
return time.getTime() > nowDate || time.getTime() > oneYearLater;//||&&
}
},
};
},
created() {
this.init();
this.initMoreOptionsSelectorValues();
},
computed: {
},
watch: {
option: {
handler: function () {
this.$nextTick(() => {
this.lineDivHeight = 0;
// let elem = document.getElementById("moreOptionTypeDiv");
if(this.option.filters.length > 1){
let countPageHeight = (this.option.filters.length)* 32 + (this.option.filters.length-1)*5;
if(countPageHeight > 32){
this.lineDivHeight = (countPageHeight-32)/2-11;
}
}
});
},
deep: true
}
},
methods: {
initSelectOption(opt){
this.loading = true;
this.option = opt;
this.$nextTick(() => {
this.loading = false;
});
},
addFilterOptions: function (type){
this.optionLoad = false;
let otherOptionKeys = this.getFilterOptionKey("");
if(otherOptionKeys.length > 0 && this.option.filters.length < 5) {
let addOptions = {
type: "",
id: getUUID(),
name: "",
compType: "selector",
isShow: false,
itemOptions: this.priorityFilters,
};
this.option.filters.push(addOptions);
} else {
this.$alert(this.$t('commons.report_statistics.alert.cannot_add_more_options'));
}
this.$nextTick(() => {
this.optionLoad = true;
});
},
getFilterOptions(type){
let optionArray = [];
if(this.moreOptionsSelectorValues && this.moreOptionsSelectorValues[type]){
optionArray = this.moreOptionsSelectorValues[type];
}
return optionArray;
},
getFilterOptionKey(type){
let optionArray = [];
for(let i = 0; i < this.moreOptionsSelectorKeys.length; i++){
let keyObj = this.moreOptionsSelectorKeys[i];
let inOptions = false;
if(keyObj.type !== type){
for(let j = 0; j < this.option.filters.length; j ++){
if(keyObj.type === this.option.filters[j].type){
inOptions = true;
}
}
}
if(!inOptions){
optionArray.push(keyObj);
}
}
return optionArray;
},
removeFilterOptions: function (type){
let removeOptionsIndex = -1;
for(let index = 0; index < this.option.filters.length; index ++){
let item = this.option.filters[index];
if(item.type === type){
removeOptionsIndex = index;
}
}
if(removeOptionsIndex >= 0){
this.option.filters.splice(removeOptionsIndex,1);
}
if(this.option.filters.length === 0){
let addOptions = {
type: "",
id: getUUID(),
name: "",
compType: "selector",
isShow: false,
itemOptions: this.priorityFilters,
};
this.option.filters.push(addOptions);
}
},
init: function () {
this.$emit('filterCharts', this.option);
},
onTimeChange() {
if (this.option.times[1] > new Date().getTime()) {
this.$alert(this.$t('commons.report_statistics.alert.end_time_cannot_over_than_start_time'));
}
},
initMoreOptionsSelectorValues() {
let selectParam = {
projectId:getCurrentProjectID()
};
this.$post('/report/test/case/count/initDatas',selectParam, response => {
this.moreOptionsSelectorValues = response.data;
});
},
timeRangeUnitChange(val){
if(val === 'day'){
this.timeRangeNumberMax = 31;
}else if(val === 'month'){
this.timeRangeNumberMax = 12;
}else {
this.timeRangeNumberMax = 1;
}
this.option.timeFilter.timeRange = 1;
},
getOption(){
return this.option;
}
},
};
</script>
<style scoped>
.tip {
float: left;
font-size: 14px;
border-radius: 2px;
border-left: 2px solid #783887;
margin: 0px 10px 0px;
}
.ms-row {
margin: 0px 10px 0px;
}
.ms-card {
width: 480px;
}
.top-line-box{
border-top: 1px solid;
border-left: 1px solid;
margin-left: 32px;
margin-top: 10px;
border-top-left-radius: 10px;
}
.bottom-line-box{
border-bottom: 1px solid;
border-left: 1px solid;
margin-left: 32px;
border-bottom-left-radius: 10px;
}
/deep/ .el-select__tags-text {
display: inline-block;
max-width: 50px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<div v-loading="loading" class="ms-div">
<el-card :style="{ width: w+'px'}">
<el-row style="padding-top: 10px">
<p class="tip"><span style="margin-left: 5px"></span>{{$t('commons.report_statistics.excel')}} </p>
</el-row>
<el-row>
<el-table
:data="tableData"
:max-height="tableHeight"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
row-key="id"
border
class="ms-table">
<el-table-column
prop="name"
:label="groupName">
</el-table-column>
<el-table-column
prop="allCount"
label="总计">
</el-table-column>
<el-table-column
prop="testCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.functional')"
v-if="isShowColumn('testCase')"
>
</el-table-column>
<el-table-column
prop="apiCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.api')"
v-if="isShowColumn('apiCase')"
>
</el-table-column>
<el-table-column
prop="scenarioCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.scene')"
v-if="isShowColumn('scenarioCase')"
>
</el-table-column>
<el-table-column
prop="loadCaseCount"
:label="$t('api_test.home_page.failed_case_list.table_value.case_type.load')"
v-if="isShowColumn('loadCase')"
>
</el-table-column>
</el-table>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
name: "TestAnalysisTable",
components: {},
props: {
tableData: Array,
groupName: String,
showColoums: Array,
},
data() {
return {
tableHeight : "100px",
w: document.documentElement.clientWidth - 760,
loading: false,
}
},
created() {
this.getTableHeight();
},
methods: {
isShowColumn(type){
if(this.showColoums){
return this.showColoums.findIndex(item => item=== type) >= 0;
}else {
return false;
}
},
getTableHeight(){
let countNumber = document.documentElement.clientHeight * 0.4 /1 - 140;
countNumber = Math.ceil(countNumber);
this.tableHeight = countNumber + 'px';
}
},
}
</script>
<style scoped>
.tip {
float: left;
font-size: 14px;
border-radius: 2px;
border-left: 2px solid #783887;
margin: 0px 20px 0px;
}
.ms-div {
margin-bottom: 20px;
}
.ms-table {
width: 95%;
margin: 20px;
}
/deep/ .el-card__body {
padding: 0px;
}
</style>

View File

@ -0,0 +1,168 @@
<template>
<div :style="{ height: h + 'px'}">
<el-container v-loading="loading" id="reportAnalysis" style="overflow: scroll">
<el-container class="ms-row">
<el-aside :width="!isHide ?'235px':'0px'" style="margin-left: 5px; max-height: 843px">
<history-report-data report-type="TEST_CASE_ANALYSIS"
@selectReport="selectReport" @removeHistoryReportId="removeHistoryReportId"
ref="historyReport"/>
</el-aside>
<el-main class="ms-main">
<div>
<test-analysis-chart @hidePage="hidePage" @orderCharts="orderCharts" ref="analysisChart" :load-option="loadOption"/>
</div>
<div class="ms-row" v-if="!isHide">
<test-analysis-table :tableData="tableData"/>
</div>
</el-main>
<el-aside :width="!isHide ?'485px':'0px'">
<test-analysis-filter @filterCharts="filterCharts" ref="analysisFilter"/>
</el-aside>
</el-container>
</el-container>
</div>
</template>
<script>
import TestAnalysisChart from "./chart/TestAnalysisChart";
import TestAnalysisTable from "./table/TestAnalysisTable";
import TestAnalysisFilter from "./filter/TestAnalysisFilter";
import {exportPdf, getCurrentProjectID} from "@/common/js/utils";
import html2canvas from 'html2canvas';
import HistoryReportData from "../base/HistoryReportData";
export default {
name: "TestAnalysisContainer",
components: {TestAnalysisChart, TestAnalysisTable, TestAnalysisFilter, HistoryReportData},
data() {
return {
isHide: false,
loading: false,
options: {},
loadOption: {
legend: {},
xAxis: {},
yAxis: {},
label: {},
tooltip: {},
series: []
},
tableData: [],
h: document.documentElement.clientHeight - 40,
}
},
methods: {
handleExport() {
let name = this.$t('commons.report_statistics.test_case_analysis');
this.$nextTick(function () {
setTimeout(() => {
html2canvas(document.getElementById('reportAnalysis'), {
scale: 2
}).then(function (canvas) {
exportPdf(name, [canvas]);
});
}, 1000);
});
},
hidePage(isHide) {
this.isHide = isHide;
},
close() {
this.$emit('closePage');
},
init(opt) {
this.loading = true;
this.options = opt;
this.$post(' /report/test/analysis/getReport', opt, response => {
let data = response.data.chartDTO;
let tableDTOs = response.data.tableDTOs;
this.initPic(data,tableDTOs);
});
},
filterCharts(opt) {
this.init(opt);
},
orderCharts(order) {
this.options.order = order;
this.filterCharts(this.options);
},
saveReport() {
let obj = {};
obj.projectId = getCurrentProjectID();
obj.selectOption = JSON.stringify(this.options);
let dataOptionObj = {
loadOption: this.loadOption,
pieOption: this.pieOption,
tableData: this.tableData,
};
obj.dataOption = JSON.stringify(dataOptionObj);
obj.reportType = 'TEST_CASE_ANALYSIS';
this.$post("/history/report/saveReport", obj, response => {
this.$alert(this.$t('commons.save_success'));
this.$refs.historyReport.initReportData();
});
},
initPic(loadOption,tableData){
this.loading = true;
if (loadOption) {
this.loadOption.legend = loadOption.legend;
this.loadOption.xAxis = loadOption.xAxis;
this.loadOption.series = loadOption.series;
this.loadOption.grid = {
bottom: '75px',//
}
this.loadOption.series.forEach(item => {
item.type = this.$refs.analysisChart.chartType;
})
}
if (tableData) {
this.tableData = tableData;
}
this.loading = false;
},
selectReport(selectId){
if(selectId){
this.loading = true;
let paramObj = {
id:selectId
}
this.$post('/history/report/selectById',paramObj, response => {
let reportData = response.data;
if(reportData){
if(reportData.dataOption){
let dataOptionObj = JSON.parse(reportData.dataOption);
this.initPic(dataOptionObj.loadOption,dataOptionObj.pieOption,dataOptionObj.tableData);
}
if(reportData.selectOption){
let selectOptionObj = JSON.parse(reportData.selectOption);
this.$refs.analysisFilter.initSelectOption(selectOptionObj);
}
}
this.loading = false;
}, (error) => {
this.loading = false;
});
this.$emit('initHistoryReportId',selectId);
}
},
removeHistoryReportId(){
this.$emit('initHistoryReportId',"");
},
selectAndSaveReport(){
let opt = this.$refs.analysisFilter.getOption();
this.options = opt;
this.saveReport();
}
},
}
</script>
<style scoped>
.ms-row {
padding-top: 10px;
}
/deep/ .el-main {
padding: 0px 20px 0px;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<div v-loading="loading">
<el-card class="ms-test-chart" :style="{ width: w+'px', height: h + 'px'}" ref="msDrawer">
<el-row class="ms-row">
<p class="tip"><span style="margin-left: 5px"></span> {{$t('commons.report_statistics.chart')}} </p>
<div class="ms-test-chart-header">
<el-select v-model="chartType" class="ms-col-type" size="mini" style="width: 100px" @change="generateOption">
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in charts"/>
</el-select>
<span style="margin: 0px 10px 10px">|</span>
<el-select v-model="order" class="ms-col-type" size="mini" style="width: 120px" @change="orderCharts">
<el-option :key="t.id" :value="t.id" :label="t.name" v-for="t in orders"/>
</el-select>
<span style="margin: 0px 10px 10px">|</span>
<font-awesome-icon v-if="!isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'expand-alt']" size="lg" @click="fullScreen"/>
<font-awesome-icon v-if="isFullScreen && showFullScreen" class="report-alt-ico" :icon="['fa', 'compress-alt']" size="lg" @click="unFullScreen"/>
</div>
</el-row>
<el-row>
<ms-chart ref="chart1" :options="loadOption" class="chart-config" :autoresize="true"/>
</el-row>
</el-card>
</div>
</template>
<script>
//
import echarts from "echarts";
import MsChart from "@/business/components/common/chart/MsChart";
export default {
name: "TestAnalysisChart",
components: {MsChart},
props: {
loadOption: {},
},
data() {
return {
x: 0,
y: 0,
w: document.documentElement.clientWidth - 760,
h: document.documentElement.clientHeight / 1.7,
isFullScreen: false,
originalW: 100,
originalH: 100,
showFullScreen: {
type: Boolean,
default() {
return true;
}
},
//
chartType: "line",
charts: [{id: 'line', name: this.$t('commons.report_statistics.line')}, {id: 'bar', name: this.$t('commons.report_statistics.bar')}],
order: "",
orders: [{id: '', name: '默认排序'},{id: 'desc', name: this.$t('commons.report_statistics.desc')}, {id: 'asc', name: this.$t('commons.report_statistics.asc')}],
loading: false,
options: {},
}
},
methods: {
orderCharts() {
this.$emit('orderCharts', this.order);
},
generateOption() {
this.loadOption.series.forEach(item => {
item.type = this.chartType;
})
this.reload();
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
fullScreen() {
this.originalW = this.w;
this.originalH = this.h;
this.w = document.body.clientWidth - 50;
this.h = document.body.clientHeight;
this.isFullScreen = true;
this.$emit('hidePage', true);
},
unFullScreen() {
this.w = this.originalW;
this.h = this.originalH;
this.isFullScreen = false;
this.$emit('hidePage', false);
},
getOptions(){
return this.loadOption;
}
},
}
</script>
<style scoped>
.ms-test-chart-header {
z-index: 999;
width: 320px;
float: right;
margin-right: 10px;
}
.report-alt-ico {
font-size: 15px;
margin: 0px 10px 0px;
color: #8c939d;
}
.report-alt-ico:hover {
color: black;
cursor: pointer;
font-size: 18px;
}
/deep/ .echarts {
height: calc(100vh / 1.95);
}
.tip {
float: left;
font-size: 14px;
border-radius: 2px;
border-left: 2px solid #783887;
margin: 0px 20px 0px;
}
.ms-row {
padding-top: 10px;
}
.chart-config {
width: 100%;
}
/deep/ .el-card__body {
padding: 0px;
}
</style>

View File

@ -0,0 +1,225 @@
<template>
<div v-loading="loading">
<el-card :style="{height: h + 'px'}" class="ms-card">
<el-row style="padding-top: 10px">
<p class="tip"><span style="margin-left: 5px"></span> {{$t('commons.report_statistics.options')}}</p>
</el-row>
<el-row class="ms-row">
<p>{{$t('commons.report_statistics.type')}}</p>
<el-checkbox v-model="option.createCase">{{$t('commons.report_statistics.add_case')}}</el-checkbox>
<el-checkbox v-model="option.updateCase">{{$t('commons.report_statistics.change_case')}}</el-checkbox>
</el-row>
<el-row class="ms-row">
<p> {{$t('api_monitor.date')}}</p>
<el-date-picker
size="small"
v-model="option.times"
type="datetimerange"
value-format="timestamp"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="datePickerOptions"
style="width: 100%">
</el-date-picker>
</el-row>
<el-row class="ms-row">
<p>{{$t('commons.project')}}</p>
<ms-select-tree size="small" :data="items" :default-key="projectDefaultKey" @getValue="setProjects" :obj="obj" clearable checkStrictly multiple ref="projectSelector"/>
</el-row>
<el-row class="ms-row">
<p>{{$t('test_track.module.module')}}</p>
<ms-select-tree size="small" :data="modules" :default-key="moduleDefaultKey" :disabled="disabled" @getValue="setModules" :obj="moduleObj" clearable checkStrictly multiple ref="moduleSelector"/>
</el-row>
<el-row class="ms-row">
<p>{{$t('api_test.automation.case_level')}}</p>
<el-select class="ms-http-select" size="small" v-model="option.prioritys" multiple style="width: 100%">
<el-option v-for="item in priorityFilters" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</el-row>
<el-row class="ms-row">
<p>{{$t('test_track.case.maintainer')}}</p>
<ms-select-tree size="small" :data="maintainerOptions" :default-key="userDefaultKey" @getValue="setUsers" :obj="moduleObj" clearable checkStrictly multiple ref="userSelector"/>
</el-row>
</el-card>
</div>
</template>
<script>
import {getCurrentProjectID} from "@/common/js/utils";
import MsSelectTree from "@/business/components/common/select-tree/SelectTree";
export default {
name: "TestAnalysisTable",
components: {MsSelectTree},
data() {
return {
option: {createCase: true, updateCase: true, projects: [], times: [new Date().getTime() - 6 * 24 * 3600 * 1000, new Date().getTime()]},
h: document.documentElement.clientHeight + 80,
disabled: false,
loading: false,
result: {},
items: [],
projectDefaultKey:[],
moduleDefaultKey:[],
userDefaultKey:[],
modules: [],
maintainerOptions: [],
priorityFilters: [
{id: 'P0', label: 'P0'},
{id: 'P1', label: 'P1'},
{id: 'P2', label: 'P2'},
{id: 'P3', label: 'P3'}
],
syncReport: true,
moduleObj: {
id: 'id',
label: 'name',
},
obj: {
id: 'id',
label: 'label',
},
datePickerOptions: {
disabledDate: (time) => {
let nowDate = new Date();
let oneDay = 1000 * 60 * 60 * 24;
let oneYearLater = new Date(nowDate.getTime() + (oneDay * 365));
return time.getTime() > nowDate || time.getTime() > oneYearLater;//||&&
}
},
}
},
created() {
this.init();
this.initUsers();
},
watch: {
option: {
handler: function () {
if(this.syncReport){
this.$emit('filterCharts', this.option);
}
},
deep: true
}
},
methods: {
initSelectOption(opt){
if(opt){
this.syncReport = false;
this.loading = true;
this.option = opt;
if(opt.projects){
this.projectDefaultKey = opt.projects;
}else {
this.projectDefaultKey = [];
}
if(opt.modules && this.projectDefaultKey.length === 1){
this.moduleDefaultKey = opt.modules;
}else {
this.moduleDefaultKey = [];
}
if(opt.users){
this.userDefaultKey = opt.users;
}else {
this.userDefaultKey = [];
}
this.$nextTick(() => {
this.loading = false;
this.syncReport = true;
});
}
},
init: function () {
this.result = this.$get("/project/listAll", response => {
let projects = response.data;
if (projects) {
this.items = [];
projects.forEach(item => {
let data = {id: item.id, label: item.name};
this.items.push(data);
})
}
})
},
onTimeChange() {
if (this.option.times[1] > new Date().getTime()) {
this.$error("结束时间不能超过当前时间");
}
},
initModule() {
this.result = this.$get("/case/node/list/" + this.option.projects[0], response => {
this.modules = response.data;
this.$refs.moduleSelector.setKeys(this.moduleDefaultKey);
})
},
initUsers() {
this.$post('/user/project/member/tester/list', {projectId: getCurrentProjectID()}, response => {
this.maintainerOptions = response.data;
});
},
setProjects(key, data) {//
if(!key || key === ""){
key = [];
}
this.option.projects = key;
this.modules = [];
if (key && key.length > 1) {
this.moduleDefaultKey = [];
this.disabled = true;
} else {
this.disabled = false;
}
if (this.option.projects && this.option.projects.length == 1) {
this.initModule();
}
if(this.syncReport){
this.$emit('filterCharts', this.option);
}
},
setModules(key, data) {//
if(!key || key === ""){
key = [];
}
this.option.modules = key;
if(this.syncReport){
this.$emit('filterCharts', this.option);
}
},
setUsers(key, data) {//
if(!key || key === ""){
key = [];
}
this.option.users = key;
if(this.syncReport){
this.$emit('filterCharts', this.option);
}
},
getOption(){
return this.option;
}
},
}
</script>
<style scoped>
.tip {
float: left;
font-size: 14px;
border-radius: 2px;
border-left: 2px solid #783887;
margin: 0px 10px 0px;
}
.ms-row {
margin: 0px 10px 0px;
}
.ms-card {
width: 480px;
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<div v-loading="loading" class="ms-div">
<el-card :style="{ width: w+'px'}">
<el-row style="padding-top: 10px">
<p class="tip"><span style="margin-left: 5px"></span>{{$t('commons.report_statistics.excel')}} </p>
</el-row>
<el-row>
<el-table
:data="tableData"
:height="h"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
row-key="id"
border
class="ms-table">
<el-table-column
prop="name"
:label="$t('api_monitor.date')"
sortable>
</el-table-column>
<el-table-column
prop="createCount"
:label="$t('commons.report_statistics.add_case')"
sortable>
</el-table-column>
<el-table-column
prop="updateCount"
sortable
:label="$t('commons.report_statistics.change_case')">
</el-table-column>
</el-table>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
name: "TestAnalysisTable",
components: {},
props: {
tableData: Array,
},
data() {
return {
w: document.documentElement.clientWidth - 760,
h: document.body.clientHeight / 2.3 - 20,
loading: false,
}
},
methods: {},
}
</script>
<style scoped>
.tip {
float: left;
font-size: 14px;
border-radius: 2px;
border-left: 2px solid #783887;
margin: 0px 20px 0px;
}
.ms-div {
margin-bottom: 20px;
}
.ms-table {
width: 95%;
margin: 20px;
}
/deep/ .el-card__body {
padding: 0px;
}
</style>

@ -1 +1 @@
Subproject commit d99efd7e4f70846553444065fab8159e65035525
Subproject commit e0da1d3ef611b0901e42520f6d68a04cabec4c08

View File

@ -43,6 +43,7 @@ export default {
annotation: 'Annotation',
clear: 'Clear',
save: 'Save',
save_as: 'Save as',
update: 'Update',
save_success: 'Saved successfully',
delete_success: 'Deleted successfully',

View File

@ -43,6 +43,7 @@ export default {
annotation: '注释',
clear: '清空',
save: '保存',
save_as: '另存为',
update: '更新',
save_success: '保存成功',
delete_success: '删除成功',

View File

@ -43,6 +43,7 @@ export default {
annotation: '註釋',
clear: '清空',
save: '保存',
save_as: '另存為',
update: '更新',
save_success: '保存成功',
delete_success: '刪除成功',