diff --git a/backend/src/main/java/io/metersphere/base/domain/TestCaseNode.java b/backend/src/main/java/io/metersphere/base/domain/TestCaseNode.java index d17174d90e..7ab9e4040b 100644 --- a/backend/src/main/java/io/metersphere/base/domain/TestCaseNode.java +++ b/backend/src/main/java/io/metersphere/base/domain/TestCaseNode.java @@ -19,5 +19,7 @@ public class TestCaseNode 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/TestCaseNodeExample.java b/backend/src/main/java/io/metersphere/base/domain/TestCaseNodeExample.java index 9b76aaada3..b1d94317e9 100644 --- a/backend/src/main/java/io/metersphere/base/domain/TestCaseNodeExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/TestCaseNodeExample.java @@ -245,72 +245,72 @@ public class TestCaseNodeExample { } public Criteria andNameIsNull() { - addCriterion("name is null"); + addCriterion("`name` is null"); return (Criteria) this; } public Criteria andNameIsNotNull() { - addCriterion("name is not null"); + addCriterion("`name` is not null"); return (Criteria) this; } public Criteria andNameEqualTo(String value) { - addCriterion("name =", value, "name"); + addCriterion("`name` =", value, "name"); return (Criteria) this; } public Criteria andNameNotEqualTo(String value) { - addCriterion("name <>", value, "name"); + addCriterion("`name` <>", value, "name"); return (Criteria) this; } public Criteria andNameGreaterThan(String value) { - addCriterion("name >", value, "name"); + addCriterion("`name` >", value, "name"); return (Criteria) this; } public Criteria andNameGreaterThanOrEqualTo(String value) { - addCriterion("name >=", value, "name"); + addCriterion("`name` >=", value, "name"); return (Criteria) this; } public Criteria andNameLessThan(String value) { - addCriterion("name <", value, "name"); + addCriterion("`name` <", value, "name"); return (Criteria) this; } public Criteria andNameLessThanOrEqualTo(String value) { - addCriterion("name <=", value, "name"); + addCriterion("`name` <=", value, "name"); return (Criteria) this; } public Criteria andNameLike(String value) { - addCriterion("name like", value, "name"); + addCriterion("`name` like", value, "name"); return (Criteria) this; } public Criteria andNameNotLike(String value) { - addCriterion("name not like", value, "name"); + addCriterion("`name` not like", value, "name"); return (Criteria) this; } public Criteria andNameIn(List values) { - addCriterion("name in", values, "name"); + addCriterion("`name` in", values, "name"); return (Criteria) this; } public Criteria andNameNotIn(List values) { - addCriterion("name not in", values, "name"); + addCriterion("`name` not in", values, "name"); return (Criteria) this; } public Criteria andNameBetween(String value1, String value2) { - addCriterion("name between", value1, value2, "name"); + addCriterion("`name` between", value1, value2, "name"); return (Criteria) this; } public Criteria andNameNotBetween(String value1, String value2) { - addCriterion("name not between", value1, value2, "name"); + addCriterion("`name` not between", value1, value2, "name"); return (Criteria) this; } @@ -385,62 +385,62 @@ public class TestCaseNodeExample { } public Criteria andLevelIsNull() { - addCriterion("level is null"); + addCriterion("`level` is null"); return (Criteria) this; } public Criteria andLevelIsNotNull() { - addCriterion("level is not null"); + addCriterion("`level` is not null"); return (Criteria) this; } public Criteria andLevelEqualTo(Integer value) { - addCriterion("level =", value, "level"); + addCriterion("`level` =", value, "level"); return (Criteria) this; } public Criteria andLevelNotEqualTo(Integer value) { - addCriterion("level <>", value, "level"); + addCriterion("`level` <>", value, "level"); return (Criteria) this; } public Criteria andLevelGreaterThan(Integer value) { - addCriterion("level >", value, "level"); + addCriterion("`level` >", value, "level"); return (Criteria) this; } public Criteria andLevelGreaterThanOrEqualTo(Integer value) { - addCriterion("level >=", value, "level"); + addCriterion("`level` >=", value, "level"); return (Criteria) this; } public Criteria andLevelLessThan(Integer value) { - addCriterion("level <", value, "level"); + addCriterion("`level` <", value, "level"); return (Criteria) this; } public Criteria andLevelLessThanOrEqualTo(Integer value) { - addCriterion("level <=", value, "level"); + addCriterion("`level` <=", value, "level"); return (Criteria) this; } public Criteria andLevelIn(List values) { - addCriterion("level in", values, "level"); + addCriterion("`level` in", values, "level"); return (Criteria) this; } public Criteria andLevelNotIn(List values) { - addCriterion("level not in", values, "level"); + addCriterion("`level` not in", values, "level"); return (Criteria) this; } public Criteria andLevelBetween(Integer value1, Integer value2) { - addCriterion("level between", value1, value2, "level"); + addCriterion("`level` between", value1, value2, "level"); return (Criteria) this; } public Criteria andLevelNotBetween(Integer value1, Integer value2) { - addCriterion("level not between", value1, value2, "level"); + addCriterion("`level` not between", value1, value2, "level"); return (Criteria) this; } @@ -563,6 +563,66 @@ public class TestCaseNodeExample { 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/TestCaseNodeMapper.java b/backend/src/main/java/io/metersphere/base/mapper/TestCaseNodeMapper.java index f722b5b2a9..4e88faf0e7 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/TestCaseNodeMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/TestCaseNodeMapper.java @@ -2,9 +2,8 @@ package io.metersphere.base.mapper; import io.metersphere.base.domain.TestCaseNode; import io.metersphere.base.domain.TestCaseNodeExample; -import org.apache.ibatis.annotations.Param; - import java.util.List; +import org.apache.ibatis.annotations.Param; public interface TestCaseNodeMapper { long countByExample(TestCaseNodeExample example); @@ -15,9 +14,6 @@ public interface TestCaseNodeMapper { int insert(TestCaseNode record); - int insertBatch(@Param("records") List records); - - int insertSelective(TestCaseNode record); List selectByExample(TestCaseNodeExample example); diff --git a/backend/src/main/java/io/metersphere/base/mapper/TestCaseNodeMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/TestCaseNodeMapper.xml index 937db25de9..a85376c6b8 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/TestCaseNodeMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/TestCaseNodeMapper.xml @@ -9,6 +9,7 @@ + @@ -69,7 +70,7 @@ - id, project_id, name, parent_id, level, create_time, update_time + id, project_id, `name`, parent_id, `level`, create_time, update_time, pos @@ -188,13 +181,13 @@ project_id = #{record.projectId,jdbcType=VARCHAR}, - name = #{record.name,jdbcType=VARCHAR}, + `name` = #{record.name,jdbcType=VARCHAR}, parent_id = #{record.parentId,jdbcType=VARCHAR}, - level = #{record.level,jdbcType=INTEGER}, + `level` = #{record.level,jdbcType=INTEGER}, create_time = #{record.createTime,jdbcType=BIGINT}, @@ -202,6 +195,9 @@ update_time = #{record.updateTime,jdbcType=BIGINT}, + + pos = #{record.pos,jdbcType=DOUBLE}, + @@ -211,11 +207,12 @@ update test_case_node set id = #{record.id,jdbcType=VARCHAR}, project_id = #{record.projectId,jdbcType=VARCHAR}, - name = #{record.name,jdbcType=VARCHAR}, + `name` = #{record.name,jdbcType=VARCHAR}, parent_id = #{record.parentId,jdbcType=VARCHAR}, - level = #{record.level,jdbcType=INTEGER}, + `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} @@ -227,13 +224,13 @@ project_id = #{projectId,jdbcType=VARCHAR}, - name = #{name,jdbcType=VARCHAR}, + `name` = #{name,jdbcType=VARCHAR}, parent_id = #{parentId,jdbcType=VARCHAR}, - level = #{level,jdbcType=INTEGER}, + `level` = #{level,jdbcType=INTEGER}, create_time = #{createTime,jdbcType=BIGINT}, @@ -241,17 +238,21 @@ update_time = #{updateTime,jdbcType=BIGINT}, + + pos = #{pos,jdbcType=DOUBLE}, + where id = #{id,jdbcType=VARCHAR} update test_case_node set project_id = #{projectId,jdbcType=VARCHAR}, - name = #{name,jdbcType=VARCHAR}, + `name` = #{name,jdbcType=VARCHAR}, parent_id = #{parentId,jdbcType=VARCHAR}, - level = #{level,jdbcType=INTEGER}, + `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/track/controller/TestCaseNodeController.java b/backend/src/main/java/io/metersphere/track/controller/TestCaseNodeController.java index e1109a1428..0a7f52ee72 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestCaseNodeController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestCaseNodeController.java @@ -77,4 +77,9 @@ public class TestCaseNodeController { public void dragNode(@RequestBody DragNodeRequest node) { testCaseNodeService.dragNode(node); } + + @PostMapping("/pos") + public void treeSort(@RequestBody List ids) { + testCaseNodeService.sort(ids); + } } 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 4c66a29c5f..5610abfe9f 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java @@ -58,6 +58,8 @@ public class TestCaseNodeService { node.setCreateTime(System.currentTimeMillis()); node.setUpdateTime(System.currentTimeMillis()); node.setId(UUID.randomUUID().toString()); + double pos = getNextLevelPos(node.getProjectId(), node.getLevel()); + node.setPos(pos); testCaseNodeMapper.insertSelective(node); return node.getId(); } @@ -93,7 +95,7 @@ public class TestCaseNodeService { public List getNodeTreeByProjectId(String projectId) { TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId); - testCaseNodeExample.setOrderByClause("create_time asc"); + testCaseNodeExample.setOrderByClause("pos asc"); List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); return getNodeTrees(nodes); } @@ -486,6 +488,8 @@ public class TestCaseNodeService { testCaseNode.setUpdateTime(System.currentTimeMillis()); testCaseNode.setLevel(level); testCaseNode.setId(UUID.randomUUID().toString()); + double pos = getNextLevelPos(projectId, level); + testCaseNode.setPos(pos); testCaseNodeMapper.insert(testCaseNode); return testCaseNode.getId(); } @@ -574,4 +578,60 @@ public class TestCaseNodeService { return projectMapper.selectByPrimaryKey(projectId); } + private TestCaseNode getCaseNode(String id) { + return testCaseNodeMapper.selectByPrimaryKey(id); + } + + + /** + * 测试用例同级模块排序 + * @param ids + */ + public void sort(List ids) { + // 获取同级相邻节点 + 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; + } + + // todo pos 低于阈值时,触发更新方法,重新计算此目录的所有同级目录的 pos 值 + + caseNode.setPos(pos); + testCaseNodeMapper.updateByPrimaryKeySelective(caseNode); + } + + public double getNextLevelPos(String projectId, int level) { + TestCaseNodeExample example = new TestCaseNodeExample(); + example.createCriteria().andProjectIdEqualTo(projectId).andLevelEqualTo(level); + example.setOrderByClause("pos desc"); + List list = testCaseNodeMapper.selectByExample(example); + if (!CollectionUtils.isEmpty(list)) { + return list.get(0).getPos() + 65536; + } else { + return 65536; + } + } + } diff --git a/backend/src/main/resources/db/migration/V39__test_case_node_add_pos.sql b/backend/src/main/resources/db/migration/V39__test_case_node_add_pos.sql new file mode 100644 index 0000000000..36978c314a --- /dev/null +++ b/backend/src/main/resources/db/migration/V39__test_case_node_add_pos.sql @@ -0,0 +1,58 @@ +alter table test_case_node add pos double null; + +DROP PROCEDURE IF EXISTS pos_cursor; +DELIMITER // +CREATE PROCEDURE pos_cursor() +BEGIN + DECLARE projectId VARCHAR(64); + DECLARE nodeId VARCHAR(64); + DECLARE pos DOUBLE; + DECLARE level INT; + DECLARE done INT DEFAULT 0; + DECLARE cursor1 CURSOR FOR (SELECT DISTINCT project_id + FROM test_case_node + WHERE pos IS NULL); + DECLARE cursor2 CURSOR FOR (select id + from test_case_node + where project_id = projectId + and test_case_node.level = level + order by create_time); + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; + OPEN cursor1; + outer_loop: + LOOP + FETCH cursor1 INTO projectId; + IF done + THEN + LEAVE outer_loop; + END IF; + SET level = 1; + select max(test_case_node.level) into @max_level from test_case_node where project_id = projectId; + while level <= @max_level + do + set pos = 65536; + OPEN cursor2; + inner_loop: + LOOP + FETCH cursor2 INTO nodeId; + IF done + THEN + LEAVE inner_loop; + END IF; + UPDATE test_case_node + SET test_case_node.pos = pos + WHERE id = nodeId; + SET pos = pos + 65536; + END LOOP; + SET done = 0; + CLOSE cursor2; + set level = level + 1; + end while; + + END LOOP; + CLOSE cursor1; +END // +DELIMITER ; + +CALL pos_cursor(); +DROP PROCEDURE IF EXISTS pos_cursor; \ No newline at end of file diff --git a/frontend/src/business/components/track/common/NodeTree.vue b/frontend/src/business/components/track/common/NodeTree.vue index 49aac93523..835d6cfc9b 100644 --- a/frontend/src/business/components/track/common/NodeTree.vue +++ b/frontend/src/business/components/track/common/NodeTree.vue @@ -73,7 +73,8 @@ export default { children: "children", label: "label" }, - disabled: false + disabled: false, + list: [] }; }, props: { @@ -108,8 +109,12 @@ export default { methods: { handleDragEnd(draggingNode, dropNode, dropType, ev) { let param = this.buildParam(draggingNode, dropNode, dropType); + + this.list = []; + this.getNodeTree(this.treeNodes,draggingNode.data.id, this.list); this.$post("/case/node/drag", param, () => { draggingNode.data.level = param.level; + this.$post("/case/node/pos", this.list); this.refreshTable(); }, (error) => { this.refreshNode(); @@ -148,6 +153,22 @@ export default { param.nodeIds = nodeIds; return param; }, + getNodeTree(nodes, id, list) { + if (!nodes) { + return; + } + for (let i = 0; i < nodes.length; i++) { + if (nodes[i].id === id) { + i - 1 >= 0 ? list[0] = nodes[i-1].id : list[0] = ""; + list[1] = nodes[i].id; + i + 1 < nodes.length ? list[2] = nodes[i+1].id : list[2] = ""; + return; + } + if (nodes[i].children) { + this.getNodeTree(nodes[i].children, id, list); + } + } + }, refreshTable() { this.$emit('refreshTable'); },