From 27c18ef8a39d40fca3750104bf383288c09348fc Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Tue, 15 Dec 2020 10:32:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor=EF=BC=9A=E6=A8=A1=E5=9D=97=E6=A0=91?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E9=87=8D=E6=9E=84:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/dto/definition/ApiModuleDTO.java | 11 +- .../api/service/ApiModuleService.java | 63 +---- .../io/metersphere/base/domain/ApiModule.java | 2 + .../base/domain/ApiModuleExample.java | 60 ++++ .../base/mapper/ApiModuleMapper.java | 3 +- .../base/mapper/ApiModuleMapper.xml | 27 +- .../base/mapper/ext/ExtApiModuleMapper.java | 2 + .../base/mapper/ext/ExtApiModuleMapper.xml | 8 + .../mapper/ext/ExtTestCaseNodeMapper.java | 17 ++ .../base/mapper/ext/ExtTestCaseNodeMapper.xml | 31 +++ .../metersphere/service/NodeTreeService.java | 238 ++++++++++++++++ .../track/domain/ReportResultComponent.java | 7 +- .../track/dto/TestCaseNodeDTO.java | 9 +- .../io/metersphere/track/dto/TreeNodeDTO.java | 31 +++ .../track/service/TestCaseNodeService.java | 260 ++---------------- backend/src/main/java/io/metersphere/xpack | 2 +- .../db/migration/V46__api_definition.sql | 1 + .../src/main/resources/generatorConfig.xml | 4 +- frontend/src/business/components/xpack | 2 +- 19 files changed, 458 insertions(+), 320 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.java create mode 100644 backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.xml create mode 100644 backend/src/main/java/io/metersphere/service/NodeTreeService.java create mode 100644 backend/src/main/java/io/metersphere/track/dto/TreeNodeDTO.java diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/ApiModuleDTO.java b/backend/src/main/java/io/metersphere/api/dto/definition/ApiModuleDTO.java index 8641539e3c..d76ac995fc 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/ApiModuleDTO.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/ApiModuleDTO.java @@ -1,16 +1,11 @@ package io.metersphere.api.dto.definition; -import io.metersphere.base.domain.ApiModule; +import io.metersphere.track.dto.TreeNodeDTO; import lombok.Getter; import lombok.Setter; -import java.util.List; - @Getter @Setter -public class ApiModuleDTO extends ApiModule { - - private String label; - private List children; - +public class ApiModuleDTO extends TreeNodeDTO { + private String protocol; } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java index db748be12b..f4effabc77 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java @@ -11,10 +11,12 @@ import io.metersphere.base.domain.ApiModuleExample; import io.metersphere.base.mapper.ApiDefinitionMapper; import io.metersphere.base.mapper.ApiModuleMapper; import io.metersphere.base.mapper.ext.ExtApiDefinitionMapper; +import io.metersphere.base.mapper.ext.ExtApiModuleMapper; import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.exception.MSException; -import io.metersphere.commons.utils.BeanUtils; + import io.metersphere.i18n.Translator; +import io.metersphere.service.NodeTreeService; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; @@ -28,11 +30,13 @@ import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) -public class ApiModuleService { +public class ApiModuleService extends NodeTreeService { @Resource ApiModuleMapper apiModuleMapper; @Resource + ExtApiModuleMapper extApiModuleMapper; + @Resource private ApiDefinitionMapper apiDefinitionMapper; @Resource private ExtApiDefinitionMapper extApiDefinitionMapper; @@ -41,61 +45,10 @@ public class ApiModuleService { SqlSessionFactory sqlSessionFactory; public List getNodeTreeByProjectId(String projectId, String protocol) { - ApiModuleExample apiDefinitionNodeExample = new ApiModuleExample(); - apiDefinitionNodeExample.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol); - apiDefinitionNodeExample.setOrderByClause("create_time asc"); - List nodes = apiModuleMapper.selectByExample(apiDefinitionNodeExample); - return getNodeTrees(nodes); + List apiModules = extApiModuleMapper.getNodeTreeByProjectId(projectId, protocol); + return getNodeTrees(apiModules); } - public List getNodeTrees(List nodes) { - - List nodeTreeList = new ArrayList<>(); - Map> nodeLevelMap = new HashMap<>(); - nodes.forEach(node -> { - Integer level = node.getLevel(); - if (nodeLevelMap.containsKey(level)) { - nodeLevelMap.get(level).add(node); - } else { - List apiModules = new ArrayList<>(); - apiModules.add(node); - nodeLevelMap.put(node.getLevel(), apiModules); - } - }); - List rootNodes = Optional.ofNullable(nodeLevelMap.get(1)).orElse(new ArrayList<>()); - rootNodes.forEach(rootNode -> nodeTreeList.add(buildNodeTree(nodeLevelMap, rootNode))); - return nodeTreeList; - } - - /** - * 递归构建节点树 - * - * @param nodeLevelMap - * @param rootNode - * @return - */ - private ApiModuleDTO buildNodeTree(Map> nodeLevelMap, ApiModule rootNode) { - - ApiModuleDTO nodeTree = new ApiModuleDTO(); - BeanUtils.copyBean(nodeTree, rootNode); - nodeTree.setLabel(rootNode.getName()); - - List lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1); - if (lowerNodes == null) { - return nodeTree; - } - List children = Optional.ofNullable(nodeTree.getChildren()).orElse(new ArrayList<>()); - lowerNodes.forEach(node -> { - if (node.getParentId() != null && node.getParentId().equals(rootNode.getId())) { - children.add(buildNodeTree(nodeLevelMap, node)); - nodeTree.setChildren(children); - } - }); - - return nodeTree; - } - - public String addNode(ApiModule node) { validateNode(node); return addNodeWithoutValidate(node); diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiModule.java b/backend/src/main/java/io/metersphere/base/domain/ApiModule.java index b0d6aae0bc..f8424b82f2 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ApiModule.java +++ b/backend/src/main/java/io/metersphere/base/domain/ApiModule.java @@ -21,5 +21,7 @@ public class ApiModule implements Serializable { private Long updateTime; + private Double pos; + private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiModuleExample.java b/backend/src/main/java/io/metersphere/base/domain/ApiModuleExample.java index e5d8fd052b..716ad8065f 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ApiModuleExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/ApiModuleExample.java @@ -633,6 +633,66 @@ public class ApiModuleExample { addCriterion("update_time not between", value1, value2, "updateTime"); 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(Double value) { + addCriterion("pos =", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosNotEqualTo(Double value) { + addCriterion("pos <>", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosGreaterThan(Double value) { + addCriterion("pos >", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosGreaterThanOrEqualTo(Double value) { + addCriterion("pos >=", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosLessThan(Double value) { + addCriterion("pos <", value, "pos"); + return (Criteria) this; + } + + public Criteria andPosLessThanOrEqualTo(Double 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(Double value1, Double value2) { + addCriterion("pos between", value1, value2, "pos"); + return (Criteria) this; + } + + public Criteria andPosNotBetween(Double value1, Double value2) { + addCriterion("pos not between", value1, value2, "pos"); + return (Criteria) this; + } } public static class Criteria extends GeneratedCriteria { diff --git a/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.java index 34007517af..9c19d70f8e 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.java @@ -2,9 +2,8 @@ package io.metersphere.base.mapper; import io.metersphere.base.domain.ApiModule; import io.metersphere.base.domain.ApiModuleExample; -import org.apache.ibatis.annotations.Param; - import java.util.List; +import org.apache.ibatis.annotations.Param; public interface ApiModuleMapper { long countByExample(ApiModuleExample example); diff --git a/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.xml index 8c41d2bbab..8dcc5f8f9c 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ApiModuleMapper.xml @@ -10,6 +10,7 @@ + @@ -70,7 +71,7 @@ - id, project_id, `name`, protocol, parent_id, `level`, create_time, update_time + id, project_id, `name`, protocol, parent_id, `level`, create_time, update_time, pos @@ -198,6 +207,9 @@ update_time = #{record.updateTime,jdbcType=BIGINT}, + + pos = #{record.pos,jdbcType=DOUBLE}, + @@ -212,7 +224,8 @@ parent_id = #{record.parentId,jdbcType=VARCHAR}, `level` = #{record.level,jdbcType=INTEGER}, create_time = #{record.createTime,jdbcType=BIGINT}, - update_time = #{record.updateTime,jdbcType=BIGINT} + update_time = #{record.updateTime,jdbcType=BIGINT}, + pos = #{record.pos,jdbcType=DOUBLE} @@ -241,6 +254,9 @@ update_time = #{updateTime,jdbcType=BIGINT}, + + pos = #{pos,jdbcType=DOUBLE}, + where id = #{id,jdbcType=VARCHAR} @@ -252,7 +268,8 @@ parent_id = #{parentId,jdbcType=VARCHAR}, `level` = #{level,jdbcType=INTEGER}, create_time = #{createTime,jdbcType=BIGINT}, - update_time = #{updateTime,jdbcType=BIGINT} + update_time = #{updateTime,jdbcType=BIGINT}, + pos = #{pos,jdbcType=DOUBLE} where id = #{id,jdbcType=VARCHAR} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.java index 4da2a52de5..40b59b7b48 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.java @@ -1,5 +1,6 @@ package io.metersphere.base.mapper.ext; +import io.metersphere.api.dto.definition.ApiModuleDTO; import io.metersphere.base.domain.ApiModule; import org.apache.ibatis.annotations.Param; @@ -7,4 +8,5 @@ import java.util.List; public interface ExtApiModuleMapper { int insertBatch(@Param("records") List records); + List getNodeTreeByProjectId(@Param("projectId") String projectId, @Param("protocol") String protocol); } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.xml index d689eb7421..6e68684928 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiModuleMapper.xml @@ -13,4 +13,12 @@ #{emp.updateTime,jdbcType=BIGINT}) + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.java new file mode 100644 index 0000000000..14be67cc78 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.java @@ -0,0 +1,17 @@ +package io.metersphere.base.mapper.ext; + +import io.metersphere.track.dto.TestCaseNodeDTO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ExtTestCaseNodeMapper { + + List getNodeTreeByProjectId(@Param("projectId") String projectId); + + List getNodeTreeByProjectIds(@Param("projectIds") List projectIds); + + TestCaseNodeDTO get(String id); + + void updatePos(String id, Double pos); +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.xml new file mode 100644 index 0000000000..22c23b53e3 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseNodeMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + update test_case_node set pos = #{pos} + where id = #{id} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/service/NodeTreeService.java b/backend/src/main/java/io/metersphere/service/NodeTreeService.java new file mode 100644 index 0000000000..87a1deb73f --- /dev/null +++ b/backend/src/main/java/io/metersphere/service/NodeTreeService.java @@ -0,0 +1,238 @@ +package io.metersphere.service; + +import io.metersphere.commons.utils.BeanUtils; +import io.metersphere.track.dto.TestCaseNodeDTO; +import io.metersphere.track.dto.TreeNodeDTO; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +public class NodeTreeService { + + protected static final double LIMIT_POS = 64; + protected static final double DEFAULT_POS = 65536; + + public List getNodeTrees(List nodes) { + List nodeTreeList = new ArrayList<>(); + Map> nodeLevelMap = new HashMap<>(); + nodes.forEach(node -> { + Integer level = node.getLevel(); + if (nodeLevelMap.containsKey(level)) { + nodeLevelMap.get(level).add(node); + } else { + List testCaseNodes = new ArrayList<>(); + testCaseNodes.add(node); + nodeLevelMap.put(node.getLevel(), testCaseNodes); + } + }); + List rootNodes = Optional.ofNullable(nodeLevelMap.get(1)).orElse(new ArrayList<>()); + rootNodes.forEach(rootNode -> nodeTreeList.add(buildNodeTree(nodeLevelMap, rootNode))); + return nodeTreeList; + } + + /** + * 递归构建节点树 + * + * @param nodeLevelMap + * @param rootNode + * @return + */ + public T buildNodeTree(Map> nodeLevelMap, T rootNode) { + + T nodeTree = (T) new TreeNodeDTO(); + BeanUtils.copyBean(nodeTree, rootNode); + nodeTree.setLabel(rootNode.getName()); + + List lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1); + if (lowerNodes == null) { + return nodeTree; + } + + List children = Optional.ofNullable(nodeTree.getChildren()).orElse(new ArrayList<>()); + + lowerNodes.forEach(node -> { + if (node.getParentId() != null && node.getParentId().equals(rootNode.getId())) { + children.add(buildNodeTree(nodeLevelMap, node)); + nodeTree.setChildren(children); + } + }); + return nodeTree; + } + + /** + * 去除没有数据的节点 + * + * @param rootNode + * @param nodeIds + * @return 是否剪枝 + */ + public boolean pruningTree(T rootNode, List nodeIds) { + + List children = rootNode.getChildren(); + + if (children == null || children.isEmpty()) { + //叶子节点,并且该节点无数据 + if (!nodeIds.contains(rootNode.getId())) { + return true; + } + } + + if (children != null) { + Iterator iterator = children.iterator(); + while (iterator.hasNext()) { + T subNode = iterator.next(); + if (pruningTree(subNode, nodeIds)) { + iterator.remove(); + } + } + + if (children.isEmpty() && !nodeIds.contains(rootNode.getId())) { + return true; + } + } + + return false; + } + + /** + * 根据目标节点路径,创建相关节点 + * + * @param pathIterator 遍历子路径 + * @param path 当前路径 + * @param treeNode 当前节点 + * @param pathMap 记录节点路径对应的nodeId + */ + protected void createNodeByPathIterator(Iterator pathIterator, String path, TestCaseNodeDTO treeNode, + Map pathMap, String projectId, Integer level) { + + List children = treeNode.getChildren(); + + if (children == null || children.isEmpty() || !pathIterator.hasNext()) { + pathMap.put(path, treeNode.getId()); + if (pathIterator.hasNext()) { + createNodeByPath(pathIterator, pathIterator.next().trim(), treeNode, projectId, level, path, pathMap); + } + return; + } + + String nodeName = pathIterator.next().trim(); + + Boolean hasNode = false; + + for (TestCaseNodeDTO child : children) { + if (StringUtils.equals(nodeName, child.getName())) { + hasNode = true; + createNodeByPathIterator(pathIterator, path + "/" + child.getName(), + child, pathMap, projectId, level + 1); + } + ; + } + + //若子节点中不包含该目标节点,则在该节点下创建 + if (!hasNode) { + createNodeByPath(pathIterator, nodeName, treeNode, projectId, level, path, pathMap); + } + + } + + /** + * @param pathIterator 迭代器,遍历子节点 + * @param nodeName 当前节点 + * @param pNode 父节点 + */ + protected void createNodeByPath(Iterator pathIterator, String nodeName, + TestCaseNodeDTO pNode, String projectId, Integer level, + String rootPath, Map pathMap) { + + StringBuilder path = new StringBuilder(rootPath); + + path.append("/" + nodeName); + + String pid = null; + //创建过不创建 + if (pathMap.get(path.toString()) != null) { + pid = pathMap.get(path.toString()); + level++; + } else { + pid = insertNode(nodeName, pNode == null ? null : pNode.getId(), projectId, level); + pathMap.put(path.toString(), pid); + level++; + } + + while (pathIterator.hasNext()) { + String nextNodeName = pathIterator.next(); + path.append("/" + nextNodeName); + if (pathMap.get(path.toString()) != null) { + pid = pathMap.get(path.toString()); + level++; + } else { + pid = insertNode(nextNodeName, pid, projectId, level); + pathMap.put(path.toString(), pid); + level++; + } + } + } + + + /** + * 测试用例同级模块排序 + * + * @param ids 被拖拽模块相邻的前一个模块 id, + * 被拖拽的模块 id, + * 被拖拽模块相邻的后一个模块 id + */ + public void sort(List ids) { + // 获取相邻节点 id + String before = ids.get(0); + String id = ids.get(1); + String after = ids.get(2); + + T beforeNode = null; + T afterNode = null; + + T node = getNode(id); + + // 获取相邻节点 + if (StringUtils.isNotBlank(before)) { + beforeNode = getNode(before); + beforeNode = beforeNode.getLevel().equals(node.getLevel()) ? beforeNode : null; + } + + if (StringUtils.isNotBlank(after)) { + afterNode = getNode(after); + afterNode = afterNode.getLevel().equals(node.getLevel()) ? afterNode : null; + } + + double pos; + + if (beforeNode == null) { + pos = afterNode != null ? afterNode.getPos() / 2.0 : DEFAULT_POS; + } else { + pos = afterNode != null ? (beforeNode.getPos() + afterNode.getPos()) / 2.0 : beforeNode.getPos() + DEFAULT_POS; + } + + node.setPos(pos); + updatePos(node.getId(), node.getPos()); + + // pos 低于阈值时,触发更新方法,重新计算此目录的所有同级目录的 pos 值 + if (pos < LIMIT_POS) { + refreshPos(node.getProjectId(), node.getLevel(), node.getParentId()); + } + } + + + public String insertNode(String nodeName, String pId, String projectId, Integer level) { + return ""; + } + + public void updatePos(String id, Double pos) { + } + + protected void refreshPos(String projectId, int level, String parentId) { + } + + public T getNode(String id) { + return null; + } + +} diff --git a/backend/src/main/java/io/metersphere/track/domain/ReportResultComponent.java b/backend/src/main/java/io/metersphere/track/domain/ReportResultComponent.java index 400b94ab47..536717d7d9 100644 --- a/backend/src/main/java/io/metersphere/track/domain/ReportResultComponent.java +++ b/backend/src/main/java/io/metersphere/track/domain/ReportResultComponent.java @@ -4,6 +4,7 @@ import io.metersphere.base.domain.Project; import io.metersphere.base.domain.TestCaseNode; import io.metersphere.base.domain.TestCaseNodeExample; import io.metersphere.base.mapper.TestCaseNodeMapper; +import io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper; import io.metersphere.commons.constants.TestPlanTestCaseStatus; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.MathUtils; @@ -29,11 +30,9 @@ public class ReportResultComponent extends ReportComponent { public void init() { TestCaseNodeService testCaseNodeService = (TestCaseNodeService) CommonBeanFactory.getBean("testCaseNodeService"); - TestCaseNodeMapper testCaseNodeMapper = (TestCaseNodeMapper) CommonBeanFactory.getBean("testCaseNodeMapper"); + ExtTestCaseNodeMapper extTestCaseNodeMapper = (ExtTestCaseNodeMapper) CommonBeanFactory.getBean("extTestCaseNodeMapper"); TestPlanProjectService testPlanProjectService = (TestPlanProjectService) CommonBeanFactory.getBean("testPlanProjectService"); - TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); - testCaseNodeExample.createCriteria().andProjectIdIn(testPlanProjectService.getProjectIdsByPlanId(testPlan.getId())); - List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); + List nodes = extTestCaseNodeMapper.getNodeTreeByProjectIds(testPlanProjectService.getProjectIdsByPlanId(testPlan.getId())); nodeTrees = testCaseNodeService.getNodeTrees(nodes); nodeTrees.forEach(item -> { Set childIds = new HashSet<>(); diff --git a/backend/src/main/java/io/metersphere/track/dto/TestCaseNodeDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestCaseNodeDTO.java index 53123e119a..d74eca69ce 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestCaseNodeDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestCaseNodeDTO.java @@ -1,16 +1,9 @@ package io.metersphere.track.dto; -import io.metersphere.base.domain.TestCaseNode; import lombok.Getter; import lombok.Setter; -import java.util.List; - @Getter @Setter -public class TestCaseNodeDTO extends TestCaseNode { - - private String label; - private List children; - +public class TestCaseNodeDTO extends TreeNodeDTO { } diff --git a/backend/src/main/java/io/metersphere/track/dto/TreeNodeDTO.java b/backend/src/main/java/io/metersphere/track/dto/TreeNodeDTO.java new file mode 100644 index 0000000000..0cf7feb21b --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/dto/TreeNodeDTO.java @@ -0,0 +1,31 @@ +package io.metersphere.track.dto; + +import io.metersphere.api.dto.definition.ApiModuleDTO; +import lombok.Data; + +import java.util.List; + +@Data +public class TreeNodeDTO { + private String id; + + private String projectId; + + private String name; + + private String parentId; + + private Integer level; + + private Long createTime; + + private Long updateTime; + + private Double pos; + + private String label; + + private List children; + + private static final long serialVersionUID = 1L; +} diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java index 79d84fc311..bf50a0beff 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java @@ -5,11 +5,12 @@ import com.google.common.util.concurrent.AtomicDouble; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtTestCaseMapper; +import io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper; import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.exception.MSException; -import io.metersphere.commons.utils.BeanUtils; import io.metersphere.exception.ExcelException; import io.metersphere.i18n.Translator; +import io.metersphere.service.NodeTreeService; import io.metersphere.track.dto.TestCaseDTO; import io.metersphere.track.dto.TestCaseNodeDTO; import io.metersphere.track.request.testcase.DragNodeRequest; @@ -29,11 +30,13 @@ import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) -public class TestCaseNodeService { +public class TestCaseNodeService extends NodeTreeService { @Resource TestCaseNodeMapper testCaseNodeMapper; @Resource + ExtTestCaseNodeMapper extTestCaseNodeMapper; + @Resource TestCaseMapper testCaseMapper; @Resource TestPlanMapper testPlanMapper; @@ -54,8 +57,6 @@ public class TestCaseNodeService { @Resource TestCaseReviewMapper testCaseReviewMapper; - private static final double LIMIT_POS = 64; - public String addNode(TestCaseNode node) { validateNode(node); node.setCreateTime(System.currentTimeMillis()); @@ -96,64 +97,8 @@ public class TestCaseNodeService { } public List getNodeTreeByProjectId(String projectId) { - TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); - testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId); - testCaseNodeExample.setOrderByClause("pos asc"); - List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); - return getNodeTrees(nodes); - } - - public List getNodeTrees(List nodes) { - - List nodeTreeList = new ArrayList<>(); - - Map> nodeLevelMap = new HashMap<>(); - - nodes.forEach(node -> { - Integer level = node.getLevel(); - if (nodeLevelMap.containsKey(level)) { - nodeLevelMap.get(level).add(node); - } else { - List testCaseNodes = new ArrayList<>(); - testCaseNodes.add(node); - nodeLevelMap.put(node.getLevel(), testCaseNodes); - } - }); - - List rootNodes = Optional.ofNullable(nodeLevelMap.get(1)).orElse(new ArrayList<>()); - rootNodes.forEach(rootNode -> nodeTreeList.add(buildNodeTree(nodeLevelMap, rootNode))); - - return nodeTreeList; - } - - /** - * 递归构建节点树 - * - * @param nodeLevelMap - * @param rootNode - * @return - */ - private TestCaseNodeDTO buildNodeTree(Map> nodeLevelMap, TestCaseNode rootNode) { - - TestCaseNodeDTO nodeTree = new TestCaseNodeDTO(); - BeanUtils.copyBean(nodeTree, rootNode); - nodeTree.setLabel(rootNode.getName()); - - List lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1); - if (lowerNodes == null) { - return nodeTree; - } - - List children = Optional.ofNullable(nodeTree.getChildren()).orElse(new ArrayList<>()); - - lowerNodes.forEach(node -> { - if (node.getParentId() != null && node.getParentId().equals(rootNode.getId())) { - children.add(buildNodeTree(nodeLevelMap, node)); - nodeTree.setChildren(children); - } - }); - - return nodeTree; + List testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId); + return getNodeTrees(testCaseNodes); } public int editNode(DragNodeRequest request) { @@ -247,9 +192,7 @@ public class TestCaseNodeService { return null; } - TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); - testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId); - List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); + List testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId); List caseIds = testPlanTestCases.stream() .map(TestPlanTestCase::getCaseId) @@ -261,7 +204,7 @@ public class TestCaseNodeService { .map(TestCase::getNodeId) .collect(Collectors.toList()); - List nodeTrees = getNodeTrees(nodes); + List nodeTrees = getNodeTrees(testCaseNodes); Iterator iterator = nodeTrees.iterator(); while (iterator.hasNext()) { @@ -280,9 +223,7 @@ public class TestCaseNodeService { return null; } - TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); - testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId); - List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); + List testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId); TestCaseExample testCaseExample = new TestCaseExample(); @@ -291,7 +232,7 @@ public class TestCaseNodeService { .map(TestCase::getNodeId) .collect(Collectors.toList()); - List nodeTrees = getNodeTrees(nodes); + List nodeTrees = getNodeTrees(testCaseNodes); Iterator iterator = nodeTrees.iterator(); while (iterator.hasNext()) { @@ -304,41 +245,6 @@ public class TestCaseNodeService { return nodeTrees; } - /** - * 去除没有数据的节点 - * - * @param rootNode - * @param nodeIds - * @return 是否剪枝 - */ - public boolean pruningTree(TestCaseNodeDTO rootNode, List nodeIds) { - - List children = rootNode.getChildren(); - - if (children == null || children.isEmpty()) { - //叶子节点,并且该节点无数据 - if (!nodeIds.contains(rootNode.getId())) { - return true; - } - } - - if (children != null) { - Iterator iterator = children.iterator(); - while (iterator.hasNext()) { - TestCaseNodeDTO subNode = iterator.next(); - if (pruningTree(subNode, nodeIds)) { - iterator.remove(); - } - } - - if (children.isEmpty() && !nodeIds.contains(rootNode.getId())) { - return true; - } - } - - return false; - } - public List getAllNodeByPlanId(QueryNodeRequest request) { String planId = request.getTestPlanId(); String projectId = request.getProjectId(); @@ -405,86 +311,8 @@ public class TestCaseNodeService { } - /** - * 根据目标节点路径,创建相关节点 - * - * @param pathIterator 遍历子路径 - * @param path 当前路径 - * @param treeNode 当前节点 - * @param pathMap 记录节点路径对应的nodeId - */ - private void createNodeByPathIterator(Iterator pathIterator, String path, TestCaseNodeDTO treeNode, - Map pathMap, String projectId, Integer level) { - - List children = treeNode.getChildren(); - - if (children == null || children.isEmpty() || !pathIterator.hasNext()) { - pathMap.put(path, treeNode.getId()); - if (pathIterator.hasNext()) { - createNodeByPath(pathIterator, pathIterator.next().trim(), treeNode, projectId, level, path, pathMap); - } - return; - } - - String nodeName = pathIterator.next().trim(); - - Boolean hasNode = false; - - for (TestCaseNodeDTO child : children) { - if (StringUtils.equals(nodeName, child.getName())) { - hasNode = true; - createNodeByPathIterator(pathIterator, path + "/" + child.getName(), - child, pathMap, projectId, level + 1); - } - ; - } - - //若子节点中不包含该目标节点,则在该节点下创建 - if (!hasNode) { - createNodeByPath(pathIterator, nodeName, treeNode, projectId, level, path, pathMap); - } - - } - - /** - * @param pathIterator 迭代器,遍历子节点 - * @param nodeName 当前节点 - * @param pNode 父节点 - */ - private void createNodeByPath(Iterator pathIterator, String nodeName, - TestCaseNodeDTO pNode, String projectId, Integer level, - String rootPath, Map pathMap) { - - StringBuilder path = new StringBuilder(rootPath); - - path.append("/" + nodeName); - - String pid = null; - //创建过不创建 - if (pathMap.get(path.toString()) != null) { - pid = pathMap.get(path.toString()); - level++; - } else { - pid = insertTestCaseNode(nodeName, pNode == null ? null : pNode.getId(), projectId, level); - pathMap.put(path.toString(), pid); - level++; - } - - while (pathIterator.hasNext()) { - String nextNodeName = pathIterator.next(); - path.append("/" + nextNodeName); - if (pathMap.get(path.toString()) != null) { - pid = pathMap.get(path.toString()); - level++; - } else { - pid = insertTestCaseNode(nextNodeName, pid, projectId, level); - pathMap.put(path.toString(), pid); - level++; - } - } - } - - private String insertTestCaseNode(String nodeName, String pId, String projectId, Integer level) { + @Override + public String insertNode(String nodeName, String pId, String projectId, Integer level) { TestCaseNode testCaseNode = new TestCaseNode(); testCaseNode.setName(nodeName.trim()); testCaseNode.setParentId(pId); @@ -587,51 +415,14 @@ public class TestCaseNodeService { return testCaseNodeMapper.selectByPrimaryKey(id); } + @Override + public TestCaseNodeDTO getNode(String id) { + return extTestCaseNodeMapper.get(id); + } - /** - * 测试用例同级模块排序 - * - * @param ids 被拖拽模块相邻的前一个模块 id, - * 被拖拽的模块 id, - * 被拖拽模块相邻的后一个模块 id - */ - public void sort(List ids) { - // 获取相邻节点 id - String before = ids.get(0); - String id = ids.get(1); - String after = ids.get(2); - - TestCaseNode beforeCase = null; - TestCaseNode afterCase = null; - - TestCaseNode caseNode = getCaseNode(id); - - // 获取相邻节点 - if (StringUtils.isNotBlank(before)) { - beforeCase = getCaseNode(before); - beforeCase = beforeCase.getLevel().equals(caseNode.getLevel()) ? beforeCase : null; - } - - if (StringUtils.isNotBlank(after)) { - afterCase = getCaseNode(after); - afterCase = afterCase.getLevel().equals(caseNode.getLevel()) ? afterCase : null; - } - - double pos; - - if (beforeCase == null) { - pos = afterCase != null ? afterCase.getPos() / 2.0 : 65536; - } else { - pos = afterCase != null ? (beforeCase.getPos() + afterCase.getPos()) / 2.0 : beforeCase.getPos() + 65536; - } - - caseNode.setPos(pos); - testCaseNodeMapper.updateByPrimaryKeySelective(caseNode); - - // pos 低于阈值时,触发更新方法,重新计算此目录的所有同级目录的 pos 值 - if (pos < LIMIT_POS) { - refreshPos(caseNode.getProjectId(), caseNode.getLevel(), caseNode.getParentId()); - } + @Override + public void updatePos(String id, Double pos) { + extTestCaseNodeMapper.updatePos(id, pos); } /** @@ -661,14 +452,15 @@ public class TestCaseNodeService { * @param level node level * @param parentId node parent id */ - private void refreshPos(String projectId, int level, String parentId) { + @Override + protected void refreshPos(String projectId, int level, String parentId) { List nodes = getPos(projectId, level, parentId, "pos asc"); if (!CollectionUtils.isEmpty(nodes)) { SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); TestCaseNodeMapper testCaseNodeMapper = sqlSession.getMapper(TestCaseNodeMapper.class); - AtomicDouble pos = new AtomicDouble(65536); + AtomicDouble pos = new AtomicDouble(DEFAULT_POS); nodes.forEach((node) -> { - node.setPos(pos.getAndAdd(65536)); + node.setPos(pos.getAndAdd(DEFAULT_POS)); testCaseNodeMapper.updateByPrimaryKey(node); }); sqlSession.flushStatements(); @@ -687,9 +479,9 @@ public class TestCaseNodeService { private double getNextLevelPos(String projectId, int level, String parentId) { List list = getPos(projectId, level, parentId, "pos desc"); if (!CollectionUtils.isEmpty(list) && list.get(0) != null && list.get(0).getPos() != null) { - return list.get(0).getPos() + 65536; + return list.get(0).getPos() + DEFAULT_POS; } else { - return 65536; + return DEFAULT_POS; } } diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index 1fe20ba15a..bb494fc68a 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit 1fe20ba15a7ca3fe9f77ddf866021e7c7dfe5969 +Subproject commit bb494fc68a2367359c9048fa7250c7618de4afb6 diff --git a/backend/src/main/resources/db/migration/V46__api_definition.sql b/backend/src/main/resources/db/migration/V46__api_definition.sql index 3509a1a023..188e584390 100644 --- a/backend/src/main/resources/db/migration/V46__api_definition.sql +++ b/backend/src/main/resources/db/migration/V46__api_definition.sql @@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS `api_module` ( `protocol` varchar(64) NOT NULL COMMENT 'Node protocol', `parent_id` varchar(50) DEFAULT NULL COMMENT 'Parent node ID', `level` int(10) DEFAULT 1 COMMENT 'Node level', + `pos` double DEFAULT NULL COMMENT 'Node order', `create_time` bigint(13) NOT NULL COMMENT 'Create timestamp', `update_time` bigint(13) NOT NULL COMMENT 'Update timestamp', PRIMARY KEY (`id`) diff --git a/backend/src/main/resources/generatorConfig.xml b/backend/src/main/resources/generatorConfig.xml index 60e77f17f1..b178cd6d05 100644 --- a/backend/src/main/resources/generatorConfig.xml +++ b/backend/src/main/resources/generatorConfig.xml @@ -64,8 +64,8 @@ - -
+
+ \ No newline at end of file diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index 5835db186d..a22a3005d9 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit 5835db186d17a3d305073e58affb4e88a71b32f0 +Subproject commit a22a3005d9bd254793fcf634d72539cbdf31be3a