Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
5d4c53e15b
|
@ -17,7 +17,12 @@ public class TestCaseNodeController {
|
|||
|
||||
@GetMapping("/list/{projectId}")
|
||||
public List<TestCaseNodeDTO> getNodeByProjectId(@PathVariable String projectId){
|
||||
return testCaseNodeService.getNodeByProjectId(projectId);
|
||||
return testCaseNodeService.getNodeTreeByProjectId(projectId);
|
||||
}
|
||||
|
||||
@GetMapping("/list/plan/{planId}")
|
||||
public List<TestCaseNodeDTO> getNodeByPlanId(@PathVariable String planId){
|
||||
return testCaseNodeService.getNodeByPlanId(planId);
|
||||
}
|
||||
|
||||
@PostMapping("/add")
|
||||
|
@ -35,7 +40,4 @@ public class TestCaseNodeController {
|
|||
//nodeIds 包含删除节点ID及其所有子节点ID
|
||||
return testCaseNodeService.deleteNode(nodeIds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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<TestCaseNodeDTO> getNodeByProjectId(String projectId) {
|
||||
|
||||
List<TestCaseNodeDTO> nodeTreeList = new ArrayList<>();
|
||||
|
||||
public List<TestCaseNodeDTO> getNodeTreeByProjectId(String projectId) {
|
||||
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
|
||||
testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId);
|
||||
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
|
||||
return getNodeTrees(nodes);
|
||||
}
|
||||
|
||||
private List<TestCaseNodeDTO> getNodeTrees(List<TestCaseNode> nodes) {
|
||||
|
||||
List<TestCaseNodeDTO> nodeTreeList = new ArrayList<>();
|
||||
|
||||
Map<Integer, List<TestCaseNode>> nodeLevelMap = new HashMap<>();
|
||||
|
||||
|
@ -57,6 +65,7 @@ public class TestCaseNodeService {
|
|||
|
||||
List<TestCaseNode> 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<TestCaseNode> testCaseNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
|
||||
if(testCaseNodes == null){
|
||||
List<TestCaseNode> lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
|
||||
if(lowerNodes == null){
|
||||
return nodeTree;
|
||||
}
|
||||
|
||||
List<TestCaseNodeDTO> 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<TestCaseNodeDTO> getNodeByPlanId(String planId) {
|
||||
|
||||
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
|
||||
|
||||
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
|
||||
testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(planId);
|
||||
List<TestPlanTestCase> testPlanTestCases = testPlanTestCaseMapper.selectByExample(testPlanTestCaseExample);
|
||||
|
||||
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
|
||||
testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId());
|
||||
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
|
||||
|
||||
List<String> caseIds = testPlanTestCases.stream()
|
||||
.map(TestPlanTestCase::getCaseId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Integer> nodeIds = nodes.stream()
|
||||
.filter(node -> caseIds.contains(node.getId()))
|
||||
.map(TestCaseNode::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(nodes);
|
||||
|
||||
Iterator<TestCaseNodeDTO> 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<Integer> nodeIds) {
|
||||
|
||||
List<TestCaseNodeDTO> children = rootNode.getChildren();
|
||||
|
||||
if(children != null) {
|
||||
Iterator<TestCaseNodeDTO> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
class="project_menu">
|
||||
<el-submenu index="1" popper-class="submenu" v-permission="['test_user', 'test_viewer']">
|
||||
<template slot="title">
|
||||
{{currentProject.name}}
|
||||
{{currentProject == null ? '' : currentProject.name}}
|
||||
</template>
|
||||
<el-scrollbar style="height:500px">
|
||||
<label v-for="(item,index) in projects" :key="index">
|
||||
|
@ -19,7 +19,7 @@
|
|||
</el-scrollbar>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
<node-tree class="node_tree" :project-id="currentProject.id"
|
||||
<node-tree class="node_tree"
|
||||
@nodeSelectEvent="getCaseByNodeIds"
|
||||
@refresh="getCaseByNodeIds"
|
||||
ref="nodeTree"></node-tree>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
:filter-node-method="filterNode"
|
||||
:expand-on-click-node="false"
|
||||
draggable
|
||||
|
||||
ref="tree">
|
||||
|
||||
<span class="custom-tree-node father" slot-scope="{ node, data }" @click="selectNode(node)">
|
||||
|
@ -58,7 +57,6 @@
|
|||
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -92,7 +90,6 @@
|
|||
this.getNodeTree();
|
||||
},
|
||||
methods: {
|
||||
|
||||
handleDragEnd(draggingNode, dropNode, dropType, ev) {
|
||||
let param = {};
|
||||
param.id = draggingNode.data.id;
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
|
||||
<div class="plan_container">
|
||||
<el-container>
|
||||
<el-aside width="250px">
|
||||
<plan-node-tree
|
||||
:plan-id="planId"
|
||||
class="node_tree"
|
||||
@nodeSelectEvent="get"
|
||||
@refresh="refresh"
|
||||
ref="nodeTree"></plan-node-tree>
|
||||
</el-aside>
|
||||
|
||||
</el-container>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import PlanNodeTree from "./components/PlanNodeTree";
|
||||
|
||||
export default {
|
||||
name: "TestPlanView",
|
||||
components: {PlanNodeTree},
|
||||
data() {
|
||||
return {
|
||||
currentProject: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
planId: function () {
|
||||
return this.$route.params.planId;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
|
||||
},
|
||||
get() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.plan_container {
|
||||
background: white;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -0,0 +1,141 @@
|
|||
<template>
|
||||
|
||||
<div>
|
||||
<el-input :placeholder="$t('test_track.search_module')" v-model="filterText" size="small"></el-input>
|
||||
|
||||
<el-tree
|
||||
class="filter-tree node-tree"
|
||||
:data="treeNodes"
|
||||
node-key="id"
|
||||
@node-drag-end="handleDragEnd"
|
||||
:filter-node-method="filterNode"
|
||||
:expand-on-click-node="false"
|
||||
draggable
|
||||
ref="tree">
|
||||
|
||||
<span class="custom-tree-node father" slot-scope="{node}" @click="selectNode(node)">
|
||||
{{node.label}}
|
||||
</span>
|
||||
</el-tree>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "NodeTree",
|
||||
data() {
|
||||
return {
|
||||
filterText: '',
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
},
|
||||
form: {
|
||||
name: '',
|
||||
},
|
||||
formLabelWidth: '80px',
|
||||
dialogTableVisible: false,
|
||||
dialogFormVisible: false,
|
||||
editType: '',
|
||||
editData: {},
|
||||
treeNodes: [],
|
||||
defaultKeys: []
|
||||
};
|
||||
},
|
||||
props: {
|
||||
planId: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterText(val) {
|
||||
this.$refs.tree.filter(val);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
console.log("created");
|
||||
console.log(this.planId);
|
||||
this.getNodeTree();
|
||||
},
|
||||
methods: {
|
||||
handleDragEnd(draggingNode, dropNode, dropType, ev) {
|
||||
let param = {};
|
||||
param.id = draggingNode.data.id;
|
||||
if(dropType === 'inner'){
|
||||
param.pId = dropNode.data.id;
|
||||
param.level = dropNode.data.level + 1;
|
||||
} else {
|
||||
if(dropNode.parent.id === 0){
|
||||
param.pId = 0;
|
||||
param.level = 1;
|
||||
} else {
|
||||
param.pId = dropNode.parent.data.id;
|
||||
param.level = dropNode.parent.data.level + 1;
|
||||
}
|
||||
}
|
||||
this.$post('/case/node/edit', param);
|
||||
},
|
||||
selectNode(node) {
|
||||
let nodeIds = [];
|
||||
this.getChildNodeId(node, nodeIds);
|
||||
this.$emit("nodeSelectEvent", nodeIds);
|
||||
},
|
||||
getChildNodeId(rootNode, nodeIds) {
|
||||
//递归获取所有子节点ID
|
||||
nodeIds.push(rootNode.data.id);
|
||||
for (let i = 0; i < rootNode.childNodes.length; i++){
|
||||
this.getChildNodeId(rootNode.childNodes[i], nodeIds);
|
||||
}
|
||||
return nodeIds;
|
||||
},
|
||||
filterNode(value, data) {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
},
|
||||
getNodeTree() {
|
||||
if(this.planId){
|
||||
this.$get("/case/node/list/plan/" + this.planId, response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-dropdown-link {
|
||||
cursor: pointer;
|
||||
color: #409EFF;
|
||||
}
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.custom-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.node-tree {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.father .child{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.father:hover .child{
|
||||
display:block;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -33,7 +33,8 @@
|
|||
|
||||
<el-table
|
||||
:data="tableData"
|
||||
class="test-content">
|
||||
class="test-content"
|
||||
@row-click="intoPlan">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('commons.name')"
|
||||
|
@ -94,8 +95,12 @@
|
|||
width="160"
|
||||
:label="$t('commons.operating')">
|
||||
<template slot-scope="scope">
|
||||
<el-button @click="handleEdit(scope.row)" type="primary" icon="el-icon-edit" size="mini" circle/>
|
||||
<el-button @click="handleDelete(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle/>
|
||||
<el-button @click="handleEdit(scope.row)"
|
||||
@click.stop="deleteVisible = true" type="primary"
|
||||
icon="el-icon-edit" size="mini" circle/>
|
||||
<el-button @click="handleDelete(scope.row)"
|
||||
@click.stop="deleteVisible = true" type="danger"
|
||||
icon="el-icon-delete" size="mini" circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -198,6 +203,9 @@
|
|||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
intoPlan(row, event, column) {
|
||||
this.$router.push('/track/plan/view/' + row.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue