Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
f57b3dcc15
|
@ -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<ApiModuleDTO> children;
|
||||
|
||||
public class ApiModuleDTO extends TreeNodeDTO<ApiModuleDTO> {
|
||||
private String protocol;
|
||||
}
|
||||
|
|
|
@ -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<ApiModuleDTO> {
|
||||
|
||||
@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<ApiModuleDTO> getNodeTreeByProjectId(String projectId, String protocol) {
|
||||
ApiModuleExample apiDefinitionNodeExample = new ApiModuleExample();
|
||||
apiDefinitionNodeExample.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol);
|
||||
apiDefinitionNodeExample.setOrderByClause("create_time asc");
|
||||
List<ApiModule> nodes = apiModuleMapper.selectByExample(apiDefinitionNodeExample);
|
||||
return getNodeTrees(nodes);
|
||||
List<ApiModuleDTO> apiModules = extApiModuleMapper.getNodeTreeByProjectId(projectId, protocol);
|
||||
return getNodeTrees(apiModules);
|
||||
}
|
||||
|
||||
public List<ApiModuleDTO> getNodeTrees(List<ApiModule> nodes) {
|
||||
|
||||
List<ApiModuleDTO> nodeTreeList = new ArrayList<>();
|
||||
Map<Integer, List<ApiModule>> nodeLevelMap = new HashMap<>();
|
||||
nodes.forEach(node -> {
|
||||
Integer level = node.getLevel();
|
||||
if (nodeLevelMap.containsKey(level)) {
|
||||
nodeLevelMap.get(level).add(node);
|
||||
} else {
|
||||
List<ApiModule> apiModules = new ArrayList<>();
|
||||
apiModules.add(node);
|
||||
nodeLevelMap.put(node.getLevel(), apiModules);
|
||||
}
|
||||
});
|
||||
List<ApiModule> 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<Integer, List<ApiModule>> nodeLevelMap, ApiModule rootNode) {
|
||||
|
||||
ApiModuleDTO nodeTree = new ApiModuleDTO();
|
||||
BeanUtils.copyBean(nodeTree, rootNode);
|
||||
nodeTree.setLabel(rootNode.getName());
|
||||
|
||||
List<ApiModule> lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
|
||||
if (lowerNodes == null) {
|
||||
return nodeTree;
|
||||
}
|
||||
List<ApiModuleDTO> 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);
|
||||
|
|
|
@ -21,5 +21,7 @@ public class ApiModule implements Serializable {
|
|||
|
||||
private Long updateTime;
|
||||
|
||||
private Double pos;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -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<Double> values) {
|
||||
addCriterion("pos in", values, "pos");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPosNotIn(List<Double> 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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<result column="level" jdbcType="INTEGER" property="level" />
|
||||
<result column="create_time" jdbcType="BIGINT" property="createTime" />
|
||||
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
|
||||
<result column="pos" jdbcType="DOUBLE" property="pos" />
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
<where>
|
||||
|
@ -70,7 +71,7 @@
|
|||
</where>
|
||||
</sql>
|
||||
<sql id="Base_Column_List">
|
||||
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
|
||||
</sql>
|
||||
<select id="selectByExample" parameterType="io.metersphere.base.domain.ApiModuleExample" resultMap="BaseResultMap">
|
||||
select
|
||||
|
@ -105,10 +106,12 @@
|
|||
<insert id="insert" parameterType="io.metersphere.base.domain.ApiModule">
|
||||
insert into api_module (id, project_id, `name`,
|
||||
protocol, parent_id, `level`,
|
||||
create_time, update_time)
|
||||
create_time, update_time, pos
|
||||
)
|
||||
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
|
||||
#{protocol,jdbcType=VARCHAR}, #{parentId,jdbcType=VARCHAR}, #{level,jdbcType=INTEGER},
|
||||
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT})
|
||||
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{pos,jdbcType=DOUBLE}
|
||||
)
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiModule">
|
||||
insert into api_module
|
||||
|
@ -137,6 +140,9 @@
|
|||
<if test="updateTime != null">
|
||||
update_time,
|
||||
</if>
|
||||
<if test="pos != null">
|
||||
pos,
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
|
@ -163,6 +169,9 @@
|
|||
<if test="updateTime != null">
|
||||
#{updateTime,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="pos != null">
|
||||
#{pos,jdbcType=DOUBLE},
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
<select id="countByExample" parameterType="io.metersphere.base.domain.ApiModuleExample" resultType="java.lang.Long">
|
||||
|
@ -198,6 +207,9 @@
|
|||
<if test="record.updateTime != null">
|
||||
update_time = #{record.updateTime,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="record.pos != null">
|
||||
pos = #{record.pos,jdbcType=DOUBLE},
|
||||
</if>
|
||||
</set>
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
|
@ -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}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
|
@ -241,6 +254,9 @@
|
|||
<if test="updateTime != null">
|
||||
update_time = #{updateTime,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="pos != null">
|
||||
pos = #{pos,jdbcType=DOUBLE},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
|
@ -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}
|
||||
</update>
|
||||
</mapper>
|
|
@ -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<ApiModule> records);
|
||||
List<ApiModuleDTO> getNodeTreeByProjectId(@Param("projectId") String projectId, @Param("protocol") String protocol);
|
||||
}
|
|
@ -13,4 +13,12 @@
|
|||
#{emp.updateTime,jdbcType=BIGINT})
|
||||
</foreach>
|
||||
</insert>
|
||||
<select id="getNodeTreeByProjectId" resultType="io.metersphere.api.dto.definition.ApiModuleDTO">
|
||||
select
|
||||
<include refid="io.metersphere.base.mapper.ApiModuleMapper.Base_Column_List"/>
|
||||
from api_module
|
||||
where api_module.project_id = #{projectId}
|
||||
and api_module.protocol = #{protocol}
|
||||
order by create_time asc
|
||||
</select>
|
||||
</mapper>
|
|
@ -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<TestCaseNodeDTO> getNodeTreeByProjectId(@Param("projectId") String projectId);
|
||||
|
||||
List<TestCaseNodeDTO> getNodeTreeByProjectIds(@Param("projectIds") List<String> projectIds);
|
||||
|
||||
TestCaseNodeDTO get(String id);
|
||||
|
||||
void updatePos(String id, Double pos);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper">
|
||||
<select id="getNodeTreeByProjectId" resultType="io.metersphere.track.dto.TestCaseNodeDTO">
|
||||
select
|
||||
<include refid="io.metersphere.base.mapper.TestCaseNodeMapper.Base_Column_List"/>
|
||||
from test_case_node
|
||||
where test_case_node.project_id = #{projectId}
|
||||
order by pos asc
|
||||
</select>
|
||||
<select id="getNodeTreeByProjectIds" resultType="io.metersphere.track.dto.TestCaseNodeDTO">
|
||||
select
|
||||
<include refid="io.metersphere.base.mapper.TestCaseNodeMapper.Base_Column_List"/>
|
||||
from test_case_node
|
||||
where test_case_node.project_id
|
||||
in
|
||||
<foreach collection="projectIds" item="projectId" index="index" open="(" close=")" separator=",">
|
||||
#{projectId}
|
||||
</foreach>
|
||||
</select>
|
||||
<select id="get" resultType="io.metersphere.track.dto.TestCaseNodeDTO">
|
||||
select
|
||||
<include refid="io.metersphere.base.mapper.TestCaseNodeMapper.Base_Column_List"/>
|
||||
from test_case_node
|
||||
where id = #{id}
|
||||
</select>
|
||||
<update id="updatePos">
|
||||
update test_case_node set pos = #{pos}
|
||||
where id = #{id}
|
||||
</update>
|
||||
</mapper>
|
|
@ -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<T extends TreeNodeDTO> {
|
||||
|
||||
protected static final double LIMIT_POS = 64;
|
||||
protected static final double DEFAULT_POS = 65536;
|
||||
|
||||
public List<T> getNodeTrees(List<T> nodes) {
|
||||
List<T> nodeTreeList = new ArrayList<>();
|
||||
Map<Integer, List<T>> nodeLevelMap = new HashMap<>();
|
||||
nodes.forEach(node -> {
|
||||
Integer level = node.getLevel();
|
||||
if (nodeLevelMap.containsKey(level)) {
|
||||
nodeLevelMap.get(level).add(node);
|
||||
} else {
|
||||
List<T> testCaseNodes = new ArrayList<>();
|
||||
testCaseNodes.add(node);
|
||||
nodeLevelMap.put(node.getLevel(), testCaseNodes);
|
||||
}
|
||||
});
|
||||
List<T> 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<Integer, List<T>> nodeLevelMap, T rootNode) {
|
||||
|
||||
T nodeTree = (T) new TreeNodeDTO();
|
||||
BeanUtils.copyBean(nodeTree, rootNode);
|
||||
nodeTree.setLabel(rootNode.getName());
|
||||
|
||||
List<T> lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
|
||||
if (lowerNodes == null) {
|
||||
return nodeTree;
|
||||
}
|
||||
|
||||
List<T> 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<String> nodeIds) {
|
||||
|
||||
List<T> children = rootNode.getChildren();
|
||||
|
||||
if (children == null || children.isEmpty()) {
|
||||
//叶子节点,并且该节点无数据
|
||||
if (!nodeIds.contains(rootNode.getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
Iterator<T> 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<String> pathIterator, String path, TestCaseNodeDTO treeNode,
|
||||
Map<String, String> pathMap, String projectId, Integer level) {
|
||||
|
||||
List<TestCaseNodeDTO> 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<String> pathIterator, String nodeName,
|
||||
TestCaseNodeDTO pNode, String projectId, Integer level,
|
||||
String rootPath, Map<String, String> 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<String> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
|
||||
List<TestCaseNodeDTO> nodes = extTestCaseNodeMapper.getNodeTreeByProjectIds(testPlanProjectService.getProjectIdsByPlanId(testPlan.getId()));
|
||||
nodeTrees = testCaseNodeService.getNodeTrees(nodes);
|
||||
nodeTrees.forEach(item -> {
|
||||
Set<String> childIds = new HashSet<>();
|
||||
|
|
|
@ -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<TestCaseNodeDTO> children;
|
||||
|
||||
public class TestCaseNodeDTO extends TreeNodeDTO<TestCaseNodeDTO> {
|
||||
}
|
||||
|
|
|
@ -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<T> {
|
||||
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<T> children;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -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<TestCaseNodeDTO> {
|
||||
|
||||
@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<TestCaseNodeDTO> getNodeTreeByProjectId(String projectId) {
|
||||
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
|
||||
testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId);
|
||||
testCaseNodeExample.setOrderByClause("pos asc");
|
||||
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
|
||||
return getNodeTrees(nodes);
|
||||
}
|
||||
|
||||
public List<TestCaseNodeDTO> getNodeTrees(List<TestCaseNode> nodes) {
|
||||
|
||||
List<TestCaseNodeDTO> nodeTreeList = new ArrayList<>();
|
||||
|
||||
Map<Integer, List<TestCaseNode>> nodeLevelMap = new HashMap<>();
|
||||
|
||||
nodes.forEach(node -> {
|
||||
Integer level = node.getLevel();
|
||||
if (nodeLevelMap.containsKey(level)) {
|
||||
nodeLevelMap.get(level).add(node);
|
||||
} else {
|
||||
List<TestCaseNode> testCaseNodes = new ArrayList<>();
|
||||
testCaseNodes.add(node);
|
||||
nodeLevelMap.put(node.getLevel(), testCaseNodes);
|
||||
}
|
||||
});
|
||||
|
||||
List<TestCaseNode> 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<Integer, List<TestCaseNode>> nodeLevelMap, TestCaseNode rootNode) {
|
||||
|
||||
TestCaseNodeDTO nodeTree = new TestCaseNodeDTO();
|
||||
BeanUtils.copyBean(nodeTree, rootNode);
|
||||
nodeTree.setLabel(rootNode.getName());
|
||||
|
||||
List<TestCaseNode> lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
|
||||
if (lowerNodes == null) {
|
||||
return nodeTree;
|
||||
}
|
||||
|
||||
List<TestCaseNodeDTO> 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<TestCaseNodeDTO> 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<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
|
||||
List<TestCaseNodeDTO> testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId);
|
||||
|
||||
List<String> caseIds = testPlanTestCases.stream()
|
||||
.map(TestPlanTestCase::getCaseId)
|
||||
|
@ -261,7 +204,7 @@ public class TestCaseNodeService {
|
|||
.map(TestCase::getNodeId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(nodes);
|
||||
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(testCaseNodes);
|
||||
|
||||
Iterator<TestCaseNodeDTO> iterator = nodeTrees.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
|
@ -280,9 +223,7 @@ public class TestCaseNodeService {
|
|||
return null;
|
||||
}
|
||||
|
||||
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
|
||||
testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId);
|
||||
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
|
||||
List<TestCaseNodeDTO> testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId);
|
||||
|
||||
|
||||
TestCaseExample testCaseExample = new TestCaseExample();
|
||||
|
@ -291,7 +232,7 @@ public class TestCaseNodeService {
|
|||
.map(TestCase::getNodeId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(nodes);
|
||||
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(testCaseNodes);
|
||||
|
||||
Iterator<TestCaseNodeDTO> 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<String> nodeIds) {
|
||||
|
||||
List<TestCaseNodeDTO> children = rootNode.getChildren();
|
||||
|
||||
if (children == null || children.isEmpty()) {
|
||||
//叶子节点,并且该节点无数据
|
||||
if (!nodeIds.contains(rootNode.getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
Iterator<TestCaseNodeDTO> 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<TestCaseNodeDTO> 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<String> pathIterator, String path, TestCaseNodeDTO treeNode,
|
||||
Map<String, String> pathMap, String projectId, Integer level) {
|
||||
|
||||
List<TestCaseNodeDTO> 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<String> pathIterator, String nodeName,
|
||||
TestCaseNodeDTO pNode, String projectId, Integer level,
|
||||
String rootPath, Map<String, String> 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<String> 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<TestCaseNode> 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<TestCaseNode> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 1fe20ba15a7ca3fe9f77ddf866021e7c7dfe5969
|
||||
Subproject commit bb494fc68a2367359c9048fa7250c7618de4afb6
|
|
@ -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`)
|
||||
|
|
|
@ -64,8 +64,8 @@
|
|||
|
||||
<!--要生成的数据库表 -->
|
||||
|
||||
<table tableName="api_scenario"/>
|
||||
<table tableName="api_scenario_report"/>
|
||||
<table tableName="api_module"/>
|
||||
<!--<table tableName="api_scenario_report"/>-->
|
||||
|
||||
</context>
|
||||
</generatorConfiguration>
|
|
@ -2,23 +2,15 @@
|
|||
<ms-container>
|
||||
|
||||
<ms-aside-container>
|
||||
<node-tree
|
||||
class="node-tree"
|
||||
v-loading="result.loading"
|
||||
<test-case-node-tree
|
||||
@nodeSelectEvent="nodeChange"
|
||||
@refresh="refresh"
|
||||
:tree-nodes="treeNodes"
|
||||
@refreshTable="refresh"
|
||||
:type="'edit'"
|
||||
:draggable="nodeTreeDraggable"
|
||||
:select-node.sync="selectNode"
|
||||
@refreshTable="refreshTable"
|
||||
:current-project="{id:currentProject}"
|
||||
ref="nodeTree"/>
|
||||
</ms-aside-container>
|
||||
|
||||
<ms-main-container>
|
||||
<test-case-list
|
||||
:current-project="{id:currentProject}"
|
||||
:select-node-ids="selectNodeIds"
|
||||
:select-parent-nodes="selectParentNodes"
|
||||
@testCaseEdit="editTestCase"
|
||||
|
@ -36,7 +28,6 @@
|
|||
:read-only="testCaseReadOnly"
|
||||
:tree-nodes="treeNodes"
|
||||
:select-node="selectNode"
|
||||
:current-project="{id:currentProject}"
|
||||
ref="testCaseEditDialog">
|
||||
</test-case-edit>
|
||||
|
||||
|
@ -52,7 +43,6 @@
|
|||
|
||||
import NodeTree from '../common/NodeTree';
|
||||
import TestCaseEdit from './components/TestCaseEdit';
|
||||
import {PROJECT_ID, ROLE_TEST_MANAGER, ROLE_TEST_USER} from '../../../../common/js/constants';
|
||||
import TestCaseList from "./components/TestCaseList";
|
||||
import SelectMenu from "../common/SelectMenu";
|
||||
import TestCaseMove from "./components/TestCaseMove";
|
||||
|
@ -61,10 +51,12 @@ import MsAsideContainer from "../../common/components/MsAsideContainer";
|
|||
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||
import {checkoutTestManagerOrTestUser, hasRoles} from "../../../../common/js/utils";
|
||||
import BatchMove from "./components/BatchMove";
|
||||
import TestCaseNodeTree from "../common/TestCaseNodeTree";
|
||||
|
||||
export default {
|
||||
name: "TestCase",
|
||||
components: {
|
||||
TestCaseNodeTree,
|
||||
MsMainContainer,
|
||||
MsAsideContainer, MsContainer, TestCaseMove, TestCaseList, NodeTree, TestCaseEdit, SelectMenu, BatchMove
|
||||
},
|
||||
|
@ -76,35 +68,25 @@ export default {
|
|||
pageSize: 5,
|
||||
total: 0,
|
||||
projects: [],
|
||||
currentProject: null,
|
||||
treeNodes: [],
|
||||
selectNodeIds: [],
|
||||
selectParentNodes: [],
|
||||
testCaseReadOnly: true,
|
||||
selectNode: {},
|
||||
nodeTreeDraggable: true,
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.currentProject = localStorage.getItem(PROJECT_ID);
|
||||
},
|
||||
mounted() {
|
||||
this.init(this.$route);
|
||||
},
|
||||
watch: {
|
||||
'$route'(to, from) {
|
||||
// console.log(this.$route.params.projectId)
|
||||
this.init(to);
|
||||
},
|
||||
currentProject() {
|
||||
this.refresh();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(route) {
|
||||
let path = route.path;
|
||||
if (path.indexOf("/track/case/edit") >= 0 || path.indexOf("/track/case/create") >= 0) {
|
||||
// this.getProjects();
|
||||
this.testCaseReadOnly = false;
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.testCaseReadOnly = true;
|
||||
|
@ -113,50 +95,10 @@ export default {
|
|||
this.openRecentTestCaseEditDialog(caseId);
|
||||
this.$router.push('/track/case/all');
|
||||
}
|
||||
// else if (route.params.projectId) {
|
||||
// this.getProjects();
|
||||
// this.getProjectById(route.params.projectId);
|
||||
// }
|
||||
},
|
||||
// getProjects() {
|
||||
// this.$get("/project/listAll", (response) => {
|
||||
// this.projects = response.data;
|
||||
// let lastProject = JSON.parse(localStorage.getItem(CURRENT_PROJECT));
|
||||
// if (lastProject) {
|
||||
// let hasCurrentProject = false;
|
||||
// for (let i = 0; i < this.projects.length; i++) {
|
||||
// if (this.projects[i].id == lastProject.id) {
|
||||
// this.currentProject = lastProject;
|
||||
// hasCurrentProject = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (!hasCurrentProject) {
|
||||
// this.setCurrentProject(this.projects[0]);
|
||||
// }
|
||||
// } else {
|
||||
// if (this.projects.length > 0) {
|
||||
// this.setCurrentProject(this.projects[0]);
|
||||
// }
|
||||
// }
|
||||
// // this.checkProject();
|
||||
// });
|
||||
// },
|
||||
// checkProject() {
|
||||
// if (this.currentProject === null) {
|
||||
// this.$alert(this.$t('test_track.case.no_project'), {
|
||||
// confirmButtonText: this.$t('project.create'),
|
||||
// callback: action => {
|
||||
// this.$router.push("/track/project/create");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// changeProject(project) {
|
||||
// this.setCurrentProject(project);
|
||||
// },
|
||||
nodeChange(nodeIds, pNodes) {
|
||||
nodeChange(node, nodeIds, pNodes) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
this.selectNode = node;
|
||||
this.selectParentNodes = pNodes;
|
||||
},
|
||||
refreshTable() {
|
||||
|
@ -182,17 +124,11 @@ export default {
|
|||
this.testCaseReadOnly = true;
|
||||
this.$refs.testCaseEditDialog.open(testCase);
|
||||
},
|
||||
// getProjectByCaseId(caseId) {
|
||||
// return this.$get('/test/case/project/' + caseId, async response => {
|
||||
// this.setCurrentProject(response.data);
|
||||
// });
|
||||
// },
|
||||
refresh() {
|
||||
this.selectNodeIds = [];
|
||||
this.selectParentNodes = [];
|
||||
this.selectNode = {};
|
||||
this.refreshTable();
|
||||
this.getNodeTree();
|
||||
},
|
||||
openRecentTestCaseEditDialog(caseId) {
|
||||
if (caseId) {
|
||||
|
@ -206,35 +142,7 @@ export default {
|
|||
this.$refs.testCaseEditDialog.open();
|
||||
}
|
||||
},
|
||||
// getProjectById(id) {
|
||||
// if (id && id != 'all') {
|
||||
// this.$get('/project/get/' + id, response => {
|
||||
// let project = response.data;
|
||||
// this.setCurrentProject(project);
|
||||
// // this.$router.push('/track/case/all');
|
||||
// });
|
||||
// }
|
||||
// if (id === 'all') {
|
||||
// this.refresh();
|
||||
// }
|
||||
// },
|
||||
// setCurrentProject(project) {
|
||||
// if (project) {
|
||||
// this.currentProject = project;
|
||||
// localStorage.setItem(CURRENT_PROJECT, JSON.stringify(project));
|
||||
// }
|
||||
// this.refresh();
|
||||
// },
|
||||
getNodeTree() {
|
||||
if (!hasRoles(ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
|
||||
this.nodeTreeDraggable = false;
|
||||
}
|
||||
if (this.currentProject) {
|
||||
this.result = this.$get("/case/node/list/" + this.currentProject, response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
moveToNode(selectIds) {
|
||||
if (selectIds.size < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.select_manipulate'));
|
||||
|
|
|
@ -262,6 +262,7 @@ import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
|
|||
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
|
||||
import {Message} from "element-ui";
|
||||
import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment";
|
||||
import {getCurrentProjectID} from "../../../../../common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "TestCaseEdit",
|
||||
|
@ -269,6 +270,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
result: {},
|
||||
projectId: "",
|
||||
dialogFormVisible: false,
|
||||
form: {
|
||||
name: '',
|
||||
|
@ -329,9 +331,6 @@ export default {
|
|||
selectNode: {
|
||||
type: Object
|
||||
},
|
||||
currentProject: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getSelectOptions();
|
||||
|
@ -340,9 +339,6 @@ export default {
|
|||
treeNodes() {
|
||||
this.getModuleOptions();
|
||||
},
|
||||
currentProject() {
|
||||
this.getTestOptions();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reload() {
|
||||
|
@ -351,7 +347,7 @@ export default {
|
|||
},
|
||||
open(testCase) {
|
||||
this.resetForm();
|
||||
|
||||
this.projectId = getCurrentProjectID();
|
||||
if (window.history && window.history.pushState) {
|
||||
history.pushState(null, null, document.URL);
|
||||
window.addEventListener('popstate', this.close);
|
||||
|
@ -489,8 +485,8 @@ export default {
|
|||
param.nodePath = item.path;
|
||||
}
|
||||
});
|
||||
if (this.currentProject.id) {
|
||||
param.projectId = this.currentProject.id;
|
||||
if (this.projectId) {
|
||||
param.projectId = this.projectId;
|
||||
}
|
||||
param.name = param.name.trim();
|
||||
if (param.method != 'auto') {
|
||||
|
@ -563,8 +559,8 @@ export default {
|
|||
},
|
||||
getTestOptions() {
|
||||
this.testOptions = [];
|
||||
if (this.currentProject.id && this.form.type != '' && this.form.type != 'functional') {
|
||||
this.result = this.$get('/' + this.form.type + '/list/' + this.currentProject.id, response => {
|
||||
if (this.projectId && this.form.type != '' && this.form.type != 'functional') {
|
||||
this.result = this.$get('/' + this.form.type + '/list/' + this.projectId, response => {
|
||||
this.testOptions = response.data;
|
||||
this.testOptions.unshift({id: 'other', name: this.$t('test_track.case.other')})
|
||||
});
|
||||
|
|
|
@ -111,8 +111,8 @@
|
|||
<script>
|
||||
import ElUploadList from "element-ui/packages/upload/src/upload-list";
|
||||
import MsTableButton from '../../../../components/common/components/MsTableButton';
|
||||
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
|
||||
import {TokenKey, WORKSPACE_ID} from '../../../../../common/js/constants';
|
||||
import {getCurrentProjectID, listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
|
||||
import {TokenKey} from '../../../../../common/js/constants';
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
|
@ -129,11 +129,6 @@
|
|||
isLoading: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
projectId: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleExceed(files, fileList) {
|
||||
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
|
||||
|
@ -176,6 +171,7 @@
|
|||
},
|
||||
open() {
|
||||
listenGoBack(this.close);
|
||||
this.projectId = getCurrentProjectID();
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
close() {
|
||||
|
|
|
@ -25,8 +25,7 @@
|
|||
|
||||
</template>
|
||||
|
||||
<test-case-import :projectId="currentProject == null? null : currentProject.id" @refresh="refresh"
|
||||
ref="testCaseImport"/>
|
||||
<test-case-import @refresh="refresh" ref="testCaseImport"/>
|
||||
|
||||
<el-table
|
||||
border
|
||||
|
@ -176,6 +175,7 @@ import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEve
|
|||
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
|
||||
import TestCaseDetail from "./TestCaseDetail";
|
||||
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
||||
import {getCurrentProjectID} from "../../../../../common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "TestCaseList",
|
||||
|
@ -264,13 +264,11 @@ export default {
|
|||
],
|
||||
maintainer: [],
|
||||
},
|
||||
currentCaseId: null
|
||||
currentCaseId: null,
|
||||
projectId: ""
|
||||
}
|
||||
},
|
||||
props: {
|
||||
currentProject: {
|
||||
type: Object
|
||||
},
|
||||
selectNodeIds: {
|
||||
type: Array
|
||||
},
|
||||
|
@ -282,9 +280,6 @@ export default {
|
|||
this.initTableData();
|
||||
},
|
||||
watch: {
|
||||
currentProject() {
|
||||
this.initTableData();
|
||||
},
|
||||
selectNodeIds() {
|
||||
this.currentPage = 1;
|
||||
this.initTableData();
|
||||
|
@ -292,6 +287,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
initTableData() {
|
||||
this.projectId = getCurrentProjectID();
|
||||
this.condition.planId = "";
|
||||
this.condition.nodeIds = [];
|
||||
if (this.planId) {
|
||||
|
@ -302,13 +298,11 @@ export default {
|
|||
// param.nodeIds = this.selectNodeIds;
|
||||
this.condition.nodeIds = this.selectNodeIds;
|
||||
}
|
||||
if (this.currentProject.id) {
|
||||
this.getData();
|
||||
}
|
||||
this.getData();
|
||||
},
|
||||
getData() {
|
||||
if (this.currentProject) {
|
||||
this.condition.projectId = this.currentProject.id;
|
||||
if (this.projectId) {
|
||||
this.condition.projectId = this.projectId;
|
||||
this.result = this.$post(this.buildPagePath('/test/case/list'), this.condition, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
|
@ -416,7 +410,7 @@ export default {
|
|||
method: 'post',
|
||||
responseType: 'blob',
|
||||
// data: {ids: [...this.selectIds]}
|
||||
data: {ids: ids, projectId: this.currentProject.id}
|
||||
data: {ids: ids, projectId: this.projectId}
|
||||
};
|
||||
this.result = this.$request(config).then(response => {
|
||||
const filename = this.$t('test_track.case.test_case') + ".xlsx";
|
||||
|
|
|
@ -41,6 +41,12 @@
|
|||
},
|
||||
filterData() {
|
||||
this.data = this.nodes;
|
||||
for (let index in this.data) {
|
||||
if (this.data[index].id === 'root') {
|
||||
this.data.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.data.length > 4) {
|
||||
let lastData = this.data[this.data.length - 1];
|
||||
this.data.splice(1, this.data.length);
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
<template>
|
||||
<div v-loading="result.loading">
|
||||
<el-input :placeholder="$t('test_track.module.search')" v-model="filterText" size="small">
|
||||
<template v-if="type == 'edit'" v-slot:append>
|
||||
<el-button :disabled="disabled" icon="el-icon-folder-add" @click="openEditNodeDialog('add')"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<div>
|
||||
<slot name="header">
|
||||
<el-input :placeholder="$t('test_track.module.search')" v-model="filterText" size="small" :clearable="true"/>
|
||||
</slot>
|
||||
|
||||
<el-tree
|
||||
class="filter-tree node-tree"
|
||||
:data="treeNodes"
|
||||
:data="extendTreeNodes"
|
||||
:default-expanded-keys="expandedNode"
|
||||
node-key="id"
|
||||
@node-drag-end="handleDragEnd"
|
||||
|
@ -17,63 +15,70 @@
|
|||
:filter-node-method="filterNode"
|
||||
:expand-on-click-node="false"
|
||||
highlight-current
|
||||
:draggable="draggable"
|
||||
:draggable="!disabled"
|
||||
ref="tree">
|
||||
|
||||
<template v-slot:default="{node,data}">
|
||||
<span class="custom-tree-node father" @click="handleNodeSelect(node)">
|
||||
<span class="node-icon">
|
||||
<i class="el-icon-folder"></i>
|
||||
</span>
|
||||
<span class="custom-tree-node father" @click="handleNodeSelect(node)">
|
||||
|
||||
<span class="node-title">{{node.label}}</span>
|
||||
|
||||
<span v-if="type == 'edit' && !disabled" class="node-operate child">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:open-delay="200"
|
||||
:content="$t('test_track.module.rename')"
|
||||
placement="top">
|
||||
<i @click.stop="openEditNodeDialog('edit', data)" class="el-icon-edit"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:open-delay="200"
|
||||
:content="$t('test_track.module.add_submodule')"
|
||||
placement="top">
|
||||
<i @click.stop="openEditNodeDialog('add', data)" class="el-icon-circle-plus-outline"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark"
|
||||
:open-delay="200" :content="$t('commons.delete')" placement="top">
|
||||
<i @click.stop="remove(node, data)" class="el-icon-delete"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span v-if="data.isEdit" @click.stop>
|
||||
<el-input @blur.stop="save(node, data)" v-model="data.name" class="name-input" size="mini"/>
|
||||
</span>
|
||||
|
||||
<span v-if="!data.isEdit" class="node-icon">
|
||||
<i class="el-icon-folder"/>
|
||||
</span>
|
||||
<span v-if="!data.isEdit" class="node-title" v-text="data.name"/>
|
||||
|
||||
<span v-if="!disabled" class="node-operate child">
|
||||
<el-tooltip
|
||||
v-if="data.id != 'root'"
|
||||
class="item"
|
||||
effect="dark"
|
||||
:open-delay="200"
|
||||
:content="$t('test_track.module.rename')"
|
||||
placement="top">
|
||||
<i @click.stop="edit(node, data)" class="el-icon-edit"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:open-delay="200"
|
||||
:content="$t('test_track.module.add_submodule')"
|
||||
placement="top">
|
||||
<i @click.stop="append(node, data)" class="el-icon-circle-plus-outline"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
v-if="data.id != 'root'"
|
||||
class="item" effect="dark"
|
||||
:open-delay="200"
|
||||
:content="$t('commons.delete')"
|
||||
placement="top">
|
||||
<i @click.stop="remove(node, data)" class="el-icon-delete"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
<node-edit ref="nodeEdit" :current-project="currentProject" :tree-nodes="treeNodes" @refresh="refreshNode" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NodeEdit from "./NodeEdit";
|
||||
import {checkoutTestManagerOrTestUser, hasRoles} from "../../../../common/js/utils";
|
||||
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "NodeTree",
|
||||
components: { NodeEdit },
|
||||
name: "MsNodeTree",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
expandedNode: [],
|
||||
filterText: "",
|
||||
expandedNode: [],
|
||||
defaultProps: {
|
||||
children: "children",
|
||||
label: "label"
|
||||
},
|
||||
disabled: false,
|
||||
list: []
|
||||
extendTreeNodes: []
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
@ -84,43 +89,144 @@ export default {
|
|||
treeNodes: {
|
||||
type: Array
|
||||
},
|
||||
selectNode: {
|
||||
type: Object
|
||||
allLabel: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t("commons.all_label.case");
|
||||
}
|
||||
},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
currentProject: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
treeNodes() {
|
||||
this.init();
|
||||
},
|
||||
filterText(val) {
|
||||
this.$refs.tree.filter(val);
|
||||
this.filter(val);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.disabled = true;
|
||||
computed: {
|
||||
disabled() {
|
||||
return this.type != 'edit' || !checkoutTestManagerOrTestUser();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.extendTreeNodes = [];
|
||||
this.extendTreeNodes.unshift({
|
||||
"id": "root",
|
||||
"name": this.allLabel,
|
||||
"level": 0,
|
||||
"children": this.treeNodes,
|
||||
});
|
||||
if (this.expandedNode.length === 0) {
|
||||
this.expandedNode.push("root");
|
||||
}
|
||||
},
|
||||
handleNodeSelect(node) {
|
||||
let nodeIds = [];
|
||||
let pNodes = [];
|
||||
this.getChildNodeId(node.data, nodeIds);
|
||||
this.getParentNodes(node, pNodes);
|
||||
this.$emit("nodeSelectEvent", node, nodeIds, pNodes);
|
||||
},
|
||||
filterNode(value, data) {
|
||||
if (!value) return true;
|
||||
if (data.label) {
|
||||
return data.label.indexOf(value) !== -1;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
filter(val) {
|
||||
this.$refs.tree.filter(val);
|
||||
},
|
||||
nodeExpand(data) {
|
||||
if (data.id) {
|
||||
this.expandedNode.push(data.id);
|
||||
}
|
||||
},
|
||||
nodeCollapse(data) {
|
||||
if (data.id) {
|
||||
this.expandedNode.splice(this.expandedNode.indexOf(data.id), 1);
|
||||
}
|
||||
},
|
||||
edit(node, data) {
|
||||
this.$set(data, 'isEdit', true);
|
||||
},
|
||||
append(node, data) {
|
||||
const newChild = {
|
||||
id: undefined,
|
||||
isEdit: false,
|
||||
name: "",
|
||||
children: []
|
||||
};
|
||||
if (!data.children) {
|
||||
this.$set(data, 'children', [])
|
||||
}
|
||||
data.children.push(newChild);
|
||||
this.edit(node, newChild);
|
||||
this.$nextTick(() => {
|
||||
this.$refs.tree.setCurrentKey(data.id);
|
||||
});
|
||||
},
|
||||
save(node, data) {
|
||||
if (data.name.trim() === '') {
|
||||
this.$warning(this.$t('test_track.case.input_name'));
|
||||
return;
|
||||
}
|
||||
let param = {};
|
||||
this.buildSaveParam(param, node.parent.data, data);
|
||||
if (param.type === 'edit') {
|
||||
this.$emit('edit', param);
|
||||
} else {
|
||||
this.$emit('add', param);
|
||||
}
|
||||
this.$set(data, 'isEdit', false);
|
||||
},
|
||||
remove(node, data) {
|
||||
let tip = '确定删除节点 ' + data.label + ' 及其子节点下所有资源' + '?';
|
||||
// let info = this.$t("test_track.module.delete_confirm") + data.label + "," + this.$t("test_track.module.delete_all_resource") + "?";
|
||||
this.$alert(tip, "", {
|
||||
confirmButtonText: this.$t("commons.confirm"),
|
||||
callback: action => {
|
||||
if (action === "confirm") {
|
||||
let nodeIds = [];
|
||||
this.getChildNodeId(node.data, nodeIds);
|
||||
this.$emit('remove', nodeIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
handleDragEnd(draggingNode, dropNode, dropType, ev) {
|
||||
if (dropType === "none" || dropType === undefined) {
|
||||
return;
|
||||
}
|
||||
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();
|
||||
});
|
||||
let list = [];
|
||||
this.getNodeTree(this.treeNodes, draggingNode.data.id, list);
|
||||
if (param.parentId === 'root') {
|
||||
param.parentId = undefined;
|
||||
}
|
||||
this.$emit('drag', param, list);
|
||||
},
|
||||
buildSaveParam(param, parentData, data) {
|
||||
if (data.id) {
|
||||
param.nodeIds = [];
|
||||
param.type = 'edit';
|
||||
param.id = data.id;
|
||||
param.level = data.level;
|
||||
this.getChildNodeId(data, param.nodeIds);
|
||||
} else {
|
||||
param.level = 1;
|
||||
param.type = 'add';
|
||||
if (parentData.id != 'root') {
|
||||
// 非根节点
|
||||
param.parentId = parentData.id;
|
||||
param.level = parentData.level + 1;
|
||||
}
|
||||
}
|
||||
param.name = data.name.trim();
|
||||
param.label = data.name;
|
||||
},
|
||||
buildParam(draggingNode, dropNode, dropType) {
|
||||
let param = {};
|
||||
|
@ -141,7 +247,8 @@ export default {
|
|||
}
|
||||
let nodeIds = [];
|
||||
this.getChildNodeId(draggingNode.data, nodeIds);
|
||||
if (dropNode.level == 1 && dropType != "inner") {
|
||||
if (dropNode.data.level == 1 && dropType != "inner") {
|
||||
// nodeTree 为需要修改的子节点
|
||||
param.nodeTree = draggingNode.data;
|
||||
} else {
|
||||
for (let i = 0; i < this.treeNodes.length; i++) {
|
||||
|
@ -151,7 +258,6 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
param.nodeIds = nodeIds;
|
||||
return param;
|
||||
},
|
||||
|
@ -171,9 +277,6 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
refreshTable() {
|
||||
this.$emit('refreshTable');
|
||||
},
|
||||
findTreeByNodeId(rootNode, nodeId) {
|
||||
if (rootNode.id == nodeId) {
|
||||
return rootNode;
|
||||
|
@ -186,41 +289,6 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
remove(node, data) {
|
||||
this.$alert(
|
||||
this.$t("test_track.module.delete_confirm") +
|
||||
data.label +
|
||||
"," +
|
||||
this.$t("test_track.module.delete_all_resource") +
|
||||
"?",
|
||||
"",
|
||||
{
|
||||
confirmButtonText: this.$t("commons.confirm"),
|
||||
callback: action => {
|
||||
if (action === "confirm") {
|
||||
let nodeIds = [];
|
||||
this.getChildNodeId(node.data, nodeIds);
|
||||
this.$post("/case/node/delete", nodeIds, () => {
|
||||
const parent = node.parent;
|
||||
const children = parent.data.children || parent.data;
|
||||
const index = children.findIndex(d => d.id === data.id);
|
||||
children.splice(index, 1);
|
||||
this.$success(this.$t("commons.delete_success"));
|
||||
this.$emit("refresh");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
handleNodeSelect(node) {
|
||||
let nodeIds = [];
|
||||
let pNodes = [];
|
||||
this.getChildNodeId(node.data, nodeIds);
|
||||
this.getParentNodes(node, pNodes);
|
||||
this.$emit("nodeSelectEvent", nodeIds, pNodes);
|
||||
this.$emit("update:selectNode", node);
|
||||
},
|
||||
getChildNodeId(rootNode, nodeIds) {
|
||||
//递归获取所有子节点ID
|
||||
nodeIds.push(rootNode.id);
|
||||
|
@ -238,30 +306,6 @@ export default {
|
|||
pNodes.push(rootNode.data);
|
||||
}
|
||||
},
|
||||
filterNode(value, data) {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
},
|
||||
openEditNodeDialog(type, data) {
|
||||
let nodeIds = [];
|
||||
if (type == 'edit') {
|
||||
this.getChildNodeId(data, nodeIds);
|
||||
}
|
||||
this.$refs.nodeEdit.open(type, data, nodeIds);
|
||||
},
|
||||
refreshNode() {
|
||||
this.$emit("refresh");
|
||||
},
|
||||
nodeExpand(data) {
|
||||
if (data.id) {
|
||||
this.expandedNode.push(data.id);
|
||||
}
|
||||
},
|
||||
nodeCollapse(data) {
|
||||
if (data.id) {
|
||||
this.expandedNode.splice(this.expandedNode.indexOf(data.id), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -311,4 +355,14 @@ export default {
|
|||
color: #409eff;
|
||||
margin: 0px 5px;
|
||||
}
|
||||
|
||||
.name-input {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.name-input >>> .el-input__inner {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<ms-node-tree
|
||||
v-loading="result.loading"
|
||||
:tree-nodes="treeNodes"
|
||||
:type="'edit'"
|
||||
@add="add"
|
||||
@edit="edit"
|
||||
@drag="drag"
|
||||
@remove="remove"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
ref="nodeTree"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NodeEdit from "./NodeEdit";
|
||||
import {getCurrentProjectID} from "../../../../common/js/utils";
|
||||
import MsNodeTree from "./NodeTree";
|
||||
|
||||
export default {
|
||||
name: "TestCaseNodeTree",
|
||||
components: {MsNodeTree, NodeEdit },
|
||||
data() {
|
||||
return {
|
||||
defaultProps: {
|
||||
children: "children",
|
||||
label: "label"
|
||||
},
|
||||
result: {},
|
||||
treeNodes: [],
|
||||
projectId: ""
|
||||
};
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "view"
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.projectId = getCurrentProjectID();
|
||||
this.list();
|
||||
},
|
||||
methods: {
|
||||
|
||||
list() {
|
||||
if (this.projectId) {
|
||||
this.result = this.$get("/case/node/list/" + this.projectId, response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
edit(param) {
|
||||
param.projectId = this.projectId;
|
||||
this.$post("/case/node/edit", param, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.list();
|
||||
this.$emit("refreshTable");
|
||||
}, (error) => {
|
||||
this.list();
|
||||
});
|
||||
},
|
||||
add(param) {
|
||||
param.projectId = this.projectId;
|
||||
this.$post("/case/node/add", param, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.list();
|
||||
}, (error) => {
|
||||
this.list();
|
||||
});
|
||||
},
|
||||
remove(nodeIds) {
|
||||
this.$post("/case/node/delete", nodeIds, () => {
|
||||
this.list();
|
||||
this.$emit("refreshTable")
|
||||
}, (error) => {
|
||||
this.list();
|
||||
});
|
||||
},
|
||||
drag(param, list) {
|
||||
this.$post("/case/node/drag", param, () => {
|
||||
this.$post("/case/node/pos", list);
|
||||
this.list();
|
||||
}, (error) => {
|
||||
this.list();
|
||||
});
|
||||
},
|
||||
nodeChange(node, nodeIds, pNodes) {
|
||||
this.$emit("nodeSelectEvent", node, nodeIds, pNodes);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
<template v-slot:aside>
|
||||
<node-tree class="node-tree"
|
||||
v-loading="result.loading"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
@refresh="refresh"
|
||||
:tree-nodes="treeNodes"
|
||||
ref="nodeTree"/>
|
||||
</template>
|
||||
|
@ -237,7 +237,7 @@
|
|||
this.selectIds.add(row.id);
|
||||
}
|
||||
},
|
||||
nodeChange(nodeIds, nodeNames) {
|
||||
nodeChange(node, nodeIds, nodeNames) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
this.selectNodeNames = nodeNames;
|
||||
},
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
<node-tree class="node-tree"
|
||||
v-loading="result.loading"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
@refresh="refresh"
|
||||
:tree-nodes="treeNodes"
|
||||
:draggable="false"
|
||||
ref="nodeTree"/>
|
||||
</template>
|
||||
<template v-slot:main>
|
||||
|
@ -81,7 +79,7 @@
|
|||
openTestCaseRelevanceDialog() {
|
||||
this.$refs.testCaseRelevance.open();
|
||||
},
|
||||
nodeChange(nodeIds, pNodes) {
|
||||
nodeChange(node, nodeIds, pNodes) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
this.selectParentNodes = pNodes;
|
||||
// 切换node后,重置分页数
|
||||
|
|
|
@ -32,7 +32,8 @@ export default {
|
|||
|
||||
.test-plan-header-bar {
|
||||
height: 50px;
|
||||
background-color: white;
|
||||
background-color: #FFF;
|
||||
border-bottom: 2px solid #E6E6E6;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
|
@ -48,4 +49,8 @@ export default {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.menu-ul >>> .el-menu-item {
|
||||
height: 51px !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -8,11 +8,10 @@
|
|||
:title="$t('test_track.review_view.review')"
|
||||
@dataChange="changeReview"/>
|
||||
<node-tree class="node-tree"
|
||||
:all-label="$t('commons.all_label.review')"
|
||||
v-loading="result.loading"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
@refresh="refresh"
|
||||
:tree-nodes="treeNodes"
|
||||
:draggable="false"
|
||||
ref="nodeTree"/>
|
||||
</ms-aside-container>
|
||||
|
||||
|
@ -108,7 +107,7 @@ export default {
|
|||
});
|
||||
});
|
||||
},
|
||||
nodeChange(nodeIds, pNodes) {
|
||||
nodeChange(node, nodeIds, pNodes) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
this.selectParentNodes = pNodes;
|
||||
},
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-dialog :title="$t('test_track.review_view.relevance_case')" :visible.sync="dialogFormVisible" @close="close"
|
||||
width="60%" v-loading="result.loading"
|
||||
width="60%"
|
||||
:close-on-click-modal="false"
|
||||
top="50px">
|
||||
|
||||
<el-container class="main-content">
|
||||
<el-aside class="tree-aside" width="250px">
|
||||
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :
|
||||
$t('test_track.switch_project') }}
|
||||
</el-link>
|
||||
<node-tree class="node-tree" @nodeSelectEvent="nodeChange" @refresh="refresh" :tree-nodes="treeNodes"
|
||||
<select-menu
|
||||
:data="projects"
|
||||
width="160px"
|
||||
:current-data="currentProject"
|
||||
:title="$t('test_track.switch_project')"
|
||||
@dataChange="changeProject"/>
|
||||
<node-tree class="node-tree"
|
||||
:all-label="$t('commons.all_label.review')"
|
||||
v-loading="result.loading"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
:tree-nodes="treeNodes"
|
||||
ref="nodeTree"/>
|
||||
</el-aside>
|
||||
|
||||
|
@ -21,6 +28,7 @@
|
|||
@filter-change="filter" row-key="id"
|
||||
@select-all="handleSelectAll"
|
||||
@select="handleSelectionChange"
|
||||
v-loading="result.loading"
|
||||
height="50vh"
|
||||
ref="table">
|
||||
|
||||
|
@ -98,10 +106,12 @@
|
|||
import {_filter} from "../../../../../../common/js/utils";
|
||||
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
||||
import elTableInfiniteScroll from 'el-table-infinite-scroll';
|
||||
import SelectMenu from "../../../common/SelectMenu";
|
||||
|
||||
export default {
|
||||
name: "TestReviewRelevance",
|
||||
components: {
|
||||
SelectMenu,
|
||||
NodeTree,
|
||||
MsDialogFooter,
|
||||
PriorityTableItem,
|
||||
|
@ -119,6 +129,7 @@
|
|||
data() {
|
||||
return {
|
||||
result: {},
|
||||
currentProject: {},
|
||||
dialogFormVisible: false,
|
||||
isCheckAll: false,
|
||||
testReviews: [],
|
||||
|
@ -254,7 +265,7 @@
|
|||
this.selectIds.add(row.id);
|
||||
}
|
||||
},
|
||||
nodeChange(nodeIds, nodeNames) {
|
||||
nodeChange(node, nodeIds, nodeNames) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
this.selectNodeNames = nodeNames;
|
||||
},
|
||||
|
@ -297,6 +308,7 @@
|
|||
this.$post("/test/case/review/projects", {reviewId: this.reviewId}, res => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
this.currentProject = data[0];
|
||||
this.projects = data;
|
||||
this.projectId = data[0].id;
|
||||
this.projectName = data[0].name;
|
||||
|
@ -318,11 +330,15 @@
|
|||
this.testReviews = [];
|
||||
this.getReviews(true);
|
||||
},
|
||||
changeProject(project) {
|
||||
this.projectId = project.id;
|
||||
},
|
||||
|
||||
getProjectNode(projectId) {
|
||||
const index = this.projects.findIndex(project => project.id === projectId);
|
||||
if (index !== -1) {
|
||||
this.projectName = this.projects[index].name;
|
||||
this.currentProject = this.projects[index];
|
||||
}
|
||||
if (projectId) {
|
||||
this.projectId = projectId;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 5835db186d17a3d305073e58affb4e88a71b32f0
|
||||
Subproject commit a22a3005d9bd254793fcf634d72539cbdf31be3a
|
|
@ -165,7 +165,11 @@ export default {
|
|||
current_user: "Current user"
|
||||
}
|
||||
},
|
||||
monitor: "monitor"
|
||||
monitor: "monitor",
|
||||
all_label: {
|
||||
case: "all",
|
||||
review: "all"
|
||||
}
|
||||
},
|
||||
license: {
|
||||
title: 'Authorization management',
|
||||
|
|
|
@ -165,7 +165,11 @@ export default {
|
|||
current_user: "是当前用户"
|
||||
}
|
||||
},
|
||||
monitor: "监控"
|
||||
monitor: "监控",
|
||||
all_label: {
|
||||
case: "全部用例",
|
||||
review: "全部评审"
|
||||
}
|
||||
},
|
||||
license: {
|
||||
title: '授权管理',
|
||||
|
|
|
@ -165,7 +165,11 @@ export default {
|
|||
current_user: "是當前用戶"
|
||||
}
|
||||
},
|
||||
monitor:"監控"
|
||||
monitor:"監控",
|
||||
all_label: {
|
||||
case: "全部用例",
|
||||
review: "全部評審"
|
||||
}
|
||||
},
|
||||
license: {
|
||||
title: '授權管理',
|
||||
|
|
Loading…
Reference in New Issue