refactor(测试计划): 优化测试计划关联接口的模块树

This commit is contained in:
wxg0103 2024-06-20 18:53:44 +08:00 committed by Craftsman
parent e79fc35e0c
commit 9ff9a3d25a
8 changed files with 175 additions and 61 deletions

View File

@ -27,4 +27,7 @@ public class BasePlanCaseBatchRequest extends TableBatchProcessDTO implements Se
@Schema(description = "计划集id")
private String collectionId;
@Schema(description = "项目Id")
private String projectId;
}

View File

@ -1,17 +1,23 @@
package io.metersphere.plan.dto.request;
import io.metersphere.api.dto.definition.ApiTestCasePageRequest;
import io.metersphere.system.dto.sdk.BasePageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
/**
* @author wx
*/
@Data
public class TestPlanApiCaseRequest extends ApiTestCasePageRequest {
public class TestPlanApiCaseRequest extends BasePageRequest {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "测试计划id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan.id.not_blank}")
@ -20,4 +26,26 @@ public class TestPlanApiCaseRequest extends ApiTestCasePageRequest {
@Schema(description = "计划集id")
private String collectionId;
@Schema(description = "接口pk")
@Size(max = 50, message = "{api_definition.id.length_range}")
private String apiDefinitionId;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@Size(max = 50, message = "{api_definition.project_id.length_range}")
private String projectId;
@Schema(description = "接口协议", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> protocols = new ArrayList<>();
@Schema(description = "模块ID")
private List<@NotBlank String> moduleIds;
@Schema(description = "版本fk")
@Size(max = 50, message = "{api_definition.version_id.length_range}")
private String versionId;
@Schema(description = "版本来源")
@Size(max = 50, message = "{api_definition.ref_id.length_range}")
private String refId;
}

View File

@ -1,15 +1,21 @@
package io.metersphere.plan.dto.request;
import io.metersphere.api.dto.scenario.ApiScenarioPageRequest;
import io.metersphere.system.dto.sdk.BasePageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.List;
/**
* @author wx
*/
@Data
public class TestPlanApiScenarioRequest extends ApiScenarioPageRequest {
@EqualsAndHashCode(callSuper = false)
public class TestPlanApiScenarioRequest extends BasePageRequest {
@Schema(description = "测试计划id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan.id.not_blank}")
@ -18,4 +24,29 @@ public class TestPlanApiScenarioRequest extends ApiScenarioPageRequest {
@Schema(description = "计划集id")
private String collectionId;
@Schema(description = "场景pk")
@Size(min = 1, max = 50, message = "{api_scenario_step.scenario_id.length_range}")
private String scenarioId;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@Size(max = 50, message = "{api_definition.project_id.length_range}")
private String projectId;
@Schema(description = "版本fk")
@Size(min = 1, max = 50, message = "{api_definition.version_id.length_range}")
private String versionId;
@Schema(description = "版本引用fk")
@Size(min = 1, max = 50, message = "{api_definition.ref_id.length_range}")
private String refId;
@Schema(description = "模块ID(根据模块树查询时要把当前节点以及子节点都放在这里。)")
private List<@NotBlank String> moduleIds;
@Schema(description = "删除状态(状态为 1 时为回收站数据)")
private Boolean deleted = false;
@Schema(description = "查询时排除的ID")
private List<String> excludeIds = new ArrayList<>();
}

View File

@ -60,13 +60,13 @@ public interface ExtTestPlanApiCaseMapper {
Long getMaxPosByCollectionId(String collectionId);
/**
* 获取计划下的功能用例集合
*
* @param planIds 测试计划ID集合
* @return 计划功能用例集合
*/
List<TestPlanApiCase> getPlanApiCaseByIds(@Param("planIds") List<String> planIds);
/**
* 获取计划下的功能用例集合
*
* @param planIds 测试计划ID集合
* @return 计划功能用例集合
*/
List<TestPlanApiCase> getPlanApiCaseByIds(@Param("planIds") List<String> planIds);
List<TestPlanApiCase> getApiCaseExecuteInfoByIds(@Param("ids") List<String> ids);

View File

@ -451,6 +451,9 @@
<if test="request.apiDefinitionId != null and request.apiDefinitionId!=''">
and atc.api_definition_id = #{request.apiDefinitionId}
</if>
<if test="request.projectId != null and request.projectId!=''">
and atc.project_id = #{request.projectId}
</if>
<if test="request.keyword != null and request.keyword !=''">
and (
atc.name like concat('%', #{request.keyword},'%')
@ -462,7 +465,14 @@
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and a.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
<choose>
<when test="nodeId.contains('_root')">
'root'
</when>
<otherwise>
#{nodeId}
</otherwise>
</choose>
</foreach>
</if>
<if test="request.collectionId != null and request.collectionId != ''">
@ -540,7 +550,8 @@
</sql>
<select id="countModuleIdByRequest" resultType="io.metersphere.functional.dto.FunctionalCaseModuleCountDTO">
SELECT a.module_id AS moduleId, count(atc.id) AS dataCount, atc.project_id AS projectId, project.name AS projectName
SELECT CASE WHEN a.module_id = 'root' THEN CONCAT(atc.project_id, '_', a.module_id) ELSE a.module_id END AS moduleId,
count(atc.id) AS dataCount, atc.project_id AS projectId, project.name AS projectName
FROM test_plan_api_case t
INNER JOIN api_test_case atc ON t.api_case_id = atc.id
INNER JOIN api_definition a ON atc.api_definition_id = a.id
@ -548,7 +559,7 @@
WHERE t.test_plan_id = #{request.testPlanId}
AND atc.deleted = #{deleted}
<include refid="queryApiCaseWhereCondition"/>
GROUP BY module_id
GROUP BY moduleId
</select>
<select id="selectIdByProjectIdAndTestPlanId" resultType="java.lang.String">
@ -577,7 +588,7 @@
LEFT JOIN project p ON atc.project_id = p.id
LEFT JOIN api_definition a on atc.api_definition_id = a.id
WHERE tpac.test_plan_id = #{testPlanId}
AND atc.deleted = false AND a.module_id = 'root'
AND atc.deleted = false
ORDER BY atc.pos
</select>

View File

@ -122,10 +122,20 @@
<if test="request.scenarioId != null and request.scenarioId != ''">
and api_scenario.id = #{request.scenarioId}
</if>
<if test="request.projectId != null and request.projectId != ''">
and api_scenario.project_id = #{request.projectId}
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and api_scenario.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
<choose>
<when test="nodeId.contains('_root')">
'root'
</when>
<otherwise>
#{nodeId}
</otherwise>
</choose>
</foreach>
</if>
<if test="request.collectionId != null and request.collectionId != ''">
@ -323,14 +333,15 @@
<select id="countModuleIdByRequest" resultType="io.metersphere.functional.dto.FunctionalCaseModuleCountDTO">
SELECT api_scenario.module_id AS moduleId, count(api_scenario.id) AS dataCount, api_scenario.project_id AS projectId, project.name AS projectName
SELECT CASE WHEN api_scenario.module_id = 'root' THEN CONCAT(api_scenario.project_id, '_', api_scenario.module_id) ELSE api_scenario.module_id END AS moduleId,
count(api_scenario.id) AS dataCount, api_scenario.project_id AS projectId, project.name AS projectName
FROM test_plan_api_scenario
INNER JOIN api_scenario on api_scenario.id = test_plan_api_scenario.api_scenario_id
INNER JOIN project ON api_scenario.project_id = project.id
WHERE test_plan_api_scenario.test_plan_id = #{request.testPlanId}
AND api_scenario.deleted = #{deleted}
<include refid="queryApiScenarioWhereCondition"/>
GROUP BY module_id
GROUP BY moduleId
</select>
<select id="caseCount"
@ -372,7 +383,7 @@
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'
AND api_scenario.deleted = false
ORDER BY api_scenario.pos
</select>

View File

@ -3,6 +3,7 @@ package io.metersphere.plan.service;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.ApiDefinitionDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCasePageRequest;
import io.metersphere.api.mapper.ApiReportMapper;
import io.metersphere.api.mapper.ApiTestCaseMapper;
import io.metersphere.api.mapper.ExtApiDefinitionModuleMapper;
@ -213,7 +214,9 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
if (CollectionUtils.isEmpty(request.getProtocols())) {
return new ArrayList<>();
}
return apiTestCaseService.page(request, false, isRepeat, request.getTestPlanId());
ApiTestCasePageRequest pageRequest = new ApiTestCasePageRequest();
BeanUtils.copyBean(pageRequest, request);
return apiTestCaseService.page(pageRequest, false, isRepeat, request.getTestPlanId());
}
@ -245,9 +248,8 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
private Map<String, String> getModuleName(List<TestPlanApiCasePageResponse> apiCaseList) {
List<String> moduleIds = apiCaseList.stream().map(TestPlanApiCasePageResponse::getModuleId).toList();
List<ApiDefinitionModule> modules = extApiDefinitionModuleMapper.getNameInfoByIds(moduleIds);
Map<String, String> moduleNameMap = modules.stream()
return modules.stream()
.collect(Collectors.toMap(ApiDefinitionModule::getId, ApiDefinitionModule::getName));
return moduleNameMap;
}
private void handleCaseAndEnv(List<TestPlanApiCasePageResponse> apiCaseList, Map<String, String> projectMap, Map<String, String> userMap, String testPlanId, Map<String, String> moduleNameMap) {
@ -363,7 +365,12 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
request.setModuleIds(null);
List<FunctionalCaseModuleCountDTO> projectModuleCountDTOList = extTestPlanApiCaseMapper.countModuleIdByRequest(request, false);
Map<String, List<FunctionalCaseModuleCountDTO>> projectCountMap = projectModuleCountDTOList.stream().collect(Collectors.groupingBy(FunctionalCaseModuleCountDTO::getProjectId));
Map<String, Long> projectModuleCountMap = new HashMap<>();
//projectModuleCountDTOList转新的map key 是moduleId value是数量 stream实现
Map<String, Long> projectModuleCountMap = projectModuleCountDTOList.stream()
.collect(Collectors.groupingBy(
FunctionalCaseModuleCountDTO::getModuleId,
Collectors.summingLong(FunctionalCaseModuleCountDTO::getDataCount)));
projectCountMap.forEach((projectId, moduleCountDTOList) -> {
List<ModuleCountDTO> moduleCountDTOS = new ArrayList<>();
for (FunctionalCaseModuleCountDTO functionalCaseModuleCountDTO : moduleCountDTOList) {
@ -444,27 +451,37 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
*/
private List<BaseTreeNode> getModuleTree(String testPlanId) {
List<BaseTreeNode> returnList = new ArrayList<>();
List<ProjectOptionDTO> rootIds = extTestPlanApiCaseMapper.selectRootIdByTestPlanId(testPlanId);
Map<String, List<ProjectOptionDTO>> projectRootMap = rootIds.stream().collect(Collectors.groupingBy(ProjectOptionDTO::getName));
List<ProjectOptionDTO> moduleLists = extTestPlanApiCaseMapper.selectRootIdByTestPlanId(testPlanId);
// 获取所有的项目id
List<String> projectIds = moduleLists.stream().map(ProjectOptionDTO::getName).distinct().toList();
// moduleLists中id=root的数据
List<ProjectOptionDTO> rootModuleList = moduleLists.stream().filter(item -> StringUtils.equals(item.getId(), ModuleConstants.DEFAULT_NODE_ID)).toList();
Map<String, List<ProjectOptionDTO>> projectRootMap = rootModuleList.stream().collect(Collectors.groupingBy(ProjectOptionDTO::getName));
List<ApiCaseModuleDTO> apiCaseModuleIds = extTestPlanApiCaseMapper.selectBaseByProjectIdAndTestPlanId(testPlanId);
Map<String, List<ApiCaseModuleDTO>> projectModuleMap = apiCaseModuleIds.stream().collect(Collectors.groupingBy(ApiCaseModuleDTO::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 = apiDefinitionModuleService.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());
projectIds.forEach(projectId -> {
// 如果projectRootMap中没有projectId说明该项目没有根节点 不需要创建
// projectModuleMap中没有projectId说明该项目没有模块 不需要创建
// 如果都有 需要创建完整的数结构
boolean needCreateRoot = MapUtils.isNotEmpty(projectRootMap) && projectRootMap.containsKey(projectId);
boolean needCreateModule = MapUtils.isNotEmpty(projectModuleMap) && projectModuleMap.containsKey(projectId);
// 项目名称是
String projectName = needCreateModule ? projectModuleMap.get(projectId).getFirst().getProjectName() : projectRootMap.get(projectId).getFirst().getProjectName();
// 构建项目那一层级
BaseTreeNode projectNode = new BaseTreeNode(projectId, projectName, "PROJECT");
returnList.add(projectNode);
List<String> projectModuleIds = moduleList.stream().map(ApiCaseModuleDTO::getId).toList();
List<BaseTreeNode> nodeByNodeIds = apiDefinitionModuleService.getNodeByNodeIds(projectModuleIds);
boolean haveVirtualRootNode = CollectionUtils.isEmpty(projectRootMap.get(projectId));
List<BaseTreeNode> baseTreeNodes = apiDefinitionModuleService.buildTreeAndCountResource(nodeByNodeIds, !haveVirtualRootNode, Translator.get("functional_case.module.default.name"));
List<BaseTreeNode> nodeByNodeIds = new ArrayList<>();
if (needCreateModule) {
List<String> projectModuleIds = projectModuleMap.get(projectId).stream().map(ApiCaseModuleDTO::getId).toList();
nodeByNodeIds = apiDefinitionModuleService.getNodeByNodeIds(projectModuleIds);
}
List<BaseTreeNode> baseTreeNodes = apiDefinitionModuleService.buildTreeAndCountResource(nodeByNodeIds, needCreateRoot, Translator.get("api_unplanned_request"));
for (BaseTreeNode baseTreeNode : baseTreeNodes) {
if (StringUtils.equals(baseTreeNode.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
// 默认拼项目id
baseTreeNode.setId(projectId + "_" + ModuleConstants.DEFAULT_NODE_ID);
}
projectNode.addChild(baseTreeNode);
}
});

View File

@ -3,6 +3,7 @@ package io.metersphere.plan.service;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.scenario.ApiScenarioDTO;
import io.metersphere.api.dto.scenario.ApiScenarioDetail;
import io.metersphere.api.dto.scenario.ApiScenarioPageRequest;
import io.metersphere.api.mapper.ApiScenarioMapper;
import io.metersphere.api.mapper.ApiScenarioModuleMapper;
import io.metersphere.api.mapper.ApiScenarioReportMapper;
@ -141,7 +142,7 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
@Override
public long copyResource(String originalTestPlanId, String newTestPlanId, Map<String, String> oldCollectionIdToNewCollectionId, String operator, long operatorTime) {
List<TestPlanApiScenario> copyList = new ArrayList<>();
String defaultCollectionId = extTestPlanCollectionMapper.selectDefaultCollectionId(newTestPlanId,CaseType.SCENARIO_CASE.getKey());
String defaultCollectionId = extTestPlanCollectionMapper.selectDefaultCollectionId(newTestPlanId, CaseType.SCENARIO_CASE.getKey());
extTestPlanApiScenarioMapper.selectByTestPlanIdAndNotDeleted(originalTestPlanId).forEach(originalCase -> {
TestPlanApiScenario newCase = new TestPlanApiScenario();
BeanUtils.copyBean(newCase, originalCase);
@ -271,8 +272,9 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
* @return
*/
public List<ApiScenarioDTO> getApiScenarioPage(TestPlanApiScenarioRequest request, boolean isRepeat) {
List<ApiScenarioDTO> scenarioPage = apiScenarioService.getScenarioPage(request, isRepeat, request.getTestPlanId());
return scenarioPage;
ApiScenarioPageRequest apiScenarioPageRequest = new ApiScenarioPageRequest();
BeanUtils.copyBean(apiScenarioPageRequest, request);
return apiScenarioService.getScenarioPage(apiScenarioPageRequest, isRepeat, request.getTestPlanId());
}
public TestPlanOperationResponse sortNode(ResourceSortRequest request, LogInsertModule logInsertModule) {
@ -497,7 +499,11 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
request.setModuleIds(null);
List<FunctionalCaseModuleCountDTO> projectModuleCountDTOList = extTestPlanApiScenarioMapper.countModuleIdByRequest(request, false);
Map<String, List<FunctionalCaseModuleCountDTO>> projectCountMap = projectModuleCountDTOList.stream().collect(Collectors.groupingBy(FunctionalCaseModuleCountDTO::getProjectId));
Map<String, Long> projectModuleCountMap = new HashMap<>();
Map<String, Long> projectModuleCountMap = projectModuleCountDTOList.stream()
.collect(Collectors.groupingBy(
FunctionalCaseModuleCountDTO::getModuleId,
Collectors.summingLong(FunctionalCaseModuleCountDTO::getDataCount)));
projectCountMap.forEach((projectId, moduleCountDTOList) -> {
List<ModuleCountDTO> moduleCountDTOS = new ArrayList<>();
for (FunctionalCaseModuleCountDTO functionalCaseModuleCountDTO : moduleCountDTOList) {
@ -571,27 +577,34 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
*/
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<ProjectOptionDTO> moduleLists = extTestPlanApiScenarioMapper.selectRootIdByTestPlanId(testPlanId);
// 获取所有的项目id
List<String> projectIds = moduleLists.stream().map(ProjectOptionDTO::getName).distinct().toList();
// moduleLists中id=root的数据
List<ProjectOptionDTO> rootModuleList = moduleLists.stream().filter(item -> StringUtils.equals(item.getId(), ModuleConstants.DEFAULT_NODE_ID)).toList();
Map<String, List<ProjectOptionDTO>> projectRootMap = rootModuleList.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());
projectIds.forEach(projectId -> {
boolean needCreateRoot = MapUtils.isNotEmpty(projectRootMap) && projectRootMap.containsKey(projectId);
boolean needCreateModule = MapUtils.isNotEmpty(projectModuleMap) && projectModuleMap.containsKey(projectId);
// 项目名称是
String projectName = needCreateModule ? projectModuleMap.get(projectId).getFirst().getProjectName() : projectRootMap.get(projectId).getFirst().getProjectName();
// 构建项目那一层级
BaseTreeNode projectNode = new BaseTreeNode(projectId, projectName, "PROJECT");
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"));
List<BaseTreeNode> nodeByNodeIds = new ArrayList<>();
if (needCreateModule) {
List<String> projectModuleIds = projectModuleMap.get(projectId).stream().map(ApiScenarioModuleDTO::getId).toList();
nodeByNodeIds = apiScenarioModuleService.getNodeByNodeIds(projectModuleIds);
}
List<BaseTreeNode> baseTreeNodes = apiScenarioModuleService.buildTreeAndCountResource(nodeByNodeIds, needCreateRoot, Translator.get("api_unplanned_scenario"));
for (BaseTreeNode baseTreeNode : baseTreeNodes) {
if (StringUtils.equals(baseTreeNode.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
// 默认拼项目id
baseTreeNode.setId(projectId + "_" + ModuleConstants.DEFAULT_NODE_ID);
}
projectNode.addChild(baseTreeNode);
}
});