fix(测试计划): 复制计划或计划组暂未复制定时任务

--bug=1042641 --user=宋昌昌 【测试计划】计划列表-操作-复制计划成功-首次进入计划详情-测试规划展示为空 https://www.tapd.cn/55049933/s/1532263
This commit is contained in:
song-cc-rock 2024-06-20 12:03:16 +08:00 committed by Craftsman
parent f1fc2f302f
commit d71ce02d4c
6 changed files with 123 additions and 43 deletions

View File

@ -47,6 +47,8 @@ public class TestPlanController {
@Resource @Resource
private TestPlanService testPlanService; private TestPlanService testPlanService;
@Resource @Resource
private TestPlanScheduleService testPlanScheduleService;
@Resource
private TestPlanManagementService testPlanManagementService; private TestPlanManagementService testPlanManagementService;
@Resource @Resource
private TestPlanStatisticsService testPlanStatisticsService; private TestPlanStatisticsService testPlanStatisticsService;
@ -245,7 +247,7 @@ public class TestPlanController {
@CheckOwner(resourceId = "#request.getResourceId()", resourceType = "test_plan") @CheckOwner(resourceId = "#request.getResourceId()", resourceType = "test_plan")
public String scheduleConfig(@Validated @RequestBody BaseScheduleConfigRequest request) { public String scheduleConfig(@Validated @RequestBody BaseScheduleConfigRequest request) {
testPlanManagementService.checkModuleIsOpen(request.getResourceId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN)); testPlanManagementService.checkModuleIsOpen(request.getResourceId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
return testPlanService.scheduleConfig(request, SessionUtils.getUserId()); return testPlanScheduleService.scheduleConfig(request, SessionUtils.getUserId());
} }
@GetMapping(value = "/schedule-config-delete/{testPlanId}") @GetMapping(value = "/schedule-config-delete/{testPlanId}")

View File

@ -2,6 +2,7 @@ package io.metersphere.plan.service;
import io.metersphere.plan.domain.*; import io.metersphere.plan.domain.*;
import io.metersphere.plan.dto.response.TestPlanResponse; import io.metersphere.plan.dto.response.TestPlanResponse;
import io.metersphere.plan.job.TestPlanScheduleJob;
import io.metersphere.plan.mapper.ExtTestPlanMapper; import io.metersphere.plan.mapper.ExtTestPlanMapper;
import io.metersphere.plan.mapper.TestPlanCollectionMapper; import io.metersphere.plan.mapper.TestPlanCollectionMapper;
import io.metersphere.plan.mapper.TestPlanConfigMapper; import io.metersphere.plan.mapper.TestPlanConfigMapper;
@ -12,7 +13,11 @@ import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.Schedule;
import io.metersphere.system.dto.request.schedule.BaseScheduleConfigRequest;
import io.metersphere.system.schedule.ScheduleService;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -33,7 +38,10 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService {
private ExtTestPlanMapper extTestPlanMapper; private ExtTestPlanMapper extTestPlanMapper;
@Resource @Resource
private TestPlanMapper testPlanMapper; private TestPlanMapper testPlanMapper;
@Resource
private ScheduleService scheduleService;
@Resource
private TestPlanScheduleService testPlanScheduleService;
@Resource @Resource
private TestPlanGroupService testPlanGroupService; private TestPlanGroupService testPlanGroupService;
@Resource @Resource
@ -237,6 +245,10 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService {
beansOfType.forEach((k, v) -> { beansOfType.forEach((k, v) -> {
v.copyResource(originalTestPlan.getId(), testPlan.getId(), oldCollectionIdToNewCollectionId, operator, operatorTime); v.copyResource(originalTestPlan.getId(), testPlan.getId(), oldCollectionIdToNewCollectionId, operator, operatorTime);
}); });
// 复制计划-定时任务信息
copySchedule(originalTestPlan.getId(), testPlan.getId(), operator);
return testPlan; return testPlan;
} }
@ -280,9 +292,32 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService {
for (TestPlan child : childList) { for (TestPlan child : childList) {
copyPlan(child, testPlanGroup.getId(), TestPlanConstants.TEST_PLAN_TYPE_GROUP, operatorTime, operator); copyPlan(child, testPlanGroup.getId(), TestPlanConstants.TEST_PLAN_TYPE_GROUP, operatorTime, operator);
} }
// 复制计划组-定时任务信息
copySchedule(originalGroup.getId(), testPlanGroup.getId(), operator);
return testPlanGroup; return testPlanGroup;
} }
/**
* 复制 计划/计划组 定时任务
* @param resourceId 来源ID
* @param targetId 目标ID
* @param operator 操作人
*/
private void copySchedule(String resourceId, String targetId, String operator) {
Schedule originalSchedule = scheduleService.getScheduleByResource(resourceId, TestPlanScheduleJob.class.getName());
if (originalSchedule != null) {
// 来源的 "计划/计划组" 存在定时任务即复制, 无论开启或关闭
BaseScheduleConfigRequest scheduleRequest = new BaseScheduleConfigRequest();
scheduleRequest.setEnable(originalSchedule.getEnable());
scheduleRequest.setCron(originalSchedule.getValue());
// noinspection unchecked
scheduleRequest.setRunConfig(JSON.parseMap(originalSchedule.getConfig()));
scheduleRequest.setResourceId(targetId);
testPlanScheduleService.scheduleConfig(scheduleRequest, operator);
}
}
private String getCopyName(String name, long oldNum, long newNum) { private String getCopyName(String name, long oldNum, long newNum) {
if (!StringUtils.startsWith(name, "copy_")) { if (!StringUtils.startsWith(name, "copy_")) {
name = "copy_" + name; name = "copy_" + name;

View File

@ -0,0 +1,67 @@
package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanExample;
import io.metersphere.plan.job.TestPlanScheduleJob;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.constants.ScheduleResourceType;
import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.request.ScheduleConfig;
import io.metersphere.system.dto.request.schedule.BaseScheduleConfigRequest;
import io.metersphere.system.schedule.ScheduleService;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanScheduleService {
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private ScheduleService scheduleService;
public String scheduleConfig(BaseScheduleConfigRequest request, String operator) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getResourceId());
if (testPlan == null) {
throw new MSException(Translator.get("test_plan.not.exist"));
}
ScheduleConfig scheduleConfig = ScheduleConfig.builder()
.resourceId(testPlan.getId())
.key(testPlan.getId())
.projectId(testPlan.getProjectId())
.name(testPlan.getName())
.enable(request.isEnable())
.cron(request.getCron())
.resourceType(ScheduleResourceType.TEST_PLAN.name())
.config(JSON.toJSONString(request.getRunConfig()))
.build();
if (request.isEnable() && StringUtils.equalsIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
//配置开启的测试计划组定时任务要将组下的所有测试计划定时任务都关闭掉
TestPlanExample example = new TestPlanExample();
example.createCriteria().andGroupIdEqualTo(testPlan.getId()).andStatusNotEqualTo(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED);
example.setOrderByClause("pos asc");
List<TestPlan> children = testPlanMapper.selectByExample(example);
for (TestPlan child : children) {
scheduleService.updateIfExist(child.getId(), false, TestPlanScheduleJob.getJobKey(testPlan.getId()),
TestPlanScheduleJob.getTriggerKey(testPlan.getId()),
TestPlanScheduleJob.class, operator);
}
}
return scheduleService.scheduleConfig(
scheduleConfig,
TestPlanScheduleJob.getJobKey(testPlan.getId()),
TestPlanScheduleJob.getTriggerKey(testPlan.getId()),
TestPlanScheduleJob.class,
operator);
}
}

View File

@ -13,14 +13,15 @@ import io.metersphere.project.request.ProjectApplicationRequest;
import io.metersphere.project.service.ProjectApplicationService; import io.metersphere.project.service.ProjectApplicationService;
import io.metersphere.sdk.constants.*; import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.*; import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.SubListUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.ScheduleExample; import io.metersphere.system.domain.ScheduleExample;
import io.metersphere.system.domain.TestPlanModule; import io.metersphere.system.domain.TestPlanModule;
import io.metersphere.system.domain.TestPlanModuleExample; import io.metersphere.system.domain.TestPlanModuleExample;
import io.metersphere.system.domain.User; import io.metersphere.system.domain.User;
import io.metersphere.system.dto.LogInsertModule; import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.dto.request.ScheduleConfig;
import io.metersphere.system.dto.request.schedule.BaseScheduleConfigRequest;
import io.metersphere.system.dto.sdk.OptionDTO; import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.constants.OperationLogType;
@ -884,40 +885,6 @@ public class TestPlanService extends TestPlanBaseUtilsService {
return new TestPlanOperationResponse(1); return new TestPlanOperationResponse(1);
} }
public String scheduleConfig(BaseScheduleConfigRequest request, String operator) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getResourceId());
if (testPlan == null) {
throw new MSException(Translator.get("test_plan.not.exist"));
}
ScheduleConfig scheduleConfig = ScheduleConfig.builder()
.resourceId(testPlan.getId())
.key(testPlan.getId())
.projectId(testPlan.getProjectId())
.name(testPlan.getName())
.enable(request.isEnable())
.cron(request.getCron())
.resourceType(ScheduleResourceType.TEST_PLAN.name())
.config(JSON.toJSONString(request.getRunConfig()))
.build();
if (request.isEnable() && StringUtils.equalsIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
//配置开启的测试计划组定时任务要将组下的所有测试计划定时任务都关闭掉
List<TestPlan> children = this.selectNotArchivedChildren(testPlan.getId());
for (TestPlan child : children) {
scheduleService.updateIfExist(child.getId(), false, TestPlanScheduleJob.getJobKey(testPlan.getId()),
TestPlanScheduleJob.getTriggerKey(testPlan.getId()),
TestPlanScheduleJob.class, operator);
}
}
return scheduleService.scheduleConfig(
scheduleConfig,
TestPlanScheduleJob.getJobKey(testPlan.getId()),
TestPlanScheduleJob.getTriggerKey(testPlan.getId()),
TestPlanScheduleJob.class,
operator);
}
public void deleteScheduleConfig(String testPlanId) { public void deleteScheduleConfig(String testPlanId) {
scheduleService.deleteByResourceId(testPlanId, TestPlanScheduleJob.getJobKey(testPlanId), TestPlanScheduleJob.getTriggerKey(testPlanId)); scheduleService.deleteByResourceId(testPlanId, TestPlanScheduleJob.getJobKey(testPlanId), TestPlanScheduleJob.getTriggerKey(testPlanId));

View File

@ -2182,24 +2182,26 @@ public class TestPlanTests extends BaseTest {
@Test @Test
@Order(303) @Order(303)
public void testCopy() throws Exception { public void testCopy() throws Exception {
//1. 已归档的不能再归档计划 无用例 // 1. 已归档的不能再归档计划 无用例
requestGet(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_1")).andExpect(status().is5xxServerError()); requestGet(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_1")).andExpect(status().is5xxServerError());
//2.计划 有用例 // 2.计划 有用例
MvcResult mvcResult1 = this.requestGetWithOkAndReturn(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_4")); MvcResult mvcResult1 = this.requestGetWithOkAndReturn(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_4"));
String returnStr1 = mvcResult1.getResponse().getContentAsString(); String returnStr1 = mvcResult1.getResponse().getContentAsString();
ResultHolder holder1 = JSON.parseObject(returnStr1, ResultHolder.class); ResultHolder holder1 = JSON.parseObject(returnStr1, ResultHolder.class);
String returnId1 = holder1.getData().toString(); String returnId1 = holder1.getData().toString();
Assertions.assertNotNull(returnId1); Assertions.assertNotNull(returnId1);
//3.计划组 无计划 // 3.计划组 无计划
MvcResult mvcResult2 = this.requestGetWithOkAndReturn(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_2")); MvcResult mvcResult2 = this.requestGetWithOkAndReturn(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_2"));
String returnStr2 = mvcResult2.getResponse().getContentAsString(); String returnStr2 = mvcResult2.getResponse().getContentAsString();
ResultHolder holder2 = JSON.parseObject(returnStr2, ResultHolder.class); ResultHolder holder2 = JSON.parseObject(returnStr2, ResultHolder.class);
String returnId2 = holder2.getData().toString(); String returnId2 = holder2.getData().toString();
Assertions.assertNotNull(returnId2); Assertions.assertNotNull(returnId2);
// 4.计划组 有子计划
this.requestGetWithOk(String.format(URL_TEST_PLAN_COPY, "oasis_test_plan_id_1"));
} }
@Test @Test

View File

@ -20,8 +20,15 @@ VALUES ('wx_test_plan_id_1', 5000, 'songtianyang-fix-wx', 'NONE', '1', '测试
'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000,
1714980158000, '11'), 1714980158000, '11'),
('wx_test_plan_id_7', 30000, 'songtianyang-fix-wx', 'NONE', '1', '测试组4下计划', 'COMPLETED', 'TEST_PLAN', NULL, ('wx_test_plan_id_7', 30000, 'songtianyang-fix-wx', 'NONE', '1', '测试组4下计划', 'COMPLETED', 'TEST_PLAN', NULL,
1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'); 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('oasis_test_plan_id_1', 30000, 'songtianyang-fix-wx', 'NONE', '1', '计划组-复制', 'COMPLETED', 'GROUP', NULL,
1714980158000, 'admin', 1714980158000, 'admin', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('oasis_test_plan_id_2', 30000, 'songtianyang-fix-wx', 'oasis_test_plan_id_1', '1', '计划-复制', 'COMPLETED', 'TEST_PLAN', NULL,
1714980158000, 'admin', 1714980158000, 'admin', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
-- 定时任务(复制)
INSERT INTO `schedule` (`id`, `key`, `type`, `value`, `job`, `resource_type`, `enable`, `resource_id`, `create_user`, `create_time`, `update_time`, `project_id`, `name`, `config`) VALUE
('schedule-id-oasis', 'oasis_test_plan_id_2', 'CRON', '0 0 0/6 * * ?', 'io.metersphere.plan.job.TestPlanScheduleJob', 'TEST_PLAN', true, 'oasis_test_plan_id_2', 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'songtianyang-fix-wx', '计划-复制', '{"runMode":"SERIAL"}');
INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`, `test_plan_collection_id`) VALUES INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`, `test_plan_collection_id`) VALUES
('wx_tpfc_1', 'wx_test_plan_id_4', 'wx_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1, '123'), ('wx_tpfc_1', 'wx_test_plan_id_4', 'wx_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1, '123'),