diff --git a/backend/src/main/java/io/metersphere/track/dto/TestCaseReportStatusResultDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestCaseReportStatusResultDTO.java index ea29555e7c..588c03cfa7 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestCaseReportStatusResultDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestCaseReportStatusResultDTO.java @@ -3,9 +3,11 @@ package io.metersphere.track.dto; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; + @Getter @Setter -public class TestCaseReportStatusResultDTO { +public class TestCaseReportStatusResultDTO implements Serializable { private String status; private Integer count; } diff --git a/backend/src/main/java/io/metersphere/track/dto/TestPlanApiResultReportDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestPlanApiResultReportDTO.java index cd7429d6f9..39db3b91d3 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestPlanApiResultReportDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestPlanApiResultReportDTO.java @@ -3,11 +3,12 @@ package io.metersphere.track.dto; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.List; @Getter @Setter -public class TestPlanApiResultReportDTO { +public class TestPlanApiResultReportDTO implements Serializable { private List apiCaseData; private List apiScenarioData; private List apiScenarioStepData; diff --git a/backend/src/main/java/io/metersphere/track/dto/TestPlanFunctionResultReportDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestPlanFunctionResultReportDTO.java index 8f5dec4eb7..8114135a47 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestPlanFunctionResultReportDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestPlanFunctionResultReportDTO.java @@ -3,11 +3,12 @@ package io.metersphere.track.dto; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.List; @Getter @Setter -public class TestPlanFunctionResultReportDTO { +public class TestPlanFunctionResultReportDTO implements Serializable { private List caseData; private List issueData; } diff --git a/backend/src/main/java/io/metersphere/track/dto/TestPlanLoadResultReportDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestPlanLoadResultReportDTO.java index c2c8ff6e5a..0d4da50a9b 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestPlanLoadResultReportDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestPlanLoadResultReportDTO.java @@ -3,11 +3,12 @@ package io.metersphere.track.dto; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.List; @Getter @Setter -public class TestPlanLoadResultReportDTO { +public class TestPlanLoadResultReportDTO implements Serializable { private List caseData; } diff --git a/backend/src/main/java/io/metersphere/track/dto/TestPlanUiResultReportDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestPlanUiResultReportDTO.java index f670bfb4cf..b116256f15 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestPlanUiResultReportDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestPlanUiResultReportDTO.java @@ -3,11 +3,12 @@ package io.metersphere.track.dto; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.List; @Getter @Setter -public class TestPlanUiResultReportDTO { +public class TestPlanUiResultReportDTO implements Serializable { //历史的case数据 private List uiScenarioCaseData; //场景的分类统计数据 diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanMessageService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanMessageService.java index daa74d6e41..afe0937c13 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanMessageService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanMessageService.java @@ -1,5 +1,7 @@ package io.metersphere.track.service; +import io.metersphere.api.dto.automation.TestPlanFailureApiDTO; +import io.metersphere.api.dto.automation.TestPlanFailureScenarioDTO; import io.metersphere.api.service.ShareInfoService; import io.metersphere.base.domain.TestPlan; import io.metersphere.base.domain.TestPlanReport; @@ -11,6 +13,7 @@ import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.LogUtil; import io.metersphere.dto.BaseSystemConfigDTO; +import io.metersphere.dto.TestPlanUiScenarioDTO; import io.metersphere.dto.UserDTO; import io.metersphere.i18n.Translator; import io.metersphere.notice.sender.NoticeModel; @@ -18,8 +21,11 @@ import io.metersphere.notice.service.NoticeSendService; import io.metersphere.service.ProjectService; import io.metersphere.service.SystemParameterService; import io.metersphere.service.UserService; +import io.metersphere.track.dto.TestPlanCaseDTO; import io.metersphere.track.dto.TestPlanDTOWithMetric; +import io.metersphere.track.dto.TestPlanSimpleReportDTO; import org.apache.commons.beanutils.BeanMap; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; @@ -28,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; +import java.util.stream.Collectors; @Service @Transactional @@ -100,11 +107,11 @@ public class TestPlanMessageService { // 已结束:超过了计划结束时间(如有) 或 测试进度=100% 且 通过率非100% Long plannedEndTime = testPlan.getPlannedEndTime(); long currentTime = System.currentTimeMillis(); - if(Objects.nonNull(plannedEndTime) && currentTime >= plannedEndTime){ + if (Objects.nonNull(plannedEndTime) && currentTime >= plannedEndTime) { return TestPlanStatus.Finished.name(); } - if(testRate >= FULL_MARKS && passRate < FULL_MARKS){ + if (testRate >= FULL_MARKS && passRate < FULL_MARKS) { return TestPlanStatus.Finished.name(); } @@ -134,9 +141,9 @@ public class TestPlanMessageService { } else { subject = Translator.get("task_notification"); } - // 计算通过率 - TestPlanDTOWithMetric testPlanDTOWithMetric = BeanUtils.copyBean(new TestPlanDTOWithMetric(), testPlan); - testPlanService.calcTestPlanRate(Collections.singletonList(testPlanDTOWithMetric)); + // 计算各种属性 + TestPlanSimpleReportDTO report = testPlanReportService.getReport(testPlanReport.getId()); + Map caseCountMap = calculateCaseCount(report); String creator = testPlanReport.getCreator(); UserDTO userDTO = userService.getUserDTO(creator); @@ -149,7 +156,17 @@ public class TestPlanMessageService { paramMap.put("operator", userDTO.getName()); paramMap.put("executor", userDTO.getId()); } - paramMap.putAll(new BeanMap(testPlanDTOWithMetric)); + paramMap.putAll(new BeanMap(report)); + + // 执行率 通过率 两位小数 + if (report.getPassRate() != null && !report.getPassRate().isNaN()) { + paramMap.put("passRate", String.format("%.2f", report.getPassRate())); + } + if (report.getExecuteRate() != null && !report.getExecuteRate().isNaN()) { + paramMap.put("executeRate", String.format("%.2f", report.getExecuteRate())); + } + + paramMap.putAll(caseCountMap); String testPlanShareUrl = shareInfoService.getTestPlanShareUrl(testPlanReport.getId(), creator); paramMap.put("planShareUrl", baseSystemConfigDTO.getUrl() + "/sharePlanReport" + testPlanShareUrl); @@ -191,4 +208,140 @@ public class TestPlanMessageService { } } } + + private Map calculateCaseCount(TestPlanSimpleReportDTO report) { + Map result = new HashMap<>(); + // 功能用例 + result.put("functionAllCount", 0L); + result.put("functionPreparedCount", 0L); + result.put("functionSuccessCount", 0L); + result.put("functionFailedCount", 0L); + result.put("functionBlockedCount", 0L); + result.put("functionSkippedCount", 0L); + // + result.put("apiCaseSuccessCount", 0L); + result.put("apiCaseFailedCount", 0L); + result.put("apiCaseUnExecuteCount", 0L); + result.put("apiCaseErrorReportCount", 0L); + result.put("apiCaseAllCount", 0L); + // + result.put("apiScenarioSuccessCount", 0L); + result.put("apiScenarioFailedCount", 0L); + result.put("apiScenarioUnExecuteCount", 0L); + result.put("apiScenarioErrorReportCount", 0L); + result.put("apiScenarioAllCount", 0L); + // + result.put("uiScenarioSuccessCount", 0L); + result.put("uiScenarioFailedCount", 0L); + result.put("uiScenarioUnExecuteCount", 0L); + result.put("uiScenarioAllCount", 0L); + // + result.put("loadCaseAllCount", 0L); + + + List functionAllCases = report.getFunctionAllCases(); + if (CollectionUtils.isNotEmpty(functionAllCases)) { + Map functionCountMap = functionAllCases.stream().collect(Collectors.groupingBy(TestPlanCaseDTO::getStatus, Collectors.counting())); + // ["Prepare", "Pass", "Failure", "Blocking", "Skip"] + functionCountMap.forEach((k, v) -> { + switch (k) { + case "Prepare": + result.put("functionPreparedCount", v); + break; + case "Pass": + result.put("functionSuccessCount", v); + break; + case "Failure": + result.put("functionFailedCount", v); + break; + case "Blocking": + result.put("functionBlockedCount", v); + break; + case "Skip": + result.put("functionSkippedCount", v); + break; + default: + break; + } + }); + result.put("functionAllCount", (long) functionAllCases.size()); + } + + List apiAllCases = report.getApiAllCases(); + if (CollectionUtils.isNotEmpty(apiAllCases)) { + Map apiCountMap = apiAllCases.stream().collect(Collectors.groupingBy(TestPlanFailureApiDTO::getExecResult, Collectors.counting())); + // ["success", "error", "default", "errorReportResult"] + apiCountMap.forEach((k, v) -> { + switch (k) { + case "success": + result.put("apiCaseSuccessCount", v); + break; + case "error": + result.put("apiCaseFailedCount", v); + break; + case "default": + result.put("apiCaseUnExecuteCount", v); + break; + case "errorReportResult": + result.put("apiCaseErrorReportCount", v); + break; + default: + break; + } + }); + result.put("apiCaseAllCount", (long) apiAllCases.size()); + } + + List scenarioAllCases = report.getScenarioAllCases(); + if (CollectionUtils.isNotEmpty(scenarioAllCases)) { + Map scenarioCountMap = scenarioAllCases.stream().collect(Collectors.groupingBy(TestPlanFailureScenarioDTO::getLastResult, Collectors.counting())); + // ["Fail", "Success", "unexecute", "errorReportResult"] + scenarioCountMap.forEach((k, v) -> { + switch (k) { + case "Success": + result.put("apiScenarioSuccessCount", v); + break; + case "Fail": + result.put("apiScenarioFailedCount", v); + break; + case "unexecute": + result.put("apiScenarioUnExecuteCount", v); + break; + case "errorReportResult": + result.put("apiScenarioErrorReportCount", v); + break; + default: + break; + } + }); + result.put("apiScenarioAllCount", (long) scenarioAllCases.size()); + } + + List uiAllCases = report.getUiAllCases(); + if (CollectionUtils.isNotEmpty(uiAllCases)) { + Map uiCountMap = uiAllCases.stream().collect(Collectors.groupingBy(TestPlanUiScenarioDTO::getLastResult, Collectors.counting())); + uiCountMap.forEach((k, v) -> { + switch (k) { + case "Success": + result.put("uiScenarioSuccessCount", v); + break; + case "Fail": + result.put("uiScenarioFailedCount", v); + break; + case "unexecute": + result.put("uiScenarioUnExecuteCount", v); + break; + default: + break; + } + }); + result.put("uiScenarioAllCount", (long) uiAllCases.size()); + } + + if (CollectionUtils.isNotEmpty(report.getLoadAllCases())) { + result.put("loadCaseAllCount", (long) report.getLoadAllCases().size()); + } + + return result; + } } diff --git a/frontend/src/business/components/project/notification/track/TestPlanTaskNotification.vue b/frontend/src/business/components/project/notification/track/TestPlanTaskNotification.vue index 0bf6b0c621..44621d65ad 100644 --- a/frontend/src/business/components/project/notification/track/TestPlanTaskNotification.vue +++ b/frontend/src/business/components/project/notification/track/TestPlanTaskNotification.vue @@ -145,6 +145,98 @@ export default { label: this.$t('report.plan_share_url'), value: 'planShareUrl', }, + { + label: this.$t('test_track.report.exacutive_rate'), + value: 'executeRate' + }, + { + label: this.$t('test_track.report.total_number_tests'), + value: 'caseCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_count'), + value: 'functionAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_success_count'), + value: 'functionSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_failed_count'), + value: 'functionFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_blocked_count'), + value: 'functionBlockedCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_prepared_count'), + value: 'functionPreparedCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_skipped_count'), + value: 'functionSkippedCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_count'), + value: 'apiCaseAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_success_count'), + value: 'apiCaseSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_failed_count'), + value: 'apiCaseFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_un_execute_count'), + value: 'apiCaseUnExecuteCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_error_report_count'), + value: 'apiCaseErrorReportCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_count'), + value: 'apiScenarioAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_success_count'), + value: 'apiScenarioSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_failed_count'), + value: 'apiScenarioFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_un_execute_count'), + value: 'apiScenarioUnExecuteCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_error_report_count'), + value: 'apiScenarioErrorReportCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_count'), + value: 'uiScenarioAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_success_count'), + value: 'uiScenarioSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_failed_count'), + value: 'uiScenarioFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_un_execute_count'), + value: 'uiScenarioUnExecuteCount' + }, + { + label: this.$t('test_track.plan.test_plan_load_case_count'), + value: 'loadCaseAllCount' + }, ], }; }, diff --git a/frontend/src/business/components/track/plan/components/ScheduleNotification.vue b/frontend/src/business/components/track/plan/components/ScheduleNotification.vue index 3a129ec129..c20560af22 100644 --- a/frontend/src/business/components/track/plan/components/ScheduleNotification.vue +++ b/frontend/src/business/components/track/plan/components/ScheduleNotification.vue @@ -147,6 +147,98 @@ export default { label: this.$t('report.plan_share_url'), value: 'planShareUrl', }, + { + label: this.$t('test_track.report.exacutive_rate'), + value: 'executeRate' + }, + { + label: this.$t('test_track.report.total_number_tests'), + value: 'caseCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_count'), + value: 'functionAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_success_count'), + value: 'functionSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_failed_count'), + value: 'functionFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_blocked_count'), + value: 'functionBlockedCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_prepared_count'), + value: 'functionPreparedCount' + }, + { + label: this.$t('test_track.plan.test_plan_test_case_skipped_count'), + value: 'functionSkippedCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_count'), + value: 'apiCaseAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_success_count'), + value: 'apiCaseSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_failed_count'), + value: 'apiCaseFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_un_execute_count'), + value: 'apiCaseUnExecuteCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_case_error_report_count'), + value: 'apiCaseErrorReportCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_count'), + value: 'apiScenarioAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_success_count'), + value: 'apiScenarioSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_failed_count'), + value: 'apiScenarioFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_un_execute_count'), + value: 'apiScenarioUnExecuteCount' + }, + { + label: this.$t('test_track.plan.test_plan_api_scenario_error_report_count'), + value: 'apiScenarioErrorReportCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_count'), + value: 'uiScenarioAllCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_success_count'), + value: 'uiScenarioSuccessCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_failed_count'), + value: 'uiScenarioFailedCount' + }, + { + label: this.$t('test_track.plan.test_plan_ui_scenario_un_execute_count'), + value: 'uiScenarioUnExecuteCount' + }, + { + label: this.$t('test_track.plan.test_plan_load_case_count'), + value: 'loadCaseAllCount' + }, ], }; }, diff --git a/frontend/src/i18n/track/en-US.js b/frontend/src/i18n/track/en-US.js index 00143da60a..bc39dc90df 100644 --- a/frontend/src/i18n/track/en-US.js +++ b/frontend/src/i18n/track/en-US.js @@ -234,9 +234,25 @@ export default { is_scenario_executing: 'Scenario Executing', is_performance_executing: 'Performance Executing', test_plan_test_case_count: "Track Case Count", + test_plan_test_case_success_count: "Function case success count", + test_plan_test_case_failed_count: "Failed count of functional case", + test_plan_test_case_blocked_count: "Function Case Blocked Count", + test_plan_test_case_prepared_count: "Function Case Prepared Count", + test_plan_test_case_skipped_count: "Function case skipped count", test_plan_api_case_count: "Api Case Count", + test_plan_api_case_success_count: "Api Case success Count", + test_plan_api_case_failed_count: "Api Case failed Count", + test_plan_api_case_un_execute_count: "Api Case unexecuted Count", + test_plan_api_case_error_report_count: "Api Case error report Count", test_plan_api_scenario_count: "Scenario Case Count", - test_plan_ui_scenario_count: "Ui Scenario Case Count", + test_plan_api_scenario_success_count: "Scenario Case success Count", + test_plan_api_scenario_failed_count: "Scenario Case failed Count", + test_plan_api_scenario_un_execute_count: "Scenario Case unexecuted Count", + test_plan_api_scenario_error_report_count: "Scenario Case error report Count", + test_plan_ui_scenario_count: "UI Scenario Case Count", + test_plan_ui_scenario_success_count: "UI Scenario Case success Count", + test_plan_ui_scenario_failed_count: "UI Scenario Case failed Count", + test_plan_ui_scenario_un_execute_count: "UI Scenario Case unexecuted Count", test_plan_load_case_count: "Load Case Count", test_plan_component_case_count: "Component Case Count", data_name: "Data Name", diff --git a/frontend/src/i18n/track/zh-CN.js b/frontend/src/i18n/track/zh-CN.js index 52c01db53f..9690055ea0 100644 --- a/frontend/src/i18n/track/zh-CN.js +++ b/frontend/src/i18n/track/zh-CN.js @@ -227,9 +227,25 @@ export default { is_scenario_executing: '是否执行场景', is_performance_executing: '是否执行性能', test_plan_test_case_count: "功能用例数", + test_plan_test_case_success_count: "功能用例成功数", + test_plan_test_case_failed_count: "功能用例失败数", + test_plan_test_case_blocked_count: "功能用例阻塞数", + test_plan_test_case_prepared_count: "功能用例未开始数", + test_plan_test_case_skipped_count: "功能用例跳过数", test_plan_api_case_count: "接口用例数", + test_plan_api_case_success_count: "接口用例成功数", + test_plan_api_case_failed_count: "接口用例失败数", + test_plan_api_case_un_execute_count: "接口用例未执行数", + test_plan_api_case_error_report_count: "接口用例误报数", test_plan_api_scenario_count: "场景用例数", + test_plan_api_scenario_success_count: "场景用例成功数", + test_plan_api_scenario_failed_count: "场景用例失败数", + test_plan_api_scenario_un_execute_count: "场景用例未执行数", + test_plan_api_scenario_error_report_count: "场景用例误报数", test_plan_ui_scenario_count: "UI 场景用例数", + test_plan_ui_scenario_success_count: "UI 场景用例成功数", + test_plan_ui_scenario_failed_count: "UI 场景用例失败数", + test_plan_ui_scenario_un_execute_count: "UI 场景用例未执行数", test_plan_load_case_count: "性能用例数", test_plan_component_case_count: "步骤用例数", data_name: "数据名称", diff --git a/frontend/src/i18n/track/zh-TW.js b/frontend/src/i18n/track/zh-TW.js index 6b38e03816..471eca4372 100644 --- a/frontend/src/i18n/track/zh-TW.js +++ b/frontend/src/i18n/track/zh-TW.js @@ -227,9 +227,25 @@ export default { is_scenario_executing: '是否執行場景', is_performance_executing: '是否執行性能', test_plan_test_case_count: "功能用例數", + test_plan_test_case_success_count: "功能用例成功數", + test_plan_test_case_failed_count: "功能用例失敗數", + test_plan_test_case_blocked_count: "功能用例阻塞數", + test_plan_test_case_prepared_count: "功能用例未開始數", + test_plan_test_case_skipped_count: "功能用例跳過數", test_plan_api_case_count: "接口用例數", + test_plan_api_case_success_count: "接口用例成功數", + test_plan_api_case_failed_count: "接口用例失敗數", + test_plan_api_case_un_execute_count: "接口用例未執行數", + test_plan_api_case_error_report_count: "接口用例誤報數", test_plan_api_scenario_count: "場景用例數", + test_plan_api_scenario_success_count: "場景用例成功數", + test_plan_api_scenario_failed_count: "場景用例失敗數", + test_plan_api_scenario_un_execute_count: "場景用例未執行數", + test_plan_api_scenario_error_report_count: "場景用例誤報數", test_plan_ui_scenario_count: "UI 場景用例數", + test_plan_ui_scenario_success_count: "UI 場景用例成功數", + test_plan_ui_scenario_failed_count: "UI 場景用例失敗數", + test_plan_ui_scenario_un_execute_count: "UI 場景用例未執行數", test_plan_load_case_count: "性能用例數", test_plan_component_case_count: "步驟用例數", data_name: "數據名稱",