refactor(测试计划): 优化任务计划执行历史

This commit is contained in:
song-cc-rock 2024-11-19 18:44:45 +08:00 committed by Craftsman
parent 3747eedfec
commit ccc10bacc2
6 changed files with 104 additions and 25 deletions

View File

@ -127,13 +127,21 @@ public class TestPlanReportController {
} }
@GetMapping("/get-task/{taskId}") @GetMapping("/get-task/{taskId}")
@Operation(summary = "测试计划|组-任务-执行结果") @Operation(summary = "测试计划|组-执行历史-执行结果")
@RequiresPermissions(value = {PermissionConstants.TEST_PLAN_REPORT_READ, PermissionConstants.TEST_PLAN_READ_EXECUTE}, logical = Logical.OR) @RequiresPermissions(value = {PermissionConstants.TEST_PLAN_REPORT_READ, PermissionConstants.TEST_PLAN_READ_EXECUTE}, logical = Logical.OR)
@CheckOwner(resourceId = "#taskId", resourceType = "exec_task") @CheckOwner(resourceId = "#taskId", resourceType = "exec_task")
public TestPlanTaskReportResponse getTaskDetail(@PathVariable String taskId) { public TestPlanTaskReportResponse getTaskDetail(@PathVariable String taskId) {
return testPlanReportService.getTaskDetail(taskId); return testPlanReportService.getTaskDetail(taskId);
} }
@GetMapping("/get-result/{taskId}")
@Operation(summary = "测试计划|组-任务-执行结果")
@RequiresPermissions(value = {PermissionConstants.TEST_PLAN_REPORT_READ, PermissionConstants.TEST_PLAN_READ_EXECUTE}, logical = Logical.OR)
@CheckOwner(resourceId = "#reportId", resourceType = "test_plan_report")
public TestPlanReportDetailResponse getTaskResult(@PathVariable String taskId) {
return testPlanReportService.getTaskResult(taskId);
}
@GetMapping("/get-layout/{reportId}") @GetMapping("/get-layout/{reportId}")
@Operation(summary = "测试计划-报告-组件布局") @Operation(summary = "测试计划-报告-组件布局")
@RequiresPermissions(value = {PermissionConstants.TEST_PLAN_REPORT_READ, PermissionConstants.TEST_PLAN_READ_EXECUTE}, logical = Logical.OR) @RequiresPermissions(value = {PermissionConstants.TEST_PLAN_REPORT_READ, PermissionConstants.TEST_PLAN_READ_EXECUTE}, logical = Logical.OR)

View File

@ -24,4 +24,8 @@ public class TestPlanExecuteHisDTO {
private Long endTime; private Long endTime;
@Schema(description = "报告是否删除") @Schema(description = "报告是否删除")
private Boolean deleted; private Boolean deleted;
@Schema(description = "报告ID")
private String reportId;
@Schema(description = "执行结果是否删除")
private Boolean resultDeleted;
} }

View File

@ -6,6 +6,7 @@ import io.metersphere.system.serializer.CustomRateSerializer;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
@ -15,6 +16,7 @@ import java.util.List;
@Data @Data
public class TestPlanTaskReportResponse implements Serializable { public class TestPlanTaskReportResponse implements Serializable {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Schema(description = "报告ID") @Schema(description = "报告ID")
@ -40,10 +42,16 @@ public class TestPlanTaskReportResponse implements Serializable {
@Schema(description = "执行完成率(实时)") @Schema(description = "执行完成率(实时)")
@JsonSerialize(using = CustomRateSerializer.class) @JsonSerialize(using = CustomRateSerializer.class)
private Double executeRate; private Double executeRate;
@Schema(description = "接口明细总数")
private Integer apiCaseTotal;
@Schema(description = "场景明细总数")
private Integer apiScenarioTotal;
@Data @Data
public static class ChildPlan { public static class ChildPlan {
private String id; private String id;
private String name; private String name;
private Integer apiCaseTotal;
private Integer apiScenarioTotal;
} }
} }

View File

@ -659,7 +659,7 @@
<select id="listHis" resultType="io.metersphere.plan.dto.TestPlanExecuteHisDTO"> <select id="listHis" resultType="io.metersphere.plan.dto.TestPlanExecuteHisDTO">
select et.id, from_unixtime(et.create_time / 1000, '%Y%m%d%H%i%s') as num, et.trigger_mode triggerMode, et.status execStatus, et.result execResult, select et.id, from_unixtime(et.create_time / 1000, '%Y%m%d%H%i%s') as num, et.trigger_mode triggerMode, et.status execStatus, et.result execResult,
et.create_user operationUser, et.start_time startTime, et.end_time endTime et.create_user operationUser, et.start_time startTime, et.end_time endTime, arrt.report_id reportId
from exec_task et where et.resource_id = #{request.testPlanId} from exec_task et where et.resource_id = #{request.testPlanId}
<include refid="filterTask"/> <include refid="filterTask"/>
order by et.create_time desc order by et.create_time desc

View File

@ -34,10 +34,7 @@ import io.metersphere.system.mapper.BaseUserMapper;
import io.metersphere.system.mapper.ExecTaskMapper; import io.metersphere.system.mapper.ExecTaskMapper;
import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.service.BaseTaskHubService; import io.metersphere.system.service.*;
import io.metersphere.system.service.CommonFileService;
import io.metersphere.system.service.FileService;
import io.metersphere.system.service.SimpleUserService;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.RateCalculateUtils; import io.metersphere.system.utils.RateCalculateUtils;
import io.metersphere.system.utils.ServiceUtils; import io.metersphere.system.utils.ServiceUtils;
@ -130,6 +127,8 @@ public class TestPlanReportService {
private ApiReportRelateTaskMapper apiReportRelateTaskMapper; private ApiReportRelateTaskMapper apiReportRelateTaskMapper;
@Resource @Resource
private ExecTaskMapper execTaskMapper; private ExecTaskMapper execTaskMapper;
@Resource
private UserLoginService userLoginService;
private static final int MAX_REPORT_NAME_LENGTH = 300; private static final int MAX_REPORT_NAME_LENGTH = 300;
@ -766,16 +765,10 @@ public class TestPlanReportService {
*/ */
public TestPlanReportDetailResponse getReport(String reportId) { public TestPlanReportDetailResponse getReport(String reportId) {
TestPlanReport planReport = checkReport(reportId); TestPlanReport planReport = checkReport(reportId);
TestPlanReportSummaryExample example = new TestPlanReportSummaryExample();
example.createCriteria().andTestPlanReportIdEqualTo(reportId);
List<TestPlanReportSummary> testPlanReportSummaries = testPlanReportSummaryMapper.selectByExampleWithBLOBs(example);
if (CollectionUtils.isEmpty(testPlanReportSummaries)) {
throw new MSException(Translator.get("test_plan_report_detail_not_exist"));
}
TestPlanReportSummary reportSummary = testPlanReportSummaries.getFirst();
TestPlanReportDetailResponse planReportDetail = new TestPlanReportDetailResponse(); TestPlanReportDetailResponse planReportDetail = new TestPlanReportDetailResponse();
BeanUtils.copyBean(planReportDetail, planReport); BeanUtils.copyBean(planReportDetail, planReport);
// 用例总数需单独返回, 不然前端表格不展示, 影响执行中的数据 // 用例总数需单独返回, 不然前端表格不展示, 影响执行中的数据
TestPlanReportSummary reportSummary = getReportSummary(reportId);
int caseTotal = (int) (reportSummary.getFunctionalCaseCount() + reportSummary.getApiCaseCount() + reportSummary.getApiScenarioCount()); int caseTotal = (int) (reportSummary.getFunctionalCaseCount() + reportSummary.getApiCaseCount() + reportSummary.getApiScenarioCount());
planReportDetail.setCaseTotal(caseTotal); planReportDetail.setCaseTotal(caseTotal);
planReportDetail.setFunctionalTotal(reportSummary.getFunctionalCaseCount().intValue()); planReportDetail.setFunctionalTotal(reportSummary.getFunctionalCaseCount().intValue());
@ -822,31 +815,56 @@ public class TestPlanReportService {
} }
/** /**
* 获取计划任务执行结果 * 获取计划任务执行结果 (执行历史)
* @param taskId 任务ID * @param taskId 任务ID
* @return 计划执行结果 * @return 计划| 执行结果
*/ */
public TestPlanTaskReportResponse getTaskDetail(String taskId) { public TestPlanTaskReportResponse getTaskDetail(String taskId) {
TestPlanTaskReportResponse testPlanTaskReportResponse = new TestPlanTaskReportResponse(); TestPlanTaskReportResponse taskDetail = new TestPlanTaskReportResponse();
ExecTask task = execTaskMapper.selectByPrimaryKey(taskId); ExecTask task = execTaskMapper.selectByPrimaryKey(taskId);
BeanUtils.copyBean(testPlanTaskReportResponse, task); BeanUtils.copyBean(taskDetail, task);
Map<String, String> userNameMap = userLoginService.getUserNameMap(List.of(taskDetail.getCreateUser()));
taskDetail.setCreateUser(userNameMap.getOrDefault(taskDetail.getCreateUser(), taskDetail.getCreateUser()));
ApiReportRelateTaskExample example = new ApiReportRelateTaskExample(); ApiReportRelateTaskExample example = new ApiReportRelateTaskExample();
example.createCriteria().andTaskResourceIdEqualTo(taskId); example.createCriteria().andTaskResourceIdEqualTo(taskId);
List<ApiReportRelateTask> taskReports = apiReportRelateTaskMapper.selectByExample(example); List<ApiReportRelateTask> taskReports = apiReportRelateTaskMapper.selectByExample(example);
if (CollectionUtils.isEmpty(taskReports)) { if (CollectionUtils.isEmpty(taskReports)) {
// 暂未生成报告 // 暂未生成报告
return testPlanTaskReportResponse; return taskDetail;
} }
String reportId = taskReports.getFirst().getReportId(); String reportId = taskReports.getFirst().getReportId();
List<TestPlanReport> planChildrenTask = extTestPlanReportMapper.getPlanChildrenTask(reportId); List<TestPlanReport> planChildrenTask = extTestPlanReportMapper.getPlanChildrenTask(reportId);
List<TestPlanTaskReportResponse.ChildPlan> childPlans = planChildrenTask.stream().map(childTask -> { List<TestPlanTaskReportResponse.ChildPlan> childPlans = planChildrenTask.stream().map(childTask -> {
TestPlanTaskReportResponse childTaskDetail = calcTaskExecActual(childTask.getId(), new TestPlanTaskReportResponse());
TestPlanTaskReportResponse.ChildPlan childPlan = new TestPlanTaskReportResponse.ChildPlan(); TestPlanTaskReportResponse.ChildPlan childPlan = new TestPlanTaskReportResponse.ChildPlan();
childPlan.setId(childTask.getId()); BeanUtils.copyBean(childPlan, childTaskDetail);
childPlan.setName(childTask.getName());
return childPlan; return childPlan;
}).toList(); }).toList();
testPlanTaskReportResponse.setChildPlans(childPlans); taskDetail.setChildPlans(childPlans);
return calcTaskExecActual(reportId, testPlanTaskReportResponse); return calcTaskExecActual(reportId, taskDetail);
}
/**
* 获取计划任务执行结果 (任务中心)
* @param taskId 任务ID
* @return 计划| 执行结果
*/
public TestPlanReportDetailResponse getTaskResult(String taskId) {
TestPlanReportDetailResponse taskResult = new TestPlanReportDetailResponse();
ExecTask task = execTaskMapper.selectByPrimaryKey(taskId);
ApiReportRelateTaskExample example = new ApiReportRelateTaskExample();
example.createCriteria().andTaskResourceIdEqualTo(taskId);
List<ApiReportRelateTask> taskReports = apiReportRelateTaskMapper.selectByExample(example);
if (CollectionUtils.isEmpty(taskReports)) {
// 暂未生成报告
return null;
}
String reportId = taskReports.getFirst().getReportId();
// 子计划报告存在
List<TestPlanReport> planChildrenTask = extTestPlanReportMapper.getPlanChildrenTask(reportId);
List<TestPlanReportDetailResponse> childPlans = planChildrenTask.stream().map(childTask -> calcTaskExecFinish(childTask.getId(), childTask.getName(), new TestPlanReportDetailResponse())).toList();
taskResult.setChildren(childPlans);
return calcTaskExecFinish(reportId, task.getTaskName(), taskResult);
} }
public List<TestPlanReportComponent> getLayout(String reportId) { public List<TestPlanReportComponent> getLayout(String reportId) {
@ -1402,13 +1420,14 @@ public class TestPlanReportService {
private TestPlanTaskReportResponse calcTaskExecActual(String reportId, TestPlanTaskReportResponse testPlanTaskReportResponse) { private TestPlanTaskReportResponse calcTaskExecActual(String reportId, TestPlanTaskReportResponse testPlanTaskReportResponse) {
testPlanTaskReportResponse.setReportId(reportId); testPlanTaskReportResponse.setReportId(reportId);
List<String> calcReportIds = getActualReportIds(reportId); List<String> calcReportIds = getActualReportIds(reportId);
// 计算接口用例 // 历史执行结果只需统计 接口 + 场景 (数据来源: 关联表实时状态数据)
List<CaseStatusCountMap> apiCountMapList = extTestPlanReportApiCaseMapper.countExecuteResult(calcReportIds); List<CaseStatusCountMap> apiCountMapList = extTestPlanReportApiCaseMapper.countExecuteResult(calcReportIds);
CaseCount apiCaseCount = countMap(apiCountMapList); CaseCount apiCaseCount = countMap(apiCountMapList);
// 计算场景用例
List<CaseStatusCountMap> scenarioCountMapList = extTestPlanReportApiScenarioMapper.countExecuteResult(calcReportIds); List<CaseStatusCountMap> scenarioCountMapList = extTestPlanReportApiScenarioMapper.countExecuteResult(calcReportIds);
CaseCount scenarioCaseCount = countMap(scenarioCountMapList); CaseCount scenarioCaseCount = countMap(scenarioCountMapList);
// 汇总接口&&场景用例的执行情况 // 汇总接口&&场景用例的执行情况
testPlanTaskReportResponse.setApiCaseTotal(apiCaseCount.sum());
testPlanTaskReportResponse.setApiScenarioTotal(scenarioCaseCount.sum());
CaseCount caseCount = CountUtils.summarizeProperties(List.of(apiCaseCount, scenarioCaseCount)); CaseCount caseCount = CountUtils.summarizeProperties(List.of(apiCaseCount, scenarioCaseCount));
testPlanTaskReportResponse.setExecuteCaseCount(caseCount); testPlanTaskReportResponse.setExecuteCaseCount(caseCount);
// 完成率 = (总数 - 未执行数) / 总数 // 完成率 = (总数 - 未执行数) / 总数
@ -1417,6 +1436,28 @@ public class TestPlanReportService {
return testPlanTaskReportResponse; return testPlanTaskReportResponse;
} }
/**
* 计算计划任务的用例执行情况(取计划报告的最终汇总)
* @return 用例执行情况
*/
private TestPlanReportDetailResponse calcTaskExecFinish(String reportId, String detailName, TestPlanReportDetailResponse detail) {
detail.setId(reportId);
detail.setName(detailName);
TestPlanReport report = checkReport(reportId);
BeanUtils.copyBean(detail, report);
TestPlanReportSummary summary = getReportSummary(reportId);
// 任务执行结果只需统计 接口 + 场景 (数据来源: 报告汇总)
detail.setApiCaseCount(summary.getApiExecuteResult() == null ? CaseCount.builder().build() : JSON.parseObject(new String(summary.getApiExecuteResult()), CaseCount.class));
detail.setApiScenarioCount(summary.getScenarioExecuteResult() == null ? CaseCount.builder().build() : JSON.parseObject(new String(summary.getScenarioExecuteResult()), CaseCount.class));
detail.setApiCaseTotal(detail.getApiCaseCount().sum());
detail.setApiScenarioTotal(detail.getApiScenarioCount().sum());
detail.setExecuteCount(CountUtils.summarizeProperties(List.of(detail.getApiCaseCount(), detail.getApiScenarioCount())));
CaseCount executeCount = detail.getExecuteCount();
detail.setExecuteRate(RateCalculateUtils.divWithPrecision((executeCount.sum() - executeCount.getPending()), executeCount.sum(), 2));
detail.setPassRate(RateCalculateUtils.divWithPrecision(executeCount.getSuccess(), executeCount.sum(), 2));
return detail;
}
/** /**
* 获取实际的报告ID集合 (计划组报告则会返回多个) * 获取实际的报告ID集合 (计划组报告则会返回多个)
* @param reportId 报告ID * @param reportId 报告ID
@ -1434,4 +1475,19 @@ public class TestPlanReportService {
return List.of(reportId); return List.of(reportId);
} }
} }
/**
* 获取报告汇总详情
* @param reportId 报告ID
* @return 汇总详情
*/
private TestPlanReportSummary getReportSummary(String reportId) {
TestPlanReportSummaryExample example = new TestPlanReportSummaryExample();
example.createCriteria().andTestPlanReportIdEqualTo(reportId);
List<TestPlanReportSummary> testPlanReportSummaries = testPlanReportSummaryMapper.selectByExampleWithBLOBs(example);
if (CollectionUtils.isEmpty(testPlanReportSummaries)) {
throw new MSException(Translator.get("test_plan_report_detail_not_exist"));
}
return testPlanReportSummaries.getFirst();
}
} }

View File

@ -872,7 +872,10 @@ public class TestPlanService extends TestPlanBaseUtilsService {
List<String> userIds = hisList.stream().map(TestPlanExecuteHisDTO::getOperationUser).distinct().toList(); List<String> userIds = hisList.stream().map(TestPlanExecuteHisDTO::getOperationUser).distinct().toList();
List<OptionDTO> userOptions = baseUserMapper.selectUserOptionByIds(userIds); List<OptionDTO> userOptions = baseUserMapper.selectUserOptionByIds(userIds);
Map<String, String> userMap = userOptions.stream().collect(Collectors.toMap(OptionDTO::getId, OptionDTO::getName)); Map<String, String> userMap = userOptions.stream().collect(Collectors.toMap(OptionDTO::getId, OptionDTO::getName));
hisList.forEach(his -> his.setOperationUser(userMap.getOrDefault(his.getOperationUser(), his.getOperationUser()))); hisList.forEach(his -> {
his.setOperationUser(userMap.getOrDefault(his.getOperationUser(), his.getOperationUser()));
his.setResultDeleted(StringUtils.isEmpty(his.getReportId()));
});
return hisList; return hisList;
} }