feat(测试计划): 新增批量取消关联用例的接口

This commit is contained in:
guoyuqi 2024-05-11 18:47:10 +08:00 committed by 刘瑞斌
parent 472e7ae5eb
commit eae3398f94
8 changed files with 194 additions and 6 deletions

View File

@ -1,10 +1,9 @@
package io.metersphere.plan.controller;
import com.alibaba.excel.util.StringUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.functional.request.ReviewFunctionalCasePageRequest;
import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
@ -86,4 +85,13 @@ public class TestPlanFunctionalCaseController {
public Map<String, Long> moduleCount(@Validated @RequestBody TestPlanCaseRequest request) {
return testPlanFunctionalCaseService.moduleCount(request);
}
@PostMapping("/batch/disassociate")
@Operation(summary = "测试计划-计划详情-列表-批量取消关联用例")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_ASSOCIATION)
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
public TestPlanAssociationResponse batchDisassociate(@Validated @RequestBody BasePlanCaseBatchRequest request) {
testPlanManagementService.checkModuleIsOpen(request.getTestPlanId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN_FUNCTIONAL_CASE));
return testPlanFunctionalCaseService.disassociate(request, new LogInsertModule(SessionUtils.getUserId(), "/test-plan/functional/case/association", HttpMethodConstants.POST.name()));
}
}

View File

@ -0,0 +1,27 @@
package io.metersphere.plan.dto.request;
import io.metersphere.system.dto.table.TableBatchProcessDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author wx
*/
@Data
public class BasePlanCaseBatchRequest extends TableBatchProcessDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "测试计划id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan.id.not_blank}")
private String testPlanId;
@Schema(description = "模块id")
private List<String> moduleIds;
}

View File

@ -5,6 +5,7 @@ import io.metersphere.functional.dto.FunctionalCaseModuleDTO;
import io.metersphere.functional.dto.ProjectOptionDTO;
import io.metersphere.plan.dto.AssociationNode;
import io.metersphere.plan.dto.ResourceSelectParam;
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
import io.metersphere.plan.dto.response.TestPlanCasePageResponse;
import io.metersphere.project.dto.NodeSortQueryParam;
@ -35,4 +36,6 @@ public interface ExtTestPlanFunctionalCaseMapper {
List<FunctionalCaseModuleCountDTO> countModuleIdByRequest(@Param("request") TestPlanCaseRequest request, @Param("deleted") boolean deleted);
long caseCount(@Param("request") TestPlanCaseRequest request, @Param("deleted") boolean deleted);
List<String> getIds(@Param("request") BasePlanCaseBatchRequest request, @Param("deleted") boolean deleted);
}

View File

@ -44,6 +44,19 @@
ORDER BY #{orderString}
</if>
</select>
<select id="getIds" resultType="java.lang.String">
SELECT
tpfc.id as id
FROM
test_plan_functional_case tpfc
LEFT JOIN functional_case ON tpfc.functional_case_id = functional_case.id
WHERE
tpfc.test_plan_id = #{request.testPlanId}
AND functional_case.deleted = #{deleted}
<include refid="queryWhereConditionByBatchQueryRequest"/>
</select>
<select id="selectDragInfoById" resultType="io.metersphere.plan.dto.AssociationNode">
SELECT id, pos
FROM test_plan_functional_case
@ -138,6 +151,35 @@
</include>
</sql>
<sql id="queryWhereConditionByBatchQueryRequest">
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and functional_case.module_id in
<foreach collection="request.moduleIds" item="moduleId" separator="," open="(" close=")">
#{moduleId}
</foreach>
</if>
<if test="request.condition.keyword != null">
and (
functional_case.name like concat('%', #{request.keyword},'%')
or functional_case.num like concat('%', #{request.keyword},'%')
or functional_case.tags like concat('%', #{request.keyword},'%')
)
</if>
<include refid="filters">
<property name="filter" value="request.condition.filter"/>
</include>
<choose>
<when test='request.condition.searchMode == "AND"'>
AND <include refid="batchQueryCombine"/>
</when>
<when test='request.condition.searchMode == "OR"'>
and (
<include refid="batchQueryCombine"/>
)
</when>
</choose>
</sql>
<sql id="filters">
<if test="${filter} != null and ${filter}.size() > 0">
<foreach collection="${filter}.entrySet()" index="key" item="values">
@ -202,6 +244,16 @@
</if>
</sql>
<sql id="batchQueryCombine">
<if test="request.condition.combine != null">
<include refid="combine">
<property name="condition" value="request.condition.combine"/>
<property name="searchMode" value="request.condition.searchMode"/>
</include>
</if>
1=1
</sql>
<sql id="queryCombine">
<if test="request.combine != null">
<include refid="combine">

View File

@ -2,10 +2,10 @@ package io.metersphere.plan.service;
import io.metersphere.bug.dto.CaseRelateBugDTO;
import io.metersphere.bug.mapper.ExtBugRelateCaseMapper;
import io.metersphere.functional.domain.FunctionalCaseModule;
import io.metersphere.functional.dto.FunctionalCaseCustomFieldDTO;
import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO;
import io.metersphere.functional.dto.FunctionalCaseModuleDTO;
import io.metersphere.functional.domain.FunctionalCaseModule;
import io.metersphere.functional.dto.ProjectOptionDTO;
import io.metersphere.functional.service.FunctionalCaseService;
import io.metersphere.plan.domain.TestPlan;
@ -13,6 +13,8 @@ import io.metersphere.plan.domain.TestPlanFunctionalCase;
import io.metersphere.plan.domain.TestPlanFunctionalCaseExample;
import io.metersphere.plan.dto.AssociationNodeSortDTO;
import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
@ -42,6 +44,7 @@ import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.*;
import java.util.stream.Collectors;
@ -120,6 +123,13 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
}
public void deleteTestPlanResource(@Validated TestPlanResourceAssociationParam associationParam) {
TestPlanFunctionalCaseExample testPlanFunctionalCaseExample = new TestPlanFunctionalCaseExample();
testPlanFunctionalCaseExample.createCriteria().andIdIn(associationParam.getResourceIdList());
testPlanFunctionalCaseMapper.deleteByExample(testPlanFunctionalCaseExample);
//TODO:更新执行历史的删除状态为true
}
public TestPlanResourceSortResponse sortNode(ResourceSortRequest request, LogInsertModule logInsertModule) {
TestPlanFunctionalCase dragNode = testPlanFunctionalCaseMapper.selectByPrimaryKey(request.getDragNodeId());
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestPlanId());
@ -255,4 +265,26 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
}
public TestPlanAssociationResponse disassociate(BasePlanCaseBatchRequest request, LogInsertModule logInsertModule) {
List<String> selectIds = doSelectIds(request);
return super.disassociate(
TestPlanResourceConstants.RESOURCE_FUNCTIONAL_CASE,
request,
logInsertModule,
selectIds,
this::deleteTestPlanResource);
}
private List<String> doSelectIds(BasePlanCaseBatchRequest request) {
if (request.isSelectAll()) {
List<String> ids = extTestPlanFunctionalCaseMapper.getIds(request, false);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
ids.removeAll(request.getExcludeIds());
}
return ids;
} else {
return request.getSelectIds();
}
}
}

View File

@ -3,6 +3,7 @@ package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanConfig;
import io.metersphere.plan.dto.*;
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
@ -86,6 +87,29 @@ public abstract class TestPlanResourceService {
return response;
}
/**
* 取消关联资源od
*
* @return TestPlanAssociationResponse
*/
public TestPlanAssociationResponse disassociate(
String resourceType,
BasePlanCaseBatchRequest request,
@Validated LogInsertModule logInsertModule,
List<String>associationIdList,
Consumer<TestPlanResourceAssociationParam> disassociate) {
TestPlanAssociationResponse response = new TestPlanAssociationResponse();
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestPlanId());
//获取有效ID
if (CollectionUtils.isNotEmpty(associationIdList)) {
TestPlanResourceAssociationParam associationParam = new TestPlanResourceAssociationParam(associationIdList, testPlan.getProjectId(), testPlan.getId(), testPlan.getNum(), logInsertModule.getOperator());
disassociate.accept(associationParam);
response.setAssociationCount(associationIdList.size());
testPlanResourceLogService.saveDeleteLog(testPlan, new ResourceLogInsertModule(resourceType, logInsertModule));
}
return response;
}
/**
* 构建节点排序的参数
*

View File

@ -1,9 +1,14 @@
package io.metersphere.plan.controller;
import io.metersphere.plan.domain.TestPlanFunctionalCase;
import io.metersphere.plan.domain.TestPlanFunctionalCaseExample;
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
@ -13,6 +18,7 @@ import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -23,6 +29,11 @@ public class TestPlanCaseControllerTests extends BaseTest {
public static final String FUNCTIONAL_CASE_TREE_URL = "/test-plan/functional/case/tree/";
public static final String FUNCTIONAL_CASE_TREE_COUNT_URL = "/test-plan/functional/case/module/count";
public static final String FUNCTIONAL_CASE_DISASSOCIATE_URL = "/test-plan/functional/case/batch/disassociate";
@Resource
private TestPlanFunctionalCaseMapper testPlanFunctionalCaseMapper;
@Test
@Order(1)
@Sql(scripts = {"/dml/init_test_plan_case_relate_bug.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
@ -69,4 +80,25 @@ public class TestPlanCaseControllerTests extends BaseTest {
}
@Test
@Order(4)
public void disassociateBatch() throws Exception {
BasePlanCaseBatchRequest request = new BasePlanCaseBatchRequest();
request.setTestPlanId("gyq_disassociate_plan_1");
request.setSelectAll(true);
request.setExcludeIds(List.of("gyq_disassociate_case_2"));
this.requestPostWithOk(FUNCTIONAL_CASE_DISASSOCIATE_URL, request);
TestPlanFunctionalCaseExample testPlanFunctionalCaseExample = new TestPlanFunctionalCaseExample();
testPlanFunctionalCaseExample.createCriteria().andTestPlanIdEqualTo("gyq_disassociate_plan_1");
List<TestPlanFunctionalCase> testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(testPlanFunctionalCaseExample);
Assertions.assertEquals(1,testPlanFunctionalCases.size());
request = new BasePlanCaseBatchRequest();
request.setTestPlanId("gyq_disassociate_plan_1");
request.setSelectAll(false);
request.setSelectIds(List.of("gyq_disassociate_case_2"));
this.requestPostWithOk(FUNCTIONAL_CASE_DISASSOCIATE_URL, request);
testPlanFunctionalCases = testPlanFunctionalCaseMapper.selectByExample(testPlanFunctionalCaseExample);
Assertions.assertEquals(0,testPlanFunctionalCases.size());
}
}

View File

@ -9,23 +9,33 @@ VALUES ('bug_1', 100001, 'oasis', 'admin', 'admin', 'admin', UNIX_TIMESTAMP() *
INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`)
VALUES
('plan_1', 20000, '123', 'wx_test_plan_id_3', 't_1', '测试关联', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('plan_2', 25000, '1234', 'wx_test_plan_id_3', 'root', '测试关联1', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
('plan_2', 25000, '1234', 'wx_test_plan_id_3', 'root', '测试关联1', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
('gyq_disassociate_plan_1', 25000, 'gyq_disassociate', 'wx_test_plan_id_3', 'root', '测试取消关联1', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`)
VALUES
('relate_case_1', 'plan_1', 'fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1),
('relate_case_2', 'plan_1', 'fc_2', 1714980158000, 'admin', NULL, NULL, NULL, 1),
('relate_case_3', 'plan_2', 'fc_3', 1714980158000, 'admin', NULL, NULL, NULL, 1);
('relate_case_3', 'plan_2', 'fc_3', 1714980158000, 'admin', NULL, NULL, NULL, 1),
('gyq_disassociate_case_1', 'gyq_disassociate_plan_1', 'gyq_disassociate_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1),
('gyq_disassociate_case_2', 'gyq_disassociate_plan_1', 'gyq_disassociate_fc_2', 1714980158000, 'admin', NULL, NULL, NULL, 1);
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
VALUES
('fc_1', 1, 't_1', '123', '100001', '111', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
('fc_2', 2, 't_1', '123', '100001', '222', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
('fc_3', 3, 'root', '123', '100001', '333', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
('fc_3', 3, 'root', '123', '100001', '333', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
('gyq_disassociate_fc_1', 4, 'root', 'gyq_disassociate', '100001', 'disassociate1', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'gyq_disassociate_fc_1', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
('gyq_disassociate_fc_2', 5, 'root', 'gyq_disassociate', '100001', 'disassociate2', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'gyq_disassociate_fc_2', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
INSERT INTO project_version(id, project_id, name, description, status, latest, publish_time, start_time, end_time, create_time, create_user)
values ('v3.0.0','123','v3', null, 'open', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO project(id, num, organization_id, name, description, create_time, update_time, update_user, create_user, delete_time, deleted, delete_user, enable, module_setting)
VALUE ('gyq_disassociate', null, 'organization_plan_associate_case_project', 'test_plan_associate_case_name', null,
UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'admin', null, false, null, true, '["bugManagement","caseManagement","apiTest","testPlan"]');
INSERT INTO `test_plan_module`(`id`, `project_id`, `name`, `parent_id`, `pos`, `create_time`, `update_time`, `create_user`, `update_user`)
VALUES