diff --git a/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java b/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java index b4939ef381..26cbc1ba0d 100644 --- a/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java +++ b/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java @@ -17,7 +17,12 @@ public class TestCaseNodeController { @GetMapping("/list/{projectId}") public List getNodeByProjectId(@PathVariable String projectId){ - return testCaseNodeService.getNodeByProjectId(projectId); + return testCaseNodeService.getNodeTreeByProjectId(projectId); + } + + @GetMapping("/list/plan/{planId}") + public List getNodeByPlanId(@PathVariable String planId){ + return testCaseNodeService.getNodeByPlanId(planId); } @PostMapping("/add") @@ -35,7 +40,4 @@ public class TestCaseNodeController { //nodeIds 包含删除节点ID及其所有子节点ID return testCaseNodeService.deleteNode(nodeIds); } - - - } diff --git a/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java b/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java index 813854c82b..06a1b20944 100644 --- a/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java +++ b/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java @@ -1,11 +1,11 @@ package io.metersphere.service; -import io.metersphere.base.domain.TestCaseExample; -import io.metersphere.base.domain.TestCaseNode; -import io.metersphere.base.domain.TestCaseNodeExample; +import io.metersphere.base.domain.*; import io.metersphere.base.mapper.TestCaseMapper; import io.metersphere.base.mapper.TestCaseNodeMapper; +import io.metersphere.base.mapper.TestPlanMapper; +import io.metersphere.base.mapper.TestPlanTestCaseMapper; import io.metersphere.commons.utils.BeanUtils; import io.metersphere.dto.TestCaseNodeDTO; import org.springframework.stereotype.Service; @@ -13,6 +13,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; +import java.util.stream.Collectors; @Service @Transactional(rollbackFor = Exception.class) @@ -22,6 +23,10 @@ public class TestCaseNodeService { TestCaseNodeMapper testCaseNodeMapper; @Resource TestCaseMapper testCaseMapper; + @Resource + TestPlanMapper testPlanMapper; + @Resource + TestPlanTestCaseMapper testPlanTestCaseMapper; public int addNode(TestCaseNode node) { @@ -34,13 +39,16 @@ public class TestCaseNodeService { return node.getId(); } - public List getNodeByProjectId(String projectId) { - - List nodeTreeList = new ArrayList<>(); - + public List getNodeTreeByProjectId(String projectId) { TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId); List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); + return getNodeTrees(nodes); + } + + private List getNodeTrees(List nodes) { + + List nodeTreeList = new ArrayList<>(); Map> nodeLevelMap = new HashMap<>(); @@ -57,6 +65,7 @@ public class TestCaseNodeService { List rootNodes = Optional.ofNullable(nodeLevelMap.get(1)).orElse(new ArrayList<>()); rootNodes.forEach(rootNode -> nodeTreeList.add(buildNodeTree(nodeLevelMap, rootNode))); + return nodeTreeList; } @@ -72,14 +81,14 @@ public class TestCaseNodeService { BeanUtils.copyBean(nodeTree, rootNode); nodeTree.setLabel(rootNode.getName()); - List testCaseNodes = nodeLevelMap.get(rootNode.getLevel() + 1); - if(testCaseNodes == null){ + List lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1); + if(lowerNodes == null){ return nodeTree; } List childrens = Optional.ofNullable(nodeTree.getChildren()).orElse(new ArrayList<>()); - testCaseNodes.forEach(node -> { + lowerNodes.forEach(node -> { if (node.getpId().equals(rootNode.getId())){ childrens.add(buildNodeTree(nodeLevelMap, node)); nodeTree.setChildren(childrens); @@ -103,4 +112,69 @@ public class TestCaseNodeService { testCaseNodeExample.createCriteria().andIdIn(nodeIds); return testCaseNodeMapper.deleteByExample(testCaseNodeExample); } + + public List getNodeByPlanId(String planId) { + + TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId); + + TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample(); + testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(planId); + List testPlanTestCases = testPlanTestCaseMapper.selectByExample(testPlanTestCaseExample); + + TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample(); + testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId()); + List nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample); + + List caseIds = testPlanTestCases.stream() + .map(TestPlanTestCase::getCaseId) + .collect(Collectors.toList()); + + List nodeIds = nodes.stream() + .filter(node -> caseIds.contains(node.getId())) + .map(TestCaseNode::getId) + .collect(Collectors.toList()); + + List nodeTrees = getNodeTrees(nodes); + + Iterator iterator = nodeTrees.iterator(); + while(iterator.hasNext()){ + TestCaseNodeDTO rootNode = iterator.next(); + if(pruningTree(rootNode, nodeIds)){ + iterator.remove(); + } + } + + return nodeTrees; + } + + /** + * 去除没有数据的节点 + * @param rootNode + * @param nodeIds + * @return 是否剪枝 + * */ + public boolean pruningTree(TestCaseNodeDTO rootNode, List nodeIds) { + + List children = rootNode.getChildren(); + + if(children != null) { + Iterator iterator = children.iterator(); + while(iterator.hasNext()){ + TestCaseNodeDTO subNode = iterator.next(); + if(pruningTree(subNode, nodeIds)){ + iterator.remove(); + } + } + } + + if(children == null || children.isEmpty()){ + //叶子节点,并且该节点无数据 + if(!nodeIds.contains(rootNode.getId())){ + return true; + } + } + + return false; + } + } diff --git a/backend/src/test/java/io/metersphere/service/TestCaseTest.java b/backend/src/test/java/io/metersphere/service/TestCaseTest.java index 739e767db4..5e7975760d 100644 --- a/backend/src/test/java/io/metersphere/service/TestCaseTest.java +++ b/backend/src/test/java/io/metersphere/service/TestCaseTest.java @@ -18,14 +18,13 @@ public class TestCaseTest { @Test public void addNode() { - TestCaseNode node = new TestCaseNode(); node.setName("node01"); node.setProjectId("2ade216b-01a6-43d0-b48c-4a3898306096"); node.setCreateTime(System.currentTimeMillis()); node.setUpdateTime(System.currentTimeMillis()); testCaseNodeService.addNode(node); - } + } diff --git a/frontend/src/business/components/common/router/router.js b/frontend/src/business/components/common/router/router.js index 1d42fc3e7d..a0c342e013 100644 --- a/frontend/src/business/components/common/router/router.js +++ b/frontend/src/business/components/common/router/router.js @@ -26,6 +26,7 @@ import PerformanceReportView from "../../performance/report/PerformanceReportVie import FunctionalReportView from "../../functional/report/FunctionalReportView"; import TrackHome from "../../track/home/TrackHome"; import TestPlan from "../../track/plan/TestPlan"; +import TestPlanView from "../../track/plan/TestPlanView"; import TestCase from "../../track/case/TestCase"; import TestTrack from "../../track/TestTrack"; @@ -212,6 +213,11 @@ const router = new VueRouter({ name: "testPlan", component: TestPlan }, + { + path: "plan/view/:planId", + name: "planView", + component: TestPlanView + }, { path: "project/:type", name: "trackProject", diff --git a/frontend/src/business/components/track/case/TestCase.vue b/frontend/src/business/components/track/case/TestCase.vue index d1559eccb8..4a15cbe80d 100644 --- a/frontend/src/business/components/track/case/TestCase.vue +++ b/frontend/src/business/components/track/case/TestCase.vue @@ -7,7 +7,7 @@ class="project_menu"> - @@ -130,7 +130,7 @@ moduleOptions.push(option); if(node.children){ for (let i = 0; i < node.children.length; i++){ - this.buildNodePath(node.children[i], { path: '/' + node.children[i].name }, moduleOptions); + this.buildNodePath(node.children[i], { path: '/' + node.name }, moduleOptions); } } }, @@ -170,6 +170,7 @@ .case_container { background: white; + height: 600px; } .node_tree { @@ -177,13 +178,8 @@ } .project_menu { - /*border-style:none;*/ margin-left: 20px; height: 50px; } - .case_container { - height: 600px; - } - diff --git a/frontend/src/business/components/track/case/components/NodeTree.vue b/frontend/src/business/components/track/case/components/NodeTree.vue index f82a8c77b0..12a5c06abb 100644 --- a/frontend/src/business/components/track/case/components/NodeTree.vue +++ b/frontend/src/business/components/track/case/components/NodeTree.vue @@ -14,7 +14,6 @@ :filter-node-method="filterNode" :expand-on-click-node="false" draggable - ref="tree"> @@ -58,7 +57,6 @@ - + + diff --git a/frontend/src/business/components/track/plan/components/PlanNodeTree.vue b/frontend/src/business/components/track/plan/components/PlanNodeTree.vue new file mode 100644 index 0000000000..4a045c245e --- /dev/null +++ b/frontend/src/business/components/track/plan/components/PlanNodeTree.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/frontend/src/business/components/track/plan/components/TestPlanList.vue b/frontend/src/business/components/track/plan/components/TestPlanList.vue index 34a82df17d..5b296c5650 100644 --- a/frontend/src/business/components/track/plan/components/TestPlanList.vue +++ b/frontend/src/business/components/track/plan/components/TestPlanList.vue @@ -33,7 +33,8 @@ + class="test-content" + @row-click="intoPlan"> @@ -198,6 +203,9 @@ type: 'success' }); }); + }, + intoPlan(row, event, column) { + this.$router.push('/track/plan/view/' + row.id); } } }