diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java index 041ceb77b3..c3a8545d54 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java @@ -19,10 +19,8 @@ import io.metersphere.api.parser.step.StepParser; import io.metersphere.api.parser.step.StepParserFactory; import io.metersphere.api.service.ApiCommonService; import io.metersphere.api.service.ApiFileResourceService; -import io.metersphere.api.service.definition.ApiDefinitionModuleService; import io.metersphere.api.service.definition.ApiDefinitionService; import io.metersphere.api.service.definition.ApiTestCaseService; -import io.metersphere.api.service.queue.ApiExecutionSetService; import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiScenarioBatchOperationUtils; import io.metersphere.functional.domain.FunctionalCaseTestExample; @@ -65,6 +63,7 @@ import io.metersphere.system.service.OperationHistoryService; import io.metersphere.system.service.UserLoginService; import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; +import io.metersphere.system.utils.ScheduleUtils; import io.metersphere.system.utils.ServiceUtils; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotBlank; @@ -79,10 +78,6 @@ import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionUtils; -import org.quartz.CronExpression; -import org.quartz.CronScheduleBuilder; -import org.quartz.CronTrigger; -import org.quartz.TriggerBuilder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -242,7 +237,7 @@ public class ApiScenarioService extends MoveNodeService { } item.setScheduleConfig(request); if (schedule.getEnable()) { - item.setNextTriggerTime(getNextTriggerTime(schedule.getValue())); + item.setNextTriggerTime(ScheduleUtils.getNextTriggerTime(schedule.getValue())); } } if (MapUtils.isNotEmpty(reportMap) && reportMap.containsKey(item.getLastReportId())) { @@ -251,22 +246,6 @@ public class ApiScenarioService extends MoveNodeService { }); } - /** - * 获取下次执行时间(getFireTimeAfter,也可以下下次...) - * - * @param cron cron表达式 - * @return 下次执行时间 - */ - private static Long getNextTriggerTime(String cron) { - if (!CronExpression.isValidExpression(cron)) { - return null; - } - CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("Calculate Date").withSchedule(CronScheduleBuilder.cronSchedule(cron)).build(); - Date time0 = trigger.getStartTime(); - Date time1 = trigger.getFireTimeAfter(time0); - return time1 == null ? 0 : time1.getTime(); - } - private Set extractUserIds(List list) { return list.stream() .flatMap(apiScenario -> Stream.of(apiScenario.getUpdateUser(), apiScenario.getDeleteUser(), apiScenario.getCreateUser())) diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/utils/ScheduleUtils.java b/backend/services/system-setting/src/main/java/io/metersphere/system/utils/ScheduleUtils.java new file mode 100644 index 0000000000..502c52c6b2 --- /dev/null +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/utils/ScheduleUtils.java @@ -0,0 +1,27 @@ +package io.metersphere.system.utils; + + +import org.quartz.CronExpression; +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.TriggerBuilder; + +import java.util.Date; + +public class ScheduleUtils { + /** + * 获取下次执行时间(getFireTimeAfter,也可以下下次...) + * + * @param cron cron表达式 + * @return 下次执行时间 + */ + public static Long getNextTriggerTime(String cron) { + if (!CronExpression.isValidExpression(cron)) { + return null; + } + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("Calculate Date").withSchedule(CronScheduleBuilder.cronSchedule(cron)).build(); + Date time0 = trigger.getStartTime(); + Date time1 = trigger.getFireTimeAfter(time0); + return time1 == null ? 0 : time1.getTime(); + } +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java index dc21627274..2a998fd450 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java @@ -2,6 +2,7 @@ package io.metersphere.plan.dto.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.metersphere.plan.serializer.CustomRateSerializer; +import io.metersphere.system.dto.request.schedule.BaseScheduleConfigRequest; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; @@ -55,4 +56,8 @@ public class TestPlanStatisticsResponse { private Integer apiScenarioCount = 0; @Schema(description = "缺陷数量") private Integer bugCount = 0; + @Schema(description = "定时任务配置") + private BaseScheduleConfigRequest scheduleConfig; + @Schema(description = "定时任务下一次执行时间") + private Long nextTriggerTime; } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java index 49eaa0a488..f748702b57 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java @@ -10,6 +10,13 @@ import io.metersphere.plan.mapper.ExtTestPlanFunctionalCaseMapper; import io.metersphere.plan.mapper.TestPlanConfigMapper; import io.metersphere.plan.utils.RateCalculateUtils; import io.metersphere.sdk.constants.ExecStatus; +import io.metersphere.sdk.constants.ScheduleResourceType; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.domain.Schedule; +import io.metersphere.system.domain.ScheduleExample; +import io.metersphere.system.dto.request.schedule.BaseScheduleConfigRequest; +import io.metersphere.system.mapper.ScheduleMapper; +import io.metersphere.system.utils.ScheduleUtils; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Service; @@ -29,6 +36,8 @@ public class TestPlanStatisticsService { private ExtTestPlanFunctionalCaseMapper extTestPlanFunctionalCaseMapper; @Resource private ExtTestPlanBugMapper extTestPlanBugMapper; + @Resource + private ScheduleMapper scheduleMapper; /** * 计划的用例统计数据 @@ -83,6 +92,13 @@ public class TestPlanStatisticsService { // 计划-功能用例的关联数据 List planFunctionalCases = extTestPlanFunctionalCaseMapper.getPlanFunctionalCaseByIds(planIds); Map> planFunctionalCaseMap = planFunctionalCases.stream().collect(Collectors.groupingBy(TestPlanFunctionalCase::getTestPlanId)); + + //查询定时任务 + ScheduleExample scheduleExample = new ScheduleExample(); + scheduleExample.createCriteria().andResourceIdIn(planIds).andResourceTypeEqualTo(ScheduleResourceType.TEST_PLAN.name()); + List schedules = scheduleMapper.selectByExample(scheduleExample); + Map scheduleMap = schedules.stream().collect(Collectors.toMap(Schedule::getResourceId, t -> t)); + // TODO: 计划-接口用例的关联数据 planIds.forEach(planId -> { TestPlanStatisticsResponse statisticsResponse = new TestPlanStatisticsResponse(); @@ -115,6 +131,23 @@ public class TestPlanStatisticsService { statisticsResponse.setPassRate(RateCalculateUtils.divWithPrecision(statisticsResponse.getSuccessCount(), statisticsResponse.getCaseTotal(), 2)); statisticsResponse.setExecuteRate(RateCalculateUtils.divWithPrecision(statisticsResponse.getCaseTotal() - statisticsResponse.getPendingCount(), statisticsResponse.getCaseTotal(), 2)); planStatisticsResponses.add(statisticsResponse); + + //定时任务 + if (scheduleMap.containsKey(planId)) { + Schedule schedule = scheduleMap.get(planId); + BaseScheduleConfigRequest request = new BaseScheduleConfigRequest(); + request.setEnable(schedule.getEnable()); + request.setCron(schedule.getValue()); + request.setResourceId(planId); + if (schedule.getConfig() != null) { + request.setRunConfig(JSON.parseObject(schedule.getConfig(), Map.class)); + } + statisticsResponse.setScheduleConfig(request); + if (schedule.getEnable()) { + statisticsResponse.setNextTriggerTime(ScheduleUtils.getNextTriggerTime(schedule.getValue())); + } + } + }); return planStatisticsResponses; } 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 650971d8f9..61241dded8 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 @@ -8,6 +8,7 @@ import io.metersphere.plan.domain.*; import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanOperationResponse; import io.metersphere.plan.dto.response.TestPlanResponse; +import io.metersphere.plan.dto.response.TestPlanStatisticsResponse; import io.metersphere.plan.mapper.ExtTestPlanMapper; import io.metersphere.plan.mapper.TestPlanMapper; import io.metersphere.plan.mapper.TestPlanReportMapper; @@ -1365,10 +1366,23 @@ public class TestPlanTests extends BaseTest { this.requestGet(String.format(URL_POST_TEST_PLAN_SCHEDULE_DELETE, groupTestPlanId7)).andExpect(status().is5xxServerError()); //恢复 testPlanTestService.resetProjectModule(project, PROJECT_MODULE); + + //正是测试 MvcResult result = this.requestPostAndReturn(URL_POST_TEST_PLAN_SCHEDULE, request); ResultHolder resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class); String scheduleId = resultHolder.getData().toString(); testPlanTestService.checkSchedule(scheduleId, groupTestPlanId7, request.isEnable()); + //检查统计接口查询的是否正确 + List statisticsResponses = JSON.parseArray( + JSON.toJSONString( + JSON.parseObject( + this.requestPostAndReturn(URL_POST_TEST_PLAN_STATISTICS, List.of(groupTestPlanId7)) + .getResponse().getContentAsString(), ResultHolder.class).getData()), + TestPlanStatisticsResponse.class); + Assertions.assertTrue(statisticsResponses.size() == 1); + Assertions.assertTrue(statisticsResponses.getFirst().getNextTriggerTime() > 0); + Assertions.assertTrue(statisticsResponses.getFirst().getScheduleConfig().isEnable()); + //增加日志检查 LOG_CHECK_LIST.add( @@ -1383,6 +1397,17 @@ public class TestPlanTests extends BaseTest { //检查两个scheduleId是否相同 Assertions.assertEquals(scheduleId, newScheduleId); testPlanTestService.checkSchedule(newScheduleId, groupTestPlanId7, request.isEnable()); + //检查统计接口查询的是否正确 + statisticsResponses = JSON.parseArray( + JSON.toJSONString( + JSON.parseObject( + this.requestPostAndReturn(URL_POST_TEST_PLAN_STATISTICS, List.of(groupTestPlanId7)) + .getResponse().getContentAsString(), ResultHolder.class).getData()), + TestPlanStatisticsResponse.class); + Assertions.assertTrue(statisticsResponses.size() == 1); + Assertions.assertTrue(statisticsResponses.getFirst().getNextTriggerTime() == null); + Assertions.assertFalse(statisticsResponses.getFirst().getScheduleConfig().isEnable()); + //测试各种corn表达式用于校验正则的准确性 String[] cornStrArr = new String[]{ @@ -1451,6 +1476,16 @@ public class TestPlanTests extends BaseTest { //测试删除 this.requestGetWithOk(String.format(URL_POST_TEST_PLAN_SCHEDULE_DELETE, groupTestPlanId7)); testPlanTestService.checkScheduleIsRemove(groupTestPlanId7); + //检查统计接口查询的是否正确 + statisticsResponses = JSON.parseArray( + JSON.toJSONString( + JSON.parseObject( + this.requestPostAndReturn(URL_POST_TEST_PLAN_STATISTICS, List.of(groupTestPlanId7)) + .getResponse().getContentAsString(), ResultHolder.class).getData()), + TestPlanStatisticsResponse.class); + Assertions.assertTrue(statisticsResponses.size() == 1); + Assertions.assertTrue(statisticsResponses.getFirst().getNextTriggerTime() == null); + Assertions.assertTrue(statisticsResponses.getFirst().getScheduleConfig() == null); } @Test