refactor(测试计划): 模块树视图&计划集视图

This commit is contained in:
WangXu10 2024-06-06 15:18:18 +08:00 committed by 刘瑞斌
parent faa5fcfb39
commit a4d9799a0f
9 changed files with 189 additions and 27 deletions

View File

@ -0,0 +1,14 @@
package io.metersphere.plan.constants;
public class TreeTypeEnums {
/**
* 模块
*/
public static final String MODULE = "MODULE";
/**
* 计划集
*/
public static final String COLLECTION = "COLLECTION";
}

View File

@ -2,10 +2,7 @@ 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.TestPlanApiCaseBatchRequest; import io.metersphere.plan.dto.request.*;
import io.metersphere.plan.dto.request.TestPlanApiCaseRequest;
import io.metersphere.plan.dto.request.TestPlanApiCaseUpdateRequest;
import io.metersphere.plan.dto.request.TestPlanDisassociationRequest;
import io.metersphere.plan.dto.response.TestPlanApiCasePageResponse; import io.metersphere.plan.dto.response.TestPlanApiCasePageResponse;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse; import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
import io.metersphere.plan.service.TestPlanApiCaseLogService; import io.metersphere.plan.service.TestPlanApiCaseLogService;
@ -27,7 +24,10 @@ import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -58,16 +58,16 @@ public class TestPlanApiCaseController {
@Operation(summary = "测试计划-已关联接口用例模块数量") @Operation(summary = "测试计划-已关联接口用例模块数量")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ) @RequiresPermissions(PermissionConstants.TEST_PLAN_READ)
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan") @CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
public Map<String, Long> moduleCount(@Validated @RequestBody TestPlanApiCaseRequest request) { public Map<String, Long> moduleCount(@Validated @RequestBody TestPlanApiCaseModuleRequest request) {
return testPlanApiCaseService.moduleCount(request); return testPlanApiCaseService.moduleCount(request);
} }
@GetMapping("/tree/{testPlanId}") @PostMapping("/tree")
@Operation(summary = "测试计划-已关联接口用例列表模块树") @Operation(summary = "测试计划-已关联接口用例列表模块树")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ) @RequiresPermissions(PermissionConstants.TEST_PLAN_READ)
@CheckOwner(resourceId = "#testPlanId", resourceType = "test_plan") @CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
public List<BaseTreeNode> getTree(@PathVariable String testPlanId) { public List<BaseTreeNode> getTree(@Validated @RequestBody TestPlanApiCaseTreeRequest request) {
return testPlanApiCaseService.getTree(testPlanId); return testPlanApiCaseService.getTree(request);
} }
@PostMapping("/disassociate") @PostMapping("/disassociate")

View File

@ -0,0 +1,17 @@
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;
/**
* @author wx
*/
@Data
public class TestPlanApiCaseModuleRequest extends TestPlanApiCaseRequest{
@Schema(description = "类型:模块/计划集", allowableValues = {"MODULE","COLLECTION"},requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan.type.not_blank}")
private String treeType = TreeTypeEnums.COLLECTION;
}

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 TestPlanApiCaseTreeRequest 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

@ -12,6 +12,7 @@ import io.metersphere.plan.dto.request.TestPlanApiCaseRequest;
import io.metersphere.plan.dto.request.TestPlanApiRequest; import io.metersphere.plan.dto.request.TestPlanApiRequest;
import io.metersphere.plan.dto.response.TestPlanApiCasePageResponse; import io.metersphere.plan.dto.response.TestPlanApiCasePageResponse;
import io.metersphere.project.dto.DropNode; import io.metersphere.project.dto.DropNode;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.dto.NodeSortQueryParam; import io.metersphere.project.dto.NodeSortQueryParam;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@ -52,4 +53,6 @@ public interface ExtTestPlanApiCaseMapper {
List<String> getIds(@Param("request") TestPlanApiCaseBatchRequest request, @Param("deleted") boolean deleted); List<String> getIds(@Param("request") TestPlanApiCaseBatchRequest request, @Param("deleted") boolean deleted);
void batchUpdateExecutor(@Param("ids") List<String> ids, @Param("userId") String userId); void batchUpdateExecutor(@Param("ids") List<String> ids, @Param("userId") String userId);
List<ModuleCountDTO> collectionCountByRequest(@Param("testPlanId") String testPlanId);
} }

View File

@ -128,9 +128,9 @@
<if test="request.projectId != null and request.projectId != ''"> <if test="request.projectId != null and request.projectId != ''">
and api_definition.project_id = #{request.projectId} and api_definition.project_id = #{request.projectId}
</if> </if>
<if test="request.protocol != null and request.protocol != ''"> <!--<if test="request.protocol != null and request.protocol != ''">
AND api_definition.protocol = #{request.protocol} AND api_definition.protocol = #{request.protocol}
</if> </if>-->
<include refid="filters"> <include refid="filters">
<property name="filter" value="request.filter"/> <property name="filter" value="request.filter"/>
</include> </include>
@ -437,9 +437,9 @@
<sql id="queryApiCaseWhereCondition"> <sql id="queryApiCaseWhereCondition">
<if test="request.protocol != null and request.protocol!=''"> <!--<if test="request.protocol != null and request.protocol!=''">
and a.protocol = #{request.protocol} and a.protocol = #{request.protocol}
</if> </if>-->
<if test="request.apiDefinitionId != null and request.apiDefinitionId!=''"> <if test="request.apiDefinitionId != null and request.apiDefinitionId!=''">
and atc.api_definition_id = #{request.apiDefinitionId} and atc.api_definition_id = #{request.apiDefinitionId}
</if> </if>
@ -640,4 +640,17 @@
</update> </update>
<select id="collectionCountByRequest" parameterType="java.lang.String" resultType="io.metersphere.project.dto.ModuleCountDTO">
SELECT
test_plan_api_case.test_plan_collection_id AS moduleId,
count( test_plan_api_case.id ) AS dataCount
FROM
api_test_case
INNER JOIN test_plan_api_case ON api_test_case.id = test_plan_api_case.api_case_id
WHERE
api_test_case.deleted = FALSE
AND test_plan_api_case.test_plan_id = #{testPlanId}
GROUP BY
test_plan_api_case.test_plan_collection_id
</select>
</mapper> </mapper>

View File

@ -7,8 +7,11 @@ import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.service.definition.ApiTestCaseService;
import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO;
import io.metersphere.functional.dto.ProjectOptionDTO; import io.metersphere.functional.dto.ProjectOptionDTO;
import io.metersphere.plan.constants.TreeTypeEnums;
import io.metersphere.plan.domain.TestPlanApiCase; import io.metersphere.plan.domain.TestPlanApiCase;
import io.metersphere.plan.domain.TestPlanApiCaseExample; import io.metersphere.plan.domain.TestPlanApiCaseExample;
import io.metersphere.plan.domain.TestPlanCollection;
import io.metersphere.plan.domain.TestPlanCollectionExample;
import io.metersphere.plan.dto.ApiCaseModuleDTO; import io.metersphere.plan.dto.ApiCaseModuleDTO;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount; import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam; import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
@ -17,6 +20,7 @@ import io.metersphere.plan.dto.response.TestPlanApiCasePageResponse;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse; import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
import io.metersphere.plan.mapper.ExtTestPlanApiCaseMapper; import io.metersphere.plan.mapper.ExtTestPlanApiCaseMapper;
import io.metersphere.plan.mapper.TestPlanApiCaseMapper; import io.metersphere.plan.mapper.TestPlanApiCaseMapper;
import io.metersphere.plan.mapper.TestPlanCollectionMapper;
import io.metersphere.project.domain.Project; import io.metersphere.project.domain.Project;
import io.metersphere.project.domain.ProjectExample; import io.metersphere.project.domain.ProjectExample;
import io.metersphere.project.dto.ModuleCountDTO; import io.metersphere.project.dto.ModuleCountDTO;
@ -73,6 +77,8 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
private static final String CASE_MODULE_COUNT_ALL = "all"; private static final String CASE_MODULE_COUNT_ALL = "all";
@Resource @Resource
private SqlSessionFactory sqlSessionFactory; private SqlSessionFactory sqlSessionFactory;
@Resource
private TestPlanCollectionMapper testPlanCollectionMapper;
@Override @Override
public void deleteBatchByTestPlanId(List<String> testPlanIdList) { public void deleteBatchByTestPlanId(List<String> testPlanIdList) {
@ -237,7 +243,41 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
} }
public Map<String, Long> moduleCount(TestPlanApiCaseRequest request) { public Map<String, Long> moduleCount(TestPlanApiCaseModuleRequest request) {
switch (request.getTreeType()) {
case TreeTypeEnums.MODULE:
return getModuleCount(request);
case TreeTypeEnums.COLLECTION:
return getCollectionCount(request);
default:
return new HashMap<>();
}
}
/**
* 已关联接口用例规划视图统计
*
* @param request
* @return
*/
private Map<String, Long> getCollectionCount(TestPlanApiCaseModuleRequest request) {
Map<String, Long> projectModuleCountMap = new HashMap<>();
List<ModuleCountDTO> list = extTestPlanApiCaseMapper.collectionCountByRequest(request.getTestPlanId());
list.forEach(item -> {
projectModuleCountMap.put(item.getModuleId(), (long) item.getDataCount());
});
long allCount = extTestPlanApiCaseMapper.caseCount(request, false);
projectModuleCountMap.put(CASE_MODULE_COUNT_ALL, allCount);
return projectModuleCountMap;
}
/**
* 已关联接口用例模块树统计
*
* @param request
* @return
*/
private Map<String, Long> getModuleCount(TestPlanApiCaseModuleRequest request) {
request.setModuleIds(null); request.setModuleIds(null);
List<FunctionalCaseModuleCountDTO> projectModuleCountDTOList = extTestPlanApiCaseMapper.countModuleIdByRequest(request, false); List<FunctionalCaseModuleCountDTO> projectModuleCountDTOList = extTestPlanApiCaseMapper.countModuleIdByRequest(request, false);
Map<String, List<FunctionalCaseModuleCountDTO>> projectCountMap = projectModuleCountDTOList.stream().collect(Collectors.groupingBy(FunctionalCaseModuleCountDTO::getProjectId)); Map<String, List<FunctionalCaseModuleCountDTO>> projectCountMap = projectModuleCountDTOList.stream().collect(Collectors.groupingBy(FunctionalCaseModuleCountDTO::getProjectId));
@ -282,10 +322,45 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
/** /**
* 已关联接口用例模块树 * 已关联接口用例模块树
* *
* @param request
* @return
*/
public List<BaseTreeNode> getTree(TestPlanApiCaseTreeRequest request) {
switch (request.getTreeType()) {
case TreeTypeEnums.MODULE:
return getModuleTree(request.getTestPlanId());
case TreeTypeEnums.COLLECTION:
return getCollectionTree(request.getTestPlanId());
default:
return new ArrayList<>();
}
}
/**
* 已关联接口用例规划视图树
*
* @param testPlanId * @param testPlanId
* @return * @return
*/ */
public List<BaseTreeNode> getTree(String testPlanId) { private List<BaseTreeNode> getCollectionTree(String testPlanId) {
List<BaseTreeNode> returnList = new ArrayList<>();
TestPlanCollectionExample collectionExample = new TestPlanCollectionExample();
collectionExample.createCriteria().andTypeEqualTo(CaseType.API_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.API_CASE.getKey());
returnList.add(baseTreeNode);
});
return returnList;
}
/**
* 模块树
*
* @param testPlanId
* @return
*/
private List<BaseTreeNode> getModuleTree(String testPlanId) {
List<BaseTreeNode> returnList = new ArrayList<>(); List<BaseTreeNode> returnList = new ArrayList<>();
List<ProjectOptionDTO> rootIds = extTestPlanApiCaseMapper.selectRootIdByTestPlanId(testPlanId); List<ProjectOptionDTO> rootIds = extTestPlanApiCaseMapper.selectRootIdByTestPlanId(testPlanId);
Map<String, List<ProjectOptionDTO>> projectRootMap = rootIds.stream().collect(Collectors.groupingBy(ProjectOptionDTO::getName)); Map<String, List<ProjectOptionDTO>> projectRootMap = rootIds.stream().collect(Collectors.groupingBy(ProjectOptionDTO::getName));

View File

@ -1,9 +1,6 @@
package io.metersphere.plan.controller; package io.metersphere.plan.controller;
import io.metersphere.plan.dto.request.TestPlanApiCaseBatchRequest; import io.metersphere.plan.dto.request.*;
import io.metersphere.plan.dto.request.TestPlanApiCaseRequest;
import io.metersphere.plan.dto.request.TestPlanApiCaseUpdateRequest;
import io.metersphere.plan.dto.request.TestPlanDisassociationRequest;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest; import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.controller.handler.ResultHolder;
@ -25,7 +22,7 @@ public class TestPlanApiCaseControllerTests extends BaseTest {
public static final String API_CASE_PAGE = "/test-plan/api/case/page"; public static final String API_CASE_PAGE = "/test-plan/api/case/page";
public static final String API_CASE_TREE_COUNT = "/test-plan/api/case/module/count"; public static final String API_CASE_TREE_COUNT = "/test-plan/api/case/module/count";
public static final String API_CASE_TREE_MODULE_TREE = "/test-plan/api/case/tree/"; public static final String API_CASE_TREE_MODULE_TREE = "/test-plan/api/case/tree";
public static final String API_CASE_DISASSOCIATE = "/test-plan/api/case/disassociate"; public static final String API_CASE_DISASSOCIATE = "/test-plan/api/case/disassociate";
public static final String API_CASE_BATCH_DISASSOCIATE = "/test-plan/api/case/batch/disassociate"; public static final String API_CASE_BATCH_DISASSOCIATE = "/test-plan/api/case/batch/disassociate";
public static final String API_CASE_BATCH_UPDATE_EXECUTOR_URL = "/test-plan/api/case/batch/update/executor"; public static final String API_CASE_BATCH_UPDATE_EXECUTOR_URL = "/test-plan/api/case/batch/update/executor";
@ -54,27 +51,41 @@ public class TestPlanApiCaseControllerTests extends BaseTest {
@Test @Test
@Order(2) @Order(2)
public void testApiCaseCount() throws Exception { public void testApiCaseCount() throws Exception {
TestPlanApiCaseRequest request = new TestPlanApiCaseRequest(); TestPlanApiCaseModuleRequest request = new TestPlanApiCaseModuleRequest();
request.setTestPlanId("wxxx_1"); request.setTestPlanId("wxxx_1");
request.setProjectId("wxx_1234"); request.setProjectId("wxx_1234");
request.setProtocol("HTTP"); request.setProtocol("HTTP");
request.setCurrent(1); request.setCurrent(1);
request.setPageSize(10); request.setPageSize(10);
request.setTreeType("MODULE");
MvcResult mvcResult = this.requestPostWithOkAndReturn(API_CASE_TREE_COUNT, request); MvcResult mvcResult = this.requestPostWithOkAndReturn(API_CASE_TREE_COUNT, request);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
Assertions.assertNotNull(resultHolder); Assertions.assertNotNull(resultHolder);
request.setTreeType("COLLECTION");
this.requestPostWithOkAndReturn(API_CASE_TREE_COUNT, request);
} }
@Test @Test
@Order(3) @Order(3)
public void testApiCaseModuleTree() throws Exception { public void testApiCaseModuleTree() throws Exception {
MvcResult mvcResult = this.requestGetWithOkAndReturn(API_CASE_TREE_MODULE_TREE + "wxxx_1"); TestPlanApiCaseTreeRequest request = new TestPlanApiCaseTreeRequest();
request.setTestPlanId("wxxx_1");
request.setTreeType("MODULE");
this.requestPostWithOkAndReturn(API_CASE_TREE_MODULE_TREE, request);
request.setTestPlanId("wxxx_2");
MvcResult mvcResult = this.requestPostWithOkAndReturn(API_CASE_TREE_MODULE_TREE, request);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
Assertions.assertNotNull(resultHolder); Assertions.assertNotNull(resultHolder);
this.requestGetWithOkAndReturn(API_CASE_TREE_MODULE_TREE + "wxxx_2"); request.setTestPlanId("wxxx_2");
request.setTreeType("COLLECTION");
MvcResult mvcResult1 = this.requestPostWithOkAndReturn(API_CASE_TREE_MODULE_TREE, request);
String returnData1 = mvcResult1.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder1 = JSON.parseObject(returnData1, ResultHolder.class);
Assertions.assertNotNull(resultHolder1);
} }

View File

@ -34,10 +34,11 @@ VALUES
('wxxx_2', 'wxxx_1', 'wxxx_api_case_2', '1', NULL, NULL, 'admin', 1716370415311, 'admin', 2, 'wxxx_2', 1716370415311), ('wxxx_2', 'wxxx_1', 'wxxx_api_case_2', '1', NULL, NULL, 'admin', 1716370415311, 'admin', 2, 'wxxx_2', 1716370415311),
('wxxx_3', 'wxxx_2', 'wxxx_api_case_3', '1', NULL, NULL, 'admin', 1716370415311, 'admin', 2, 'wxxx_2', 1716370415311); ('wxxx_3', 'wxxx_2', 'wxxx_api_case_3', '1', NULL, NULL, 'admin', 1716370415311, 'admin', 2, 'wxxx_2', 1716370415311);
INSERT INTO `test_plan_collection`(`id`, `test_plan_id`, `name`, `type`, `environment_id`, `test_resource_pool_id`, `pos`, `create_user`, `create_time`) 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 VALUES
('wxxx_1', 'wxxx_1', 'wxxx_1', 'API', 'NONE', 'NONE', 1, 'admin', 1716370415311), ('wxxx_1', 'wxxx_1', 'wxxx_1', 'API', 'NONE', 'NONE', 1, 'admin', 1716370415311, 'NONE'),
('wxxx_2', 'wxxx_1', 'wxxx_2', 'API', '123', 'NONE', 2, 'admin', 1716370415311); ('wxxx_2', 'wxxx_1', 'wxxx_2', 'API', '123', 'NONE', 2, 'admin', 1716370415311, 'NONE'),
('wxxx_3', 'wxxx_2', 'wxxx_3', 'API', 'NONE', 'NONE', 3, 'admin', 1716370415311, 'wxxx_1');