diff --git a/backend/framework/sdk/src/main/resources/i18n/plan.properties b/backend/framework/sdk/src/main/resources/i18n/plan.properties index bf3cb79e11..fd56a7635c 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan.properties @@ -118,4 +118,12 @@ test_plan_report_name_length_range=报告名称长度过长 test_plan_schedule=测试计划-定時任務 test_plan_default_functional_collection_name=基本功能点 test_plan_default_api_collection_name=单接口验证 -test_plan_default_scenario_collection_name=业务流程验证 \ No newline at end of file +test_plan_default_scenario_collection_name=业务流程验证 +test_plan.mind.test_plan=测试规划 +test_plan.mind.serial=串 +test_plan.mind.parallel=并 +test_plan.mind.strip=条 +test_plan.mind.case_count=用例数 +test_plan.mind.environment=环境 +test_plan.mind.test_resource_pool=资源池 + diff --git a/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties index 6c5c7a6f21..ed357002fd 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties @@ -121,4 +121,11 @@ test_plan_report_name_length_range=The report name is too long test_plan_schedule=Test plan schedule test_plan_default_functional_collection_name=Basic function point test_plan_default_api_collection_name=Single interface verification -test_plan_default_scenario_collection_name=Business process verification \ No newline at end of file +test_plan_default_scenario_collection_name=Business process verification +test_plan.mind.test_plan=Test plan +test_plan.mind.serial=Serial +test_plan.mind.parallel=Parallel +test_plan.mind.strip=Strip +test_plan.mind.case_count=Count +test_plan.mind.environment=Environment +test_plan.mind.test_resource_pool=Resource pool \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties index a8b0e423cf..e455d5f849 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties @@ -121,4 +121,11 @@ test_plan_report_name_length_range=报告名称长度过长 test_plan_schedule=测试计划-定時任務 test_plan_default_functional_collection_name=基本功能点 test_plan_default_api_collection_name=单接口验证 -test_plan_default_scenario_collection_name=业务流程验证 \ No newline at end of file +test_plan_default_scenario_collection_name=业务流程验证 +test_plan.mind.test_plan=测试规划 +test_plan.mind.serial=串 +test_plan.mind.parallel=并 +test_plan.mind.strip=条 +test_plan.mind.case_count=用例数 +test_plan.mind.environment=环境 +test_plan.mind.test_resource_pool=资源池 \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties index 077d0e5ec5..fa673078f1 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties @@ -120,4 +120,11 @@ test_plan_report_name_length_range=报告名称长度过长 test_plan_schedule=測試計劃-定時任務 test_plan_default_functional_collection_name=基本功能點 test_plan_default_api_collection_name=單接口驗證 -test_plan_default_scenario_collection_name=業務流程驗證 \ No newline at end of file +test_plan_default_scenario_collection_name=業務流程驗證 +test_plan.mind.test_plan=測試規劃 +test_plan.mind.serial=串 +test_plan.mind.parallel=並 +test_plan.mind.strip=條 +test_plan.mind.case_count=用例數 +test_plan.mind.environment=環境 +test_plan.mind.test_resource_pool=資源池 \ No newline at end of file diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseMinderService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseMinderService.java index 9adf973b50..cbea95d49a 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseMinderService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseMinderService.java @@ -294,9 +294,10 @@ public class FunctionalCaseMinderService { Map newModuleMap = new HashMap<>(); //处理模块 - dealModule(request, userId, moduleMapper, newModuleMap); + List moduleNodeIds = dealModule(request, userId, moduleMapper, newModuleMap); //处理用例 + List caseNodeIds = new ArrayList<>(); if (CollectionUtils.isNotEmpty(request.getUpdateCaseList())) { Map> resourceMap = request.getUpdateCaseList().stream().collect(Collectors.groupingBy(FunctionalCaseChangeRequest::getType)); //处理新增 @@ -310,6 +311,7 @@ public class FunctionalCaseMinderService { FunctionalCase functionalCase = addCase(request, userId, functionalCaseChangeRequest, caseMapper, newModuleMap); String caseId = functionalCase.getId(); //附属表 + caseNodeIds.add(caseId); FunctionalCaseBlob functionalCaseBlob = addCaseBlob(functionalCaseChangeRequest, caseId, caseBlobMapper); //保存自定义字段 List functionalCaseCustomFields = addCustomFields(functionalCaseChangeRequest, caseId, caseCustomFieldMapper, defaultValueMap); @@ -366,16 +368,14 @@ public class FunctionalCaseMinderService { } //批量排序 batchSort(updatePosList, caseMapper); - } - //处理空白节点 dealAdditionalNode(request, userId, additionalNodeMapper, newModuleMap); - //TODO:删除转换的空白节点 - - - + moduleNodeIds.addAll(caseNodeIds); + if (CollectionUtils.isNotEmpty(moduleNodeIds)) { + dealMindAdditionalMode(moduleNodeIds); + } sqlSession.flushStatements(); SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); @@ -553,7 +553,8 @@ public class FunctionalCaseMinderService { } - private void dealModule(FunctionalCaseMinderEditRequest request, String userId, FunctionalCaseModuleMapper moduleMapper, Map newModuleMap) { + private List dealModule(FunctionalCaseMinderEditRequest request, String userId, FunctionalCaseModuleMapper moduleMapper, Map newModuleMap) { + List nodeIds = new ArrayList<>(); if (CollectionUtils.isNotEmpty(request.getUpdateModuleList())) { List updatePosList = new ArrayList<>(); //处理新增 @@ -573,6 +574,7 @@ public class FunctionalCaseMinderService { if (StringUtils.isNotBlank(newModuleMap.get(module.getParentId()))) { module.setParentId(newModuleMap.get(module.getParentId())); } + nodeIds.add(module.getId()); moduleMapper.insert(module); } parentModuleMap.forEach((k, v) -> { @@ -584,7 +586,7 @@ public class FunctionalCaseMinderService { List updateList = resourceMap.get(OperationLogType.UPDATE.toString()); if (CollectionUtils.isNotEmpty(updateList)) { List modules = new ArrayList<>(); - Map> parentModuleMap = getParentModuleMap(addList); + Map> parentModuleMap = getParentModuleMap(updateList); for (FunctionalCaseModuleEditRequest functionalCaseModuleEditRequest : updateList) { checkModules(functionalCaseModuleEditRequest, parentModuleMap); FunctionalCaseModule updateModule = updateModule(userId, functionalCaseModuleEditRequest, moduleMapper); @@ -604,6 +606,7 @@ public class FunctionalCaseMinderService { //批量排序 batchSortModule(updatePosList, moduleMapper); } + return nodeIds; } private static void batchSortModule(List updatePosList, FunctionalCaseModuleMapper moduleMapper) { @@ -1038,13 +1041,17 @@ public class FunctionalCaseMinderService { List additionalOptionDTOS = resourceMap.get(ModuleConstants.ROOT_NODE_PARENT_ID); if (CollectionUtils.isNotEmpty(additionalOptionDTOS)) { List mindAdditionalNodeIds = caseModuleOptionDTOS.stream().map(MinderOptionDTO::getId).toList(); - MindAdditionalNodeExample mindAdditionalNodeExample = new MindAdditionalNodeExample(); - mindAdditionalNodeExample.createCriteria().andIdIn(mindAdditionalNodeIds); - mindAdditionalNodeMapper.deleteByExample(mindAdditionalNodeExample); + dealMindAdditionalMode(mindAdditionalNodeIds); } } } + private void dealMindAdditionalMode(List mindAdditionalNodeIds) { + MindAdditionalNodeExample mindAdditionalNodeExample = new MindAdditionalNodeExample(); + mindAdditionalNodeExample.createCriteria().andIdIn(mindAdditionalNodeIds); + mindAdditionalNodeMapper.deleteByExample(mindAdditionalNodeExample); + } + public List getReviewMindFunctionalCase(FunctionalCaseReviewMindRequest request, boolean deleted, String userId, String viewStatusUserId) { List list = new ArrayList<>(); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanCollectionMinderController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanCollectionMinderController.java new file mode 100644 index 0000000000..b40211068f --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanCollectionMinderController.java @@ -0,0 +1,34 @@ +package io.metersphere.plan.controller; + +import io.metersphere.plan.dto.TestPlanCollectionMinderTreeDTO; +import io.metersphere.plan.service.TestPlanCollectionMinderService; +import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.system.security.CheckOwner; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "测试规划脑图") +@RestController +@RequestMapping("/test-plan/mind") +public class TestPlanCollectionMinderController { + + @Resource + private TestPlanCollectionMinderService testPlanCollectionMinderService; + + @GetMapping("/data/{planId}") + @Operation(summary = "测试规划脑图列表") + @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE) + @CheckOwner(resourceId = "#planId", resourceType = "test_plan") + public List getMindTestPlanCase(@PathVariable String planId) { + return testPlanCollectionMinderService.getMindTestPlanCase(planId); + } + +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanCollectionMinderTreeNodeDTO.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanCollectionMinderTreeNodeDTO.java index 251d474b1b..c9e4543c8f 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanCollectionMinderTreeNodeDTO.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanCollectionMinderTreeNodeDTO.java @@ -34,4 +34,34 @@ public class TestPlanCollectionMinderTreeNodeDTO { @Schema(description = "节点状态") private String expandState = "expand"; + @Schema(description = "测试集类型(功能/接口/场景)", requiredMode = Schema.RequiredMode.REQUIRED) + private String type; + + @Schema(description = "是否继承", requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean extended; + + @Schema(description = "是否使用环境组", requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean grouped; + + @Schema(description = "环境ID/环境组ID", requiredMode = Schema.RequiredMode.REQUIRED) + private String environmentId; + + @Schema(description = "测试资源池ID", requiredMode = Schema.RequiredMode.REQUIRED) + private String testResourcePoolId; + + @Schema(description = "是否失败重试", requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean retryOnFail; + + @Schema(description = "失败重试类型(步骤/场景)", requiredMode = Schema.RequiredMode.REQUIRED) + private String retryType; + + @Schema(description = "失败重试次数", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer retryTimes; + + @Schema(description = "失败重试间隔(单位: ms)", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer retryInterval; + + @Schema(description = "是否失败停止", requiredMode = Schema.RequiredMode.REQUIRED) + private Boolean stopOnFail; + } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanCollectionMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanCollectionMapper.java new file mode 100644 index 0000000000..172170d60b --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanCollectionMapper.java @@ -0,0 +1,12 @@ +package io.metersphere.plan.mapper; + +import io.metersphere.plan.dto.TestPlanCollectionConfigDTO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ExtTestPlanCollectionMapper { + + List getList(@Param("planId") String planId); + +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanCollectionMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanCollectionMapper.xml new file mode 100644 index 0000000000..9b592589bf --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanCollectionMapper.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanCollectionMinderService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanCollectionMinderService.java index fd32424ed4..abbf9bbbea 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanCollectionMinderService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanCollectionMinderService.java @@ -1,101 +1,220 @@ package io.metersphere.plan.service; -import io.metersphere.plan.domain.TestPlanCollection; -import io.metersphere.plan.domain.TestPlanCollectionExample; +import io.metersphere.plan.domain.*; +import io.metersphere.plan.dto.TestPlanCollectionConfigDTO; import io.metersphere.plan.dto.TestPlanCollectionMinderTreeDTO; import io.metersphere.plan.dto.TestPlanCollectionMinderTreeNodeDTO; -import io.metersphere.plan.mapper.TestPlanCollectionMapper; +import io.metersphere.plan.mapper.ExtTestPlanCollectionMapper; +import io.metersphere.plan.mapper.TestPlanApiCaseMapper; +import io.metersphere.plan.mapper.TestPlanApiScenarioMapper; +import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper; import io.metersphere.sdk.constants.ApiBatchRunMode; +import io.metersphere.sdk.constants.CaseType; import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.util.BeanUtils; +import io.metersphere.sdk.util.Translator; import jakarta.annotation.Resource; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) public class TestPlanCollectionMinderService { @Resource - private TestPlanCollectionMapper testPlanCollectionMapper; + private ExtTestPlanCollectionMapper extTestPlanCollectionMapper; + + @Resource + private TestPlanFunctionalCaseMapper testPlanFunctionalCaseMapper; + + @Resource + private TestPlanApiCaseMapper testPlanApiCaseMapper; + + @Resource + private TestPlanApiScenarioMapper testPlanApiScenarioMapper; /** * 测试计划-脑图用例列表查询 * * @return FunctionalMinderTreeDTO */ - public List getMindFunctionalCase(String planId) { + public List getMindTestPlanCase(String planId) { List list = new ArrayList<>(); - TestPlanCollectionExample testPlanCollectionExample = new TestPlanCollectionExample(); - testPlanCollectionExample.createCriteria().andTestPlanIdEqualTo(planId); - List testPlanCollections = testPlanCollectionMapper.selectByExample(testPlanCollectionExample); + List testPlanCollections = extTestPlanCollectionMapper.getList(planId); + //构造根节点 TestPlanCollectionMinderTreeNodeDTO testPlanCollectionMinderTreeNodeDTO = buildRoot(); - // TestPlanCollectionMinderTreeDTO testPlanCollectionMinderTreeDTO = new TestPlanCollectionMinderTreeDTO(); testPlanCollectionMinderTreeDTO.setData(testPlanCollectionMinderTreeNodeDTO); + //构造type节点 List children = new ArrayList<>(); - List parentList = testPlanCollections.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)).sorted(Comparator.comparing(TestPlanCollection::getPos)).toList(); + List parentList = testPlanCollections.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)).sorted(Comparator.comparing(TestPlanCollection::getPos)).toList(); buildTypeChildren(parentList, testPlanCollections, children); testPlanCollectionMinderTreeDTO.setChildren(children); list.add(testPlanCollectionMinderTreeDTO); return list; } - private static void buildTypeChildren(List parentList, List testPlanCollections, List children) { + private void buildTypeChildren(List parentList, List testPlanCollections, List children) { for (TestPlanCollection testPlanCollection : parentList) { - TestPlanCollectionMinderTreeDTO treeDTO = new TestPlanCollectionMinderTreeDTO(); - TestPlanCollectionMinderTreeNodeDTO typeTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); - typeTreeNodeDTO.setId(testPlanCollection.getId()); - typeTreeNodeDTO.setText(testPlanCollection.getName()); - typeTreeNodeDTO.setPos(testPlanCollection.getPos()); - if (StringUtils.equalsIgnoreCase(testPlanCollection.getExecuteMethod(), ApiBatchRunMode.PARALLEL.toString())) { - typeTreeNodeDTO.setPriority("并"); - } else { - typeTreeNodeDTO.setPriority("串"); - } - typeTreeNodeDTO.setExecuteMethod(testPlanCollection.getExecuteMethod()); + TestPlanCollectionMinderTreeDTO typeTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO typeTreeNodeDTO = buildTypeChildData(testPlanCollection); + //构造子节点 List collectionChildren = new ArrayList<>(); - List childrenList = testPlanCollections.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getParentId(), testPlanCollection.getId())).sorted(Comparator.comparing(TestPlanCollection::getPos)).toList(); - buildCollectionChildren(childrenList, collectionChildren); - treeDTO.setData(typeTreeNodeDTO); - treeDTO.setChildren(collectionChildren); - children.add(treeDTO); + List childrenList = testPlanCollections.stream().filter(t -> StringUtils.equalsIgnoreCase(t.getParentId(), testPlanCollection.getId())).sorted(Comparator.comparing(TestPlanCollection::getPos)).toList(); + Map> typeChildren = childrenList.stream().collect(Collectors.groupingBy(TestPlanCollectionConfigDTO::getType)); + Map> testPlanFunctionalCaseMap = getTestPlanFunctionalCases(typeChildren); + Map> testPlanApiCaseMap = getTestPlanApiCases(typeChildren); + Map> testPlanApiScenarioMap = getTestPlanApiScenarios(typeChildren); + buildCollectionChildren(childrenList, collectionChildren, testPlanFunctionalCaseMap, testPlanApiCaseMap, testPlanApiScenarioMap); + typeTreeDTO.setData(typeTreeNodeDTO); + typeTreeDTO.setChildren(collectionChildren); + children.add(typeTreeDTO); } } - private static void buildCollectionChildren(List childrenList, List collectionChildren) { - for (TestPlanCollection planCollection : childrenList) { + @NotNull + private static TestPlanCollectionMinderTreeNodeDTO buildTypeChildData(TestPlanCollection testPlanCollection) { + TestPlanCollectionMinderTreeNodeDTO typeTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + BeanUtils.copyBean(typeTreeNodeDTO, testPlanCollection); + typeTreeNodeDTO.setText(testPlanCollection.getName()); + if (StringUtils.equalsIgnoreCase(testPlanCollection.getExecuteMethod(), ApiBatchRunMode.PARALLEL.toString())) { + typeTreeNodeDTO.setPriority(Translator.get("test_plan.mind.serial")); + } else { + typeTreeNodeDTO.setPriority(Translator.get("test_plan.mind.parallel")); + } + return typeTreeNodeDTO; + } + + private Map> getTestPlanApiScenarios(Map> typeChildren) { + List testPlanCollectionConfigDTOS = typeChildren.get(CaseType.SCENARIO_CASE.getKey()); + if (CollectionUtils.isEmpty(testPlanCollectionConfigDTOS)) { + return new HashMap<>(); + } + List scenarioCollectIds = testPlanCollectionConfigDTOS.stream().map(TestPlanCollectionConfigDTO::getId).toList(); + TestPlanApiScenarioExample testPlanApiScenarioExample = new TestPlanApiScenarioExample(); + testPlanApiScenarioExample.createCriteria().andTestPlanCollectionIdIn(scenarioCollectIds); + List testPlanApiScenarios = testPlanApiScenarioMapper.selectByExample(testPlanApiScenarioExample); + return testPlanApiScenarios.stream().collect(Collectors.groupingBy(TestPlanApiScenario::getTestPlanCollectionId)); + } + + private Map> getTestPlanApiCases(Map> typeChildren) { + List testPlanCollectionConfigDTOS = typeChildren.get(CaseType.API_CASE.getKey()); + if (CollectionUtils.isEmpty(testPlanCollectionConfigDTOS)) { + return new HashMap<>(); + } + List apiCollectIds = testPlanCollectionConfigDTOS.stream().map(TestPlanCollectionConfigDTO::getId).toList(); + TestPlanApiCaseExample testPlanApiCaseExample = new TestPlanApiCaseExample(); + testPlanApiCaseExample.createCriteria().andTestPlanCollectionIdIn(apiCollectIds); + List testPlanApiCases = testPlanApiCaseMapper.selectByExample(testPlanApiCaseExample); + return testPlanApiCases.stream().collect(Collectors.groupingBy(TestPlanApiCase::getTestPlanCollectionId)); + + } + + private Map> getTestPlanFunctionalCases(Map> typeChildren) { + List testPlanCollectionConfigDTOS = typeChildren.get(CaseType.FUNCTIONAL_CASE.getKey()); + if (CollectionUtils.isEmpty(testPlanCollectionConfigDTOS)) { + return new HashMap<>(); + } + List functionalCollectIds = testPlanCollectionConfigDTOS.stream().map(TestPlanCollectionConfigDTO::getId).toList(); + TestPlanFunctionalCaseExample testPlanFunctionalCaseExample = new TestPlanFunctionalCaseExample(); + testPlanFunctionalCaseExample.createCriteria().andTestPlanCollectionIdIn(functionalCollectIds); + List testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(testPlanFunctionalCaseExample); + return testPlanFunctionalCases.stream().collect(Collectors.groupingBy(TestPlanFunctionalCase::getTestPlanCollectionId)); + + } + + + private void buildCollectionChildren(List childrenList, List collectionChildren, Map> testPlanFunctionalCaseMap, Map> testPlanApiCaseMap, Map> testPlanApiScenarioMap) { + for (TestPlanCollectionConfigDTO planCollection : childrenList) { TestPlanCollectionMinderTreeDTO collectionTreeDTO = new TestPlanCollectionMinderTreeDTO(); - TestPlanCollectionMinderTreeNodeDTO collectionTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); - collectionTreeNodeDTO.setId(planCollection.getId()); - collectionTreeNodeDTO.setText(planCollection.getName()); - collectionTreeNodeDTO.setPos(planCollection.getPos()); - if (StringUtils.equalsIgnoreCase(planCollection.getExecuteMethod(), ApiBatchRunMode.PARALLEL.toString())) { - collectionTreeNodeDTO.setPriority("并"); - } else { - collectionTreeNodeDTO.setPriority("串"); - } - collectionTreeNodeDTO.setExecuteMethod(planCollection.getExecuteMethod()); + TestPlanCollectionMinderTreeNodeDTO collectionTreeNodeDTO = buildTypeChildData(planCollection); collectionTreeNodeDTO.setResource(new ArrayList<>()); - //TODO:构造子集 - - + List endList = getEndList(testPlanFunctionalCaseMap, testPlanApiCaseMap, testPlanApiScenarioMap, planCollection); collectionTreeDTO.setData(collectionTreeNodeDTO); - collectionTreeDTO.setChildren(new ArrayList<>()); + collectionTreeDTO.setChildren(endList); collectionChildren.add(collectionTreeDTO); } } + @NotNull + private static List getEndList(Map> testPlanFunctionalCaseMap, Map> testPlanApiCaseMap, Map> testPlanApiScenarioMap, TestPlanCollectionConfigDTO planCollection) { + List endList = new ArrayList<>(); + if (StringUtils.equalsIgnoreCase(planCollection.getType(), CaseType.FUNCTIONAL_CASE.getKey())) { + buildFunctionalChild(testPlanFunctionalCaseMap, planCollection, endList); + } else if (StringUtils.equalsIgnoreCase(planCollection.getType(), CaseType.API_CASE.getKey())) { + buildApiCaseChild(testPlanApiCaseMap, planCollection, endList); + } else { + buildScenarioChild(testPlanApiScenarioMap, planCollection, endList); + } + return endList; + } + + private static void buildScenarioChild(Map> testPlanApiScenarioMap, TestPlanCollectionConfigDTO planCollection, List endList) { + TestPlanCollectionMinderTreeDTO countTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO countTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + List testPlanApiScenarios = testPlanApiScenarioMap.get(planCollection.getId()); + int count = 0; + if (CollectionUtils.isNotEmpty(testPlanApiScenarios)) { + count = testPlanApiScenarios.size(); + } + buildChild(countTreeNodeDTO, count + Translator.get("test_plan.mind.strip"), "test_plan.mind.case_count", countTreeDTO, endList); + TestPlanCollectionMinderTreeDTO envTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO envTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + buildChild(envTreeNodeDTO, planCollection.getEnvName(), "test_plan.mind.environment", envTreeDTO, endList); + TestPlanCollectionMinderTreeDTO poolTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO poolTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + buildChild(poolTreeNodeDTO, planCollection.getPoolName(), "test_plan.mind.test_resource_pool", poolTreeDTO, endList); + } + + private static void buildChild(TestPlanCollectionMinderTreeNodeDTO treeNodeDTO, String text, String key, TestPlanCollectionMinderTreeDTO treeDTO, List endList) { + treeNodeDTO.setText(text); + treeNodeDTO.setResource(List.of(Translator.get(key))); + treeDTO.setData(treeNodeDTO); + treeDTO.setChildren(new ArrayList<>()); + if (StringUtils.isNotBlank(text)) { + endList.add(treeDTO); + } + } + + private static void buildApiCaseChild(Map> testPlanApiCaseMap, TestPlanCollectionConfigDTO planCollection, List endList) { + TestPlanCollectionMinderTreeDTO countTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO countTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + List testPlanApiCases = testPlanApiCaseMap.get(planCollection.getId()); + int count = 0; + if (CollectionUtils.isNotEmpty(testPlanApiCases)) { + count = testPlanApiCases.size(); + } + buildChild(countTreeNodeDTO, count + Translator.get("test_plan.mind.strip"), "test_plan.mind.case_count", countTreeDTO, endList); + TestPlanCollectionMinderTreeDTO envTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO envTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + buildChild(envTreeNodeDTO, planCollection.getEnvName(), "test_plan.mind.environment", envTreeDTO, endList); + TestPlanCollectionMinderTreeDTO poolTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO poolTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + buildChild(poolTreeNodeDTO, planCollection.getPoolName(), "test_plan.mind.test_resource_pool", poolTreeDTO, endList); + } + + private static void buildFunctionalChild(Map> testPlanFunctionalCaseMap, TestPlanCollectionConfigDTO planCollection, List endList) { + List testPlanFunctionalCases = testPlanFunctionalCaseMap.get(planCollection.getId()); + int count = 0; + if (CollectionUtils.isNotEmpty(testPlanFunctionalCases)) { + count = testPlanFunctionalCases.size(); + } + TestPlanCollectionMinderTreeDTO countTreeDTO = new TestPlanCollectionMinderTreeDTO(); + TestPlanCollectionMinderTreeNodeDTO countTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); + buildChild(countTreeNodeDTO, count + Translator.get("test_plan.mind.strip"), "test_plan.mind.case_count", countTreeDTO, endList); + } + @NotNull private static TestPlanCollectionMinderTreeNodeDTO buildRoot() { TestPlanCollectionMinderTreeNodeDTO testPlanCollectionMinderTreeNodeDTO = new TestPlanCollectionMinderTreeNodeDTO(); testPlanCollectionMinderTreeNodeDTO.setId(ModuleConstants.DEFAULT_NODE_ID); - testPlanCollectionMinderTreeNodeDTO.setText("测试规划"); + testPlanCollectionMinderTreeNodeDTO.setText(Translator.get("test_plan.mind.test_plan")); testPlanCollectionMinderTreeNodeDTO.setResource(new ArrayList<>()); return testPlanCollectionMinderTreeNodeDTO; } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCollectionMinderControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCollectionMinderControllerTests.java new file mode 100644 index 0000000000..3a726c110b --- /dev/null +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCollectionMinderControllerTests.java @@ -0,0 +1,41 @@ +package io.metersphere.plan.controller; + +import io.metersphere.plan.dto.TestPlanCollectionMinderTreeDTO; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.base.BaseTest; +import io.metersphere.system.controller.handler.ResultHolder; +import org.junit.jupiter.api.*; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.web.servlet.MvcResult; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@AutoConfigureMockMvc +public class TestPlanCollectionMinderControllerTests extends BaseTest { + + private static final String PLAN_MIND = "/test-plan/mind/data/"; + + @Test + @Order(1) + @Sql(scripts = {"/dml/init_test_plan_mind.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) + void tesPagePlanReportSuccess() throws Exception { + + MvcResult mvcResult = this.requestGetWithOkAndReturn(PLAN_MIND+"gyq_plan_1"); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + List testPlanCollectionMinderTreeDTOS = JSON.parseArray(JSON.toJSONString(resultHolder.getData()), TestPlanCollectionMinderTreeDTO.class); + // 返回值不为空 + Assertions.assertNotNull(testPlanCollectionMinderTreeDTOS); + } + + +} diff --git a/backend/services/test-plan/src/test/resources/dml/init_test_plan_mind.sql b/backend/services/test-plan/src/test/resources/dml/init_test_plan_mind.sql new file mode 100644 index 0000000000..3f5b15b200 --- /dev/null +++ b/backend/services/test-plan/src/test/resources/dml/init_test_plan_mind.sql @@ -0,0 +1,40 @@ + + +INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`) +VALUES + ('gyq_plan_1', 5000, 'gyq_plan_project', 'NONE', '1', 'qwe', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'admin', 1714980158000, 'admin', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'), + ('gyq_plan_2', 10000, 'gyq_plan_project', 'NONE', '1', 'eeew', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'admin', 1714980158000, 'admin', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'); + + +INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) +VALUES ('gyq_plan_project', null, 'organization-associate-case-test', '用例评论项目', '系统默认创建的项目', + 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000); + +INSERT INTO test_plan_api_case(id, test_plan_id, api_case_id, environment_id, last_exec_result, last_exec_report_id, execute_user, create_time, create_user, pos, test_plan_collection_id, last_exec_time) +VALUES ('gyq_wxxx_1', 'gyq_plan_1', 'wxxx_api_case_1', 'gyq_123', NULL, NULL, 'admin', 1716370415311, 'admin', 2, 'gyq_wxxx_4', 1716370415311); + +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('gyq_functional_case_1', 'gyq_plan_1', 'functional_case_id', 1716797474979, 'admin', 'admin', 1716866691313, 'SUCCESS', 4096, 'gyq_wxxx_5'); + + +INSERT INTO `test_plan_api_scenario`(id, test_plan_id, api_scenario_id, environment_id, execute_user, last_exec_result, last_exec_report_id, create_time, create_user, pos, test_plan_collection_id, grouped, last_exec_time) +VALUES ('gyq_scenario_case_1', 'gyq_plan_1', 'api_scenario_id', 'gyq_123','admin', 'SUCCESS', 'last_exec_report_id', 1716866691313,'admin', 4096, 'gyq_wxxx_6', b'0', 1716866691313); + + +INSERT INTO `test_plan_collection`(`id`, `test_plan_id`, `name`, `type`, `environment_id`, `test_resource_pool_id`, `pos`, `create_user`, `create_time`, `parent_id`) +VALUES + ('gyq_wxxx_1', 'gyq_plan_1', '接口用例', 'API', 'NONE', 'gyq_123_pool', 1, 'admin', 1716370415311, 'NONE'), + ('gyq_wxxx_2', 'gyq_plan_1', '功能用例', 'FUNCTIONAL', 'gyq_123', 'gyq_123_pool', 2, 'admin', 1716370415311, 'NONE'), + ('gyq_wxxx_3', 'gyq_plan_1', '场景用例', 'SCENARIO', 'NONE', 'NONE', 3, 'admin', 1716370415311, 'NONE'), + ('gyq_wxxx_4', 'gyq_plan_1', '接口测试集', 'API', 'NONE', 'gyq_123_pool', 1, 'admin', 1716370415311, 'gyq_wxxx_1'), + ('gyq_wxxx_5', 'gyq_plan_1', '功能测试集', 'FUNCTIONAL', 'gyq_123', 'gyq_123_pool', 2, 'admin', 1716370415311, 'gyq_wxxx_2'), + ('gyq_wxxx_6', 'gyq_plan_1', '场景测试集', 'SCENARIO', 'NONE', 'gyq_123_pool', 3, 'admin', 1716370415311, 'gyq_wxxx_3'); + + +INSERT INTO `environment`(`id`, `name`, `project_id`, `create_user`, `update_user`, `create_time`, `update_time`, `mock`, `description`, `pos`) +VALUES ('gyq_123', 'Mock环境', 'wxx_1234', 'admin', 'admin', 1716175907000, 1716175907000, b'1', NULL, 64); + +INSERT INTO test_resource_pool (id, name, type, description, enable, create_time, update_time, create_user, all_org, deleted) +VALUES ('gyq_123_pool', '默认资源池', 'Node', '系统初始化资源池', true, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', true, false); + +