diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index b64f1bb209..6e5c3b058f 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -5,6 +5,7 @@ import io.metersphere.api.service.*; import io.metersphere.base.domain.ApiDefinitionExecResult; import io.metersphere.base.domain.ApiScenarioReport; import io.metersphere.base.domain.ApiTestReport; +import io.metersphere.base.domain.TestPlanReport; import io.metersphere.commons.constants.*; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.LogUtil; @@ -14,6 +15,7 @@ import io.metersphere.notice.sender.NoticeModel; import io.metersphere.notice.service.NoticeSendService; import io.metersphere.service.SystemParameterService; import io.metersphere.track.service.TestPlanReportService; +import io.metersphere.track.service.TestPlanService; import io.metersphere.track.service.TestPlanTestCaseService; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -197,6 +199,9 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl apiDefinitionExecResultService.saveApiResultByScheduleTask(testResult, ApiRunMode.SCHEDULE_API_PLAN.name()); List testPlanReportIdList = new ArrayList<>(); testPlanReportIdList.add(debugReportId); + for(String testPlanReportId : testPlanReportIdList) { // 更新每个测试计划的状态 + testPlanReportService.checkTestPlanStatus(testPlanReportId); + } testPlanReportService.updateReport(testPlanReportIdList, ApiRunMode.SCHEDULE_API_PLAN.name(), ReportTriggerMode.SCHEDULE.name()); } else { apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.API_PLAN.name()); diff --git a/backend/src/main/java/io/metersphere/commons/constants/TestPlanStatus.java b/backend/src/main/java/io/metersphere/commons/constants/TestPlanStatus.java index 72273b1a38..557decfe54 100644 --- a/backend/src/main/java/io/metersphere/commons/constants/TestPlanStatus.java +++ b/backend/src/main/java/io/metersphere/commons/constants/TestPlanStatus.java @@ -1,5 +1,5 @@ package io.metersphere.commons.constants; public enum TestPlanStatus { - Prepare, Underway, Completed + Prepare, Underway, Completed, Finished } diff --git a/backend/src/main/java/io/metersphere/notice/service/NoticeSendService.java b/backend/src/main/java/io/metersphere/notice/service/NoticeSendService.java index 3aea7a17cc..e94433c6fd 100644 --- a/backend/src/main/java/io/metersphere/notice/service/NoticeSendService.java +++ b/backend/src/main/java/io/metersphere/notice/service/NoticeSendService.java @@ -2,6 +2,7 @@ package io.metersphere.notice.service; import com.alibaba.nacos.client.utils.StringUtils; import io.metersphere.commons.constants.NoticeConstants; +import io.metersphere.commons.utils.LogUtil; import io.metersphere.notice.domain.MessageDetail; import io.metersphere.notice.sender.NoticeModel; import io.metersphere.notice.sender.NoticeSender; @@ -44,22 +45,26 @@ public class NoticeSendService { } public void send(String taskType, NoticeModel noticeModel) { - List messageDetails; - switch (taskType) { - case NoticeConstants.Mode.API: - messageDetails = noticeService.searchMessageByType(NoticeConstants.TaskType.JENKINS_TASK); - break; - case NoticeConstants.Mode.SCHEDULE: - messageDetails = noticeService.searchMessageByTestId(noticeModel.getTestId()); - break; - default: - messageDetails = noticeService.searchMessageByType(taskType); - break; - } - messageDetails.forEach(messageDetail -> { - if (StringUtils.equals(messageDetail.getEvent(), noticeModel.getEvent())) { - this.getNoticeSender(messageDetail).send(messageDetail, noticeModel); + try { + List messageDetails; + switch (taskType) { + case NoticeConstants.Mode.API: + messageDetails = noticeService.searchMessageByType(NoticeConstants.TaskType.JENKINS_TASK); + break; + case NoticeConstants.Mode.SCHEDULE: + messageDetails = noticeService.searchMessageByTestId(noticeModel.getTestId()); + break; + default: + messageDetails = noticeService.searchMessageByType(taskType); + break; } - }); + messageDetails.forEach(messageDetail -> { + if (StringUtils.equals(messageDetail.getEvent(), noticeModel.getEvent())) { + this.getNoticeSender(messageDetail).send(messageDetail, noticeModel); + } + }); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + } } } diff --git a/backend/src/main/java/io/metersphere/notice/service/NoticeService.java b/backend/src/main/java/io/metersphere/notice/service/NoticeService.java index 0ab7b30d26..932a14812a 100644 --- a/backend/src/main/java/io/metersphere/notice/service/NoticeService.java +++ b/backend/src/main/java/io/metersphere/notice/service/NoticeService.java @@ -5,6 +5,7 @@ import io.metersphere.base.domain.MessageTaskExample; import io.metersphere.base.mapper.MessageTaskMapper; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; +import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.i18n.Translator; import io.metersphere.notice.domain.MessageDetail; @@ -102,29 +103,34 @@ public class NoticeService { } public List searchMessageByType(String type) { - SessionUser user = SessionUtils.getUser(); - String orgId = user.getLastOrganizationId(); - List messageDetails = new ArrayList<>(); + try { + SessionUser user = SessionUtils.getUser(); + String orgId = user.getLastOrganizationId(); + List messageDetails = new ArrayList<>(); - MessageTaskExample example = new MessageTaskExample(); - example.createCriteria() - .andTaskTypeEqualTo(type) - .andOrganizationIdEqualTo(orgId); - List messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example); + MessageTaskExample example = new MessageTaskExample(); + example.createCriteria() + .andTaskTypeEqualTo(type) + .andOrganizationIdEqualTo(orgId); + List messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example); - Map> messageTaskMap = messageTaskLists.stream() - .collect(Collectors.groupingBy(NoticeService::fetchGroupKey)); - messageTaskMap.forEach((k, v) -> { - MessageDetail messageDetail = getMessageDetail(v); - messageDetails.add(messageDetail); - }); + Map> messageTaskMap = messageTaskLists.stream() + .collect(Collectors.groupingBy(NoticeService::fetchGroupKey)); + messageTaskMap.forEach((k, v) -> { + MessageDetail messageDetail = getMessageDetail(v); + messageDetails.add(messageDetail); + }); - return messageDetails.stream() - .sorted(Comparator.comparing(MessageDetail::getCreateTime, Comparator.nullsLast(Long::compareTo)).reversed()) - .collect(Collectors.toList()) - .stream() - .distinct() - .collect(Collectors.toList()); + return messageDetails.stream() + .sorted(Comparator.comparing(MessageDetail::getCreateTime, Comparator.nullsLast(Long::compareTo)).reversed()) + .collect(Collectors.toList()) + .stream() + .distinct() + .collect(Collectors.toList()); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + return new ArrayList<>(); + } } private MessageDetail getMessageDetail(List messageTasks) { diff --git a/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java b/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java index 338f682d43..35c79d3b2c 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java @@ -37,6 +37,11 @@ public class TestPlanController { @Resource CheckPermissionService checkPermissionService; + @PostMapping("/autoCheck/{testPlanId}") + public void autoCheck(@PathVariable String testPlanId){ + testPlanService.checkStatus(testPlanId); + } + @PostMapping("/list/{goPage}/{pageSize}") public Pager> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) { String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java index c6682eaf1d..6a1f028330 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java @@ -11,10 +11,7 @@ import io.metersphere.base.mapper.ext.ExtTestPlanApiCaseMapper; import io.metersphere.base.mapper.ext.ExtTestPlanMapper; import io.metersphere.base.mapper.ext.ExtTestPlanReportMapper; import io.metersphere.commons.constants.*; -import io.metersphere.commons.utils.CommonBeanFactory; -import io.metersphere.commons.utils.DateUtils; -import io.metersphere.commons.utils.ServiceUtils; -import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.commons.utils.*; import io.metersphere.dto.BaseSystemConfigDTO; import io.metersphere.i18n.Translator; import io.metersphere.notice.sender.NoticeModel; @@ -212,6 +209,15 @@ public class TestPlanReportService { } } + public void checkTestPlanStatus(String planReportId) { + try { + TestPlanReport testPlanReport = testPlanReportMapper.selectByPrimaryKey(planReportId); + testPlanService.checkStatus(testPlanReport.getTestPlanId()); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + } + } + /** * * @param planReportId 测试计划报告ID diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java index c7c1fed489..a959dbf120 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java @@ -194,7 +194,10 @@ public class TestPlanService { !TestPlanStatus.Completed.name().equals(res.getStatus())) { //已完成,写入实际完成时间 testPlan.setActualEndTime(System.currentTimeMillis()); - } + } else if (!res.getStatus().equals(TestPlanStatus.Finished.name()) && + TestPlanStatus.Finished.name().equals(testPlan.getStatus())) { + testPlan.setActualEndTime(System.currentTimeMillis()); + } // 非已结束->已结束,更新结束时间 } List userIds = new ArrayList<>(); @@ -367,7 +370,7 @@ public class TestPlanService { testPlan.setTotal(apiExecResults.size() + scenarioExecResults.size() + functionalExecResults.size() + loadResults.size()); - testPlan.setPassRate(MathUtils.getPercentWithDecimal(testPlan.getTested() == 0 ? 0 : testPlan.getPassed() * 1.0 / testPlan.getTested())); + testPlan.setPassRate(MathUtils.getPercentWithDecimal(testPlan.getTested() == 0 ? 0 : testPlan.getPassed() * 1.0 / testPlan.getTotal())); testPlan.setTestRate(MathUtils.getPercentWithDecimal(testPlan.getTotal() == 0 ? 0 : testPlan.getTested() * 1.0 / testPlan.getTotal())); }); } @@ -383,6 +386,44 @@ public class TestPlanService { return testPlans; } + public void checkStatus(String testPlanId) { // 检查执行结果,自动更新计划状态 + List statusList = new ArrayList<>(); + statusList.addAll(extTestPlanTestCaseMapper.getExecResultByPlanId(testPlanId)); + statusList.addAll(testPlanApiCaseService.getExecResultByPlanId(testPlanId)); + statusList.addAll(testPlanScenarioCaseService.getExecResultByPlanId(testPlanId)); + statusList.addAll(testPlanLoadCaseService.getStatus(testPlanId)); +// Prepare, Pass, Failure, Blocking, Skip, Underway + TestPlanDTO testPlanDTO = new TestPlanDTO(); + testPlanDTO.setId(testPlanId); + if(statusList.size() == 0) { // 原先status不是prepare, 但删除所有关联用例的情况 + testPlanDTO.setStatus(TestPlanStatus.Prepare.name()); + editTestPlan(testPlanDTO); + return; + } + int passNum = 0, prepareNum = 0, failNum = 0; + for(String res : statusList) { + if(StringUtils.equals(res, TestPlanTestCaseStatus.Pass.name()) + || StringUtils.equals(res, "success") + || StringUtils.equals(res, ScenarioStatus.Success.name())) { + passNum++; + } else if (res == null) { + prepareNum++; + } else { + failNum++; + } + } + if(passNum == statusList.size()) { // 全部通过 + testPlanDTO.setStatus(TestPlanStatus.Completed.name()); + this.editTestPlan(testPlanDTO); + } else if(prepareNum == 0 && passNum + failNum == statusList.size()) { // 已结束 + testPlanDTO.setStatus(TestPlanStatus.Finished.name()); + editTestPlan(testPlanDTO); + } else if(prepareNum != 0) { // 进行中 + testPlanDTO.setStatus(TestPlanStatus.Underway.name()); + editTestPlan(testPlanDTO); + } + } + public List listTestPlanByProject(QueryTestPlanRequest request) { List testPlans = extTestPlanMapper.list(request); return testPlans; diff --git a/frontend/src/business/components/api/definition/components/Run.vue b/frontend/src/business/components/api/definition/components/Run.vue index 5cbf8751ed..4245075e55 100644 --- a/frontend/src/business/components/api/definition/components/Run.vue +++ b/frontend/src/business/components/api/definition/components/Run.vue @@ -86,6 +86,7 @@ this.$fileUpload(url, null, bodyFiles, reqObj, response => { this.runId = response.data; this.getResult(); + this.$emit('autoCheckStatus'); // 执行结束后,自动更新计划状态 }, error => { this.$emit('runRefresh', {}); }); diff --git a/frontend/src/business/components/track/common/tableItems/plan/PlanStatusTableItem.vue b/frontend/src/business/components/track/common/tableItems/plan/PlanStatusTableItem.vue index 786cb030cc..1bee6abea9 100644 --- a/frontend/src/business/components/track/common/tableItems/plan/PlanStatusTableItem.vue +++ b/frontend/src/business/components/track/common/tableItems/plan/PlanStatusTableItem.vue @@ -2,6 +2,7 @@
+
diff --git a/frontend/src/business/components/track/plan/components/TestPlanList.vue b/frontend/src/business/components/track/plan/components/TestPlanList.vue index 473047b812..e7b880c2e0 100644 --- a/frontend/src/business/components/track/plan/components/TestPlanList.vue +++ b/frontend/src/business/components/track/plan/components/TestPlanList.vue @@ -53,6 +53,10 @@ :command="{item: scope.row, status: 'Underway'}"> {{ $t('test_track.plan.plan_status_running') }} + + {{ $t('test_track.plan.plan_status_finished') }} + {{ $t('test_track.plan.plan_status_completed') }} @@ -82,7 +86,7 @@ show-overflow-tooltip :key="index"> + @runRefresh="runRefresh" ref="runTest" @autoCheckStatus="autoCheckStatus"/> { }); }, + autoCheckStatus() { // 检查执行结果,自动更新计划状态 + this.$post('/test/plan/autoCheck/' + this.planId, (response) => { + }); + }, handleDelete(apiCase) { if (this.planId) { this.$get('/test/plan/api/case/delete/' + apiCase.id, () => { diff --git a/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue b/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue index 09a693c76d..9fbc3cafb5 100644 --- a/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue @@ -431,6 +431,7 @@ export default { }, initTableData() { + this.autoCheckStatus(); if (this.planId) { // param.planId = this.planId; this.condition.planId = this.planId; @@ -479,6 +480,10 @@ export default { } getLabel(this, TEST_PLAN_FUNCTION_TEST_CASE); }, + autoCheckStatus() { + this.$post('/test/plan/autoCheck/' + this.planId, (response) => { + }); + }, showDetail(row, event, column) { this.isReadOnly = true; this.$refs.testPlanTestCaseEdit.openTestCaseEdit(row); diff --git a/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue b/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue index db66a0d7ce..430e61a5fa 100644 --- a/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue @@ -213,6 +213,7 @@ export default { this.$refs.headerCustom.open(this.tableLabel) }, initTable() { + this.autoCheckStatus(); this.selectRows = new Set(); this.condition.testPlanId = this.planId; if (this.selectProjectId && this.selectProjectId !== 'root') { @@ -247,6 +248,10 @@ export default { getLabel(this, TEST_PLAN_LOAD_CASE); }, + autoCheckStatus() { + this.$post('/test/plan/autoCheck/' + this.planId, (response) => { + }); + }, refreshStatus() { this.refreshScheduler = setInterval(() => { // 如果有状态不是最终状态则定时查询 diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index caf212e8f6..080a830a8a 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -1242,6 +1242,7 @@ export default { input_plan_stage: "Please select stage", plan_status_prepare: "Not started", plan_status_running: "Starting", + plan_status_finished: "Finished", plan_status_completed: "Completed", plan_status_trash: "Trashed", planned_start_time: "Scheduled Start Time", diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index aed3a90c5d..be06b88245 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -1246,6 +1246,7 @@ export default { input_plan_stage: "请选择测试阶段", plan_status_prepare: "未开始", plan_status_running: "进行中", + plan_status_finished: "已结束", plan_status_completed: "已完成", plan_status_trash: "废弃", planned_start_time: "计划开始", diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 201f41e576..a0a0fcc566 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -1244,6 +1244,7 @@ export default { input_plan_stage: "請選擇測試階段", plan_status_prepare: "未開始", plan_status_running: "進行中", + plan_status_finished: "已結束", plan_status_completed: "已完成", plan_status_trash: "廢棄", planned_start_time: "計劃開始",