diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml index 0f97f67a8f..1f85d8cf5a 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/mapper/ExtBugMapper.xml @@ -187,7 +187,7 @@ #{lastStatus} - + and b.handle_user = #{request.todoParam.platformUser} ) diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanReportController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanReportController.java index a6992a6950..128c6c1512 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanReportController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanReportController.java @@ -8,10 +8,7 @@ import io.metersphere.plan.constants.TestPlanResourceConfig; import io.metersphere.plan.domain.TestPlanReportComponent; import io.metersphere.plan.dto.ReportDetailCasePageDTO; import io.metersphere.plan.dto.request.*; -import io.metersphere.plan.dto.response.TestPlanCaseExecHistoryResponse; -import io.metersphere.plan.dto.response.TestPlanReportDetailCollectionResponse; -import io.metersphere.plan.dto.response.TestPlanReportDetailResponse; -import io.metersphere.plan.dto.response.TestPlanReportPageResponse; +import io.metersphere.plan.dto.response.*; import io.metersphere.plan.service.*; import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.system.log.annotation.Log; @@ -129,6 +126,14 @@ public class TestPlanReportController { return testPlanReportService.getReport(reportId); } + @GetMapping("/get-task/{taskId}") + @Operation(summary = "测试计划|组-任务-执行结果") + @RequiresPermissions(value = {PermissionConstants.TEST_PLAN_REPORT_READ, PermissionConstants.TEST_PLAN_READ_EXECUTE}, logical = Logical.OR) + @CheckOwner(resourceId = "#taskId", resourceType = "exec_task") + public TestPlanTaskReportResponse getTaskDetail(@PathVariable String taskId) { + return testPlanReportService.getTaskDetail(taskId); + } + @GetMapping("/get-layout/{reportId}") @Operation(summary = "测试计划-报告-组件布局") @RequiresPermissions(value = {PermissionConstants.TEST_PLAN_REPORT_READ, PermissionConstants.TEST_PLAN_READ_EXECUTE}, logical = Logical.OR) diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/CaseCount.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/CaseCount.java index d2f19a449d..4a28bed646 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/CaseCount.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/CaseCount.java @@ -30,4 +30,8 @@ public class CaseCount { @Schema(description = "未执行用例数量") @Builder.Default private Integer pending = 0; + + public Integer sum() { + return success + error + fakeError + block + pending; + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanReportDetailResponse.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanReportDetailResponse.java index 5ed4101c27..cdb754e9d6 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanReportDetailResponse.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanReportDetailResponse.java @@ -9,6 +9,9 @@ import lombok.Data; import java.util.List; +/** + * @author song-cc-rock + */ @Data public class TestPlanReportDetailResponse { diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanTaskReportResponse.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanTaskReportResponse.java new file mode 100644 index 0000000000..03b57cfbe1 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanTaskReportResponse.java @@ -0,0 +1,33 @@ +package io.metersphere.plan.dto.response; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.metersphere.plan.dto.CaseCount; +import io.metersphere.system.serializer.CustomRateSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * @author song-cc-rock + */ +@Data +public class TestPlanTaskReportResponse { + + @Schema(description = "执行结果") + private String result; + @Schema(description = "执行状态") + private String status; + @Schema(description = "操作人") + private String createUser; + @Schema(description = "任务发起时间") + private Long createTime; + @Schema(description = "任务开始起时间") + private Long startTime; + @Schema(description = "任务结束时间") + private Long endTime; + @Schema(description = "执行用例统计(实时)") + private CaseCount executeCaseCount; + @Schema(description = "执行完成率(实时)") + @JsonSerialize(using = CustomRateSerializer.class) + private Double executeRate; + +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml index f498038de2..9a148d15ae 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml @@ -642,10 +642,11 @@ SELECT test_plan.project_id as projectId, count(test_plan.id) as count diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java index d1e73682b7..29131fea2f 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanExecuteService.java @@ -327,6 +327,7 @@ public class TestPlanExecuteService { execTask.setOrganizationId(project.getOrganizationId()); execTask.setTriggerMode(triggerMode); execTask.setTaskType(StringUtils.equalsIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_PLAN) ? ExecTaskType.TEST_PLAN.name() : ExecTaskType.TEST_PLAN_GROUP.name()); + execTask.setResourceId(testPlan.getId()); baseTaskHubService.insertExecTask(execTask); // 创建报告和任务的关联关系 diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanReportService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanReportService.java index cee5d0cce9..987a02a705 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanReportService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanReportService.java @@ -12,10 +12,7 @@ import io.metersphere.plan.constants.CollectionQueryType; import io.metersphere.plan.domain.*; import io.metersphere.plan.dto.*; import io.metersphere.plan.dto.request.*; -import io.metersphere.plan.dto.response.TestPlanCaseExecHistoryResponse; -import io.metersphere.plan.dto.response.TestPlanReportDetailCollectionResponse; -import io.metersphere.plan.dto.response.TestPlanReportDetailResponse; -import io.metersphere.plan.dto.response.TestPlanReportPageResponse; +import io.metersphere.plan.dto.response.*; import io.metersphere.plan.enums.TestPlanReportAttachmentSourceType; import io.metersphere.plan.mapper.*; import io.metersphere.plan.utils.CountUtils; @@ -29,10 +26,12 @@ import io.metersphere.sdk.file.FileCopyRequest; import io.metersphere.sdk.file.FileRepository; import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.util.*; +import io.metersphere.system.domain.ExecTask; import io.metersphere.system.domain.ExecTaskItem; import io.metersphere.system.domain.User; import io.metersphere.system.dto.sdk.OptionDTO; import io.metersphere.system.mapper.BaseUserMapper; +import io.metersphere.system.mapper.ExecTaskMapper; import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.service.BaseTaskHubService; @@ -128,6 +127,8 @@ public class TestPlanReportService { private ApiReportRelateTaskMapper apiReportRelateTaskMapper; @Resource private TestPlanCollectionMapper testPlanCollectionMapper; + @Resource + private ExecTaskMapper execTaskMapper; private static final int MAX_REPORT_NAME_LENGTH = 300; @@ -798,6 +799,21 @@ public class TestPlanReportService { return planReportDetail; } + public TestPlanTaskReportResponse getTaskDetail(String taskId) { + TestPlanTaskReportResponse testPlanTaskReportResponse = new TestPlanTaskReportResponse(); + ExecTask task = execTaskMapper.selectByPrimaryKey(taskId); + BeanUtils.copyBean(testPlanTaskReportResponse, task); + ApiReportRelateTaskExample example = new ApiReportRelateTaskExample(); + example.createCriteria().andTaskResourceIdEqualTo(taskId); + List taskReports = apiReportRelateTaskMapper.selectByExample(example); + if (CollectionUtils.isEmpty(taskReports)) { + // 暂未生成报告 + return testPlanTaskReportResponse; + } + String reportId = taskReports.getFirst().getReportId(); + return calcTaskExecActual(reportId, testPlanTaskReportResponse); + } + public List getLayout(String reportId) { TestPlanReportComponentExample example = new TestPlanReportComponentExample(); example.createCriteria().andTestPlanReportIdEqualTo(reportId); @@ -1403,4 +1419,24 @@ public class TestPlanReportService { testPlanReportLogService.exportLog(reports, userId, projectId, "/test-plan/report/batch-export"); } } + + /** + * 计算计划任务的用例执行情况(实时, 并不取计划报告的最终汇总) + * @return 用例执行情况 + */ + private TestPlanTaskReportResponse calcTaskExecActual(String reportId, TestPlanTaskReportResponse testPlanTaskReportResponse) { + // 计算接口用例 + List apiCountMapList = extTestPlanReportApiCaseMapper.countExecuteResult(reportId); + CaseCount apiCaseCount = countMap(apiCountMapList); + // 计算场景用例 + List scenarioCountMapList = extTestPlanReportApiScenarioMapper.countExecuteResult(reportId); + CaseCount scenarioCaseCount = countMap(scenarioCountMapList); + // 汇总接口&&场景用例的执行情况 + CaseCount caseCount = CountUtils.summarizeProperties(List.of(apiCaseCount, scenarioCaseCount)); + testPlanTaskReportResponse.setExecuteCaseCount(caseCount); + // 完成率 = (总数 - 未执行数) / 总数 + testPlanTaskReportResponse.setExecuteRate((caseCount.sum() == 0) ? + 0 : RateCalculateUtils.divWithPrecision(caseCount.sum() - caseCount.getPending(), caseCount.sum(), 2)); + return testPlanTaskReportResponse; + } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanReportControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanReportControllerTests.java index 63b8feba23..3f29059305 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanReportControllerTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanReportControllerTests.java @@ -1,5 +1,7 @@ package io.metersphere.plan.controller; +import io.metersphere.api.domain.ApiReportRelateTaskExample; +import io.metersphere.api.mapper.ApiReportRelateTaskMapper; import io.metersphere.plan.constants.CollectionQueryType; import io.metersphere.plan.domain.*; import io.metersphere.plan.dto.TestPlanShareInfo; @@ -50,6 +52,7 @@ public class TestPlanReportControllerTests extends BaseTest { private static final String GET_MANUAL_PLAN_REPORT_LAYOUT = "/test-plan/report/get-layout"; private static final String AUTO_GEN_PLAN_REPORT = "/test-plan/report/auto-gen"; private static final String GET_PLAN_REPORT = "/test-plan/report/get"; + private static final String GET_PLAN_TASK_RESULT = "/test-plan/report/get-task"; private static final String EDIT_PLAN_REPORT_AND_UPLOAD_PIC = "/test-plan/report/upload/md/file"; private static final String EDIT_PLAN_REPORT = "/test-plan/report/detail/edit"; private static final String GET_PLAN_REPORT_DETAIL_BUG_PAGE = "/test-plan/report/detail/bug/page"; @@ -86,6 +89,8 @@ public class TestPlanReportControllerTests extends BaseTest { private static String GEN_REPORT_ID; private static String GEN_SHARE_ID; + @Autowired + private ApiReportRelateTaskMapper apiReportRelateTaskMapper; @Test @Order(1) @@ -297,6 +302,10 @@ public class TestPlanReportControllerTests extends BaseTest { void testGetReportSuccess() throws Exception { this.requestGet(GET_PLAN_REPORT + "/" + GEN_REPORT_ID); this.requestGet(GET_PLAN_REPORT + "/" + "test-plan-report-id-5"); + this.requestGet(GET_PLAN_TASK_RESULT + "/" + "task-id-1"); + this.requestGet(GET_PLAN_TASK_RESULT + "/" + "task-id-2"); + // 为了不影响后续报告的清理 + cleanDefaultTaskReportRelate(); } @Test @@ -501,4 +510,10 @@ public class TestPlanReportControllerTests extends BaseTest { request.setSelectAll(true); this.requestPost(BATCH_EXPORT_REPORT, request); } + + private void cleanDefaultTaskReportRelate() { + ApiReportRelateTaskExample example = new ApiReportRelateTaskExample(); + example.createCriteria().andTaskResourceIdEqualTo("task-id-1"); + apiReportRelateTaskMapper.deleteByExample(example); + } } diff --git a/backend/services/test-plan/src/test/resources/dml/init_test_plan_report.sql b/backend/services/test-plan/src/test/resources/dml/init_test_plan_report.sql index 3351b6427d..a5b1013990 100644 --- a/backend/services/test-plan/src/test/resources/dml/init_test_plan_report.sql +++ b/backend/services/test-plan/src/test/resources/dml/init_test_plan_report.sql @@ -46,4 +46,12 @@ INSERT INTO `share_info`(`id`, `create_time`, `create_user`, `update_time`, `sha -- 功能用例执行信息 INSERT INTO test_plan_case_execute_history (`id`, `test_plan_case_id`, `test_plan_id`, `case_id`, `status`, `content`, `steps`, `deleted`, `notifier`, `create_user`, `create_time`) VALUES ('execute-his-1', 'test-plan-case-id-for-oasis', 'test-plan-id-for-oasis', 'case-id-for-oasis', 'PENDING', null, null, 0, null, 'admin', UNIX_TIMESTAMP()), - ('execute-his-2', 'test-plan-case-id-for-oasis', 'test-plan-id-for-oasis', 'case-id-for-oasis', 'PENDING', '1', '1', 0, null, 'admin', UNIX_TIMESTAMP()); \ No newline at end of file + ('execute-his-2', 'test-plan-case-id-for-oasis', 'test-plan-id-for-oasis', 'case-id-for-oasis', 'PENDING', '1', '1', 0, null, 'admin', UNIX_TIMESTAMP()); + +-- 任务-报告关联表 +INSERT INTO exec_task (id, num, task_name, status, case_count, result, task_type, trigger_mode, project_id, organization_id, create_time, create_user, start_time, end_time, integrated) VALUE + ('task-id-1', 100001, '任务-1', 'PENDING', 1, 'SUCCESS', 'TEST_PLAN', 'MANUAL', '100001100001', '100001', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0); +INSERT INTO api_report_relate_task (task_resource_id, report_id) VALUE + ('task-id-1', 'test-plan-report-id-6'); +INSERT INTO exec_task (id, num, task_name, status, case_count, result, task_type, trigger_mode, project_id, organization_id, create_time, create_user, start_time, end_time, integrated) VALUE + ('task-id-2', 100001, '任务-1', 'PENDING', 1, 'SUCCESS', 'TEST_PLAN', 'MANUAL', '100001100001', '100001', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0); \ No newline at end of file