diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java index 902892f534..0213eebb2f 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java @@ -47,6 +47,8 @@ public class TestPlanController { @Resource private TestPlanService testPlanService; @Resource + private TestPlanScheduleService testPlanScheduleService; + @Resource private TestPlanManagementService testPlanManagementService; @Resource private TestPlanStatisticsService testPlanStatisticsService; @@ -245,7 +247,7 @@ public class TestPlanController { @CheckOwner(resourceId = "#request.getResourceId()", resourceType = "test_plan") public String scheduleConfig(@Validated @RequestBody BaseScheduleConfigRequest request) { 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}") diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java index fcaa55b79f..2c45d4e191 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java @@ -2,6 +2,7 @@ package io.metersphere.plan.service; import io.metersphere.plan.domain.*; import io.metersphere.plan.dto.response.TestPlanResponse; +import io.metersphere.plan.job.TestPlanScheduleJob; import io.metersphere.plan.mapper.ExtTestPlanMapper; import io.metersphere.plan.mapper.TestPlanCollectionMapper; 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.util.BeanUtils; import io.metersphere.sdk.util.CommonBeanFactory; +import io.metersphere.sdk.util.JSON; 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.NumGenerator; import jakarta.annotation.Resource; @@ -33,7 +38,10 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService { private ExtTestPlanMapper extTestPlanMapper; @Resource private TestPlanMapper testPlanMapper; - + @Resource + private ScheduleService scheduleService; + @Resource + private TestPlanScheduleService testPlanScheduleService; @Resource private TestPlanGroupService testPlanGroupService; @Resource @@ -237,6 +245,10 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService { beansOfType.forEach((k, v) -> { v.copyResource(originalTestPlan.getId(), testPlan.getId(), oldCollectionIdToNewCollectionId, operator, operatorTime); }); + + // 复制计划-定时任务信息 + copySchedule(originalTestPlan.getId(), testPlan.getId(), operator); + return testPlan; } @@ -280,9 +292,32 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService { for (TestPlan child : childList) { copyPlan(child, testPlanGroup.getId(), TestPlanConstants.TEST_PLAN_TYPE_GROUP, operatorTime, operator); } + + // 复制计划组-定时任务信息 + copySchedule(originalGroup.getId(), testPlanGroup.getId(), operator); 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) { if (!StringUtils.startsWith(name, "copy_")) { name = "copy_" + name; diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanScheduleService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanScheduleService.java new file mode 100644 index 0000000000..bbcd1ca6ad --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanScheduleService.java @@ -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 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); + } +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java index 487d9f56f0..a69e5df1fd 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java @@ -13,14 +13,15 @@ import io.metersphere.project.request.ProjectApplicationRequest; import io.metersphere.project.service.ProjectApplicationService; import io.metersphere.sdk.constants.*; 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.TestPlanModule; import io.metersphere.system.domain.TestPlanModuleExample; import io.metersphere.system.domain.User; 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.request.PosRequest; import io.metersphere.system.log.constants.OperationLogType; @@ -884,40 +885,6 @@ public class TestPlanService extends TestPlanBaseUtilsService { 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 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) { scheduleService.deleteByResourceId(testPlanId, TestPlanScheduleJob.getJobKey(testPlanId), TestPlanScheduleJob.getTriggerKey(testPlanId)); diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java index 8c53e7e5ed..0c2d663e8d 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java @@ -2182,24 +2182,26 @@ public class TestPlanTests extends BaseTest { @Test @Order(303) public void testCopy() throws Exception { - //1. 已归档的不能再归档计划 无用例 + // 1. 已归档的不能再归档计划 无用例 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")); String returnStr1 = mvcResult1.getResponse().getContentAsString(); ResultHolder holder1 = JSON.parseObject(returnStr1, ResultHolder.class); String returnId1 = holder1.getData().toString(); Assertions.assertNotNull(returnId1); - //3.计划组 无计划 + // 3.计划组 无计划 MvcResult mvcResult2 = this.requestGetWithOkAndReturn(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_2")); String returnStr2 = mvcResult2.getResponse().getContentAsString(); ResultHolder holder2 = JSON.parseObject(returnStr2, ResultHolder.class); String returnId2 = holder2.getData().toString(); Assertions.assertNotNull(returnId2); + // 4.计划组 有子计划 + this.requestGetWithOk(String.format(URL_TEST_PLAN_COPY, "oasis_test_plan_id_1")); } @Test diff --git a/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql b/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql index 85d07e1c94..6b005bd343 100644 --- a/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql +++ b/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql @@ -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, 1714980158000, '11'), ('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 ('wx_tpfc_1', 'wx_test_plan_id_4', 'wx_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1, '123'),