diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java index a0ec51d7ab..1e42dd11ec 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanFunctionalCaseController.java @@ -2,6 +2,9 @@ package io.metersphere.plan.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +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.constants.TestPlanResourceConfig; import io.metersphere.plan.dto.request.*; @@ -14,6 +17,7 @@ import io.metersphere.request.AssociateBugPageRequest; import io.metersphere.request.BugPageProviderRequest; import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.sdk.util.BeanUtils; import io.metersphere.system.dto.LogInsertModule; import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.dto.user.UserDTO; @@ -32,6 +36,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.Collections; import java.util.List; @@ -48,6 +53,8 @@ public class TestPlanFunctionalCaseController { private TestPlanManagementService testPlanManagementService; @Resource private TestPlanFunctionalCaseService testPlanFunctionalCaseService; + @Resource + private BugService bugService; @PostMapping(value = "/sort") @Operation(summary = "测试计划功能用例-功能用例拖拽排序") @@ -201,4 +208,19 @@ public class TestPlanFunctionalCaseController { public void batchMove(@Validated @RequestBody BaseBatchMoveRequest request) { testPlanFunctionalCaseService.batchMove(request); } + + + @PostMapping("/batch/add-bug") + @Operation(summary = "测试计划-计划详情-功能用例-批量添加缺陷") + @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_EXECUTE) + @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") + public void batchAddBug(@Validated @RequestPart("request") TestPlanCaseBatchAddBugRequest 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); + testPlanFunctionalCaseService.batchAssociateBug(request, bug.getId(), SessionUtils.getUserId()); + } + + } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCaseBatchAddBugRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCaseBatchAddBugRequest.java new file mode 100644 index 0000000000..ec662ed6a5 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanCaseBatchAddBugRequest.java @@ -0,0 +1,63 @@ +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 TestPlanCaseBatchAddBugRequest 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/service/TestPlanFunctionalCaseService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java index 0e5d4e7ea9..fd8281c37d 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java @@ -723,7 +723,7 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService { public TestPlanCaseDetailResponse getFunctionalCaseDetail(String id, String userId) { TestPlanFunctionalCase planFunctionalCase = testPlanFunctionalCaseMapper.selectByPrimaryKey(id); - if(planFunctionalCase == null){ + if (planFunctionalCase == null) { throw new MSException(Translator.get("resource_not_exist")); } String caseId = planFunctionalCase.getFunctionalCaseId(); @@ -996,6 +996,7 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService { /** * 处理执行人为空过滤参数 + * * @param request 请求参数 */ protected void filterCaseRequest(TestPlanCaseRequest request) { @@ -1008,4 +1009,29 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService { } } } + + public void batchAssociateBug(TestPlanCaseBatchAddBugRequest request, String bugId, String userId) { + List ids = doSelectIds(request); + if (CollectionUtils.isNotEmpty(ids)) { + TestPlanFunctionalCaseExample example = new TestPlanFunctionalCaseExample(); + example.createCriteria().andIdIn(ids); + List caseList = testPlanFunctionalCaseMapper.selectByExample(example); + Map caseMap = caseList.stream().collect(Collectors.toMap(TestPlanFunctionalCase::getId, TestPlanFunctionalCase::getFunctionalCaseId)); + List list = new ArrayList<>(); + ids.forEach(id -> { + BugRelationCase bugRelationCase = new BugRelationCase(); + bugRelationCase.setId(IDGenerator.nextStr()); + bugRelationCase.setBugId(bugId); + bugRelationCase.setCaseId(caseMap.get(id)); + bugRelationCase.setCaseType(CaseType.FUNCTIONAL_CASE.getKey()); + bugRelationCase.setCreateUser(userId); + bugRelationCase.setCreateTime(System.currentTimeMillis()); + bugRelationCase.setUpdateTime(System.currentTimeMillis()); + bugRelationCase.setTestPlanCaseId(id); + bugRelationCase.setTestPlanId(request.getTestPlanId()); + list.add(bugRelationCase); + }); + bugRelationCaseMapper.batchInsert(list); + } + } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java index 6fd93bcc63..b1979d2ed9 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanCaseControllerTests.java @@ -2,6 +2,7 @@ package io.metersphere.plan.controller; import io.metersphere.bug.domain.BugRelationCase; import io.metersphere.bug.domain.BugRelationCaseExample; +import io.metersphere.bug.dto.response.BugCustomFieldDTO; import io.metersphere.bug.mapper.BugRelationCaseMapper; import io.metersphere.dto.BugProviderDTO; import io.metersphere.functional.domain.FunctionalCaseBlob; @@ -33,9 +34,11 @@ import org.junit.jupiter.api.*; import org.mockito.Mockito; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +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.*; @@ -63,6 +66,7 @@ public class TestPlanCaseControllerTests extends BaseTest { public static final String FUNCTIONAL_CASE_EXEC_HISTORY_URL = "/test-plan/functional/case/exec/history"; public static final String USER_URL = "/test-plan/functional/case/user-option/"; public static final String FUNCTIONAL_CASE_BATCH_MOVE_URL = "/test-plan/functional/case/batch/move"; + public static final String FUNCTIONAL_CASE_BATCH_ADD_BUG_URL = "/test-plan/functional/case/batch/add-bug"; @Resource private TestPlanFunctionalCaseMapper testPlanFunctionalCaseMapper; @Resource @@ -284,7 +288,7 @@ public class TestPlanCaseControllerTests extends BaseTest { functionalCaseStepDTO.setNum(1); functionalCaseStepDTO.setDesc("步骤描述"); functionalCaseStepDTO.setResult("结果"); - Listlist = new ArrayList<>(); + List list = new ArrayList<>(); list.add(functionalCaseStepDTO); testPlanCaseExecuteHistory.setSteps(JSON.toJSONString(list).getBytes()); testPlanCaseExecuteHistoryMapper.updateByPrimaryKeySelective(testPlanCaseExecuteHistory); @@ -438,4 +442,53 @@ public class TestPlanCaseControllerTests extends BaseTest { this.requestPostWithOk(FUNCTIONAL_CASE_BATCH_MOVE_URL, request); } + @Test + @Order(19) + 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(FUNCTIONAL_CASE_BATCH_ADD_BUG_URL, paramMap); + + request.setSelectAll(true); + paramMap = new LinkedMultiValueMap<>(); + paramMap.add("request", JSON.toJSONString(request)); + paramMap.add("files", files); + this.requestMultipartWithOkAndReturn(FUNCTIONAL_CASE_BATCH_ADD_BUG_URL, paramMap); + } + + private TestPlanCaseBatchAddBugRequest buildRequest(boolean isUpdate) { + TestPlanCaseBatchAddBugRequest request = new TestPlanCaseBatchAddBugRequest(); + request.setTestPlanId("plan_1"); + request.setProjectId("123"); + 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; + } + } diff --git a/backend/services/test-plan/src/test/resources/dml/init_test_plan_case_relate_bug.sql b/backend/services/test-plan/src/test/resources/dml/init_test_plan_case_relate_bug.sql index 4b2037434a..dff30720d8 100644 --- a/backend/services/test-plan/src/test/resources/dml/init_test_plan_case_relate_bug.sql +++ b/backend/services/test-plan/src/test/resources/dml/init_test_plan_case_relate_bug.sql @@ -74,3 +74,21 @@ VALUES INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`, `case_run_mode`) VALUES ('plan_1', b'0', b'0', 100.00, 'PARALLEL'); + + +INSERT INTO project_application (project_id, type, type_value) VALUES + ('123', 'BUG_DEFAULT_TEMPLATE', 'default-bug-template-id'), + ('123', 'BUG_SYNC_BUG_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraBugTypeId":"10009"}'), + ('123', 'BUG_SYNC_CRON_EXPRESSION', '0 0 0/1 * * ?'), + ('123', 'BUG_SYNC_MECHANISM', 'increment'), + ('123', 'BUG_SYNC_PLATFORM_KEY', 'jira'), + ('123-integration', 'BUG_SYNC_PLATFORM_KEY', 'jira'), + ('123', 'BUG_SYNC_SYNC_ENABLE', 'true'), + ('123', 'CASE_RELATED_CASE_ENABLE', 'false'), + ('123', 'CASE_RELATED_DEMAND_PLATFORM_CONFIG', '{"jiraKey":"TES","jiraDemandTypeId":"10007"}'), + ('123', 'CASE_RELATED_PLATFORM_KEY', 'jira'); + + +INSERT INTO service_integration(`id`, `plugin_id`, `enable`, `configuration`, `organization_id`) VALUES + ('621103810617343', 'jira', true, 0x504B0304140008080800BC517657000000000000000000000000030000007A6970258DC10EC2201044FF65CF06D2C498D89347B5574FBD6D8158222CD85D6268E3BF4BE3F5CDBC990DD0DAC531430FB348E65EEBE06B41AAA9289480CC1E4991130D07C022F3A366D7DA13B2373B32261592469AF1572FCF883E289362CB735BF8A4C5EE073474C3CB8E59A6F85EEFF12AE676EC4E67F8FE00504B0708384DA4307800000087000000504B01021400140008080800BC517657384DA43078000000870000000300000000000000000000000000000000007A6970504B0506000000000100010031000000A90000000000, '1'), + ('652096294625284', 'zentao', true, 0x504B030414000808080093756458000000000000000000000000030000007A6970AB564A4C49294A2D2E56B252CA282929B0D2D7373437D23334D3333230D033B3B4B230B0B050D2514A4C4ECE2FCD2B01AA4A4CC9CDCC038A1424161797E717A500859C1373F2F3D21D8C0C0C4D811245A985A5A9C525219505A940B900C7108F784F3F377FA55A00504B07088A813510680000006C000000504B01021400140008080800937564588A813510680000006C0000000300000000000000000000000000000000007A6970504B0506000000000100010031000000990000000000, '1');