feat(用例评审): 新增评审后的功能用例的模块树和模块统计接口

This commit is contained in:
guoyuqi 2024-01-09 15:53:03 +08:00 committed by 刘瑞斌
parent ecf3ca512b
commit fc038ce5a1
15 changed files with 218 additions and 10 deletions

View File

@ -9,6 +9,7 @@ import io.metersphere.functional.request.*;
import io.metersphere.functional.service.CaseReviewFunctionalCaseService;
import io.metersphere.functional.service.CaseReviewLogService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.security.CheckOwner;
@ -23,6 +24,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @author wx
@ -38,6 +40,7 @@ public class CaseReviewFunctionalCaseController {
@GetMapping("/get-ids/{reviewId}")
@Operation(summary = "用例管理-功能用例-评审列表-评审详情-获取已关联用例id集合(关联用例弹窗前调用)")
@RequiresPermissions(PermissionConstants.CASE_REVIEW_RELEVANCE)
@CheckOwner(resourceId = "#reviewId", resourceType = "case_review")
public List<String> getCaseIds(@PathVariable String reviewId) {
return caseReviewFunctionalCaseService.getCaseIdsByReviewId(reviewId);
@ -56,6 +59,27 @@ public class CaseReviewFunctionalCaseController {
}
@GetMapping("/tree/{projectId}/{reviewId}")
@Operation(summary = "用例管理-功能用例-评审列表-评审详情-已关联用例列表模块树")
@RequiresPermissions(PermissionConstants.CASE_REVIEW_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public List<BaseTreeNode> getTree(@PathVariable String projectId, @PathVariable String reviewId) {
return caseReviewFunctionalCaseService.getTree(projectId, reviewId);
}
@PostMapping("/module/count")
@Operation(summary = "用例管理-功能用例-评审列表-评审详情-已关联用例统计模块数量")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public Map<String, Long> moduleCount(@Validated @RequestBody ReviewFunctionalCasePageRequest request) {
return caseReviewFunctionalCaseService.moduleCount(request, false);
}
@PostMapping("/batch/disassociate")
@Operation(summary = "用例管理-功能用例-评审列表-评审详情-列表-批量取消关联用例")
@Log(type = OperationLogType.DISASSOCIATE, expression = "#msClass.batchDisassociateCaseLog(#request)", msClass = CaseReviewLogService.class)

View File

@ -12,7 +12,6 @@ import java.util.List;
@Data
public class FunctionalCasePageDTO extends FunctionalCase {
@Schema(description = "自定义字段集合")
private List<FunctionalCaseCustomFieldDTO> customFields;
}

View File

@ -6,6 +6,7 @@ import io.metersphere.functional.dto.ReviewFunctionalCaseDTO;
import io.metersphere.functional.request.BaseReviewCaseBatchRequest;
import io.metersphere.functional.request.FunctionalCaseReviewListRequest;
import io.metersphere.functional.request.ReviewFunctionalCasePageRequest;
import io.metersphere.project.dto.ModuleCountDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -40,4 +41,8 @@ public interface ExtCaseReviewFunctionalCaseMapper {
List<CaseReviewFunctionalCase> getListExcludes(@Param("reviewIds")List<String> reviewIds, @Param("caseIds") List<String> caseIds, @Param("deleted") boolean deleted);
List<CaseReviewFunctionalCase> getCaseIdsByIds(@Param("ids") List<String> ids);
List<ModuleCountDTO> countModuleIdByRequest(ReviewFunctionalCasePageRequest request, boolean deleted);
long caseCount(ReviewFunctionalCasePageRequest request, boolean deleted);
}

View File

@ -44,6 +44,7 @@
WHERE
review_id = #{request.reviewId}
AND functional_case.deleted = #{deleted}
AND functional_case.project_id = #{request.projectId}
<if test="userId != null and userId != ''">
AND crfc.case_id in (select case_id from case_review_functional_case_user crfcu where crfcu.review_id = #{request.reviewId} and crfcu.user_id = #{userId})
</if>
@ -394,4 +395,43 @@
#{id}
</foreach>
</select>
<select id="countModuleIdByRequest" resultType="io.metersphere.project.dto.ModuleCountDTO">
SELECT functional_case.module_id AS moduleId, count(functional_case.id) AS dataCount
FROM case_review_functional_case crfc LEFT JOIN functional_case ON crfc.case_id = functional_case.id
WHERE crfc.review_id = #{request.reviewId}
AND functional_case.deleted = #{deleted}
AND functional_case.project_id = #{request.projectId}
<choose>
<when test='request.searchMode == "AND"'>
AND <include refid="queryWhereCondition"/>
</when>
<when test='request.searchMode == "OR"'>
and (
<include refid="queryWhereCondition"/>
)
</when>
</choose>
GROUP BY module_id
</select>
<select id="caseCount"
resultType="java.lang.Long">
SELECT count(functional_case.id)
FROM case_review_functional_case crfc LEFT JOIN functional_case ON crfc.case_id = functional_case.id
WHERE crfc.review_id = #{request.reviewId}
AND functional_case.deleted = #{deleted}
AND functional_case.project_id = #{request.projectId}
<choose>
<when test='request.searchMode == "AND"'>
AND <include refid="queryWhereCondition"/>
</when>
<when test='request.searchMode == "OR"'>
and (
<include refid="queryWhereCondition"/>
)
</when>
</choose>
</select>
</mapper>

View File

@ -2,6 +2,10 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.functional.mapper.ExtCaseReviewMapper">
<resultMap id="BaseResultMapDTO" type="io.metersphere.functional.dto.CaseReviewDTO">
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
</resultMap>
<select id="checkCaseByModuleIds" resultType="io.metersphere.functional.domain.CaseReview">
SELECT
id, name, module_id, create_user
@ -265,7 +269,7 @@
</include>
</if>
<if test="${condition}.createTime != null">
<if test="${condition}.endTime != null">
case_review.end_time
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
<property name="object" value="${condition}.endTime"/>

View File

@ -50,7 +50,7 @@ public interface ExtFunctionalCaseMapper {
void recoverCaseByRefIds(@Param("refIds") List<String> refIds, @Param("userId") String userId, @Param("time") long time);
List<ModuleCountDTO> countModuleIdByKeywordAndFileType(@Param("request") FunctionalCasePageRequest request, @Param("deleted") boolean deleted);
List<ModuleCountDTO> countModuleIdByRequest(@Param("request") FunctionalCasePageRequest request, @Param("deleted") boolean deleted);
long caseCount(@Param("request") FunctionalCasePageRequest request, @Param("deleted") boolean deleted);

View File

@ -26,6 +26,10 @@
<result column="delete_time" jdbcType="BIGINT" property="deleteTime" />
</resultMap>
<resultMap id="BaseResultMapDTO" type="io.metersphere.functional.dto.FunctionalCasePageDTO">
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler" />
</resultMap>
<select id="getPos" resultType="java.lang.Long">
SELECT
pos
@ -90,7 +94,7 @@
</select>
<select id="list" resultType="io.metersphere.functional.dto.FunctionalCasePageDTO">
<select id="list" resultMap="BaseResultMapDTO">
SELECT
id,
num,
@ -570,7 +574,7 @@
and project_id = #{functionalCase.projectId}
</update>
<select id="countModuleIdByKeywordAndFileType" resultType="io.metersphere.project.dto.ModuleCountDTO">
<select id="countModuleIdByRequest" resultType="io.metersphere.project.dto.ModuleCountDTO">
SELECT module_id AS moduleId, count(id) AS dataCount
FROM functional_case
WHERE deleted = #{deleted}

View File

@ -11,6 +11,8 @@ import java.util.List;
public interface ExtFunctionalCaseModuleMapper {
List<BaseTreeNode> selectBaseByProjectId(@Param("projectId")String projectId);
List<BaseTreeNode> selectBaseByProjectIdAndReviewId(@Param("projectId")String projectId, @Param("reviewId")String reviewId);
List<BaseTreeNode> selectBaseByIds(@Param("ids") List<String> ids);
List<String> selectChildrenIdsByParentIds(@Param("ids") List<String> deleteIds);
@ -28,4 +30,5 @@ public interface ExtFunctionalCaseModuleMapper {
List<BaseTreeNode> selectApiCaseModuleByRequest(@Param("request") AssociateCaseModuleRequest request);
List<BaseTreeNode> selectIdAndParentIdByProjectIdAndReviewId(@Param("projectId")String projectId, @Param("reviewId")String reviewId);
}

View File

@ -7,6 +7,14 @@
WHERE project_id = #{projectId}
ORDER BY pos
</select>
<select id="selectBaseByProjectIdAndReviewId" resultType="io.metersphere.system.dto.sdk.BaseTreeNode">
SELECT fcm.id, fcm.name, fcm.parent_id AS parentId, 'module' AS type
FROM functional_case_module fcm
WHERE fcm.project_id = #{projectId}
AND fcm.id IN
(SELECT fc.module_id FROM functional_case fc LEFT JOIN case_review_functional_case crfc ON crfc.case_id = fc.id WHERE crfc.review_id = #{reviewId} AND fc.deleted = false)
ORDER BY pos
</select>
<select id="selectBaseByIds" resultType="io.metersphere.system.dto.sdk.BaseTreeNode">
SELECT id, name, parent_id AS parentId, 'module' AS type
FROM functional_case_module
@ -74,6 +82,14 @@
ORDER BY pos
</select>
<select id="selectIdAndParentIdByProjectIdAndReviewId" resultType="io.metersphere.system.dto.sdk.BaseTreeNode">
SELECT fcm.id, fcm.parent_id AS parentId
FROM functional_case_module fcm
WHERE fcm.project_id = #{projectId}
AND fcm.id IN
(SELECT fc.module_id FROM functional_case fc LEFT JOIN case_review_functional_case crfc ON crfc.case_id = fc.id WHERE crfc.review_id = #{reviewId} AND fc.deleted = false)
</select>
<sql id="module_request">
<where>
<if test="request.projectId != null and request.projectId != ''">

View File

@ -44,4 +44,6 @@ public class FunctionalCasePageRequest extends BasePageRequest implements Serial
@Schema(description = "排除ids")
private List<String> excludeIds;
}

View File

@ -21,6 +21,9 @@ public class ReviewFunctionalCasePageRequest extends BasePageRequest implements
@NotBlank(message = "{case_review_user.review_id.not_blank}")
private String reviewId;
@Schema(description = "用例所在项目ID(默认当前项目)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{case_review_user.project_id.not_blank}")
private String projectId;
@Schema(description = "是否只看我的", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean viewFlag;

View File

@ -12,8 +12,10 @@ import io.metersphere.functional.request.*;
import io.metersphere.project.domain.ProjectApplication;
import io.metersphere.project.domain.ProjectApplicationExample;
import io.metersphere.project.domain.ProjectVersion;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.mapper.ProjectApplicationMapper;
import io.metersphere.project.service.ModuleTreeService;
import io.metersphere.provider.BaseCaseProvider;
import io.metersphere.sdk.constants.ProjectApplicationType;
import io.metersphere.sdk.exception.MSException;
@ -44,7 +46,7 @@ import java.util.stream.Collectors;
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class CaseReviewFunctionalCaseService {
public class CaseReviewFunctionalCaseService extends ModuleTreeService {
@Resource
@ -75,6 +77,8 @@ public class CaseReviewFunctionalCaseService {
private ReviewSendNoticeService reviewSendNoticeService;
private static final String CASE_MODULE_COUNT_ALL = "all";
/**
* 通过评审id获取关联的用例id集合
*
@ -526,4 +530,47 @@ public class CaseReviewFunctionalCaseService {
caseReviewFunctionalCaseUserMapper.batchInsert(list);
}
}
public List<BaseTreeNode> getTree(String projectId, String reviewId) {
List<BaseTreeNode> functionalModuleList = extFunctionalCaseModuleMapper.selectBaseByProjectIdAndReviewId(projectId, reviewId);
return super.buildTreeAndCountResource(functionalModuleList, true, Translator.get("default.module"));
}
public Map<String, Long> moduleCount(ReviewFunctionalCasePageRequest request, boolean deleted) {
//查出每个模块节点下的资源数量 不需要按照模块进行筛选
request.setModuleIds(null);
List<ModuleCountDTO> moduleCountDTOList = extCaseReviewFunctionalCaseMapper.countModuleIdByRequest(request, deleted);
Map<String, Long> moduleCountMap = getModuleCountMap(request.getProjectId(), request.getReviewId(), moduleCountDTOList);
//查出全部用例数量
long allCount = extCaseReviewFunctionalCaseMapper.caseCount(request, deleted);
moduleCountMap.put(CASE_MODULE_COUNT_ALL, allCount);
return moduleCountMap;
}
/**
* 查找当前项目下模块每个节点对应的资源统计
*
*/
public Map<String, Long> getModuleCountMap(String projectId, String reviewId, List<ModuleCountDTO> moduleCountDTOList) {
//构建模块树并计算每个节点下的所有数量包含子节点
List<BaseTreeNode> treeNodeList = this.getTreeOnlyIdsAndResourceCount(projectId,reviewId, moduleCountDTOList);
//通过广度遍历的方式构建返回值
return super.getIdCountMapByBreadth(treeNodeList);
}
public List<BaseTreeNode> getTreeOnlyIdsAndResourceCount(String projectId, String reviewId, List<ModuleCountDTO> moduleCountDTOList) {
//节点内容只有Id和parentId
List<BaseTreeNode> fileModuleList = extFunctionalCaseModuleMapper.selectIdAndParentIdByProjectIdAndReviewId(projectId, reviewId);
return super.buildTreeAndCountResource(fileModuleList, moduleCountDTOList, true, Translator.get("default.module"));
}
@Override
public void updatePos(String id, long pos) {
}
@Override
public void refreshPos(String parentId) {
}
}

View File

@ -65,8 +65,8 @@ public class FunctionalCaseModuleService extends ModuleTreeService {
private OperationLogService operationLogService;
public List<BaseTreeNode> getTree(String projectId) {
List<BaseTreeNode> fileModuleList = extFunctionalCaseModuleMapper.selectBaseByProjectId(projectId);
return super.buildTreeAndCountResource(fileModuleList, true, Translator.get("default.module"));
List<BaseTreeNode> functionalModuleList = extFunctionalCaseModuleMapper.selectBaseByProjectId(projectId);
return super.buildTreeAndCountResource(functionalModuleList, true, Translator.get("default.module"));
}
public String add(FunctionalCaseModuleCreateRequest request, String userId) {

View File

@ -674,7 +674,7 @@ public class FunctionalCaseService {
public Map<String, Long> moduleCount(FunctionalCasePageRequest request, boolean delete) {
//查出每个模块节点下的资源数量 不需要按照模块进行筛选
request.setModuleIds(null);
List<ModuleCountDTO> moduleCountDTOList = extFunctionalCaseMapper.countModuleIdByKeywordAndFileType(request, delete);
List<ModuleCountDTO> moduleCountDTOList = extFunctionalCaseMapper.countModuleIdByRequest(request, delete);
Map<String, Long> moduleCountMap = functionalCaseModuleService.getModuleCountMap(request.getProjectId(), moduleCountDTOList);
//查出全部用例数量
long allCount = extFunctionalCaseMapper.caseCount(request, delete);

View File

@ -5,27 +5,35 @@ import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
import io.metersphere.functional.domain.CaseReviewFunctionalCase;
import io.metersphere.functional.domain.CaseReviewFunctionalCaseExample;
import io.metersphere.functional.domain.CaseReviewFunctionalCaseUser;
import io.metersphere.functional.dto.ReviewFunctionalCaseDTO;
import io.metersphere.functional.mapper.CaseReviewFunctionalCaseMapper;
import io.metersphere.functional.mapper.CaseReviewFunctionalCaseUserMapper;
import io.metersphere.functional.request.*;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.dto.sdk.BaseCondition;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.LinkedMultiValueMap;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ -44,6 +52,9 @@ public class CaseReviewFunctionalCaseControllerTests extends BaseTest {
public static final String BATCH_EDIT_REVIEWERS = "/case/review/detail/batch/edit/reviewers";
public static final String REVIEW_FUNCTIONAL_CASE_BATCH_REVIEW = "/case/review/detail/batch/review";
public static final String URL_MODULE_TREE = "/case/review/detail/tree/";
public static final String REVIEW_FUNCTIONAL_CASE_MODULE_COUNT= "/case/review/detail/module/count";
@Resource
private CaseReviewFunctionalCaseMapper caseReviewFunctionalCaseMapper;
@ -95,12 +106,33 @@ public class CaseReviewFunctionalCaseControllerTests extends BaseTest {
}}));
request.setCombine(map);
request.setViewFlag(false);
request.setProjectId("wx_test_project");
this.requestPostWithOkAndReturn(REVIEW_CASE_PAGE, request);
request.setSort(new HashMap<>() {{
put("createTime", "desc");
}});
this.requestPostWithOkAndReturn(REVIEW_CASE_PAGE, request);
MvcResult mvcResultPage = this.requestPostWithOkAndReturn(REVIEW_CASE_PAGE, request);
Pager<List<ReviewFunctionalCaseDTO>> tableData = JSON.parseObject(JSON.toJSONString(
JSON.parseObject(mvcResultPage.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData()),
Pager.class);
MvcResult moduleCountMvcResult = this.requestPostWithOkAndReturn(REVIEW_FUNCTIONAL_CASE_MODULE_COUNT, request);
Map<String, Integer> moduleCount = JSON.parseObject(JSON.toJSONString(
JSON.parseObject(moduleCountMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData()),
Map.class);
//如果没有数据则返回的模块节点也不应该有数据
boolean moduleHaveResource = false;
for (int countByModuleId : moduleCount.values()) {
if (countByModuleId > 0) {
moduleHaveResource = true;
break;
}
}
if (tableData.getTotal() > 0) {
Assertions.assertTrue(moduleHaveResource);
}
Assertions.assertTrue(moduleCount.containsKey("all"));
}
@ -299,4 +331,33 @@ public class CaseReviewFunctionalCaseControllerTests extends BaseTest {
request.setSelectIds(List.of("wx_test_10"));
this.requestPostWithOkAndReturn(BATCH_EDIT_REVIEWERS, request);
}
@Test
@Order(10)
public void emptyDataTest() throws Exception {
//空数据下检查模块树
List<BaseTreeNode> treeNodes = this.getCaseReviewModuleTreeNode("wx_test_project","wx_review_id_1");
//检查有没有默认节点
boolean hasNode = false;
for (BaseTreeNode baseTreeNode : treeNodes) {
if (org.testcontainers.shaded.org.apache.commons.lang3.StringUtils.equals(baseTreeNode.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
hasNode = true;
}
Assertions.assertNotNull(baseTreeNode.getParentId());
}
Assertions.assertTrue(hasNode);
}
private List<BaseTreeNode> getCaseReviewModuleTreeNode(String projectId, String reviewId) throws Exception {
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(URL_MODULE_TREE+"/"+projectId+"/"+reviewId).header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.header(SessionConstants.CURRENT_PROJECT, projectId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn();
String returnData = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
return JSON.parseArray(JSON.toJSONString(resultHolder.getData()), BaseTreeNode.class);
}
}