From 1ea2d56b07a686d3a40eef849d635c4c49956bcb Mon Sep 17 00:00:00 2001 From: WangXu10 Date: Mon, 2 Sep 2024 14:21:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92=E8=AF=A6=E6=83=85=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E7=94=A8=E4=BE=8B=E6=89=B9=E9=87=8F=E5=85=B3=E8=81=94?= =?UTF-8?q?=E7=BC=BA=E9=99=B7&=E6=89=B9=E9=87=8F=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TestPlanApiScenarioController.java | 29 ++++++ .../TestPlanScenarioBatchAddBugRequest.java | 62 ++++++++++++ ...tPlanScenarioBatchAssociateBugRequest.java | 17 ++++ .../service/TestPlanApiScenarioService.java | 95 ++++++++++++++++++- .../TestPlanApiScenarioControllerTests.java | 65 +++++++++++++ 5 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAddBugRequest.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAssociateBugRequest.java diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java index ce57fc2938..00f9486a3c 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanApiScenarioController.java @@ -5,6 +5,9 @@ import com.github.pagehelper.PageHelper; import io.metersphere.api.dto.scenario.ApiScenarioReportDTO; import io.metersphere.api.dto.scenario.ApiScenarioReportDetailDTO; import io.metersphere.api.service.scenario.ApiScenarioReportService; +import io.metersphere.bug.domain.Bug; +import io.metersphere.bug.dto.request.BugEditRequest; +import io.metersphere.bug.service.BugService; import io.metersphere.dto.BugProviderDTO; import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanApiScenarioPageResponse; @@ -17,6 +20,7 @@ import io.metersphere.request.BugPageProviderRequest; import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.dto.api.task.TaskRequestDTO; +import io.metersphere.sdk.util.BeanUtils; import io.metersphere.system.dto.LogInsertModule; import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.log.annotation.Log; @@ -33,6 +37,7 @@ import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; @@ -48,6 +53,8 @@ public class TestPlanApiScenarioController { private TestPlanApiScenarioBatchRunService testPlanApiScenarioBatchRunService; @Resource private ApiScenarioReportService apiScenarioReportService; + @Resource + private BugService bugService; @PostMapping("/page") @Operation(summary = "测试计划-已关联场景用例列表分页查询") @@ -181,4 +188,26 @@ public class TestPlanApiScenarioController { public void disassociateBug(@PathVariable String id) { testPlanApiScenarioService.disassociateBug(id); } + + + @PostMapping("/batch/add-bug") + @Operation(summary = "测试计划-计划详情-场景用例-批量添加缺陷") + @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_EXECUTE) + @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") + public void batchAddBug(@Validated @RequestPart("request") TestPlanScenarioBatchAddBugRequest request, + @RequestPart(value = "files", required = false) List files) { + BugEditRequest bugEditRequest = new BugEditRequest(); + BeanUtils.copyBean(bugEditRequest, request); + Bug bug = bugService.addOrUpdate(bugEditRequest, files, SessionUtils.getUserId(), SessionUtils.getCurrentOrganizationId(), false); + testPlanApiScenarioService.batchAssociateBug(request, bug.getId(), SessionUtils.getUserId()); + } + + + @PostMapping("/batch/associate-bug") + @Operation(summary = "测试计划-计划详情-场景用例-批量关联缺陷") + @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_EXECUTE) + @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") + public void batchAssociateBug(@Validated @RequestBody TestPlanScenarioBatchAssociateBugRequest request) { + testPlanApiScenarioService.batchAssociateBugByIds(request, SessionUtils.getUserId()); + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAddBugRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAddBugRequest.java new file mode 100644 index 0000000000..3eaafc1bff --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAddBugRequest.java @@ -0,0 +1,62 @@ +package io.metersphere.plan.dto.request; + +import io.metersphere.bug.dto.response.BugCustomFieldDTO; +import io.metersphere.bug.dto.response.BugFileDTO; +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 java.util.List; + +/** + * @author wx + */ +@Data +public class TestPlanScenarioBatchAddBugRequest extends BasePlanCaseBatchRequest { + + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{bug.id.not_blank}", groups = {Updated.class}) + @Size(min = 1, max = 50, message = "{bug.id.length_range}", groups = {Created.class, Updated.class}) + private String id; + + @Schema(description = "缺陷标题") + @Size(min = 1, max = 255, message = "{bug.title.length_range}", groups = {Created.class, Updated.class}) + private String title; + + @Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{bug.project_id.not_blank}", groups = {Created.class, Updated.class}) + @Size(min = 1, max = 50, message = "{bug.project_id.length_range}", groups = {Created.class, Updated.class}) + private String projectId; + + @Schema(description = "模板ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{bug.template_id.not_blank}", groups = {Created.class, Updated.class}) + @Size(min = 1, max = 50, message = "{bug.template_id.length_range}", groups = {Created.class, Updated.class}) + private String templateId; + + @Schema(description = "标签") + private List tags; + + @Schema(description = "缺陷内容") + private String description; + + @Schema(description = "自定义字段集合") + private List customFields; + + @Schema(description = "删除的本地附件集合, {文件ID") + private List deleteLocalFileIds; + + @Schema(description = "取消关联附件关系ID集合, 关联关系ID") + private List unLinkRefIds; + + @Schema(description = "关联附件集合, 文件ID") + private List linkFileIds; + + @Schema(description = "复制的附件") + private List copyFiles; + + @Schema(description = "富文本临时文件ID") + private List richTextTmpFileIds; +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAssociateBugRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAssociateBugRequest.java new file mode 100644 index 0000000000..7366f2334d --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanScenarioBatchAssociateBugRequest.java @@ -0,0 +1,17 @@ +package io.metersphere.plan.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +/** + * @author wx + */ +@Data +public class TestPlanScenarioBatchAssociateBugRequest extends BasePlanCaseBatchRequest { + @Schema(description = "缺陷ID集合", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "{bug.id.not_blank}") + private List bugIds; +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java index a11bceed7e..332ddb0071 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java @@ -13,6 +13,9 @@ import io.metersphere.api.service.scenario.ApiScenarioModuleService; import io.metersphere.api.service.scenario.ApiScenarioReportService; import io.metersphere.api.service.scenario.ApiScenarioRunService; import io.metersphere.api.service.scenario.ApiScenarioService; +import io.metersphere.bug.domain.BugRelationCase; +import io.metersphere.bug.domain.BugRelationCaseExample; +import io.metersphere.bug.mapper.BugRelationCaseMapper; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; import io.metersphere.functional.dto.ProjectOptionDTO; import io.metersphere.plan.constants.AssociateCaseType; @@ -38,6 +41,7 @@ import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.mapper.EnvironmentMapper; import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.CommonBeanFactory; +import io.metersphere.sdk.util.SubListUtils; import io.metersphere.sdk.util.Translator; import io.metersphere.system.dto.LogInsertModule; import io.metersphere.system.dto.sdk.BaseTreeNode; @@ -111,6 +115,9 @@ public class TestPlanApiScenarioService extends TestPlanResourceService { private static final String EXECUTOR = "executeUserName"; + @Resource + private BugRelationCaseMapper bugRelationCaseMapper; + @Override public List selectDistinctExecResultByProjectId(String projectId) { return extTestPlanApiScenarioMapper.selectDistinctExecResult(projectId); @@ -715,8 +722,9 @@ public class TestPlanApiScenarioService extends TestPlanResourceService { /** * 关联缺陷 (单条用例) + * * @param request 请求参数 - * @param userId 用户ID + * @param userId 用户ID */ public void associateBug(TestPlanCaseAssociateBugRequest request, String userId) { super.associateBug(request, userId, CaseType.SCENARIO_CASE.getKey()); @@ -764,6 +772,7 @@ public class TestPlanApiScenarioService extends TestPlanResourceService { /** * 处理执行人为空过滤参数 + * * @param request 请求参数 */ protected void filterCaseRequest(TestPlanApiScenarioRequest request) { @@ -776,4 +785,88 @@ public class TestPlanApiScenarioService extends TestPlanResourceService { } } } + + public void batchAssociateBug(TestPlanScenarioBatchAddBugRequest request, String bugId, String userId) { + List ids = doSelectIds(request); + if (CollectionUtils.isNotEmpty(ids)) { + handleAssociateBug(ids, userId, bugId, request.getTestPlanId()); + } + + } + + private void handleAssociateBug(List ids, String userId, String bugId, String testPlanId) { + SubListUtils.dealForSubList(ids, 500, (subList) -> { + Map caseMap = getCaseMap(subList); + List list = new ArrayList<>(); + subList.forEach(id -> { + BugRelationCase bugRelationCase = new BugRelationCase(); + bugRelationCase.setId(IDGenerator.nextStr()); + bugRelationCase.setBugId(bugId); + bugRelationCase.setCaseId(caseMap.get(id)); + bugRelationCase.setCaseType(CaseType.SCENARIO_CASE.getKey()); + bugRelationCase.setCreateUser(userId); + bugRelationCase.setCreateTime(System.currentTimeMillis()); + bugRelationCase.setUpdateTime(System.currentTimeMillis()); + bugRelationCase.setTestPlanCaseId(id); + bugRelationCase.setTestPlanId(testPlanId); + list.add(bugRelationCase); + }); + bugRelationCaseMapper.batchInsert(list); + }); + } + + public Map getCaseMap(List ids) { + TestPlanApiScenarioExample example = new TestPlanApiScenarioExample(); + example.createCriteria().andIdIn(ids); + List caseList = testPlanApiScenarioMapper.selectByExample(example); + return caseList.stream().collect(Collectors.toMap(TestPlanApiScenario::getId, TestPlanApiScenario::getApiScenarioId)); + } + + public void batchAssociateBugByIds(TestPlanScenarioBatchAssociateBugRequest request, String userId) { + List ids = doSelectIds(request); + if (CollectionUtils.isNotEmpty(ids)) { + handleAssociateBugByIds(ids, request, userId); + } + } + + public void handleAssociateBugByIds(List ids, TestPlanScenarioBatchAssociateBugRequest request, String userId) { + SubListUtils.dealForSubList(ids, 500, (subList) -> { + BugRelationCaseExample example = new BugRelationCaseExample(); + example.createCriteria().andTestPlanCaseIdIn(subList).andTestPlanIdEqualTo(request.getTestPlanId()).andBugIdIn(request.getBugIds()); + List bugRelationCases = bugRelationCaseMapper.selectByExample(example); + Map> bugMap = bugRelationCases.stream() + .collect(Collectors.groupingBy( + BugRelationCase::getTestPlanCaseId, + Collectors.mapping(BugRelationCase::getBugId, Collectors.toList()) + )); + Map caseMap = getCaseMap(subList); + List list = new ArrayList<>(); + subList.forEach(item -> { + buildAssociateBugData(item, bugMap, list, request, caseMap, userId); + }); + if (CollectionUtils.isNotEmpty(list)) { + bugRelationCaseMapper.batchInsert(list); + } + }); + } + + private void buildAssociateBugData(String id, Map> bugMap, List list, TestPlanScenarioBatchAssociateBugRequest request, Map caseMap, String userId) { + List bugIds = new ArrayList<>(request.getBugIds()); + if (bugMap.containsKey(id)) { + bugIds.removeAll(bugMap.get(id)); + } + bugIds.forEach(bugId -> { + BugRelationCase bugRelationCase = new BugRelationCase(); + bugRelationCase.setId(IDGenerator.nextStr()); + bugRelationCase.setBugId(bugId); + bugRelationCase.setCaseId(caseMap.get(id)); + bugRelationCase.setCaseType(CaseType.SCENARIO_CASE.getKey()); + bugRelationCase.setCreateUser(userId); + bugRelationCase.setCreateTime(System.currentTimeMillis()); + bugRelationCase.setUpdateTime(System.currentTimeMillis()); + bugRelationCase.setTestPlanCaseId(id); + bugRelationCase.setTestPlanId(request.getTestPlanId()); + list.add(bugRelationCase); + }); + } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java index 6158657f27..da7e8586f5 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanApiScenarioControllerTests.java @@ -17,6 +17,7 @@ import io.metersphere.api.mapper.ApiScenarioReportMapper; import io.metersphere.api.service.scenario.ApiScenarioReportService; import io.metersphere.api.service.scenario.ApiScenarioService; import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.bug.dto.response.BugCustomFieldDTO; import io.metersphere.plan.constants.AssociateCaseType; import io.metersphere.plan.domain.TestPlanApiScenario; import io.metersphere.plan.domain.TestPlanApiScenarioExample; @@ -48,9 +49,11 @@ 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.util.LinkedMultiValueMap; import java.nio.charset.StandardCharsets; import java.util.*; @@ -79,6 +82,8 @@ public class TestPlanApiScenarioControllerTests extends BaseTest { private static final String BUG_ASSOCIATE_PAGE = "/associate/bug/page"; private static final String ASSOCIATE_BUG = "/associate/bug"; private static final String DISASSOCIATE_BUG = "/disassociate/bug/{0}"; + public static final String API_SCENARIO_BATCH_ADD_BUG_URL = "/batch/add-bug"; + public static final String API_SCENARIO_BATCH_ASSOCIATE_BUG_URL = "/batch/associate-bug"; @Resource private TestPlanApiScenarioService testPlanApiScenarioService; @@ -602,4 +607,64 @@ public class TestPlanApiScenarioControllerTests extends BaseTest { void testDisassociateBug() throws Exception { this.requestGet(DISASSOCIATE_BUG, "test_plan_case_id"); } + + @Test + @Order(15) + public void testBatchAddBug() throws Exception { + TestPlanCaseBatchAddBugRequest request = buildRequest(false); + List files = new ArrayList<>(); + LinkedMultiValueMap paramMap = new LinkedMultiValueMap<>(); + paramMap.add("request", JSON.toJSONString(request)); + paramMap.add("files", files); + this.requestMultipartWithOkAndReturn(API_SCENARIO_BATCH_ADD_BUG_URL, paramMap); + + request.setSelectAll(true); + paramMap = new LinkedMultiValueMap<>(); + paramMap.add("request", JSON.toJSONString(request)); + paramMap.add("files", files); + this.requestMultipartWithOkAndReturn(API_SCENARIO_BATCH_ADD_BUG_URL, paramMap); + } + + private TestPlanCaseBatchAddBugRequest buildRequest(boolean isUpdate) { + TestPlanCaseBatchAddBugRequest request = new TestPlanCaseBatchAddBugRequest(); + request.setTestPlanId("wxxx_plan_1"); + request.setProjectId("wxx_project_1234"); + request.setTitle("default-bug-title"); + request.setDescription("default-bug-description"); + request.setTemplateId("default-bug-template"); + request.setLinkFileIds(List.of("default-bug-file-id-1")); + if (isUpdate) { + request.setId("default-bug-id"); + request.setUnLinkRefIds(List.of("default-file-association-id")); + request.setDeleteLocalFileIds(List.of("default-bug-file-id")); + request.setLinkFileIds(List.of("default-bug-file-id-2")); + } + BugCustomFieldDTO fieldDTO1 = new BugCustomFieldDTO(); + fieldDTO1.setId("custom-field"); + fieldDTO1.setName("oasis"); + BugCustomFieldDTO fieldDTO2 = new BugCustomFieldDTO(); + fieldDTO2.setId("test_field"); + fieldDTO2.setName(JSON.toJSONString(List.of("test"))); + BugCustomFieldDTO handleUserField = new BugCustomFieldDTO(); + handleUserField.setId("handleUser"); + handleUserField.setName("处理人"); + handleUserField.setValue("admin"); + BugCustomFieldDTO statusField = new BugCustomFieldDTO(); + statusField.setId("status"); + statusField.setName("状态"); + statusField.setValue("1"); + request.setCustomFields(List.of(fieldDTO1, fieldDTO2, handleUserField, statusField)); + return request; + } + + @Test + @Order(19) + public void testBatchAssociateBug() throws Exception { + TestPlanCaseBatchAssociateBugRequest request = new TestPlanCaseBatchAssociateBugRequest(); + request.setBugIds(Arrays.asList("123456")); + request.setTestPlanId("wxxx_plan_1"); + this.requestPostWithOk(API_SCENARIO_BATCH_ASSOCIATE_BUG_URL, request); + request.setSelectAll(true); + this.requestPostWithOk(API_SCENARIO_BATCH_ASSOCIATE_BUG_URL, request); + } }