Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Captain.B 2020-04-01 14:08:29 +08:00
commit 5d4c53e15b
9 changed files with 308 additions and 30 deletions

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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",

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}
}