diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlan.java b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlan.java index 08bd4a9219..79eb70c6d3 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlan.java +++ b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlan.java @@ -4,13 +4,13 @@ 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.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; @Data public class TestPlan implements Serializable { @@ -53,7 +53,7 @@ public class TestPlan implements Serializable { private String type; @Schema(description = "标签") - private List tags; + private java.util.List tags; @Schema(description = "创建时间") private Long createTime; @@ -79,9 +79,13 @@ public class TestPlan implements Serializable { @Schema(description = "实际结束时间") private Long actualEndTime; - @Schema(description = "描述;描述") + @Schema(description = "描述") private String description; + @Schema(description = "自定义排序", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{test_plan.pos.not_blank}", groups = {Created.class}) + private Long pos; + private static final long serialVersionUID = 1L; public enum Column { @@ -102,7 +106,8 @@ public class TestPlan implements Serializable { plannedEndTime("planned_end_time", "plannedEndTime", "BIGINT", false), actualStartTime("actual_start_time", "actualStartTime", "BIGINT", false), actualEndTime("actual_end_time", "actualEndTime", "BIGINT", false), - description("description", "description", "VARCHAR", false); + description("description", "description", "VARCHAR", false), + pos("pos", "pos", "BIGINT", false); private static final String BEGINNING_DELIMITER = "`"; diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanExample.java b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanExample.java index 41cfa4d4ea..98cc24d059 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanExample.java +++ b/backend/framework/domain/src/main/java/io/metersphere/plan/domain/TestPlanExample.java @@ -1327,6 +1327,66 @@ public class TestPlanExample { addCriterion("description not between", value1, value2, "description"); return (Criteria) this; } + + public Criteria andPosIsNull() { + addCriterion("pos is null"); + return (Criteria) this; + } + + public Criteria andPosIsNotNull() { + addCriterion("pos is not null"); + return (Criteria) this; + } + + public Criteria andPosEqualTo(Long value) { + addCriterion("pos =", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosNotEqualTo(Long value) { + addCriterion("pos <>", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosGreaterThan(Long value) { + addCriterion("pos >", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosGreaterThanOrEqualTo(Long value) { + addCriterion("pos >=", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosLessThan(Long value) { + addCriterion("pos <", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosLessThanOrEqualTo(Long value) { + addCriterion("pos <=", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosIn(List values) { + addCriterion("pos in", values, "pos"); + return (Criteria) this; + } + + public Criteria andPosNotIn(List values) { + addCriterion("pos not in", values, "pos"); + return (Criteria) this; + } + + public Criteria andPosBetween(Long value1, Long value2) { + addCriterion("pos between", value1, value2, "pos"); + return (Criteria) this; + } + + public Criteria andPosNotBetween(Long value1, Long value2) { + addCriterion("pos not between", value1, value2, "pos"); + return (Criteria) this; + } } public static class Criteria extends GeneratedCriteria { diff --git a/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanMapper.xml index e4ea2345a7..c4c429732b 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanMapper.xml +++ b/backend/framework/domain/src/main/java/io/metersphere/plan/mapper/TestPlanMapper.xml @@ -20,6 +20,7 @@ + @@ -120,7 +121,7 @@ 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 + actual_end_time, description, pos @@ -346,6 +353,9 @@ description = #{record.description,jdbcType=VARCHAR}, + + pos = #{record.pos,jdbcType=BIGINT}, + @@ -370,7 +380,8 @@ planned_end_time = #{record.plannedEndTime,jdbcType=BIGINT}, actual_start_time = #{record.actualStartTime,jdbcType=BIGINT}, actual_end_time = #{record.actualEndTime,jdbcType=BIGINT}, - description = #{record.description,jdbcType=VARCHAR} + description = #{record.description,jdbcType=VARCHAR}, + pos = #{record.pos,jdbcType=BIGINT} @@ -429,6 +440,9 @@ description = #{description,jdbcType=VARCHAR}, + + pos = #{pos,jdbcType=BIGINT}, + where id = #{id,jdbcType=VARCHAR} @@ -450,14 +464,15 @@ planned_end_time = #{plannedEndTime,jdbcType=BIGINT}, actual_start_time = #{actualStartTime,jdbcType=BIGINT}, actual_end_time = #{actualEndTime,jdbcType=BIGINT}, - description = #{description,jdbcType=VARCHAR} + description = #{description,jdbcType=VARCHAR}, + pos = #{pos,jdbcType=BIGINT} where id = #{id,jdbcType=VARCHAR} 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) + actual_end_time, description, pos) values (#{item.id,jdbcType=VARCHAR}, #{item.num,jdbcType=BIGINT}, #{item.projectId,jdbcType=VARCHAR}, @@ -466,7 +481,8 @@ #{item.createTime,jdbcType=BIGINT}, #{item.createUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT}, #{item.updateUser,jdbcType=VARCHAR}, #{item.plannedStartTime,jdbcType=BIGINT}, #{item.plannedEndTime,jdbcType=BIGINT}, #{item.actualStartTime,jdbcType=BIGINT}, - #{item.actualEndTime,jdbcType=BIGINT}, #{item.description,jdbcType=VARCHAR}) + #{item.actualEndTime,jdbcType=BIGINT}, #{item.description,jdbcType=VARCHAR}, #{item.pos,jdbcType=BIGINT} + ) @@ -533,6 +549,9 @@ #{item.description,jdbcType=VARCHAR} + + #{item.pos,jdbcType=BIGINT} + ) diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql index 019d16c07e..e2d40a34b8 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_13__ga_ddl.sql @@ -11,6 +11,10 @@ CREATE TABLE IF NOT EXISTS platform_source( DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '平台对接保存参数'; +-- 测试计划增加排序字段 +alter table test_plan + ADD COLUMN `pos` BIGINT NOT NULL DEFAULT 0 COMMENT '自定义排序'; + -- 修改计划配置表 ALTER TABLE test_plan_config DROP `test_planning`; diff --git a/backend/framework/sdk/src/main/resources/i18n/plan.properties b/backend/framework/sdk/src/main/resources/i18n/plan.properties index e4d0f43302..07a3050149 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan.properties @@ -86,11 +86,25 @@ test_plan_principal.user_id.not_blank=用户id不能为空 test_plan_report_content.id.not_blank=测试计划报告内容id不能为空 test_plan_report_content.test_plan_report_id.length_range=测试计划报告id长度过长 test_plan_report_content.test_plan_report_id.not_blank=测试计划报告id不能为空 +log.delete.test_plan=删除测试计划 +log.delete.test_plan_group=删除测试计划组 +log.test_plan.add=关联了资源 +log.test_plan.remove=移除了资源 +log.test_plan.move=移动了资源 +log.test_plan.move.test_plan=移动了测试计划 +log.test_plan.update=修改了资源 +log.test_plan.functional_case=功能用例 +log.test_plan.api_case=接口用例 +log.test_plan.api_scenario=接口场景 +test_plan.type.not_blank=测试计划类型不能为空 +test_plan.group.not_plan=当前测试计划组没有可归档计划 +test_plan.group.error=不是合法的测试计划组 test_plan_group.batch.log={0}测试计划组 test_plan.batch.log={0}测试计划 test_plan_report_not_exist=测试计划报告不存在 test_plan_report_id.not_blank=测试计划报告id不能为空 test_plan_report_name.not_blank=测试计划报告名称不能为空 +run_functional_case=执行功能用例 test_plan_not_exist=测试计划不存在 test_plan.report_id.not_blank=测试计划报告ID不能为空 test_plan.report.share_id.not_blank=测试计划报告分享ID不能为空 diff --git a/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties index 9e309a7d9f..0aa5888bd7 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan_en_US.properties @@ -91,12 +91,14 @@ log.delete.test_plan_group=Test plan group deleted log.test_plan.add=Association resources log.test_plan.remove=Remove resource log.test_plan.move=Move resources +log.test_plan.move.test_plan=Move test plan log.test_plan.update=Update resources log.test_plan.functional_case=Functional case log.test_plan.api_case=Api case log.test_plan.api_scenario=Api scenario test_plan.type.not_blank=Test plan type cannot be empty test_plan.group.not_plan=There are no archived plans in the current testing plan group +test_plan.group.error=Test plan group error test_plan_group.batch.log={0} test plan group test_plan.batch.log={0} plan test_plan_report_not_exist=The test plan report does not exist diff --git a/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties index a900dd2935..07a3050149 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan_zh_CN.properties @@ -91,12 +91,14 @@ log.delete.test_plan_group=删除测试计划组 log.test_plan.add=关联了资源 log.test_plan.remove=移除了资源 log.test_plan.move=移动了资源 +log.test_plan.move.test_plan=移动了测试计划 log.test_plan.update=修改了资源 log.test_plan.functional_case=功能用例 log.test_plan.api_case=接口用例 log.test_plan.api_scenario=接口场景 test_plan.type.not_blank=测试计划类型不能为空 test_plan.group.not_plan=当前测试计划组没有可归档计划 +test_plan.group.error=不是合法的测试计划组 test_plan_group.batch.log={0}测试计划组 test_plan.batch.log={0}测试计划 test_plan_report_not_exist=测试计划报告不存在 diff --git a/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties index db044d7b8f..7ed5178362 100644 --- a/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/plan_zh_TW.properties @@ -91,12 +91,14 @@ log.delete.test_plan_group=刪除測試計劃組 log.test_plan.add=關聯了資源 log.test_plan.remove=移除了資源 log.test_plan.move=移動了資源 +log.test_plan.move.test_plan=移動了測試計劃 log.test_plan.update=修改了資源 log.test_plan.functional_case=功能用例 log.test_plan.api_case=接口用例 log.test_plan.api_scenario=接口場景 test_plan.type.not_blank=測試計劃類型不能為空 test_plan.group.not_plan=當前測試計劃組沒有可歸檔計劃 +test_plan.group.error=不是合法的測試計劃組 test_plan_group.batch.log={0}測試計劃組 test_plan.batch.log={0}測試計劃 test_plan_report_not_exist=測試計劃報告不存在 diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java index d9a26996a4..3f6579f58c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java @@ -1,6 +1,5 @@ package io.metersphere.api.service.debug; -import io.metersphere.sdk.constants.ApiFileResourceType; import io.metersphere.api.constants.ApiResourceType; import io.metersphere.api.domain.ApiDebug; import io.metersphere.api.domain.ApiDebugBlob; @@ -24,6 +23,7 @@ import io.metersphere.project.domain.FileMetadata; import io.metersphere.project.dto.MoveNodeSortDTO; import io.metersphere.project.service.MoveNodeService; import io.metersphere.project.service.ProjectService; +import io.metersphere.sdk.constants.ApiFileResourceType; import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.dto.api.task.TaskRequestDTO; import io.metersphere.sdk.exception.MSException; @@ -265,7 +265,7 @@ public class ApiDebugService extends MoveNodeService { } public void moveNode(PosRequest posRequest) { - NodeMoveRequest request = super.getNodeMoveRequest(posRequest); + NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true); MoveNodeSortDTO sortDTO = super.getNodeSortDTO( posRequest.getProjectId(), request, diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java index 27d936366e..5d42d35cf9 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java @@ -25,7 +25,10 @@ import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.service.EnvironmentService; import io.metersphere.project.service.MoveNodeService; import io.metersphere.project.service.ProjectService; -import io.metersphere.sdk.constants.*; +import io.metersphere.sdk.constants.ApiFileResourceType; +import io.metersphere.sdk.constants.ApplicationNumScope; +import io.metersphere.sdk.constants.DefaultRepositoryDir; +import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.domain.OperationLogBlob; import io.metersphere.sdk.dto.api.task.TaskRequestDTO; import io.metersphere.sdk.exception.MSException; @@ -1062,7 +1065,7 @@ public class ApiDefinitionService extends MoveNodeService { } public void moveNode(PosRequest posRequest) { - NodeMoveRequest request = super.getNodeMoveRequest(posRequest); + NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true); MoveNodeSortDTO sortDTO = super.getNodeSortDTO( posRequest.getProjectId(), request, diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java index 5366dab70a..5a86dde557 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java @@ -826,7 +826,7 @@ public class ApiTestCaseService extends MoveNodeService { } public void moveNode(PosRequest posRequest) { - NodeMoveRequest request = super.getNodeMoveRequest(posRequest); + NodeMoveRequest request = super.getNodeMoveRequest(posRequest,true); MoveNodeSortDTO sortDTO = super.getNodeSortDTO( posRequest.getProjectId(), request, diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java index 7f70c34f55..422237112c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java @@ -2997,7 +2997,7 @@ public class ApiScenarioService extends MoveNodeService { } public void moveNode(PosRequest posRequest) { - NodeMoveRequest request = super.getNodeMoveRequest(posRequest); + NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true); MoveNodeSortDTO sortDTO = super.getNodeSortDTO( posRequest.getProjectId(), request, diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/dto/MoveNodeSortDTO.java b/backend/services/project-management/src/main/java/io/metersphere/project/dto/MoveNodeSortDTO.java index 43b8d0cbe6..95602ed866 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/dto/MoveNodeSortDTO.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/dto/MoveNodeSortDTO.java @@ -7,8 +7,8 @@ import lombok.Data; @Data @AllArgsConstructor public class MoveNodeSortDTO { - @Schema(description = "项目ID") - private String projectId; + @Schema(description = "排序范围ID") + private String sortRangeId; @Schema(description = "要排序的节点") private DropNode sortNode; @@ -18,6 +18,4 @@ public class MoveNodeSortDTO { @Schema(description = "后一个节点") private DropNode nextNode; - -} - +} \ No newline at end of file diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentGroupService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentGroupService.java index 821fa7ff9d..eb9ac67ed1 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentGroupService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentGroupService.java @@ -273,7 +273,7 @@ public class EnvironmentGroupService extends MoveNodeService{ } public void moveNode(PosRequest posRequest) { - NodeMoveRequest request = super.getNodeMoveRequest(posRequest); + NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true); MoveNodeSortDTO sortDTO = super.getNodeSortDTO( posRequest.getProjectId(), request, diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentService.java index 66949ee56a..0d11c2d683 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/EnvironmentService.java @@ -564,7 +564,7 @@ public class EnvironmentService extends MoveNodeService { } public void moveNode(PosRequest posRequest) { - NodeMoveRequest request = super.getNodeMoveRequest(posRequest); + NodeMoveRequest request = super.getNodeMoveRequest(posRequest,true); MoveNodeSortDTO sortDTO = super.getNodeSortDTO( posRequest.getProjectId(), request, diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java index 7b6f590834..2fef605aef 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/ModuleTreeService.java @@ -27,9 +27,6 @@ public abstract class ModuleTreeService { protected static final long LIMIT_POS = NodeSortUtils.DEFAULT_NODE_INTERVAL_POS; - //默认节点所在分支最大数量不超过200, 后续会根据需求排期进行调整 - protected static final int MAX_BRANCHES_NODE_SIZE = 200; - public BaseTreeNode getDefaultModule(String name) { //默认模块下不允许创建子模块。 它本身也就是叶子节点。 return new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, name, ModuleConstants.NODE_TYPE_DEFAULT, ModuleConstants.ROOT_NODE_PARENT_ID); diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/MoveNodeService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/MoveNodeService.java index 0251de8d65..6c20f75369 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/MoveNodeService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/MoveNodeService.java @@ -7,7 +7,6 @@ import io.metersphere.project.dto.NodeSortQueryParam; import io.metersphere.project.utils.NodeSortUtils; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.Translator; -import io.metersphere.system.dto.sdk.enums.MoveTypeEnum; import io.metersphere.system.dto.sdk.request.NodeMoveRequest; import io.metersphere.system.dto.sdk.request.PosRequest; import org.apache.commons.lang3.StringUtils; @@ -28,23 +27,29 @@ public abstract class MoveNodeService { private static final String MOVE_POS_OPERATOR_MORE = "moreThan"; private static final String DRAG_NODE_NOT_EXIST = "drag_node.not.exist"; - public NodeMoveRequest getNodeMoveRequest(PosRequest posRequest) { + /** + * 构建节点移动的请求参数 + * + * @param posRequest 拖拽的前端请求参数 + * @param isDesc 是否是降序排列 + */ + public NodeMoveRequest getNodeMoveRequest(PosRequest posRequest, boolean isDesc) { NodeMoveRequest request = new NodeMoveRequest(); request.setDragNodeId(posRequest.getMoveId()); request.setDropNodeId(posRequest.getTargetId()); - request.setDropPosition(StringUtils.equals(MoveTypeEnum.AFTER.name(), posRequest.getMoveMode()) ? -1 : 1); + request.setAndConvertDropPosition(posRequest.getMoveMode(), isDesc); return request; } /** * 构建节点排序的参数 * + * @param sortRangeId 排序范围ID * @param request 拖拽的前端请求参数 * @param selectIdNodeFunc 通过id查询节点的函数 * @param selectPosNodeFunc 通过parentId和pos运算符查询节点的函数 - * @return */ - public MoveNodeSortDTO getNodeSortDTO(String projectId , NodeMoveRequest request, Function selectIdNodeFunc, Function selectPosNodeFunc) { + public MoveNodeSortDTO getNodeSortDTO(String sortRangeId, NodeMoveRequest request, Function selectIdNodeFunc, Function selectPosNodeFunc) { if (StringUtils.equals(request.getDragNodeId(), request.getDropNodeId())) { //两种节点不能一样 throw new MSException(Translator.get("invalid_parameter") + ": drag node and drop node"); @@ -69,7 +74,7 @@ public abstract class MoveNodeService { NodeSortQueryParam sortParam = new NodeSortQueryParam(); sortParam.setPos(previousNode.getPos()); sortParam.setOperator(MOVE_POS_OPERATOR_MORE); - sortParam.setParentId(projectId); + sortParam.setParentId(sortRangeId); nextNode = selectPosNodeFunc.apply(sortParam); } else if (request.getDropPosition() == -1) { //dropPosition=-1: 放到dropNode节点前,原dropNode前面的节点之后 @@ -77,29 +82,26 @@ public abstract class MoveNodeService { NodeSortQueryParam sortParam = new NodeSortQueryParam(); sortParam.setPos(nextNode.getPos()); sortParam.setOperator(MOVE_POS_OPERATOR_LESS); - sortParam.setParentId(projectId); + sortParam.setParentId(sortRangeId); previousNode = selectPosNodeFunc.apply(sortParam); } else { throw new MSException(Translator.get("invalid_parameter") + ": dropPosition"); } - return new MoveNodeSortDTO(projectId, dragNode, previousNode, nextNode); + return new MoveNodeSortDTO(sortRangeId, dragNode, previousNode, nextNode); } //排序 public void sort(MoveNodeSortDTO sortDTO) { - // 获取相邻节点 DropNode previousNode = sortDTO.getPreviousNode(); DropNode nextNode = sortDTO.getNextNode(); - ModuleSortCountResultDTO countResultDTO = NodeSortUtils.countModuleSort( previousNode == null ? -1 : previousNode.getPos(), nextNode == null ? -1 : nextNode.getPos()); - updatePos(sortDTO.getSortNode().getId(), countResultDTO.getPos()); if (countResultDTO.isRefreshPos()) { - refreshPos(sortDTO.getProjectId()); + refreshPos(sortDTO.getSortRangeId()); } } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/utils/NodeSortUtils.java b/backend/services/project-management/src/main/java/io/metersphere/project/utils/NodeSortUtils.java index 74cdc40a1a..00a1d44418 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/utils/NodeSortUtils.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/utils/NodeSortUtils.java @@ -5,7 +5,7 @@ import io.metersphere.project.dto.ModuleSortCountResultDTO; public class NodeSortUtils { //默认节点间隔 - public static final long DEFAULT_NODE_INTERVAL_POS = 64; + public static final long DEFAULT_NODE_INTERVAL_POS = 4096; /** * 计算模块排序 diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/NodeMoveRequest.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/NodeMoveRequest.java index 168b8426ff..d4a409b85c 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/NodeMoveRequest.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/NodeMoveRequest.java @@ -1,8 +1,10 @@ package io.metersphere.system.dto.sdk.request; +import io.metersphere.system.dto.sdk.enums.MoveTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import lombok.Data; +import org.apache.commons.lang3.StringUtils; @Data public class NodeMoveRequest { @@ -16,5 +18,13 @@ public class NodeMoveRequest { @Schema(description = "放入的位置(取值:-1,,1。 -1:dropNodeId节点之前。 1:dropNodeId节点后)", requiredMode = Schema.RequiredMode.REQUIRED) private int dropPosition; + + public void setAndConvertDropPosition(String position, boolean isSortDesc) { + if (StringUtils.equals(MoveTypeEnum.BEFORE.name(), position)) { + this.dropPosition = isSortDesc ? 1 : -1; + } else { + this.dropPosition = isSortDesc ? -1 : 1; + } + } } diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/PosRequest.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/PosRequest.java index bde860feeb..be04786880 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/PosRequest.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/sdk/request/PosRequest.java @@ -2,8 +2,10 @@ package io.metersphere.system.dto.sdk.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import java.io.Serial; import java.io.Serializable; @@ -13,6 +15,8 @@ import java.io.Serializable; */ @Data @EqualsAndHashCode(callSuper = false) +@NoArgsConstructor +@AllArgsConstructor public class PosRequest implements Serializable { @Serial @@ -22,6 +26,10 @@ public class PosRequest implements Serializable { @NotBlank(message = "{case_review.project_id.not_blank}") private String projectId; + @Schema(description = "移动用例id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{functional_case_relationship_edge.source_id.not_blank}") + private String moveId; + @Schema(description = "目标用例id", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank(message = "{functional_case_relationship_edge.target_id.not_blank}") private String targetId; @@ -29,8 +37,4 @@ public class PosRequest implements Serializable { @Schema(description = "移动类型", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"BEFORE", "AFTER", "APPEND"}) @NotBlank(message = "{case_review.moveMode.not_blank}") private String moveMode; - - @Schema(description = "移动用例id", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "{functional_case_relationship_edge.source_id.not_blank}") - private String moveId; } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java index 131bd2f5e8..167996aad8 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java @@ -4,11 +4,14 @@ import io.metersphere.plan.constants.TestPlanResourceConfig; import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanDetailResponse; +import io.metersphere.plan.dto.response.TestPlanResourceSortResponse; import io.metersphere.plan.dto.response.TestPlanResponse; import io.metersphere.plan.dto.response.TestPlanStatisticsResponse; import io.metersphere.plan.service.*; import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.system.dto.LogInsertModule; +import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.log.annotation.Log; import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.notice.annotation.SendNotice; @@ -193,4 +196,13 @@ public class TestPlanController { testPlanService.filterArchivedIds(request); testPlanService.batchEdit(request, SessionUtils.getUserId()); } + + @PostMapping(value = "/sort") + @Operation(summary = "测试计划移动(测试计划拖进、拖出到测试计划组、测试计划在测试计划组内的排序") + @RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE) + @CheckOwner(resourceId = "#request.getMoveId()", resourceType = "test_plan") + public TestPlanResourceSortResponse sortTestPlan(@Validated @RequestBody PosRequest request) { + testPlanManagementService.checkModuleIsOpen(request.getMoveId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN)); + return testPlanService.sortInGroup(request, new LogInsertModule(SessionUtils.getUserId(), "/test-plan/move", HttpMethodConstants.POST.name())); + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/AssociationNode.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/AssociationNode.java deleted file mode 100644 index 8837f95c23..0000000000 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/AssociationNode.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.metersphere.plan.dto; - -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class AssociationNode { - private String id; - private long pos; -} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/AssociationNodeSortDTO.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/AssociationNodeSortDTO.java deleted file mode 100644 index 1b26f244b1..0000000000 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/AssociationNodeSortDTO.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.metersphere.plan.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor -public class AssociationNodeSortDTO { - @Schema(description = "测试计划ID") - private String testPlanId; - - @Schema(description = "要排序的节点") - private AssociationNode sortNode; - - @Schema(description = "前一个节点") - private AssociationNode previousNode; - - @Schema(description = "后一个节点") - private AssociationNode nextNode; - -} - diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResourceSortResponse.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResourceSortResponse.java index e1093ff157..fcfd49fa8c 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResourceSortResponse.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResourceSortResponse.java @@ -1,9 +1,13 @@ package io.metersphere.plan.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor +@AllArgsConstructor public class TestPlanResourceSortResponse { @Schema(description = "本次排序的数量") private long sortNodeNum; diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java index 90023b212a..3bf0ea3c20 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java @@ -1,10 +1,10 @@ package io.metersphere.plan.mapper; import io.metersphere.api.dto.definition.ApiDefinitionDTO; -import io.metersphere.plan.dto.AssociationNode; import io.metersphere.plan.dto.ResourceSelectParam; import io.metersphere.plan.dto.TestPlanCaseRunResultCount; import io.metersphere.plan.dto.request.TestPlanApiRequest; +import io.metersphere.project.dto.DropNode; import io.metersphere.project.dto.NodeSortQueryParam; import org.apache.ibatis.annotations.Param; @@ -20,9 +20,9 @@ public interface ExtTestPlanApiCaseMapper { List getIdByParam(ResourceSelectParam resourceSelectParam); - AssociationNode selectDragInfoById(String id); + DropNode selectDragInfoById(String id); - AssociationNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam); + DropNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam); List selectCaseExecResultCount(String testPlanId); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml index 91e2c9753e..e62146e357 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml @@ -51,14 +51,14 @@ - SELECT id, pos FROM test_plan_api_case WHERE id = #{0} - SELECT id, pos FROM test_plan_api_scenario WHERE id = #{0} - SELECT id, pos FROM test_plan_functional_case WHERE id = #{0} @@ -90,7 +92,7 @@ and t.group_id = 'NONE' - and t.type = 'GTOUP' + and t.type = 'GROUP' @@ -408,7 +410,35 @@ AND status != 'ARCHIVED' + + + update test_plan 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 b8d1d528be..5b935fa682 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 @@ -18,7 +18,6 @@ import io.metersphere.functional.service.FunctionalCaseAttachmentService; import io.metersphere.functional.service.FunctionalCaseModuleService; import io.metersphere.functional.service.FunctionalCaseService; import io.metersphere.plan.domain.*; -import io.metersphere.plan.dto.AssociationNodeSortDTO; import io.metersphere.plan.dto.ResourceLogInsertModule; import io.metersphere.plan.dto.TestPlanCaseRunResultCount; import io.metersphere.plan.dto.TestPlanResourceAssociationParam; @@ -28,6 +27,7 @@ import io.metersphere.plan.mapper.*; import io.metersphere.plugin.platform.dto.SelectOption; import io.metersphere.project.domain.Project; import io.metersphere.project.dto.ModuleCountDTO; +import io.metersphere.project.dto.MoveNodeSortDTO; import io.metersphere.provider.BaseAssociateBugProvider; import io.metersphere.request.AssociateBugPageRequest; import io.metersphere.request.BugPageProviderRequest; @@ -171,9 +171,9 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService { throw new MSException(Translator.get("test_plan.drag.node.error")); } TestPlanResourceSortResponse response = new TestPlanResourceSortResponse(); - AssociationNodeSortDTO sortDTO = super.getNodeSortDTO( - super.getNodeMoveRequest(request), + MoveNodeSortDTO sortDTO = super.getNodeSortDTO( request.getTestPlanId(), + super.getNodeMoveRequest(request, true), extTestPlanFunctionalCaseMapper::selectDragInfoById, extTestPlanFunctionalCaseMapper::selectNodeByPosOperator ); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanGroupService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanGroupService.java index 4895606cfe..d83becd205 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanGroupService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanGroupService.java @@ -1,8 +1,93 @@ package io.metersphere.plan.service; import io.metersphere.plan.domain.TestPlan; -import io.metersphere.plan.domain.TestPlanConfig; +import io.metersphere.plan.domain.TestPlanExample; +import io.metersphere.plan.mapper.ExtTestPlanMapper; +import io.metersphere.plan.mapper.TestPlanMapper; +import io.metersphere.project.dto.MoveNodeSortDTO; +import io.metersphere.sdk.constants.TestPlanConstants; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.dto.sdk.enums.MoveTypeEnum; +import io.metersphere.system.dto.sdk.request.PosRequest; +import io.metersphere.system.utils.ServiceUtils; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -public interface TestPlanGroupService { - boolean validateGroup(TestPlan testPlan, TestPlanConfig testPlanConfig); +import java.util.List; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestPlanGroupService extends TestPlanSortService { + + @Resource + private TestPlanMapper testPlanMapper; + @Resource + private ExtTestPlanMapper extTestPlanMapper; + + @Override + public long getNextOrder(String groupId) { + long maxPos = extTestPlanMapper.selectMaxPosByGroupId(groupId); + return maxPos + ServiceUtils.POS_STEP; + } + + @Override + public void updatePos(String id, long pos) { + TestPlan testPlan = new TestPlan(); + testPlan.setId(id); + testPlan.setPos(pos); + testPlanMapper.updateByPrimaryKeySelective(testPlan); + } + + @Override + public void refreshPos(String groupId) { + TestPlanExample testPlanExample = new TestPlanExample(); + testPlanExample.createCriteria().andGroupIdEqualTo(groupId); + testPlanExample.setOrderByClause("pos asc"); + List testPlans = testPlanMapper.selectByExample(testPlanExample); + long pos = 1; + for (TestPlan testPlanItem : testPlans) { + this.updatePos(testPlanItem.getId(), pos * ServiceUtils.POS_STEP); + pos++; + } + } + + public void sort(PosRequest request) { + TestPlan dropPlan = testPlanMapper.selectByPrimaryKey(request.getMoveId()); + TestPlan targetPlan = testPlanMapper.selectByPrimaryKey(request.getTargetId()); + + // 校验排序的参数 (暂时不支持测试计划的移入移出) + validateMoveRequest(dropPlan, targetPlan, request.getMoveMode()); + MoveNodeSortDTO sortDTO = super.getNodeSortDTO( + targetPlan.getGroupId(), + this.getNodeMoveRequest(request, false), + extTestPlanMapper::selectDragInfoById, + extTestPlanMapper::selectNodeByPosOperator + ); + + this.sort(sortDTO); + } + + private void validateMoveRequest(TestPlan dropPlan, TestPlan targetPlan, String moveType) { + //测试计划组不能进行移动操作 + if (dropPlan == null || StringUtils.equalsIgnoreCase(dropPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) { + throw new MSException(Translator.get("test_plan.drag.node.error")); + } + if (targetPlan == null || StringUtils.equalsIgnoreCase(targetPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) { + throw new MSException(Translator.get("test_plan.drag.node.error")); + } + if (StringUtils.equalsIgnoreCase(MoveTypeEnum.APPEND.name(), moveType)) { + if (!StringUtils.equalsIgnoreCase(targetPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) { + throw new MSException(Translator.get("test_plan.drag.node.error")); + } + } else if (StringUtils.equalsAnyIgnoreCase(moveType, MoveTypeEnum.BEFORE.name(), MoveTypeEnum.AFTER.name())) { + if (StringUtils.equalsAny(TestPlanConstants.TEST_PLAN_TYPE_GROUP, dropPlan.getType())) { + throw new MSException(Translator.get("test_plan.drag.node.error")); + } + } else { + throw new MSException(Translator.get("test_plan.drag.position.error")); + } + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanLogService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanLogService.java index 3e2ba14782..5208bb7dd7 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanLogService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanLogService.java @@ -11,6 +11,7 @@ import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.TestPlanConstants; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.Translator; +import io.metersphere.system.dto.LogInsertModule; import io.metersphere.system.dto.builder.LogDTOBuilder; import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogType; @@ -245,4 +246,22 @@ public class TestPlanLogService { } return dtoList; } + + public void saveMoveLog(TestPlan testPlan, String moveId, LogInsertModule logInsertModule) { + + Project project = projectMapper.selectByPrimaryKey(testPlan.getProjectId()); + LogDTO dto = LogDTOBuilder.builder() + .projectId(testPlan.getProjectId()) + .organizationId(project.getOrganizationId()) + .type(OperationLogType.UPDATE.name()) + .module(logModule) + .method(logInsertModule.getRequestMethod()) + .path(logInsertModule.getRequestUrl()) + .sourceId(moveId) + .content(Translator.get("log.test_plan.move.test_plan") + ":" + testPlan.getName() + StringUtils.SPACE) + .createUser(logInsertModule.getOperator()) + .build().getLogDTO(); + operationLogService.add(dto); + } + } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java index 9772efaace..1aa2d1efcd 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java @@ -55,9 +55,6 @@ public class TestPlanManagementService { /** * 测试计划列表查询 - * - * @param request - * @return */ public Pager> page(TestPlanTableRequest request) { Page page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), @@ -73,8 +70,6 @@ public class TestPlanManagementService { /** * 计划组子节点 - * - * @param testPlanResponses */ private void handChildren(List testPlanResponses, String projectId) { List groupIds = testPlanResponses.stream().filter(item -> StringUtils.equals(item.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)).map(TestPlanResponse::getId).toList(); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java index d1a254c04f..ca2538094a 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java @@ -1,57 +1,33 @@ package io.metersphere.plan.service; import io.metersphere.plan.domain.TestPlan; -import io.metersphere.plan.dto.AssociationNode; -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.response.TestPlanAssociationResponse; import io.metersphere.plan.mapper.TestPlanMapper; -import io.metersphere.project.dto.ModuleSortCountResultDTO; -import io.metersphere.project.dto.NodeSortQueryParam; -import io.metersphere.project.service.MoveNodeService; -import io.metersphere.project.utils.NodeSortUtils; -import io.metersphere.sdk.exception.MSException; -import io.metersphere.sdk.util.Translator; import io.metersphere.system.dto.LogInsertModule; -import io.metersphere.system.dto.sdk.request.NodeMoveRequest; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.List; import java.util.Map; import java.util.function.Consumer; -import java.util.function.Function; //测试计划关联表 通用方法 -@Service @Transactional(rollbackFor = Exception.class) -public abstract class TestPlanResourceService extends MoveNodeService { - - @Resource - private TestPlanMapper testPlanMapper; - @Resource - private TestPlanResourceLogService testPlanResourceLogService; - - protected static final long DEFAULT_NODE_INTERVAL_POS = NodeSortUtils.DEFAULT_NODE_INTERVAL_POS; - - public abstract void updatePos(String id, long pos); - - public abstract void refreshPos(String testPlanId); +public abstract class TestPlanResourceService extends TestPlanSortService { public abstract void deleteBatchByTestPlanId(List testPlanIdList); public abstract Map caseExecResultCount(String testPlanId); - private static final String MOVE_POS_OPERATOR_LESS = "lessThan"; - private static final String MOVE_POS_OPERATOR_MORE = "moreThan"; - private static final String DRAG_NODE_NOT_EXIST = "drag_node.not.exist"; - + @Resource + private TestPlanMapper testPlanMapper; + @Resource + private TestPlanResourceLogService testPlanResourceLogService; /** * 取消关联资源od @@ -75,73 +51,4 @@ public abstract class TestPlanResourceService extends MoveNodeService { } return response; } - - /** - * 构建节点排序的参数 - * - * @param request 拖拽的前端请求参数 - * @param selectIdNodeFunc 通过id查询节点的函数 - * @param selectPosNodeFunc 通过parentId和pos运算符查询节点的函数 - * @return - */ - public AssociationNodeSortDTO getNodeSortDTO(NodeMoveRequest request, String testPlanId, Function selectIdNodeFunc, Function selectPosNodeFunc) { - if (StringUtils.equals(request.getDragNodeId(), request.getDropNodeId())) { - //两种节点不能一样 - throw new MSException(Translator.get("invalid_parameter") + ": drag node and drop node"); - } - - AssociationNode dragNode = selectIdNodeFunc.apply(request.getDragNodeId()); - if (dragNode == null) { - throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDragNodeId()); - } - - AssociationNode dropNode = selectIdNodeFunc.apply(request.getDropNodeId()); - if (dropNode == null) { - throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDropNodeId()); - } - - AssociationNode previousNode; - AssociationNode nextNode; - - if (request.getDropPosition() == 1) { - //dropPosition=1: 放到dropNode节点后,原dropNode后面的节点之前 - previousNode = dropNode; - - NodeSortQueryParam sortParam = new NodeSortQueryParam(); - sortParam.setParentId(testPlanId); - sortParam.setPos(previousNode.getPos()); - sortParam.setOperator(MOVE_POS_OPERATOR_MORE); - nextNode = selectPosNodeFunc.apply(sortParam); - } else if (request.getDropPosition() == -1) { - //dropPosition=-1: 放到dropNode节点前,原dropNode前面的节点之后 - nextNode = dropNode; - NodeSortQueryParam sortParam = new NodeSortQueryParam(); - sortParam.setPos(nextNode.getPos()); - sortParam.setParentId(testPlanId); - sortParam.setOperator(MOVE_POS_OPERATOR_LESS); - previousNode = selectPosNodeFunc.apply(sortParam); - } else { - throw new MSException(Translator.get("invalid_parameter") + ": dropPosition"); - } - - return new AssociationNodeSortDTO(testPlanId, dragNode, previousNode, nextNode); - } - - //排序 - public void sort(AssociationNodeSortDTO sortDTO) { - - // 获取相邻节点 - AssociationNode previousNode = sortDTO.getPreviousNode(); - AssociationNode nextNode = sortDTO.getNextNode(); - - ModuleSortCountResultDTO countResultDTO = NodeSortUtils.countModuleSort( - previousNode == null ? -1 : previousNode.getPos(), - nextNode == null ? -1 : nextNode.getPos()); - - updatePos(sortDTO.getSortNode().getId(), countResultDTO.getPos()); - if (countResultDTO.isRefreshPos()) { - refreshPos(sortDTO.getTestPlanId()); - } - } - } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java index 0c206e9c02..47db53add6 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java @@ -3,6 +3,7 @@ package io.metersphere.plan.service; import io.metersphere.plan.domain.*; import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanDetailResponse; +import io.metersphere.plan.dto.response.TestPlanResourceSortResponse; import io.metersphere.plan.mapper.*; import io.metersphere.sdk.constants.*; import io.metersphere.sdk.exception.MSException; @@ -13,6 +14,8 @@ import io.metersphere.sdk.util.Translator; import io.metersphere.system.domain.ScheduleExample; import io.metersphere.system.domain.TestPlanModuleExample; import io.metersphere.system.domain.User; +import io.metersphere.system.dto.LogInsertModule; +import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.mapper.ScheduleMapper; import io.metersphere.system.mapper.TestPlanModuleMapper; @@ -49,6 +52,8 @@ public class TestPlanService extends TestPlanBaseUtilsService { @Resource private ExtTestPlanMapper extTestPlanMapper; @Resource + private TestPlanGroupService testPlanGroupService; + @Resource private TestPlanConfigMapper testPlanConfigMapper; @Resource private TestPlanLogService testPlanLogService; @@ -82,12 +87,6 @@ public class TestPlanService extends TestPlanBaseUtilsService { /** * 创建测试计划 - * - * @param testPlanCreateRequest - * @param operator - * @param requestUrl - * @param requestMethod - * @return */ public TestPlan add(TestPlanCreateRequest testPlanCreateRequest, String operator, String requestUrl, String requestMethod) { TestPlan testPlan = savePlanDTO(testPlanCreateRequest, operator, null); @@ -108,8 +107,7 @@ public class TestPlanService extends TestPlanBaseUtilsService { checkModule(createOrCopyRequest.getModuleId()); TestPlan createTestPlan = new TestPlan(); BeanUtils.copyBean(createTestPlan, createOrCopyRequest); - // 5.21,查询需求文档、测试用例:测试计划名称允许重复 - // validateTestPlan(createTestPlan); + initTestPlanPos(createTestPlan); createTestPlan.setId(IDGenerator.nextStr()); long operateTime = System.currentTimeMillis(); @@ -138,6 +136,22 @@ public class TestPlanService extends TestPlanBaseUtilsService { return createTestPlan; } + //校验测试计划 + private void initTestPlanPos(TestPlan createTestPlan) { + if (!StringUtils.equals(createTestPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) { + //如果测试计划组不为NONE,判断是否真实存在并未归档 + TestPlanExample example = new TestPlanExample(); + example.createCriteria().andIdEqualTo(createTestPlan.getGroupId()).andStatusNotEqualTo(TEST_PLAN_STATUS_ARCHIVED); + if (testPlanMapper.countByExample(example) == 0) { + throw new MSException(Translator.get("test_plan.group.error")); + } else { + createTestPlan.setPos(testPlanGroupService.getNextOrder(createTestPlan.getGroupId())); + } + } else { + createTestPlan.setPos(0L); + } + } + /** * 删除测试计划 @@ -179,6 +193,7 @@ public class TestPlanService extends TestPlanBaseUtilsService { private void deleteGroupByList(List testPlanGroupIds) { if (CollectionUtils.isNotEmpty(testPlanGroupIds)) { TestPlanReportService testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class); + assert testPlanReportService != null; BatchProcessUtils.consumerByString(testPlanGroupIds, (deleteGroupIds) -> { /* * 计划组删除逻辑{第一版需求: 删除组, 组下的子计划Group置为None}: @@ -189,12 +204,13 @@ public class TestPlanService extends TestPlanBaseUtilsService { testPlanExample.createCriteria().andGroupIdIn(deleteGroupIds); List deleteGroupPlans = testPlanMapper.selectByExample(testPlanExample); List deleteGroupPlanIds = deleteGroupPlans.stream().map(TestPlan::getId).toList(); - if (CollectionUtils.isNotEmpty(deleteGroupPlanIds)) { - // 级联删除子计划关联的资源(计划组不存在关联的资源) + List allDeleteIds = ListUtils.union(deleteGroupIds, deleteGroupPlanIds); + if (CollectionUtils.isNotEmpty(allDeleteIds)) { + // 级联删除子计划关联的资源(计划组不存在关联的资源,但是存在报告) this.cascadeDeleteTestPlanIds(deleteGroupPlanIds, testPlanReportService); } testPlanExample.clear(); - testPlanExample.createCriteria().andIdIn(ListUtils.union(deleteGroupIds, deleteGroupPlanIds)); + testPlanExample.createCriteria().andIdIn(allDeleteIds); testPlanMapper.deleteByExample(testPlanExample); }); } @@ -275,12 +291,6 @@ public class TestPlanService extends TestPlanBaseUtilsService { /** * 更新测试计划 - * - * @param request - * @param userId - * @param requestUrl - * @param requestMethod - * @return */ public TestPlan update(TestPlanUpdateRequest request, String userId, String requestUrl, String requestMethod) { this.checkTestPlanNotArchived(request.getId()); @@ -296,8 +306,6 @@ public class TestPlanService extends TestPlanBaseUtilsService { if (StringUtils.isNotBlank(request.getName())) { updateTestPlan.setName(request.getName()); updateTestPlan.setProjectId(testPlan.getProjectId()); - // 5.21,查询需求文档、测试用例:测试计划名称允许重复 - // validateTestPlan(updateTestPlan); } if (CollectionUtils.isNotEmpty(request.getTags())) { updateTestPlan.setTags(new ArrayList<>(request.getTags())); @@ -706,4 +714,11 @@ public class TestPlanService extends TestPlanBaseUtilsService { testPlan.setStatus(testPlanFinalStatus); testPlanMapper.updateByPrimaryKeySelective(testPlan); } + + public TestPlanResourceSortResponse sortInGroup(PosRequest request, LogInsertModule logInsertModule) { + testPlanGroupService.sort(request); + testPlanLogService.saveMoveLog(testPlanMapper.selectByPrimaryKey(request.getMoveId()), request.getMoveId(), logInsertModule); + return new TestPlanResourceSortResponse(1); + } + } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanSortService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanSortService.java new file mode 100644 index 0000000000..f9090a7db4 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanSortService.java @@ -0,0 +1,77 @@ +package io.metersphere.plan.service; + +import io.metersphere.project.service.MoveNodeService; +import io.metersphere.project.utils.NodeSortUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +//测试计划关联表 通用方法 +@Service +@Transactional(rollbackFor = Exception.class) +public abstract class TestPlanSortService extends MoveNodeService { + + protected static final long DEFAULT_NODE_INTERVAL_POS = NodeSortUtils.DEFAULT_NODE_INTERVAL_POS; + + public abstract void updatePos(String id, long pos); + + public abstract void refreshPos(String testPlanId); + + private static final String MOVE_POS_OPERATOR_LESS = "lessThan"; + private static final String MOVE_POS_OPERATOR_MORE = "moreThan"; + private static final String DRAG_NODE_NOT_EXIST = "drag_node.not.exist"; + + /** + * 构建节点排序的参数 + * + * @param request 拖拽的前端请求参数 + * @param selectIdNodeFunc 通过id查询节点的函数 + * @param selectPosNodeFunc 通过parentId和pos运算符查询节点的函数 + * @return + */ + + + // public MoveNodeSortDTO getNodeSortDTO(NodeMoveRequest request, String testPlanId, Function selectIdNodeFunc, Function selectPosNodeFunc) { + // + // + // if (StringUtils.equals(request.getDragNodeId(), request.getDropNodeId())) { + // //两种节点不能一样 + // throw new MSException(Translator.get("invalid_parameter") + ": drag node and drop node"); + // } + // + // DropNode dragNode = selectIdNodeFunc.apply(request.getDragNodeId()); + // if (dragNode == null) { + // throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDragNodeId()); + // } + // + // DropNode dropNode = selectIdNodeFunc.apply(request.getDropNodeId()); + // if (dropNode == null) { + // throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDropNodeId()); + // } + // + // DropNode previousNode; + // DropNode nextNode; + // + // if (request.getDropPosition() == 1) { + // //dropPosition=1: 放到dropNode节点后,原dropNode后面的节点之前 + // previousNode = dropNode; + // + // NodeSortQueryParam sortParam = new NodeSortQueryParam(); + // sortParam.setParentId(testPlanId); + // sortParam.setPos(previousNode.getPos()); + // sortParam.setOperator(MOVE_POS_OPERATOR_MORE); + // nextNode = selectPosNodeFunc.apply(sortParam); + // } else if (request.getDropPosition() == -1) { + // //dropPosition=-1: 放到dropNode节点前,原dropNode前面的节点之后 + // nextNode = dropNode; + // NodeSortQueryParam sortParam = new NodeSortQueryParam(); + // sortParam.setPos(nextNode.getPos()); + // sortParam.setParentId(testPlanId); + // sortParam.setOperator(MOVE_POS_OPERATOR_LESS); + // previousNode = selectPosNodeFunc.apply(sortParam); + // } else { + // throw new MSException(Translator.get("invalid_parameter") + ": dropPosition"); + // } + // return new MoveNodeSortDTO(testPlanId, dragNode, previousNode, nextNode); + // } + +} diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java index 6eedb4ee95..0757af9288 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java @@ -16,6 +16,7 @@ import io.metersphere.project.domain.Project; import io.metersphere.project.dto.filemanagement.request.FileModuleCreateRequest; import io.metersphere.project.dto.filemanagement.request.FileModuleUpdateRequest; import io.metersphere.sdk.constants.*; +import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.JSON; import io.metersphere.system.base.BaseTest; @@ -26,6 +27,7 @@ import io.metersphere.system.dto.AddProjectRequest; import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.dto.sdk.enums.MoveTypeEnum; import io.metersphere.system.dto.sdk.request.NodeMoveRequest; +import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.mapper.TestPlanModuleMapper; @@ -106,6 +108,7 @@ public class TestPlanTests extends BaseTest { private static final String URL_POST_TEST_PLAN_STATISTICS = "/test-plan/statistics"; private static final String URL_POST_TEST_PLAN_MODULE_COUNT = "/test-plan/module/count"; private static final String URL_POST_TEST_PLAN_ADD = "/test-plan/add"; + private static final String URL_POST_TEST_PLAN_SORT = "/test-plan/sort"; private static final String URL_POST_TEST_PLAN_UPDATE = "/test-plan/update"; private static final String URL_POST_TEST_PLAN_BATCH_DELETE = "/test-plan/batch-delete"; @@ -136,7 +139,7 @@ public class TestPlanTests extends BaseTest { @BeforeEach public void initTestData() { - //文件管理专用项目 + //测试计划专用项目 if (project == null) { AddProjectRequest initProject = new AddProjectRequest(); initProject.setOrganizationId("100001"); @@ -607,7 +610,6 @@ public class TestPlanTests extends BaseTest { request.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN); } - /* 抽查: testPlan_13没有设置计划开始时间、没有设置重复添加用例和自动更新状态、阈值为100、描述为空; @@ -681,25 +683,133 @@ public class TestPlanTests extends BaseTest { request.setPassThreshold(100); this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ_ADD, URL_POST_TEST_PLAN_ADD, request); + + this.checkTestPlanSortInGroup(groupTestPlanId7); } + protected void checkTestPlanSortInGroup(String groupTestPlanId7) throws Exception { + /* + 排序校验用例设计: + 1.第一个移动到最后一个。 + 2.最后一个移动到第一个(还原为原来的顺序) + 3.第三个移动到第二个 + 4.修改第一个和第二个之间的pos差小于2,将第三个移动到第二个(还原为原来的顺序),并检查pos有没有初始化 + */ + + TestPlanExample example = new TestPlanExample(); + example.createCriteria().andGroupIdEqualTo(groupTestPlanId7); + example.setOrderByClause("pos asc"); + List defaultTestPlanInGroup = testPlanMapper.selectByExample(example); + List lastTestPlanInGroup = defaultTestPlanInGroup; + TestPlan movePlan, targetPlan = null; + PosRequest posRequest = null; + TestPlanResourceSortResponse response = null; + + // 第一个移动到最后一个 + movePlan = lastTestPlanInGroup.getFirst(); + targetPlan = lastTestPlanInGroup.getLast(); + posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.AFTER.name()); + response = JSON.parseObject( + JSON.toJSONString( + JSON.parseObject( + this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest) + .getResponse().getContentAsString(), ResultHolder.class).getData()), + TestPlanResourceSortResponse.class); + //位置校验 + List newTestPlanInGroup = testPlanMapper.selectByExample(example); + Assertions.assertEquals(response.getSortNodeNum(), 1); + Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size()); + for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) { + int oldListIndex = newListIndex == newTestPlanInGroup.size() - 1 ? 0 : newListIndex + 1; + Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), lastTestPlanInGroup.get(oldListIndex).getId()); + } + lastTestPlanInGroup = newTestPlanInGroup; + + // 最后一个移动到第一个 (还原为原来的顺序) + movePlan = lastTestPlanInGroup.getLast(); + targetPlan = lastTestPlanInGroup.getFirst(); + posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.BEFORE.name()); + response = JSON.parseObject( + JSON.toJSONString( + JSON.parseObject( + this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest) + .getResponse().getContentAsString(), ResultHolder.class).getData()), + TestPlanResourceSortResponse.class); + //位置校验 + newTestPlanInGroup = testPlanMapper.selectByExample(example); + Assertions.assertEquals(response.getSortNodeNum(), 1); + Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size()); + for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) { + Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), defaultTestPlanInGroup.get(newListIndex).getId()); + } + lastTestPlanInGroup = newTestPlanInGroup; + + // 第三个移动到第二个 + movePlan = lastTestPlanInGroup.get(2); + targetPlan = lastTestPlanInGroup.get(1); + posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.BEFORE.name()); + response = JSON.parseObject( + JSON.toJSONString( + JSON.parseObject( + this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest) + .getResponse().getContentAsString(), ResultHolder.class).getData()), + TestPlanResourceSortResponse.class); + //位置校验 + newTestPlanInGroup = testPlanMapper.selectByExample(example); + Assertions.assertEquals(response.getSortNodeNum(), 1); + Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size()); + for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) { + int oldListIndex = newListIndex; + if (oldListIndex == 1) { + oldListIndex = 2; + } else if (oldListIndex == 2) { + oldListIndex = 1; + } + Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), lastTestPlanInGroup.get(oldListIndex).getId()); + } + lastTestPlanInGroup = newTestPlanInGroup; + + // 修改第一个和第二个之间的pos差为2(拖拽的最小pos差),将第三个移动到第二个(换回来),然后检查pos有没有变化 + movePlan = lastTestPlanInGroup.get(2); + targetPlan = lastTestPlanInGroup.get(1); + targetPlan.setPos(lastTestPlanInGroup.get(0).getPos() + 2); + testPlanMapper.updateByPrimaryKey(targetPlan); + + posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.BEFORE.name()); + response = JSON.parseObject( + JSON.toJSONString( + JSON.parseObject( + this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest) + .getResponse().getContentAsString(), ResultHolder.class).getData()), + TestPlanResourceSortResponse.class); + //位置校验 + newTestPlanInGroup = testPlanMapper.selectByExample(example); + Assertions.assertEquals(response.getSortNodeNum(), 1); + Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size()); + long lastPos = 0; + for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) { + Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), defaultTestPlanInGroup.get(newListIndex).getId()); + Assertions.assertTrue(newTestPlanInGroup.get(newListIndex).getPos() > (lastPos + 1)); + lastPos = newTestPlanInGroup.get(newListIndex).getPos(); + } + } @Test @Order(12) public void testPlanPageCountTest() throws Exception { - TestPlanTableRequest testPlanTableRequest = new TestPlanTableRequest(); - testPlanTableRequest.setProjectId(project.getId()); - testPlanTableRequest.setType("ALL"); - testPlanTableRequest.setPageSize(10); - testPlanTableRequest.setCurrent(1); + TestPlanTableRequest dataRequest = new TestPlanTableRequest(); + dataRequest.setProjectId(project.getId()); + dataRequest.setType("ALL"); + dataRequest.setPageSize(10); + dataRequest.setCurrent(1); //测试项目没有开启测试计划模块时能否使用 testPlanTestService.removeProjectModule(project, PROJECT_MODULE, "testPlan"); - this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest).andExpect(status().is5xxServerError()); - this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest).andExpect(status().is5xxServerError()); + this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest).andExpect(status().is5xxServerError()); + this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest).andExpect(status().is5xxServerError()); //恢复 testPlanTestService.resetProjectModule(project, PROJECT_MODULE); - MvcResult moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest); + MvcResult moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest); String moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8); Map moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class); AtomicBoolean testPlanIsEmpty = new AtomicBoolean(true); @@ -711,11 +821,19 @@ public class TestPlanTests extends BaseTest { } if (testPlanIsEmpty.get()) { - //如果没有数据,先创建999条再调用这个方法 + //如果没有数据,先创建再调用这个方法 this.testPlanAddTest(); this.testPlanPageCountTest(); } else { - //this.checkModuleCount(moduleCountMap, a1NodeCount, a2NodeCount, a3NodeCount, a1a1NodeCount, a1b1NodeCount); + //只查询组 + TestPlanTableRequest groupRequest = new TestPlanTableRequest(); + //查询游离态测试计划 + TestPlanTableRequest onlyPlanRequest = new TestPlanTableRequest(); + BeanUtils.copyBean(groupRequest, dataRequest); + BeanUtils.copyBean(onlyPlanRequest, dataRequest); + groupRequest.setType(TestPlanConstants.TEST_PLAN_TYPE_GROUP); + onlyPlanRequest.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN); + BaseTreeNode a1Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a1"); BaseTreeNode a2Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a2"); @@ -724,96 +842,82 @@ public class TestPlanTests extends BaseTest { BaseTreeNode a1b1Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a1-b1"); assert a1Node != null & a2Node != null & a3Node != null & a1a1Node != null & a1b1Node != null; - //查询测试计划列表 - MvcResult pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest); - String returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8); - ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); - Pager result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); - //返回值的页码和当前页码相同 - Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent()); - //返回的数据量不超过规定要返回的数据量相同 - Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize()); - Assertions.assertEquals(result.getTotal(), 1010); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 1010); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, groupRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 2); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, onlyPlanRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 1008); //按照名称倒叙 - testPlanTableRequest.setSort(new HashMap<>() {{ + dataRequest.setSort(new HashMap<>() {{ this.put("name", "desc"); }}); - pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest); - returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8); - resultHolder = JSON.parseObject(returnData, ResultHolder.class); - result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); - //返回值的页码和当前页码相同 - Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent()); - //返回的数据量不超过规定要返回的数据量相同 - Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize()); - Assertions.assertEquals(result.getTotal(), 1010); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 1010); //指定模块ID查询 (查询count时,不会因为选择了模块而更改了总量 - testPlanTableRequest.setModuleIds(Arrays.asList(a1Node.getId(), a1a1Node.getId(), a1b1Node.getId())); - moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest); + dataRequest.setModuleIds(Arrays.asList(a1Node.getId(), a1a1Node.getId(), a1b1Node.getId())); + moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest); moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8); moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class); - - pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest); - returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8); - resultHolder = JSON.parseObject(returnData, ResultHolder.class); - result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); - //返回值的页码和当前页码相同 - Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent()); - //返回的数据量不超过规定要返回的数据量相同 - Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize()); - + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 910); //测试根据名称模糊查询: Plan_2 预期结果: a1Node下有11条(testPlan_2,testPlan_20~testPlan_29), a1b1Node下有100条(testPlan_200~testPlan_299) - testPlanTableRequest.setModuleIds(null); - testPlanTableRequest.initKeyword("Plan_2"); - moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest); + dataRequest.setModuleIds(null); + dataRequest.initKeyword("Plan_2"); + moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest); moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8); moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class); - - - pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest); - returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8); - resultHolder = JSON.parseObject(returnData, ResultHolder.class); - result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); - //返回值的页码和当前页码相同 - Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent()); - //返回的数据量不超过规定要返回的数据量相同 - Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize()); - + long allSize = Long.parseLong(String.valueOf(moduleCountMap.get("all"))); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + allSize); //测试根据名称模糊查询(包含测试组的): Plan_7 预期结果: a1Node下有1条(testPlan_7), a2Node下有10条(testPlan_70~testPlan_79),a1b1Node下有100条(testPlan_700~testPlan_799) - testPlanTableRequest.initKeyword("Plan_7"); - testPlanTableRequest.setSort(new HashMap<>() {{ + dataRequest.initKeyword("Plan_7"); + dataRequest.setSort(new HashMap<>() {{ this.put("num", "asc"); }}); - moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest); + moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest); moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8); moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class); - - - pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest); - returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8); - resultHolder = JSON.parseObject(returnData, ResultHolder.class); - result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); - //返回值的页码和当前页码相同 - Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent()); - //返回的数据量不超过规定要返回的数据量相同 - Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize()); - + allSize = Long.parseLong(String.valueOf(moduleCountMap.get("all"))); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + allSize); //反例:参数校验(项目ID不存在) - testPlanTableRequest.setProjectId(null); - this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest).andExpect(status().isBadRequest()); - this.requestPost(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest).andExpect(status().isBadRequest()); + dataRequest.setProjectId(null); + this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest).andExpect(status().isBadRequest()); + this.requestPost(URL_POST_TEST_PLAN_PAGE, dataRequest).andExpect(status().isBadRequest()); //测试权限 - testPlanTableRequest.setProjectId(DEFAULT_PROJECT_ID); - this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest); - this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_PAGE, testPlanTableRequest); + dataRequest.setProjectId(DEFAULT_PROJECT_ID); + this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest); + this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_PAGE, dataRequest); } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java index 8afae10b12..3203283ff1 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java @@ -16,9 +16,11 @@ import io.metersphere.project.mapper.ProjectMapper; import io.metersphere.sdk.constants.*; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.SubListUtils; +import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.domain.TestPlanModuleExample; import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; +import io.metersphere.system.utils.Pager; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -433,4 +435,16 @@ public class TestPlanTestService { }); } + + public void checkTestPlanPage(String returnData, long current, long pageSize, long allData) { + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + Pager result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); + //返回值的页码和当前页码相同 + Assertions.assertEquals(result.getCurrent(), current); + //返回的数据量不超过规定要返回的数据量相同 + Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= pageSize); + if (allData > 0) { + Assertions.assertEquals(result.getTotal(), allData); + } + } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/service/mock/TestPlanGroupServiceImpl.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/service/mock/TestPlanGroupServiceImpl.java deleted file mode 100644 index d388bd090d..0000000000 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/service/mock/TestPlanGroupServiceImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.metersphere.plan.service.mock; - -import io.metersphere.plan.domain.TestPlan; -import io.metersphere.plan.domain.TestPlanConfig; -import io.metersphere.plan.service.TestPlanGroupService; -import org.springframework.stereotype.Service; - -@Service -public class TestPlanGroupServiceImpl implements TestPlanGroupService { - - @Override - public boolean validateGroup(TestPlan testPlan, TestPlanConfig testPlanConfig) { - - return true; - } - -}