feat(测试计划): 执行用例或定时任务结束后,自动更新测试计划的状态 (#1694)
* feat(测试跟踪): 测试用例下载模版增加标签列 * fix(接口定义): 扩大请求头键长度 * fix: schedule表对旧数据name字段兼容的补充 * feat(测试计划): 执行用例或定时任务结束后,自动更新测试计划的状态
This commit is contained in:
parent
d2fabf8e93
commit
36e3e001d4
|
@ -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<String> 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());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
package io.metersphere.commons.constants;
|
||||
|
||||
public enum TestPlanStatus {
|
||||
Prepare, Underway, Completed
|
||||
Prepare, Underway, Completed, Finished
|
||||
}
|
||||
|
|
|
@ -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<MessageDetail> 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<MessageDetail> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MessageDetail> searchMessageByType(String type) {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
String orgId = user.getLastOrganizationId();
|
||||
List<MessageDetail> messageDetails = new ArrayList<>();
|
||||
try {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
String orgId = user.getLastOrganizationId();
|
||||
List<MessageDetail> messageDetails = new ArrayList<>();
|
||||
|
||||
MessageTaskExample example = new MessageTaskExample();
|
||||
example.createCriteria()
|
||||
.andTaskTypeEqualTo(type)
|
||||
.andOrganizationIdEqualTo(orgId);
|
||||
List<MessageTask> messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example);
|
||||
MessageTaskExample example = new MessageTaskExample();
|
||||
example.createCriteria()
|
||||
.andTaskTypeEqualTo(type)
|
||||
.andOrganizationIdEqualTo(orgId);
|
||||
List<MessageTask> messageTaskLists = messageTaskMapper.selectByExampleWithBLOBs(example);
|
||||
|
||||
Map<String, List<MessageTask>> messageTaskMap = messageTaskLists.stream()
|
||||
.collect(Collectors.groupingBy(NoticeService::fetchGroupKey));
|
||||
messageTaskMap.forEach((k, v) -> {
|
||||
MessageDetail messageDetail = getMessageDetail(v);
|
||||
messageDetails.add(messageDetail);
|
||||
});
|
||||
Map<String, List<MessageTask>> 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<MessageTask> messageTasks) {
|
||||
|
|
|
@ -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<TestPlanDTOWithMetric>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) {
|
||||
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<String> 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<String> 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<TestPlanDTOWithMetric> listTestPlanByProject(QueryTestPlanRequest request) {
|
||||
List<TestPlanDTOWithMetric> testPlans = extTestPlanMapper.list(request);
|
||||
return testPlans;
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
this.$fileUpload(url, null, bodyFiles, reqObj, response => {
|
||||
this.runId = response.data;
|
||||
this.getResult();
|
||||
this.$emit('autoCheckStatus'); // 执行结束后,自动更新计划状态
|
||||
}, error => {
|
||||
this.$emit('runRefresh', {});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div>
|
||||
<ms-tag v-if="value == 'Prepare'" type="info" :content="$t('test_track.plan.plan_status_prepare')"/>
|
||||
<ms-tag v-if="value == 'Underway'" type="primary" :content="$t('test_track.plan.plan_status_running')"/>
|
||||
<ms-tag v-if="value == 'Finished'" type="warning" :content="$t('test_track.plan.plan_status_finished')"/>
|
||||
<ms-tag v-if="value == 'Completed'" type="success" :content="$t('test_track.plan.plan_status_completed')"/>
|
||||
<ms-tag v-if="value === 'Trash'" type="danger" effect="plain" :content="$t('test_track.plan.plan_status_trash')"/>
|
||||
</div>
|
||||
|
|
|
@ -53,6 +53,10 @@
|
|||
:command="{item: scope.row, status: 'Underway'}">
|
||||
{{ $t('test_track.plan.plan_status_running') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="!isTestManagerOrTestUser"
|
||||
:command="{item: scope.row, status: 'Finished'}">
|
||||
{{ $t('test_track.plan.plan_status_finished') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="!isTestManagerOrTestUser"
|
||||
:command="{item: scope.row, status: 'Completed'}">
|
||||
{{ $t('test_track.plan.plan_status_completed') }}
|
||||
|
@ -82,7 +86,7 @@
|
|||
show-overflow-tooltip
|
||||
:key="index">
|
||||
<template v-slot:default="scope">
|
||||
<el-progress :percentage="scope.row.testRate"></el-progress>
|
||||
<el-progress :percentage="scope.row.passRate.substring(0, scope.row.passRate.length-1)"></el-progress>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
@ -263,6 +267,7 @@ export default {
|
|||
statusFilters: [
|
||||
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
|
||||
{text: this.$t('test_track.plan.plan_status_running'), value: 'Underway'},
|
||||
{text: this.$t('test_track.plan.plan_status_finished'), value: 'Finished'},
|
||||
{text: this.$t('test_track.plan.plan_status_completed'), value: 'Completed'}
|
||||
],
|
||||
stageFilters: [
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
|
||||
<!-- 执行组件 -->
|
||||
<ms-run :debug="false" :type="'API_PLAN'" :reportId="reportId" :run-data="runData"
|
||||
@runRefresh="runRefresh" ref="runTest"/>
|
||||
@runRefresh="runRefresh" ref="runTest" @autoCheckStatus="autoCheckStatus"/>
|
||||
|
||||
<!-- 批量编辑 -->
|
||||
<batch-edit :dialog-title="$t('test_track.case.batch_edit_case')" :type-arr="typeArr" :value-arr="valueArr"
|
||||
|
@ -319,6 +319,7 @@ export default {
|
|||
this.$emit('isApiListEnableChange', data);
|
||||
},
|
||||
initTable() {
|
||||
this.autoCheckStatus();
|
||||
this.selectRows = new Set();
|
||||
this.condition.status = "";
|
||||
this.condition.moduleIds = this.selectNodeIds;
|
||||
|
@ -520,6 +521,10 @@ export default {
|
|||
this.$fileUpload("/api/definition/run", null, bodyFiles, reqObj, response => {
|
||||
});
|
||||
},
|
||||
autoCheckStatus() { // 检查执行结果,自动更新计划状态
|
||||
this.$post('/test/plan/autoCheck/' + this.planId, (response) => {
|
||||
});
|
||||
},
|
||||
handleDelete(apiCase) {
|
||||
if (this.planId) {
|
||||
this.$get('/test/plan/api/case/delete/' + apiCase.id, () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(() => {
|
||||
// 如果有状态不是最终状态则定时查询
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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: "计划开始",
|
||||
|
|
|
@ -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: "計劃開始",
|
||||
|
|
Loading…
Reference in New Issue