diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java index f9fd57622e..9323c4d3c3 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java @@ -2,12 +2,15 @@ package io.metersphere.plan.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import io.metersphere.plan.dto.request.TestPlanApiCaseTreeRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioRequest; +import io.metersphere.plan.dto.request.TestPlanApiScenarioTreeRequest; import io.metersphere.plan.dto.response.TestPlanApiScenarioPageResponse; import io.metersphere.plan.service.TestPlanApiScenarioService; import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.dto.api.task.TaskRequestDTO; +import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.security.CheckOwner; import io.metersphere.system.utils.PageUtils; import io.metersphere.system.utils.Pager; @@ -57,4 +60,12 @@ public class TestPlanApiScenarioController { public Map moduleCount(@Validated @RequestBody TestPlanApiScenarioModuleRequest request) { return testPlanApiScenarioService.moduleCount(request); } + + @PostMapping("/tree") + @Operation(summary = "测试计划-已关联接口用例列表模块树") + @RequiresPermissions(PermissionConstants.TEST_PLAN_READ) + @CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan") + public List getTree(@Validated @RequestBody TestPlanApiScenarioTreeRequest request) { + return testPlanApiScenarioService.getTree(request); + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/ApiScenarioModuleDTO.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/ApiScenarioModuleDTO.java new file mode 100644 index 0000000000..c3a5684bcf --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/ApiScenarioModuleDTO.java @@ -0,0 +1,15 @@ +package io.metersphere.plan.dto; + +import io.metersphere.api.domain.ApiScenarioModule; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * @author wx + */ +@Data +public class ApiScenarioModuleDTO extends ApiScenarioModule { + + @Schema(description = "项目名称") + private String projectName; +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanApiScenarioTreeRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanApiScenarioTreeRequest.java new file mode 100644 index 0000000000..b906bf0241 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanApiScenarioTreeRequest.java @@ -0,0 +1,28 @@ +package io.metersphere.plan.dto.request; + +import io.metersphere.plan.constants.TreeTypeEnums; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author wx + */ +@Data +public class TestPlanApiScenarioTreeRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "测试计划id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{test_plan.id.not_blank}") + private String testPlanId; + + @Schema(description = "类型:模块/计划集", allowableValues = {"MODULE","COLLECTION"},requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{test_plan.type.not_blank}") + private String treeType = TreeTypeEnums.COLLECTION; + +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java index dceebec8ca..0925c0fbf1 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java @@ -1,7 +1,10 @@ package io.metersphere.plan.mapper; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; +import io.metersphere.functional.dto.ProjectOptionDTO; import io.metersphere.plan.domain.TestPlanApiScenario; +import io.metersphere.plan.dto.ApiCaseModuleDTO; +import io.metersphere.plan.dto.ApiScenarioModuleDTO; import io.metersphere.plan.dto.ResourceSelectParam; import io.metersphere.plan.dto.TestPlanCaseRunResultCount; import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest; @@ -41,4 +44,8 @@ public interface ExtTestPlanApiScenarioMapper { List selectIdByProjectIdAndTestPlanId(@Param("projectId") String projectId, @Param("testPlanId") String testPlanId); List collectionCountByRequest(@Param("testPlanId") String testPlanId); + + List selectRootIdByTestPlanId(@Param("testPlanId") String testPlanId); + + List selectBaseByProjectIdAndTestPlanId(@Param("testPlanId") String testPlanId); } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml index 43f0f30961..df64469ac2 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml @@ -354,4 +354,23 @@ GROUP BY test_plan_api_scenario.test_plan_collection_id + + + + \ No newline at end of file diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java index db91066bf0..af90b71613 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java @@ -10,19 +10,12 @@ import io.metersphere.api.service.scenario.ApiScenarioModuleService; import io.metersphere.api.service.scenario.ApiScenarioRunService; import io.metersphere.api.service.scenario.ApiScenarioService; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; +import io.metersphere.functional.dto.ProjectOptionDTO; import io.metersphere.plan.constants.AssociateCaseType; import io.metersphere.plan.constants.TreeTypeEnums; -import io.metersphere.plan.domain.TestPlan; -import io.metersphere.plan.domain.TestPlanApiScenario; -import io.metersphere.plan.domain.TestPlanApiScenarioExample; -import io.metersphere.plan.dto.ResourceLogInsertModule; -import io.metersphere.plan.dto.TestPlanCaseRunResultCount; -import io.metersphere.plan.dto.TestPlanCollectionDTO; -import io.metersphere.plan.dto.TestPlanCollectionEnvDTO; -import io.metersphere.plan.dto.request.BaseCollectionAssociateRequest; -import io.metersphere.plan.dto.request.ResourceSortRequest; -import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest; -import io.metersphere.plan.dto.request.TestPlanApiScenarioRequest; +import io.metersphere.plan.domain.*; +import io.metersphere.plan.dto.*; +import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanApiScenarioPageResponse; import io.metersphere.plan.dto.response.TestPlanOperationResponse; import io.metersphere.plan.mapper.*; @@ -46,6 +39,7 @@ import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.utils.ServiceUtils; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; @@ -90,6 +84,8 @@ public class TestPlanApiScenarioService extends TestPlanResourceService implemen private static final String CASE_MODULE_COUNT_ALL = "all"; @Resource private ApiScenarioModuleService apiScenarioModuleService; + @Resource + private TestPlanCollectionMapper testPlanCollectionMapper; public TestPlanApiScenarioService() { GetRunScriptServiceRegister.register(ApiExecuteResourceType.TEST_PLAN_API_SCENARIO, this); @@ -420,4 +416,67 @@ public class TestPlanApiScenarioService extends TestPlanResourceService implemen return apiScenarioModuleService.buildTreeAndCountResource(nodeByNodeIds, moduleCountDTOList, true, Translator.get("functional_case.module.default.name")); } + public List getTree(TestPlanApiScenarioTreeRequest request) { + switch (request.getTreeType()) { + case TreeTypeEnums.MODULE: + return getModuleTree(request.getTestPlanId()); + case TreeTypeEnums.COLLECTION: + return getCollectionTree(request.getTestPlanId()); + default: + return new ArrayList<>(); + } + } + + /** + * 已关联接口用例规划视图树 + * + * @param testPlanId + * @return + */ + private List getCollectionTree(String testPlanId) { + List returnList = new ArrayList<>(); + TestPlanCollectionExample collectionExample = new TestPlanCollectionExample(); + collectionExample.createCriteria().andTypeEqualTo(CaseType.SCENARIO_CASE.getKey()).andParentIdNotEqualTo(ModuleConstants.ROOT_NODE_PARENT_ID).andTestPlanIdEqualTo(testPlanId); + List testPlanCollections = testPlanCollectionMapper.selectByExample(collectionExample); + testPlanCollections.forEach(item -> { + BaseTreeNode baseTreeNode = new BaseTreeNode(item.getId(), item.getName(), CaseType.SCENARIO_CASE.getKey()); + returnList.add(baseTreeNode); + }); + return returnList; + } + + /** + * 模块树 + * + * @param testPlanId + * @return + */ + private List getModuleTree(String testPlanId) { + List returnList = new ArrayList<>(); + List rootIds = extTestPlanApiScenarioMapper.selectRootIdByTestPlanId(testPlanId); + Map> projectRootMap = rootIds.stream().collect(Collectors.groupingBy(ProjectOptionDTO::getName)); + List apiCaseModuleIds = extTestPlanApiScenarioMapper.selectBaseByProjectIdAndTestPlanId(testPlanId); + Map> projectModuleMap = apiCaseModuleIds.stream().collect(Collectors.groupingBy(ApiScenarioModuleDTO::getProjectId)); + if (MapUtils.isEmpty(projectModuleMap)) { + projectRootMap.forEach((projectId, projectOptionDTOList) -> { + BaseTreeNode projectNode = new BaseTreeNode(projectId, projectOptionDTOList.get(0).getProjectName(), Project.class.getName()); + returnList.add(projectNode); + BaseTreeNode defaultNode = apiScenarioModuleService.getDefaultModule(Translator.get("functional_case.module.default.name")); + projectNode.addChild(defaultNode); + }); + return returnList; + } + projectModuleMap.forEach((projectId, moduleList) -> { + BaseTreeNode projectNode = new BaseTreeNode(projectId, moduleList.get(0).getProjectName(), Project.class.getName()); + returnList.add(projectNode); + List projectModuleIds = moduleList.stream().map(ApiScenarioModuleDTO::getId).toList(); + List nodeByNodeIds = apiScenarioModuleService.getNodeByNodeIds(projectModuleIds); + boolean haveVirtualRootNode = CollectionUtils.isEmpty(projectRootMap.get(projectId)); + List baseTreeNodes = apiScenarioModuleService.buildTreeAndCountResource(nodeByNodeIds, !haveVirtualRootNode, Translator.get("functional_case.module.default.name")); + for (BaseTreeNode baseTreeNode : baseTreeNodes) { + projectNode.addChild(baseTreeNode); + } + }); + return returnList; + } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java index d13f0447d0..3deaf09249 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java @@ -18,6 +18,7 @@ import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.plan.domain.TestPlanApiScenario; import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioRequest; +import io.metersphere.plan.dto.request.TestPlanApiScenarioTreeRequest; import io.metersphere.plan.mapper.TestPlanApiScenarioMapper; import io.metersphere.plan.service.TestPlanApiScenarioService; import io.metersphere.project.api.assertion.MsResponseCodeAssertion; @@ -56,6 +57,7 @@ public class TestPlanApiScenarioControllerTests extends BaseTest { public static final String RUN_WITH_REPORT_ID = "run/{0}?reportId={1}"; public static final String API_SCENARIO_PAGE = "page"; public static final String API_SCENARIO_TREE_COUNT = "module/count"; + public static final String API_SCENARIO_TREE = "tree"; @Resource private TestPlanApiScenarioService testPlanApiScenarioService; @@ -202,7 +204,7 @@ public class TestPlanApiScenarioControllerTests extends BaseTest { @Test @Order(4) - public void testApiCaseCount() throws Exception { + public void testApiScenarioCount() throws Exception { TestPlanApiScenarioModuleRequest request = new TestPlanApiScenarioModuleRequest(); request.setTestPlanId("wxxx_plan_1"); request.setProjectId("wxx_project_1234"); @@ -218,4 +220,25 @@ public class TestPlanApiScenarioControllerTests extends BaseTest { this.requestPostWithOkAndReturn(API_SCENARIO_TREE_COUNT, request); } + + @Test + @Order(5) + public void testApiScenarioModuleTree() throws Exception { + TestPlanApiScenarioTreeRequest request = new TestPlanApiScenarioTreeRequest(); + request.setTestPlanId("wxxx_plan_1"); + request.setTreeType("MODULE"); + this.requestPostWithOkAndReturn(API_SCENARIO_TREE, request); + request.setTestPlanId("wxxx_plan_2"); + MvcResult mvcResult = this.requestPostWithOkAndReturn(API_SCENARIO_TREE, request); + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + Assertions.assertNotNull(resultHolder); + + request.setTestPlanId("wxxx_plan_2"); + request.setTreeType("COLLECTION"); + MvcResult mvcResult1 = this.requestPostWithOkAndReturn(API_SCENARIO_TREE, request); + String returnData1 = mvcResult1.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder1 = JSON.parseObject(returnData1, ResultHolder.class); + Assertions.assertNotNull(resultHolder1); + } } diff --git a/backend/services/test-plan/src/test/resources/dml/init_test_plan_api_scenario.sql b/backend/services/test-plan/src/test/resources/dml/init_test_plan_api_scenario.sql index 4f526d2597..6570bd7f16 100644 --- a/backend/services/test-plan/src/test/resources/dml/init_test_plan_api_scenario.sql +++ b/backend/services/test-plan/src/test/resources/dml/init_test_plan_api_scenario.sql @@ -23,7 +23,7 @@ VALUES INSERT INTO `api_scenario`(`id`, `name`, `priority`, `status`, `step_total`, `request_pass_rate`, `last_report_status`, `last_report_id`, `num`, `deleted`, `pos`, `version_id`, `ref_id`, `latest`, `project_id`, `module_id`, `description`, `tags`, `grouped`, `environment_id`, `create_user`, `create_time`, `delete_time`, `delete_user`, `update_user`, `update_time`) VALUES ('wxxx_api_scenario_1', 'axx', 'P0', 'UNDERWAY', 3, '0.46', 'ERROR', '971160841641984', 100027, b'0', 5568, '718273150722066', '1023696881426432', b'1', 'wxx_project_1234', 'root', '', '[]', b'0', 'wx_env_123', '714940256100352', 1717489987182, NULL, NULL, '714940256100352', 1717557159805), - ('wxxx_api_scenario_2', 'xww', 'P0', 'UNDERWAY', 3, '0.46', 'ERROR', '971160841641984', 100027, b'0', 5568, '718273150722066', '1023696881426432', b'1', 'wxx_project_1234', 'root', '', '[]', b'0', 'wx_env_123', '714940256100352', 1717489987182, NULL, NULL, '714940256100352', 1717557159805); + ('wxxx_api_scenario_2', 'xww', 'P0', 'UNDERWAY', 3, '0.46', 'ERROR', '971160841641984', 100027, b'0', 5568, '718273150722066', '1023696881426432', b'1', 'wxx_project_1234', 'wx_scenario_module_123', '', '[]', b'0', 'wx_env_123', '714940256100352', 1717489987182, NULL, NULL, '714940256100352', 1717557159805); @@ -41,3 +41,5 @@ VALUES ('wx_env_223', '测试环境', 'wxx_project_1234', 'admin', 'admin', 1716175907000, 1716175907000, b'1', NULL, 128); +INSERT INTO `api_scenario_module`(`id`, `name`, `pos`, `create_time`, `update_time`, `update_user`, `create_user`, `project_id`, `parent_id`) +VALUES ('wx_scenario_module_123', '测试CSV', 64, 1716196253511, 1716196253511, '714940256100352', '714940256100352', '718255970852864', 'NONE');