feat(功能用例): 用例详情获取缺陷列表&关联缺陷&取消关联

This commit is contained in:
WangXu10 2024-01-08 15:43:48 +08:00 committed by Craftsman
parent a4e739eee8
commit d076c43580
21 changed files with 621 additions and 23 deletions

View File

@ -0,0 +1,38 @@
package io.metersphere.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author wx
*/
@Data
public class BugProviderDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "缺陷id")
private String id;
@Schema(description = "缺陷名称")
private String name;
@Schema(description = "处理人")
private String createUser;
@Schema(description = "处理人姓名")
private String createUserName;
@Schema(description = "缺陷状态")
private String status;
@Schema(description = "标签")
private String tags;
@Schema(description = "创建时间")
private Long createTime;
}

View File

@ -0,0 +1,51 @@
package io.metersphere.provider;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.request.AssociateBugRequest;
import io.metersphere.request.BugPageProviderRequest;
import java.util.List;
/**
* @author wx
*/
public interface BaseAssociateBugProvider {
/**
* 获取尚未关联的缺陷列表
*
* @param sourceType 关联关系表表名
* @param sourceName 关联关系表主动关联方字段名称
* @param bugColumnName 缺陷id 在关联关系表的字段名称
* @param bugPageProviderRequest 缺陷搜索条件
* @return List<BugProviderDTO>
*/
List<BugProviderDTO> getBugList(String sourceType, String sourceName, String bugColumnName, BugPageProviderRequest bugPageProviderRequest);
/**
* 获取选中的缺陷id 列表
*
* @param request request
* @param deleted deleted
* @return
*/
List<String> getSelectBugs(AssociateBugRequest request, boolean deleted);
/**
* 关联用例处理
*
* @param ids 缺陷id集合
* @param userId 用户id
* @param caseId 用例id
*/
void handleAssociateBug(List<String> ids, String userId, String caseId);
/**
* 取消关联缺陷
*
* @param id
*/
void disassociateBug(String id);
}

View File

@ -0,0 +1,41 @@
package io.metersphere.request;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.List;
/**
* @author wx
*/
@Data
public class AssociateBugRequest extends BaseProviderCondition {
@Schema(description = "不处理的ID")
List<String> excludeIds;
@Schema(description = "选择的ID", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<
@NotBlank(message = "{id must not be blank}", groups = {Created.class, Updated.class})
String
> selectIds;
@Schema(description = "是否选择所有数据")
private boolean selectAll;
@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 = "{functional_case.id.not_blank}")
private String caseId;
}

View File

@ -0,0 +1,75 @@
package io.metersphere.request;
import com.google.common.base.CaseFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* @author wx
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class BugPageProviderRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Min(value = 1, message = "当前页码必须大于0")
@Schema(description = "当前页码")
private int current;
@Min(value = 5, message = "每页显示条数必须不小于5")
@Max(value = 500, message = "每页显示条数不能大于500")
@Schema(description = "每页显示条数")
private int pageSize;
@Schema(description = "排序字段model中的字段 : asc/desc")
private Map<@Valid @Pattern(regexp = "^[A-Za-z]+$") String, @Valid @NotBlank String> sort;
@Schema(description = "关键字")
private String keyword;
@Schema(description = "匹配模式 所有/任一", allowableValues = {"AND", "OR"})
private String searchMode = "AND";
@Schema(description = "过滤字段")
private Map<String, List<String>> filter;
@Schema(description = "高级搜索")
private Map<String, Object> combine;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.project_id.not_blank}")
private String projectId;
@Schema(description = "关联关系表里主ID eg:功能用例关联缺陷时为功能用例id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case.id.not_blank}")
private String sourceId;
public String getSortString() {
if (sort == null || sort.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sort.entrySet()) {
String column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entry.getKey());
sb.append(column)
.append(StringUtils.SPACE)
.append(StringUtils.equalsIgnoreCase(entry.getValue(), "DESC") ? "DESC" : "ASC")
.append(",");
}
return sb.substring(0, sb.length() - 1);
}
}

View File

@ -18,6 +18,11 @@
<artifactId>metersphere-sdk</artifactId> <artifactId>metersphere-sdk</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>io.metersphere</groupId>
<artifactId>metersphere-provider</artifactId>
<version>${revision}</version>
</dependency>
<dependency> <dependency>
<groupId>io.metersphere</groupId> <groupId>io.metersphere</groupId>
<artifactId>metersphere-system-setting</artifactId> <artifactId>metersphere-system-setting</artifactId>

View File

@ -4,6 +4,9 @@ import io.metersphere.bug.dto.request.BugBatchUpdateRequest;
import io.metersphere.bug.dto.request.BugPageRequest; import io.metersphere.bug.dto.request.BugPageRequest;
import io.metersphere.bug.dto.response.BugDTO; import io.metersphere.bug.dto.response.BugDTO;
import io.metersphere.bug.dto.response.BugTagEditDTO; import io.metersphere.bug.dto.response.BugTagEditDTO;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.request.AssociateBugRequest;
import io.metersphere.request.BugPageProviderRequest;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@ -44,6 +47,7 @@ public interface ExtBugMapper {
/** /**
* 获取缺陷标签列表 * 获取缺陷标签列表
*
* @param ids 缺陷ID集合 * @param ids 缺陷ID集合
* @return 缺陷标签列表 * @return 缺陷标签列表
*/ */
@ -51,8 +55,13 @@ public interface ExtBugMapper {
/** /**
* 批量更新缺陷 * 批量更新缺陷
*
* @param request 请求参数 * @param request 请求参数
* @param ids 缺陷ID集合 * @param ids 缺陷ID集合
*/ */
void batchUpdate(@Param("request") BugBatchUpdateRequest request, @Param("ids") List<String> ids); void batchUpdate(@Param("request") BugBatchUpdateRequest request, @Param("ids") List<String> ids);
List<BugProviderDTO> listByProviderRequest(@Param("table") String sourceType, @Param("sourceName") String sourceName, @Param("bugColumnName") String bugColumnName, @Param("request") BugPageProviderRequest bugPageProviderRequest, @Param("deleted") boolean deleted);
List<String> getIdsByProvider(@Param("request") AssociateBugRequest request, @Param("deleted") boolean deleted);
} }

View File

@ -188,5 +188,52 @@
</if> </if>
</if> </if>
</sql> </sql>
<select id="listByProviderRequest" resultType="io.metersphere.dto.BugProviderDTO">
SELECT
b.id id,
b.title title,
b.create_user createUser,
u.`name` name,
b.`status` status,
b.tag tag,
b.create_time createTime
FROM
bug b
LEFT JOIN `user` u ON b.create_user = u.id
WHERE
b.deleted = #{deleted}
AND b.id NOT IN
(
select associate.${bugColumnName} from ${table} associate where associate.${sourceName} = #{request.sourceId}
)
<include refid="queryWhereConditionByProvider"/>
</select>
<select id="getIdsByProvider" resultType="java.lang.String">
SELECT
b.id
FROM
bug b
WHERE b.deleted =#{deleted}
<include refid="queryWhereConditionByProvider"/>
</select>
<sql id="queryWhereConditionByProvider">
<if test="request.projectId != null">
and b.project_id = #{request.projectId}
</if>
<if test="request.keyword != null">
and (
b.title like concat('%', #{request.keyword},'%')
or b.num like concat('%', #{request.keyword},'%')
)
</if>
<include refid="filter"/>
<include refid="combine">
<property name="condition" value="request.combine"/>
</include>
</sql>
</mapper> </mapper>

View File

@ -0,0 +1,70 @@
package io.metersphere.bug.provider;
import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.bug.mapper.ExtBugMapper;
import io.metersphere.bug.service.BugRelateCaseService;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.provider.BaseAssociateBugProvider;
import io.metersphere.request.AssociateBugRequest;
import io.metersphere.request.BugPageProviderRequest;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class AssociateBugProvider implements BaseAssociateBugProvider {
@Resource
private ExtBugMapper extBugMapper;
@Resource
private BugRelationCaseMapper bugRelationCaseMapper;
@Resource
private BugRelateCaseService bugRelateCaseService;
@Override
public List<BugProviderDTO> getBugList(String sourceType, String sourceName, String bugColumnName, BugPageProviderRequest bugPageProviderRequest) {
return extBugMapper.listByProviderRequest(sourceType, sourceName, bugColumnName, bugPageProviderRequest, false);
}
@Override
public List<String> getSelectBugs(AssociateBugRequest request, boolean deleted) {
if (request.isSelectAll()) {
List<String> ids = extBugMapper.getIdsByProvider(request, deleted);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
ids.removeAll(request.getExcludeIds());
}
return ids;
} else {
return request.getSelectIds();
}
}
@Override
public void handleAssociateBug(List<String> ids, String userId, String caseId) {
List<BugRelationCase> list = new ArrayList<>();
ids.forEach(id -> {
BugRelationCase bugRelationCase = new BugRelationCase();
bugRelationCase.setId(IDGenerator.nextStr());
bugRelationCase.setBugId(id);
bugRelationCase.setCaseId(caseId);
bugRelationCase.setCaseType("FUNCTIONAL");
bugRelationCase.setCreateUser(userId);
bugRelationCase.setCreateTime(System.currentTimeMillis());
bugRelationCase.setUpdateTime(System.currentTimeMillis());
list.add(bugRelationCase);
});
bugRelationCaseMapper.batchInsert(list);
}
@Override
public void disassociateBug(String id) {
bugRelateCaseService.unRelate(id);
}
}

View File

@ -0,0 +1,79 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.provider.AssociateBugProvider;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.request.AssociateBugRequest;
import io.metersphere.request.BugPageProviderRequest;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import java.util.List;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc
public class AssociateBugProviderTests extends BaseTest {
@Resource
private AssociateBugProvider associateBugProvider;
@Test
@Order(1)
@Sql(scripts = {"/dml/init_bug_relation_case.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void getBugList() throws Exception {
BugPageProviderRequest request = new BugPageProviderRequest();
request.setSourceId("wx_associate_case_id_1");
request.setProjectId("project_wx_associate_test");
request.setCurrent(1);
request.setPageSize(10);
List<BugProviderDTO> bugList = associateBugProvider.getBugList("bug_relation_case", "case_id", "bug_id", request);
String jsonString = JSON.toJSONString(bugList);
System.out.println(jsonString);
}
@Test
@Order(2)
public void getSelectBugs() throws Exception {
AssociateBugRequest request = new AssociateBugRequest();
request.setCaseId("wx_associate_case_id_1");
request.setProjectId("project_wx_associate_test");
request.setSelectAll(true);
List<String> list = associateBugProvider.getSelectBugs(request, false);
String jsonString = JSON.toJSONString(list);
System.out.println(jsonString);
request.setExcludeIds(List.of("bug_id_3"));
List<String> list1 = associateBugProvider.getSelectBugs(request, false);
String jsonString1 = JSON.toJSONString(list1);
System.out.println(jsonString1);
request.setSelectAll(false);
request.setSelectIds(List.of("bug_id_1", "bug_id_2"));
List<String> list2 = associateBugProvider.getSelectBugs(request, false);
String jsonString2 = JSON.toJSONString(list2);
System.out.println(jsonString2);
}
@Test
@Order(3)
public void testAssociateBug() throws Exception {
associateBugProvider.handleAssociateBug(List.of("bug_id_1", "bug_id_2"), "wx", "wx_associate_case_id_1");
}
@Test
@Order(4)
public void testDisassociateBug() throws Exception {
associateBugProvider.disassociateBug("wx_test_id_1");
}
}

View File

@ -0,0 +1,9 @@
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag, platform_bug_id, deleted) VALUES
('bug_id_1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0),
('bug_id_2', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0);
INSERT INTO bug_relation_case(id, case_id, bug_id, case_type, test_plan_id, test_plan_case_id, create_user, create_time, update_time)
VALUES ('wx_test_id_1', 'wx_1', 'bug_id_1', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('wx_test_id_2', 'wx_2', 'bug_id_1', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('wx_test_id_3', 'wx_3', 'bug_id_2', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case-1', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);

View File

@ -76,6 +76,9 @@ public class FunctionalCaseRelationshipController {
@GetMapping("/delete/{id}") @GetMapping("/delete/{id}")
@Operation(summary = "用例管理-功能用例-用例详情-前后置关系-取消关联")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_UPDATE)
@CheckOwner(resourceId = "#id", resourceType = "functional_case")
public void delete(@PathVariable("id") String id) { public void delete(@PathVariable("id") String id) {
functionalCaseRelationshipEdgeService.delete(id); functionalCaseRelationshipEdgeService.delete(id);
} }

View File

@ -2,6 +2,7 @@ package io.metersphere.functional.controller;
import com.github.pagehelper.Page; import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.dto.TestCaseProviderDTO; import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.functional.dto.FunctionalCaseTestDTO; import io.metersphere.functional.dto.FunctionalCaseTestDTO;
import io.metersphere.functional.request.AssociateCaseModuleRequest; import io.metersphere.functional.request.AssociateCaseModuleRequest;
@ -9,9 +10,7 @@ import io.metersphere.functional.request.DisassociateOtherCaseRequest;
import io.metersphere.functional.request.FunctionalCaseTestRequest; import io.metersphere.functional.request.FunctionalCaseTestRequest;
import io.metersphere.functional.service.FunctionalCaseLogService; import io.metersphere.functional.service.FunctionalCaseLogService;
import io.metersphere.functional.service.FunctionalTestCaseService; import io.metersphere.functional.service.FunctionalTestCaseService;
import io.metersphere.request.AssociateCaseModuleProviderRequest; import io.metersphere.request.*;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.log.annotation.Log; import io.metersphere.system.log.annotation.Log;
@ -27,10 +26,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
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.PostMapping; import org.springframework.web.bind.annotation.*;
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;
@ -74,7 +70,7 @@ public class FunctionalTestCaseController {
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
@CheckOwner(resourceId = "#request.sourceId", resourceType = "functional_case") @CheckOwner(resourceId = "#request.sourceId", resourceType = "functional_case")
public void associateCase(@Validated @RequestBody AssociateOtherCaseRequest request) { public void associateCase(@Validated @RequestBody AssociateOtherCaseRequest request) {
functionalTestCaseService.associateCase(request, false, SessionUtils.getUserId()); functionalTestCaseService.associateCase(request, false, SessionUtils.getUserId());
} }
@PostMapping("/disassociate/case") @PostMapping("/disassociate/case")
@ -83,7 +79,7 @@ public class FunctionalTestCaseController {
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
@CheckOwner(resourceId = "#request.projectId", resourceType = "project") @CheckOwner(resourceId = "#request.projectId", resourceType = "project")
public void disassociateCase(@Validated @RequestBody DisassociateOtherCaseRequest request) { public void disassociateCase(@Validated @RequestBody DisassociateOtherCaseRequest request) {
functionalTestCaseService.disassociateCase(request); functionalTestCaseService.disassociateCase(request);
} }
@ -97,5 +93,31 @@ public class FunctionalTestCaseController {
} }
@PostMapping("/associate/bug/page")
@Operation(summary = "用例管理-功能用例-关联其他用例-获取缺陷列表")
@RequiresPermissions(value = {PermissionConstants.FUNCTIONAL_CASE_READ_ADD, PermissionConstants.FUNCTIONAL_CASE_READ_UPDATE, PermissionConstants.FUNCTIONAL_CASE_READ_DELETE}, logical = Logical.OR)
@CheckOwner(resourceId = "#request.getProjectId", resourceType = "project")
public Pager<List<BugProviderDTO>> associateBugList(@Validated @RequestBody BugPageProviderRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
return PageUtils.setPageInfo(page, functionalTestCaseService.bugPage(request));
}
@PostMapping("/associate/bug")
@Operation(summary = "用例管理-功能用例-关联其他用例-关联缺陷")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_ADD)
@CheckOwner(resourceId = "#request.caseId", resourceType = "functional_case")
public void associateBug(@Validated @RequestBody AssociateBugRequest request) {
functionalTestCaseService.associateBug(request, false, SessionUtils.getUserId());
}
@GetMapping("/disassociate/bug/{id}")
@Operation(summary = "用例管理-功能用例-关联其他用例-取消关联缺陷")
@Log(type = OperationLogType.DISASSOCIATE, expression = "#msClass.disassociateBugLog(#id)", msClass = FunctionalCaseLogService.class)
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_ADD)
@CheckOwner(resourceId = "#id", resourceType = "functional_case")
public void disassociateBug(@PathVariable String id) {
functionalTestCaseService.disassociateBug(id);
}
} }

View File

@ -69,6 +69,15 @@
<sql id="queryWhereCondition"> <sql id="queryWhereCondition">
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
functional_case.module_id in
<foreach collection="request.moduleIds" item="moduleId" separator="," open="(" close=")">
#{moduleId}
</foreach>
<include refid="queryType">
<property name="searchMode" value="request.searchMode"/>
</include>
</if>
<if test="request.keyword != null"> <if test="request.keyword != null">
( (
functional_case.name like concat('%', #{request.keyword},'%') functional_case.name like concat('%', #{request.keyword},'%')

View File

@ -4,9 +4,9 @@
<select id="selectReviewers" resultType="io.metersphere.functional.dto.ReviewsDTO"> <select id="selectReviewers" resultType="io.metersphere.functional.dto.ReviewsDTO">
SELECT SELECT
case_id, case_id caseId,
GROUP_CONCAT( user_id ), GROUP_CONCAT( user_id ) userIds,
GROUP_CONCAT( `user`.NAME ) GROUP_CONCAT( `user`.NAME ) userNames
FROM FROM
case_review_functional_case_user crfcu case_review_functional_case_user crfcu
LEFT JOIN `user` ON crfcu.user_id = `user`.id LEFT JOIN `user` ON crfcu.user_id = `user`.id

View File

@ -7,6 +7,7 @@ import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
/** /**
* @author wx * @author wx
@ -23,4 +24,7 @@ public class ReviewFunctionalCasePageRequest extends BasePageRequest implements
@Schema(description = "是否只看我的", requiredMode = Schema.RequiredMode.REQUIRED) @Schema(description = "是否只看我的", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean viewFlag; private Boolean viewFlag;
@Schema(description = "模块id")
private List<String> moduleIds;
} }

View File

@ -1,6 +1,10 @@
package io.metersphere.functional.service; package io.metersphere.functional.service;
import io.metersphere.api.mapper.ApiTestCaseMapper; import io.metersphere.api.mapper.ApiTestCaseMapper;
import io.metersphere.bug.domain.Bug;
import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.mapper.BugMapper;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.functional.domain.*; import io.metersphere.functional.domain.*;
import io.metersphere.functional.dto.BaseFunctionalCaseBatchDTO; import io.metersphere.functional.dto.BaseFunctionalCaseBatchDTO;
import io.metersphere.functional.dto.FunctionalCaseHistoryLogDTO; import io.metersphere.functional.dto.FunctionalCaseHistoryLogDTO;
@ -48,7 +52,9 @@ public class FunctionalCaseLogService {
private FileAssociationMapper fileAssociationMapper; private FileAssociationMapper fileAssociationMapper;
@Resource @Resource
private ApiTestCaseMapper apiTestCaseMapper; private BugRelationCaseMapper bugRelationCaseMapper;
@Resource
private BugMapper bugMapper;
//TODO 日志(需要修改) //TODO 日志(需要修改)
@ -374,4 +380,25 @@ public class FunctionalCaseLogService {
return request.getSelectIds(); return request.getSelectIds();
} }
} }
public LogDTO disassociateBugLog(String id) {
BugRelationCase bugRelationCase = bugRelationCaseMapper.selectByPrimaryKey(id);
if (bugRelationCase != null) {
Bug bug = bugMapper.selectByPrimaryKey(bugRelationCase.getBugId());
LogDTO dto = new LogDTO(
null,
null,
bugRelationCase.getBugId(),
null,
OperationLogType.DISASSOCIATE.name(),
OperationLogModule.FUNCTIONAL_CASE,
bug.getTitle()+"缺陷");
dto.setPath("/functional/case/test/disassociate/bug/"+id);
dto.setMethod(HttpMethodConstants.GET.name());
dto.setOriginalValue(JSON.toJSONBytes(bugRelationCase));
return dto;
}
return null;
}
} }

View File

@ -1,6 +1,8 @@
package io.metersphere.functional.service; package io.metersphere.functional.service;
import io.metersphere.api.domain.ApiTestCase; import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.dto.TestCaseProviderDTO; import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.functional.constants.AssociateCaseType; import io.metersphere.functional.constants.AssociateCaseType;
import io.metersphere.functional.domain.FunctionalCaseTest; import io.metersphere.functional.domain.FunctionalCaseTest;
@ -13,9 +15,8 @@ import io.metersphere.functional.request.AssociateCaseModuleRequest;
import io.metersphere.functional.request.DisassociateOtherCaseRequest; import io.metersphere.functional.request.DisassociateOtherCaseRequest;
import io.metersphere.functional.request.FunctionalCaseTestRequest; import io.metersphere.functional.request.FunctionalCaseTestRequest;
import io.metersphere.provider.BaseAssociateApiProvider; import io.metersphere.provider.BaseAssociateApiProvider;
import io.metersphere.request.AssociateCaseModuleProviderRequest; import io.metersphere.provider.BaseAssociateBugProvider;
import io.metersphere.request.AssociateOtherCaseRequest; import io.metersphere.request.*;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.LogUtils;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.dto.sdk.BaseTreeNode;
@ -60,6 +61,10 @@ public class FunctionalTestCaseService {
private static final String UNPLANNED_API = "api_unplanned_request"; private static final String UNPLANNED_API = "api_unplanned_request";
@Resource
private BaseAssociateBugProvider baseAssociateBugProvider;
@Resource
private BugRelationCaseMapper bugRelationCaseMapper;
/** /**
* 获取功能用例未关联的接口用例列表 * 获取功能用例未关联的接口用例列表
@ -162,6 +167,36 @@ public class FunctionalTestCaseService {
} }
public List<FunctionalCaseTestDTO> hasAssociatePage(FunctionalCaseTestRequest request) { public List<FunctionalCaseTestDTO> hasAssociatePage(FunctionalCaseTestRequest request) {
return extFunctionalCaseTestMapper.getList(request); return extFunctionalCaseTestMapper.getList(request);
}
/**
* 获取功能用例未关联的缺陷列表
*
* @param request request
* @return
*/
public List<BugProviderDTO> bugPage(BugPageProviderRequest request) {
return baseAssociateBugProvider.getBugList("bug_relation_case", "case_id", "bug_id", request);
}
/**
* 关联缺陷
*
* @param request request
* @param deleted 缺陷是否删除
* @param userId 用户id
*/
public void associateBug(AssociateBugRequest request, boolean deleted, String userId) {
List<String> ids = baseAssociateBugProvider.getSelectBugs(request, deleted);
if (CollectionUtils.isNotEmpty(ids)) {
baseAssociateBugProvider.handleAssociateBug(ids, userId, request.getCaseId());
}
}
public void disassociateBug(String id) {
baseAssociateBugProvider.disassociateBug(id);
} }
} }

View File

@ -1,6 +1,7 @@
package io.metersphere.functional.config; package io.metersphere.functional.config;
import io.metersphere.provider.BaseAssociateApiProvider; import io.metersphere.provider.BaseAssociateApiProvider;
import io.metersphere.provider.BaseAssociateBugProvider;
import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
@ -10,4 +11,7 @@ public class CaseTestConfiguration {
@MockBean @MockBean
BaseAssociateApiProvider provider; BaseAssociateApiProvider provider;
@MockBean
BaseAssociateBugProvider baseAssociateBugProvider;
} }

View File

@ -3,6 +3,8 @@ package io.metersphere.functional.controller;
import io.metersphere.api.domain.ApiDefinitionModule; import io.metersphere.api.domain.ApiDefinitionModule;
import io.metersphere.api.domain.ApiTestCase; import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.api.mapper.ApiDefinitionModuleMapper; import io.metersphere.api.mapper.ApiDefinitionModuleMapper;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.dto.TestCaseProviderDTO; import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.functional.constants.AssociateCaseType; import io.metersphere.functional.constants.AssociateCaseType;
import io.metersphere.functional.constants.FunctionalCaseReviewStatus; import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
@ -15,9 +17,8 @@ import io.metersphere.functional.request.AssociateCaseModuleRequest;
import io.metersphere.functional.request.DisassociateOtherCaseRequest; import io.metersphere.functional.request.DisassociateOtherCaseRequest;
import io.metersphere.functional.request.FunctionalCaseTestRequest; import io.metersphere.functional.request.FunctionalCaseTestRequest;
import io.metersphere.provider.BaseAssociateApiProvider; import io.metersphere.provider.BaseAssociateApiProvider;
import io.metersphere.request.AssociateCaseModuleProviderRequest; import io.metersphere.provider.BaseAssociateBugProvider;
import io.metersphere.request.AssociateOtherCaseRequest; import io.metersphere.request.*;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.constants.FunctionalCaseExecuteResult; import io.metersphere.sdk.constants.FunctionalCaseExecuteResult;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest; import io.metersphere.system.base.BaseTest;
@ -29,6 +30,8 @@ import org.junit.jupiter.api.*;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
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.MvcResult;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -54,7 +57,9 @@ public class FunctionalTestCaseControllerTests extends BaseTest {
private static final String URL_HAS_CASE_PAGE = "/functional/case/test/has/associate/case/page"; private static final String URL_HAS_CASE_PAGE = "/functional/case/test/has/associate/case/page";
private static final String URL_BUG_PAGE = "/functional/case/test/associate/bug/page";
private static final String URL_ASSOCIATE_BUG = "/functional/case/test/associate/bug";
private static final String URL_DISASSOCIATE_BUG = "/functional/case/test/disassociate/bug/";
@Resource @Resource
@ -68,6 +73,10 @@ public class FunctionalTestCaseControllerTests extends BaseTest {
@Resource @Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper; private ApiDefinitionModuleMapper apiDefinitionModuleMapper;
@Resource
BaseAssociateBugProvider baseAssociateBugProvider;
@Resource
private BugRelationCaseMapper bugRelationCaseMapper;
@Test @Test
@ -142,7 +151,7 @@ public class FunctionalTestCaseControllerTests extends BaseTest {
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);
List<ApiTestCase>operations = new ArrayList<>(); List<ApiTestCase> operations = new ArrayList<>();
ApiTestCase apiTestCase = new ApiTestCase(); ApiTestCase apiTestCase = new ApiTestCase();
apiTestCase.setId("gyq_associate_case_id_1"); apiTestCase.setId("gyq_associate_case_id_1");
apiTestCase.setVersionId("11"); apiTestCase.setVersionId("11");
@ -293,4 +302,55 @@ public class FunctionalTestCaseControllerTests extends BaseTest {
functionalCaseMapper.insertSelective(functionalCase); functionalCaseMapper.insertSelective(functionalCase);
} }
@Test
@Order(8)
public void getAssociateBugList() throws Exception {
BugPageProviderRequest request = new BugPageProviderRequest();
request.setSourceId("wx_associate_case_id_1");
request.setProjectId("project_wx_associate_test");
request.setCurrent(1);
request.setPageSize(10);
BugProviderDTO bugProviderDTO = new BugProviderDTO();
bugProviderDTO.setName("第二个");
List<BugProviderDTO> operations = new ArrayList<>();
operations.add(bugProviderDTO);
Mockito.when(baseAssociateBugProvider.getBugList("functional_case_test", "case_id", "bug_id", request)).thenReturn(operations);
this.requestPostWithOkAndReturn(URL_BUG_PAGE, request);
request.setSort(new HashMap<>() {{
put("createTime", "desc");
}});
List<BugProviderDTO> bugList = baseAssociateBugProvider.getBugList("functional_case_test", "case_id", "bug_id", request);
MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_BUG_PAGE, request);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
List<BugProviderDTO> bugProviderDTOS = JSON.parseArray(JSON.toJSONString(resultHolder.getData()), BugProviderDTO.class);
Assertions.assertNotNull(bugProviderDTOS);
System.out.println(JSON.toJSONString(bugList));
}
@Test
@Order(9)
public void testAssociateBugs() throws Exception {
AssociateBugRequest request = new AssociateBugRequest();
request.setCaseId("test_1");
request.setProjectId("project_wx_associate_test");
List<String> ids = new ArrayList<>();
ids.add("bug_id_1");
Mockito.when(baseAssociateBugProvider.getSelectBugs(request, false)).thenReturn(ids);
this.requestPostWithOkAndReturn(URL_ASSOCIATE_BUG, request);
}
@Test
@Order(10)
@Sql(scripts = {"/dml/init_bug_relation_case.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void testDisassociateBug() throws Exception {
//增加日志覆盖率
this.requestGetWithOkAndReturn(URL_DISASSOCIATE_BUG + "TEST");
this.requestGetWithOkAndReturn(URL_DISASSOCIATE_BUG + "1234");
}
} }

View File

@ -0,0 +1,9 @@
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tag, platform_bug_id, deleted) VALUES
('wx_bug_id_1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0),
('wx_bug_id_2', 100001, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project_wx_associate_test"', 'bug-template-id', 'Local', 'open', 'default-tag', null, 0);
INSERT INTO bug_relation_case(id, case_id, bug_id, case_type, test_plan_id, test_plan_case_id, create_user, create_time, update_time)
VALUES ('TEST', 'wx_1', 'wx_bug_id_1', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('wx_test_id_22', 'wx_2', 'wx_bug_id_1', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('wx_test_id_33', 'wx_3', 'wx_bug_id_2', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case-1', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);

View File

@ -55,6 +55,7 @@ VALUES ('wx_test_1', 'wx_review_id_1', 'wx_case_id_1', 'PASS', 1698058347559,'ad
INSERT INTO case_review_functional_case_user(case_id, review_id, user_id) INSERT INTO case_review_functional_case_user(case_id, review_id, user_id)
VALUES ('wx_case_id_1', 'wx_review_id_1', 'admin'), VALUES ('wx_case_id_1', 'wx_review_id_1', 'admin'),
('wx_case_id_1', 'wx_review_id_1', 'gyq'), ('wx_case_id_1', 'wx_review_id_1', 'gyq'),
('wx_test_1', 'wx_review_id_1', 'admin'),
('gyq_case_id_3', 'wx_review_id_1', 'gyq2'), ('gyq_case_id_3', 'wx_review_id_1', 'gyq2'),
('gyq_case_id_4', 'wx_review_id_1', 'gyq'), ('gyq_case_id_4', 'wx_review_id_1', 'gyq'),
('wx_case_id_3', 'wx_review_id_3', 'admin'), ('wx_case_id_3', 'wx_review_id_3', 'admin'),