Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(用例管理): 增加脑图点击模块获取用例接口 #30639

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -3,6 +3,9 @@ SET SESSION innodb_lock_wait_timeout = 7200;

ALTER TABLE user_key MODIFY COLUMN description VARCHAR(1000);

CREATE INDEX idx_scene ON custom_field (scene);
CREATE INDEX idx_internal ON custom_field (internal);

-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;

Expand Down
Expand Up @@ -2,7 +2,7 @@

import com.alibaba.excel.util.StringUtils;
import io.metersphere.functional.dto.FunctionalMinderTreeDTO;
import io.metersphere.functional.request.FunctionalCasePageRequest;
import io.metersphere.functional.request.FunctionalCaseMindRequest;
import io.metersphere.functional.request.MinderReviewFunctionalCasePageRequest;
import io.metersphere.functional.service.FunctionalCaseMinderService;
import io.metersphere.sdk.constants.PermissionConstants;
Expand All @@ -18,6 +18,8 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* @author guoyuqi
*/
Expand All @@ -30,13 +32,15 @@ public class FunctionalCaseMinderController {
private FunctionalCaseMinderService functionalCaseMinderService;

@PostMapping("/list")
@Operation(summary = "用例管理-功能用例-脑图用例列表查询")
@Operation(summary = "用例管理-功能用例-脑图用例跟根据模块ID查询列表")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_MINDER)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public FunctionalMinderTreeDTO getFunctionalCaseMinderTree(@Validated @RequestBody FunctionalCasePageRequest request) {
return functionalCaseMinderService.getFunctionalCasePage(request, false);
@CheckOwner(resourceId = "#projectId()", resourceType = "project")
public List<FunctionalMinderTreeDTO> getFunctionalCaseMinderTree(@Validated @RequestBody FunctionalCaseMindRequest request) {
return functionalCaseMinderService.getMindFunctionalCase(request, false);
}



@PostMapping("/review/list")
@Operation(summary = "用例管理-用例评审-脑图用例列表查询")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_MINDER)
Expand Down
@@ -0,0 +1,61 @@
package io.metersphere.functional.dto;

import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class FunctionalCaseMindDTO {

@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.id.not_blank}", groups = {Updated.class})
@Size(min = 1, max = 50, message = "{functional_case.id.length_range}", groups = {Created.class, Updated.class})
private String id;

@Schema(description = "模块ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.module_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{functional_case.module_id.length_range}", groups = {Created.class, Updated.class})
private String moduleId;

@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.project_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{functional_case.project_id.length_range}", groups = {Created.class, Updated.class})
private String projectId;

@Schema(description = "模板ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.template_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{functional_case.template_id.length_range}", groups = {Created.class, Updated.class})
private String templateId;

@Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.name.not_blank}", groups = {Created.class})
@Size(min = 1, max = 255, message = "{functional_case.name.length_range}", groups = {Created.class, Updated.class})
private String name;

@Schema(description = "评审状态:未评审/评审中/通过/不通过/重新提审", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.review_status.not_blank}", groups = {Created.class})
@Size(min = 1, max = 64, message = "{functional_case.review_status.length_range}", groups = {Created.class, Updated.class})
private String reviewStatus;

@Schema(description = "用例等级")
private String priority;

@Schema(description = "用例步骤(JSON),step_model 为 Step 时启用")
private byte[] steps;

@Schema(description = "预期结果,step_model 为 Text 时启用")
private byte[] expectedResult;

@Schema(description = "前置条件")
private byte[] prerequisite;

@Schema(description = "备注")
private byte[] description;


}
Expand Up @@ -3,9 +3,11 @@
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.dto.BaseFunctionalCaseBatchDTO;
import io.metersphere.functional.dto.FunctionalCaseMindDTO;
import io.metersphere.functional.dto.FunctionalCasePageDTO;
import io.metersphere.functional.dto.FunctionalCaseVersionDTO;
import io.metersphere.functional.request.FunctionalCaseBatchMoveRequest;
import io.metersphere.functional.request.FunctionalCaseMindRequest;
import io.metersphere.functional.request.FunctionalCasePageRequest;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.request.AssociateOtherCaseRequest;
Expand Down Expand Up @@ -63,18 +65,25 @@ public interface ExtFunctionalCaseMapper {

/**
* 获取缺陷未关联的功能用例列表
*
* @param request provider参数
* @param deleted 是否删除状态
* @param sort 排序
* @param sort 排序
* @return 通用的列表Case集合
*/
List<TestCaseProviderDTO> listUnRelatedCaseWithBug(@Param("request") TestCasePageProviderRequest request, @Param("deleted") boolean deleted, @Param("sort") String sort);

/**
* 根据关联条件获取关联的用例ID
*
* @param request 关联参数
* @param deleted 是否删除状态
* @return 关联的用例ID集合
*/
List<String> getSelectIdsByAssociateParam(@Param("request")AssociateOtherCaseRequest request, @Param("deleted") boolean deleted);
List<String> getSelectIdsByAssociateParam(@Param("request") AssociateOtherCaseRequest request, @Param("deleted") boolean deleted);

/**
* 根据模块ID获取脑图展示数据
*/
List<FunctionalCaseMindDTO> getMinderCaseList(@Param("request") FunctionalCaseMindRequest request, @Param("deleted") boolean deleted);
}
Expand Up @@ -779,4 +779,26 @@
</foreach>
</if>
</sql>

<select id="getMinderCaseList" resultType="io.metersphere.functional.dto.FunctionalCaseMindDTO">
SELECT
fc.id, fc.name, fc.project_id, fc.module_id, fc.template_id, fc.review_status,
fcb.steps, fcb.expected_result, fcb.prerequisite, fcb.description, fccf.value as priority
FROM
functional_case fc
LEFT JOIN functional_case_blob fcb ON fcb.id = fc.id
LEFT JOIN functional_case_custom_field fccf ON fccf.case_id = fc.id
LEFT JOIN custom_field cf ON cf.id = fccf.field_id
WHERE
fc.deleted = false
AND
fc.project_Id = #{request.projectId}
AND
fc.module_id = #{request.moduleId}
AND
cf.name = 'functional_priority'
AND cf.scene = 'FUNCTIONAL'
AND cf.internal= true
</select>

</mapper>
@@ -0,0 +1,16 @@
package io.metersphere.functional.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class FunctionalCaseMindRequest {
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.project_id.not_blank}")
private String projectId;

@Schema(description = "模块ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{file_module.id.not_blank}")
private String moduleId;
}
Expand Up @@ -3,20 +3,22 @@
import io.metersphere.functional.constants.CaseReviewPassRule;
import io.metersphere.functional.constants.MinderLabel;
import io.metersphere.functional.domain.CaseReview;
import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.dto.*;
import io.metersphere.functional.mapper.CaseReviewMapper;
import io.metersphere.functional.request.FunctionalCasePageRequest;
import io.metersphere.functional.mapper.ExtFunctionalCaseMapper;
import io.metersphere.functional.request.FunctionalCaseMindRequest;
import io.metersphere.functional.request.MinderReviewFunctionalCasePageRequest;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand All @@ -39,30 +41,71 @@ public class FunctionalCaseMinderService {
private CaseReviewFunctionalCaseService caseReviewFunctionalCaseService;
@Resource
private CaseReviewMapper caseReviewMapper;
@Resource
private ExtFunctionalCaseMapper extFunctionalCaseMapper;


/**
* 功能用例-脑图用例列表查询
*
* @param request FunctionalCasePageRequest
* @param deleted 用例是否删除
* @return FunctionalMinderTreeDTO
*/
public FunctionalMinderTreeDTO getFunctionalCasePage(FunctionalCasePageRequest request, boolean deleted) {
//根据查询条件查询出所有符合的功能用例
List<FunctionalCasePageDTO> functionalCasePage = functionalCaseService.getFunctionalCasePage(request, deleted);
//根据功能用例的模块ID,查出每个模块id父级直到根节点
List<String> moduleIds = functionalCasePage.stream().map(FunctionalCase::getModuleId).distinct().toList();
Map<String, List<FunctionalCasePageDTO>> moduleCaseMap = functionalCasePage.stream().collect(Collectors.groupingBy(FunctionalCasePageDTO::getModuleId));
List<BaseTreeNode> baseTreeNodes = getBaseTreeNodes(moduleIds);
//判断虚拟节点有无数据
if (moduleCaseMap.get(ModuleConstants.DEFAULT_NODE_ID) == null && CollectionUtils.isEmpty(baseTreeNodes.get(0).getChildren())) {
baseTreeNodes.remove(0);
public List<FunctionalMinderTreeDTO> getMindFunctionalCase(FunctionalCaseMindRequest request, boolean deleted) {
List<FunctionalMinderTreeDTO> list = new ArrayList<>();
//查出当前模块下的所有用例
List<FunctionalCaseMindDTO> functionalCaseMindDTOList = extFunctionalCaseMapper.getMinderCaseList(request, deleted);
//构造父子级数据
for (FunctionalCaseMindDTO functionalCaseMindDTO : functionalCaseMindDTOList) {
FunctionalMinderTreeDTO root = new FunctionalMinderTreeDTO();
FunctionalMinderTreeNodeDTO rootData = new FunctionalMinderTreeNodeDTO();
rootData.setId(functionalCaseMindDTO.getId());
rootData.setText(functionalCaseMindDTO.getName());
rootData.setPriority(functionalCaseMindDTO.getPriority());
rootData.setStatus(functionalCaseMindDTO.getReviewStatus());
rootData.setResource(List.of(MinderLabel.CASE.toString(), functionalCaseMindDTO.getPriority()));
List<FunctionalMinderTreeDTO> children = buildChildren(functionalCaseMindDTO);
root.setChildren(children);
root.setData(rootData);
list.add(root);
}
//构建返回数据,主层级应与模块树层级相同
List<FunctionalMinderTreeDTO> functionalMinderTreeNodeDTOs = new ArrayList<>();
buildCaseTree(baseTreeNodes, moduleCaseMap, functionalMinderTreeNodeDTOs);
return getRoot(functionalMinderTreeNodeDTOs);
return list;
}

private List<FunctionalMinderTreeDTO> buildChildren(FunctionalCaseMindDTO functionalCaseMindDTO) {
List<FunctionalMinderTreeDTO> children = new ArrayList<>();
if (functionalCaseMindDTO.getSteps() != null) {
String stepText = new String(functionalCaseMindDTO.getSteps(), StandardCharsets.UTF_8);
FunctionalMinderTreeDTO stepFunctionalMinderTreeDTO = getFunctionalMinderTreeDTO(stepText, MinderLabel.TEXT_DESCRIPTION.toString());
children.add(stepFunctionalMinderTreeDTO);
}
if (functionalCaseMindDTO.getExpectedResult() != null) {
String expectedResultText = new String(functionalCaseMindDTO.getExpectedResult(), StandardCharsets.UTF_8);
FunctionalMinderTreeDTO expectedResultFunctionalMinderTreeDTO = getFunctionalMinderTreeDTO(expectedResultText, MinderLabel.EXPECTED_RESULT.toString());
children.add(expectedResultFunctionalMinderTreeDTO);
}
if (functionalCaseMindDTO.getPrerequisite() != null) {
String prerequisiteText = new String(functionalCaseMindDTO.getPrerequisite(), StandardCharsets.UTF_8);
FunctionalMinderTreeDTO prerequisiteFunctionalMinderTreeDTO = getFunctionalMinderTreeDTO(prerequisiteText, MinderLabel.PREREQUISITE.toString());
children.add(prerequisiteFunctionalMinderTreeDTO);
}
if (functionalCaseMindDTO.getDescription() != null) {
String descriptionText = new String(functionalCaseMindDTO.getDescription(), StandardCharsets.UTF_8);
FunctionalMinderTreeDTO descriptionFunctionalMinderTreeDTO = getFunctionalMinderTreeDTO(descriptionText, MinderLabel.DESCRIPTION.toString());
children.add(descriptionFunctionalMinderTreeDTO);
}
return children;
}

@NotNull
private static FunctionalMinderTreeDTO getFunctionalMinderTreeDTO(String text, String resource) {
FunctionalMinderTreeDTO functionalMinderTreeDTO = new FunctionalMinderTreeDTO();
FunctionalMinderTreeNodeDTO rootData = new FunctionalMinderTreeNodeDTO();
rootData.setText(text);
rootData.setResource(List.of(resource));
functionalMinderTreeDTO.setChildren(new ArrayList<>());
functionalMinderTreeDTO.setData(rootData);
return functionalMinderTreeDTO;
}

private static FunctionalMinderTreeDTO getRoot(List<FunctionalMinderTreeDTO> functionalMinderTreeDTOs) {
Expand All @@ -81,22 +124,6 @@ private List<BaseTreeNode> getBaseTreeNodes(List<String> moduleIds) {
return functionalCaseModuleService.buildTreeAndCountResource(nodeByNodeIds, true, Translator.get("functional_case.module.default.name"));
}

private static void buildCaseTree(List<BaseTreeNode> baseTreeNodes, Map<String, List<FunctionalCasePageDTO>> moduleCaseMap, List<FunctionalMinderTreeDTO> functionalMinderTreeNodeDTOs) {
baseTreeNodes.forEach(t -> {
//构建根节点
FunctionalMinderTreeDTO functionalMinderTreeDTO = new FunctionalMinderTreeDTO();
FunctionalMinderTreeNodeDTO functionalMinderTreeNodeDTO = getFunctionalMinderTreeNodeDTO(t);
functionalMinderTreeDTO.setData(functionalMinderTreeNodeDTO);

List<FunctionalMinderTreeDTO> children = new ArrayList<>();
//如果当前节点有用例,则用例是他的子节点
buildCaseChild(moduleCaseMap, t, children);
//查询当前节点下的子模块节点
buildModuleChild(moduleCaseMap, t, children);
functionalMinderTreeDTO.setChildren(children);
functionalMinderTreeNodeDTOs.add(functionalMinderTreeDTO);
});
}

private static FunctionalMinderTreeNodeDTO getFunctionalMinderTreeNodeDTO(BaseTreeNode t) {
FunctionalMinderTreeNodeDTO functionalMinderTreeNodeDTO = new FunctionalMinderTreeNodeDTO();
Expand All @@ -106,46 +133,6 @@ private static FunctionalMinderTreeNodeDTO getFunctionalMinderTreeNodeDTO(BaseTr
return functionalMinderTreeNodeDTO;
}

private static void buildCaseChild(Map<String, List<FunctionalCasePageDTO>> moduleCaseMap, BaseTreeNode t, List<FunctionalMinderTreeDTO> children) {
if (moduleCaseMap.get(t.getId()) != null && CollectionUtils.isNotEmpty(moduleCaseMap.get(t.getId()))) {
List<FunctionalCasePageDTO> functionalCasePageDTOS = moduleCaseMap.get(t.getId());
functionalCasePageDTOS.forEach(functionalCasePageDTO -> {
FunctionalMinderTreeDTO functionalMinderTreeChild = new FunctionalMinderTreeDTO();
//加载用例为children
FunctionalMinderTreeNodeDTO functionalMinderTreeNodeChild = new FunctionalMinderTreeNodeDTO();
functionalMinderTreeNodeChild.setId(functionalCasePageDTO.getId());
functionalMinderTreeNodeChild.setText(functionalCasePageDTO.getName());
if (CollectionUtils.isNotEmpty(functionalCasePageDTO.getCustomFields())) {
List<FunctionalCaseCustomFieldDTO> list = functionalCasePageDTO.getCustomFields().stream().filter(customField -> customField.getFieldName().equals(Translator.get("custom_field.functional_priority"))).toList();
if (CollectionUtils.isNotEmpty(list)) {
functionalMinderTreeNodeChild.setPriority(list.get(0).getDefaultValue());
}
}
functionalMinderTreeNodeChild.setResource(List.of(MinderLabel.CASE.toString()));
functionalMinderTreeChild.setData(functionalMinderTreeNodeChild);
//将用例的blob记为Case的children
children.add(functionalMinderTreeChild);
});
}
}

private static void buildModuleChild(Map<String, List<FunctionalCasePageDTO>> moduleCaseMap, BaseTreeNode t, List<FunctionalMinderTreeDTO> children) {
if (CollectionUtils.isNotEmpty(t.getChildren())) {
t.getChildren().forEach(child -> {
FunctionalMinderTreeDTO functionalMinderTreeDTOChild = new FunctionalMinderTreeDTO();
FunctionalMinderTreeNodeDTO functionalMinderTreeNodeDTOChild = getFunctionalMinderTreeNodeDTO(child);
functionalMinderTreeDTOChild.setData(functionalMinderTreeNodeDTOChild);

List<FunctionalMinderTreeDTO> childChildren = new ArrayList<>();
buildCaseChild(moduleCaseMap, child, childChildren);
functionalMinderTreeDTOChild.setChildren(childChildren);
children.add(functionalMinderTreeDTOChild);
buildModuleChild(moduleCaseMap, child, childChildren);
});
}

}

/**
* 用例评审-脑图用例列表查询
*
Expand All @@ -166,7 +153,7 @@ public FunctionalMinderTreeDTO getReviewFunctionalCasePage(MinderReviewFunctiona
baseTreeNodes.remove(0);
}
//自定义字段
Map<String, List<FunctionalCaseCustomFieldDTO>> caseCustomFiledMap = functionalCaseService.getCaseCustomFiledMap(caseIds,request.getProjectId());
Map<String, List<FunctionalCaseCustomFieldDTO>> caseCustomFiledMap = functionalCaseService.getCaseCustomFiledMap(caseIds, request.getProjectId());
//构建返回数据,主层级应与模块树层级相同
MinderSearchDTO minderSearchDTO = new MinderSearchDTO();
minderSearchDTO.setBaseTreeNodes(baseTreeNodes);
Expand Down