feat(测试计划): 已关联场景模块树视图&测试集视图

This commit is contained in:
WangXu10 2024-06-11 15:50:49 +08:00 committed by 刘瑞斌
parent b0cfbdd210
commit 84fcbc8aff
8 changed files with 177 additions and 13 deletions

View File

@ -2,12 +2,15 @@ package io.metersphere.plan.controller;
import com.github.pagehelper.Page; import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper; 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.TestPlanApiScenarioModuleRequest;
import io.metersphere.plan.dto.request.TestPlanApiScenarioRequest; 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.dto.response.TestPlanApiScenarioPageResponse;
import io.metersphere.plan.service.TestPlanApiScenarioService; import io.metersphere.plan.service.TestPlanApiScenarioService;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO; 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.security.CheckOwner;
import io.metersphere.system.utils.PageUtils; import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager; import io.metersphere.system.utils.Pager;
@ -57,4 +60,12 @@ public class TestPlanApiScenarioController {
public Map<String, Long> moduleCount(@Validated @RequestBody TestPlanApiScenarioModuleRequest request) { public Map<String, Long> moduleCount(@Validated @RequestBody TestPlanApiScenarioModuleRequest request) {
return testPlanApiScenarioService.moduleCount(request); return testPlanApiScenarioService.moduleCount(request);
} }
@PostMapping("/tree")
@Operation(summary = "测试计划-已关联接口用例列表模块树")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ)
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
public List<BaseTreeNode> getTree(@Validated @RequestBody TestPlanApiScenarioTreeRequest request) {
return testPlanApiScenarioService.getTree(request);
}
} }

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,7 +1,10 @@
package io.metersphere.plan.mapper; package io.metersphere.plan.mapper;
import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO;
import io.metersphere.functional.dto.ProjectOptionDTO;
import io.metersphere.plan.domain.TestPlanApiScenario; 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.ResourceSelectParam;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount; import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest;
@ -41,4 +44,8 @@ public interface ExtTestPlanApiScenarioMapper {
List<String> selectIdByProjectIdAndTestPlanId(@Param("projectId") String projectId, @Param("testPlanId") String testPlanId); List<String> selectIdByProjectIdAndTestPlanId(@Param("projectId") String projectId, @Param("testPlanId") String testPlanId);
List<ModuleCountDTO> collectionCountByRequest(@Param("testPlanId") String testPlanId); List<ModuleCountDTO> collectionCountByRequest(@Param("testPlanId") String testPlanId);
List<ProjectOptionDTO> selectRootIdByTestPlanId(@Param("testPlanId") String testPlanId);
List<ApiScenarioModuleDTO> selectBaseByProjectIdAndTestPlanId(@Param("testPlanId") String testPlanId);
} }

View File

@ -354,4 +354,23 @@
GROUP BY GROUP BY
test_plan_api_scenario.test_plan_collection_id test_plan_api_scenario.test_plan_collection_id
</select> </select>
<select id="selectRootIdByTestPlanId" resultType="io.metersphere.functional.dto.ProjectOptionDTO">
SELECT api_scenario.module_id as id, api_scenario.project_id as name, p.name as projectName
FROM test_plan_api_scenario
INNER JOIN api_scenario on api_scenario.id = test_plan_api_scenario.api_scenario_id
LEFT JOIN project p ON api_scenario.project_id = p.id
WHERE test_plan_api_scenario.test_plan_id = #{testPlanId}
AND api_scenario.deleted = false AND api_scenario.module_id = 'root'
ORDER BY api_scenario.pos
</select>
<select id="selectBaseByProjectIdAndTestPlanId" resultType="io.metersphere.plan.dto.ApiScenarioModuleDTO">
SELECT asm.id, asm.project_id, p.name as projectName
FROM api_scenario_module asm
LEFT JOIN project p ON asm.project_id = p.id
WHERE asm.id IN
(SELECT api_scenario.module_id FROM api_scenario LEFT JOIN test_plan_api_scenario ON api_scenario.id = test_plan_api_scenario.api_scenario_id WHERE test_plan_api_scenario.test_plan_id = #{testPlanId} AND api_scenario.deleted = false)
ORDER BY pos
</select>
</mapper> </mapper>

View File

@ -10,19 +10,12 @@ import io.metersphere.api.service.scenario.ApiScenarioModuleService;
import io.metersphere.api.service.scenario.ApiScenarioRunService; import io.metersphere.api.service.scenario.ApiScenarioRunService;
import io.metersphere.api.service.scenario.ApiScenarioService; import io.metersphere.api.service.scenario.ApiScenarioService;
import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO;
import io.metersphere.functional.dto.ProjectOptionDTO;
import io.metersphere.plan.constants.AssociateCaseType; import io.metersphere.plan.constants.AssociateCaseType;
import io.metersphere.plan.constants.TreeTypeEnums; import io.metersphere.plan.constants.TreeTypeEnums;
import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.domain.*;
import io.metersphere.plan.domain.TestPlanApiScenario; import io.metersphere.plan.dto.*;
import io.metersphere.plan.domain.TestPlanApiScenarioExample; import io.metersphere.plan.dto.request.*;
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.dto.response.TestPlanApiScenarioPageResponse; import io.metersphere.plan.dto.response.TestPlanApiScenarioPageResponse;
import io.metersphere.plan.dto.response.TestPlanOperationResponse; import io.metersphere.plan.dto.response.TestPlanOperationResponse;
import io.metersphere.plan.mapper.*; import io.metersphere.plan.mapper.*;
@ -46,6 +39,7 @@ import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.ServiceUtils; import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession; 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"; private static final String CASE_MODULE_COUNT_ALL = "all";
@Resource @Resource
private ApiScenarioModuleService apiScenarioModuleService; private ApiScenarioModuleService apiScenarioModuleService;
@Resource
private TestPlanCollectionMapper testPlanCollectionMapper;
public TestPlanApiScenarioService() { public TestPlanApiScenarioService() {
GetRunScriptServiceRegister.register(ApiExecuteResourceType.TEST_PLAN_API_SCENARIO, this); 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")); return apiScenarioModuleService.buildTreeAndCountResource(nodeByNodeIds, moduleCountDTOList, true, Translator.get("functional_case.module.default.name"));
} }
public List<BaseTreeNode> 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<BaseTreeNode> getCollectionTree(String testPlanId) {
List<BaseTreeNode> returnList = new ArrayList<>();
TestPlanCollectionExample collectionExample = new TestPlanCollectionExample();
collectionExample.createCriteria().andTypeEqualTo(CaseType.SCENARIO_CASE.getKey()).andParentIdNotEqualTo(ModuleConstants.ROOT_NODE_PARENT_ID).andTestPlanIdEqualTo(testPlanId);
List<TestPlanCollection> 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<BaseTreeNode> getModuleTree(String testPlanId) {
List<BaseTreeNode> returnList = new ArrayList<>();
List<ProjectOptionDTO> rootIds = extTestPlanApiScenarioMapper.selectRootIdByTestPlanId(testPlanId);
Map<String, List<ProjectOptionDTO>> projectRootMap = rootIds.stream().collect(Collectors.groupingBy(ProjectOptionDTO::getName));
List<ApiScenarioModuleDTO> apiCaseModuleIds = extTestPlanApiScenarioMapper.selectBaseByProjectIdAndTestPlanId(testPlanId);
Map<String, List<ApiScenarioModuleDTO>> 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<String> projectModuleIds = moduleList.stream().map(ApiScenarioModuleDTO::getId).toList();
List<BaseTreeNode> nodeByNodeIds = apiScenarioModuleService.getNodeByNodeIds(projectModuleIds);
boolean haveVirtualRootNode = CollectionUtils.isEmpty(projectRootMap.get(projectId));
List<BaseTreeNode> baseTreeNodes = apiScenarioModuleService.buildTreeAndCountResource(nodeByNodeIds, !haveVirtualRootNode, Translator.get("functional_case.module.default.name"));
for (BaseTreeNode baseTreeNode : baseTreeNodes) {
projectNode.addChild(baseTreeNode);
}
});
return returnList;
}
} }

View File

@ -18,6 +18,7 @@ import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plan.domain.TestPlanApiScenario; import io.metersphere.plan.domain.TestPlanApiScenario;
import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest;
import io.metersphere.plan.dto.request.TestPlanApiScenarioRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioRequest;
import io.metersphere.plan.dto.request.TestPlanApiScenarioTreeRequest;
import io.metersphere.plan.mapper.TestPlanApiScenarioMapper; import io.metersphere.plan.mapper.TestPlanApiScenarioMapper;
import io.metersphere.plan.service.TestPlanApiScenarioService; import io.metersphere.plan.service.TestPlanApiScenarioService;
import io.metersphere.project.api.assertion.MsResponseCodeAssertion; 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 RUN_WITH_REPORT_ID = "run/{0}?reportId={1}";
public static final String API_SCENARIO_PAGE = "page"; 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_COUNT = "module/count";
public static final String API_SCENARIO_TREE = "tree";
@Resource @Resource
private TestPlanApiScenarioService testPlanApiScenarioService; private TestPlanApiScenarioService testPlanApiScenarioService;
@ -202,7 +204,7 @@ public class TestPlanApiScenarioControllerTests extends BaseTest {
@Test @Test
@Order(4) @Order(4)
public void testApiCaseCount() throws Exception { public void testApiScenarioCount() throws Exception {
TestPlanApiScenarioModuleRequest request = new TestPlanApiScenarioModuleRequest(); TestPlanApiScenarioModuleRequest request = new TestPlanApiScenarioModuleRequest();
request.setTestPlanId("wxxx_plan_1"); request.setTestPlanId("wxxx_plan_1");
request.setProjectId("wxx_project_1234"); request.setProjectId("wxx_project_1234");
@ -218,4 +220,25 @@ public class TestPlanApiScenarioControllerTests extends BaseTest {
this.requestPostWithOkAndReturn(API_SCENARIO_TREE_COUNT, request); 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);
}
} }

View File

@ -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`) 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 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_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); ('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');