feat: 测试计划缺陷管理改造 (#2166)

* feat: 缺陷管理

* feat: 测试计划缺陷管理改造

* 代码规范

Co-authored-by: chenjianxing <jianxing.chen@fit2cloud.com>
Co-authored-by: jianxing <41557596+AgAngle@users.noreply.github.com>
This commit is contained in:
metersphere-bot 2021-04-21 21:30:35 +08:00 committed by GitHub
parent afc4e2ff10
commit 82cc5801c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2142 additions and 594 deletions

View File

@ -1,7 +1,6 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
@Data
@ -22,13 +21,7 @@ public class Issues implements Serializable {
private String platform;
private String description;
private String model;
private String projectName;
private String currentOwner;
private String projectId;
private static final long serialVersionUID = 1L;
}
}

View File

@ -0,0 +1,11 @@
package io.metersphere.base.domain;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class IssuesDao extends IssuesWithBLOBs {
private String model;
private String projectName;
}

View File

@ -643,6 +643,76 @@ public class IssuesExample {
addCriterion("platform not between", value1, value2, "platform");
return (Criteria) this;
}
public Criteria andProjectIdIsNull() {
addCriterion("project_id is null");
return (Criteria) this;
}
public Criteria andProjectIdIsNotNull() {
addCriterion("project_id is not null");
return (Criteria) this;
}
public Criteria andProjectIdEqualTo(String value) {
addCriterion("project_id =", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotEqualTo(String value) {
addCriterion("project_id <>", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThan(String value) {
addCriterion("project_id >", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThanOrEqualTo(String value) {
addCriterion("project_id >=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThan(String value) {
addCriterion("project_id <", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThanOrEqualTo(String value) {
addCriterion("project_id <=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLike(String value) {
addCriterion("project_id like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotLike(String value) {
addCriterion("project_id not like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdIn(List<String> values) {
addCriterion("project_id in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotIn(List<String> values) {
addCriterion("project_id not in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdBetween(String value1, String value2) {
addCriterion("project_id between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotBetween(String value1, String value2) {
addCriterion("project_id not between", value1, value2, "projectId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -0,0 +1,17 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IssuesWithBLOBs extends Issues implements Serializable {
private String description;
private String customFields;
private static final long serialVersionUID = 1L;
}

View File

@ -2,6 +2,7 @@ package io.metersphere.base.mapper;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.IssuesExample;
import io.metersphere.base.domain.IssuesWithBLOBs;
import java.util.List;
import org.apache.ibatis.annotations.Param;
@ -12,25 +13,25 @@ public interface IssuesMapper {
int deleteByPrimaryKey(String id);
int insert(Issues record);
int insert(IssuesWithBLOBs record);
int insertSelective(Issues record);
int insertSelective(IssuesWithBLOBs record);
List<Issues> selectByExampleWithBLOBs(IssuesExample example);
List<IssuesWithBLOBs> selectByExampleWithBLOBs(IssuesExample example);
List<Issues> selectByExample(IssuesExample example);
Issues selectByPrimaryKey(String id);
IssuesWithBLOBs selectByPrimaryKey(String id);
int updateByExampleSelective(@Param("record") Issues record, @Param("example") IssuesExample example);
int updateByExampleSelective(@Param("record") IssuesWithBLOBs record, @Param("example") IssuesExample example);
int updateByExampleWithBLOBs(@Param("record") Issues record, @Param("example") IssuesExample example);
int updateByExampleWithBLOBs(@Param("record") IssuesWithBLOBs record, @Param("example") IssuesExample example);
int updateByExample(@Param("record") Issues record, @Param("example") IssuesExample example);
int updateByPrimaryKeySelective(Issues record);
int updateByPrimaryKeySelective(IssuesWithBLOBs record);
int updateByPrimaryKeyWithBLOBs(Issues record);
int updateByPrimaryKeyWithBLOBs(IssuesWithBLOBs record);
int updateByPrimaryKey(Issues record);
}

View File

@ -10,9 +10,11 @@
<result column="reporter" jdbcType="VARCHAR" property="reporter" />
<result column="lastmodify" jdbcType="VARCHAR" property="lastmodify" />
<result column="platform" jdbcType="VARCHAR" property="platform" />
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.Issues">
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.IssuesWithBLOBs">
<result column="description" jdbcType="LONGVARCHAR" property="description" />
<result column="custom_fields" jdbcType="LONGVARCHAR" property="customFields" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -73,10 +75,10 @@
</where>
</sql>
<sql id="Base_Column_List">
id, title, `status`, create_time, update_time, reporter, lastmodify, platform
id, title, `status`, create_time, update_time, reporter, lastmodify, platform, project_id
</sql>
<sql id="Blob_Column_List">
description
description, custom_fields
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.IssuesExample" resultMap="ResultMapWithBLOBs">
select
@ -126,17 +128,17 @@
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.Issues">
<insert id="insert" parameterType="io.metersphere.base.domain.IssuesWithBLOBs">
insert into issues (id, title, `status`,
create_time, update_time, reporter,
lastmodify, platform, description
)
lastmodify, platform, project_id,
description, custom_fields)
values (#{id,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR},
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{reporter,jdbcType=VARCHAR},
#{lastmodify,jdbcType=VARCHAR}, #{platform,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR}
)
#{lastmodify,jdbcType=VARCHAR}, #{platform,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR},
#{description,jdbcType=LONGVARCHAR}, #{customFields,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.Issues">
<insert id="insertSelective" parameterType="io.metersphere.base.domain.IssuesWithBLOBs">
insert into issues
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -163,9 +165,15 @@
<if test="platform != null">
platform,
</if>
<if test="projectId != null">
project_id,
</if>
<if test="description != null">
description,
</if>
<if test="customFields != null">
custom_fields,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -192,9 +200,15 @@
<if test="platform != null">
#{platform,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
#{projectId,jdbcType=VARCHAR},
</if>
<if test="description != null">
#{description,jdbcType=LONGVARCHAR},
</if>
<if test="customFields != null">
#{customFields,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.IssuesExample" resultType="java.lang.Long">
@ -230,9 +244,15 @@
<if test="record.platform != null">
platform = #{record.platform,jdbcType=VARCHAR},
</if>
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.description != null">
description = #{record.description,jdbcType=LONGVARCHAR},
</if>
<if test="record.customFields != null">
custom_fields = #{record.customFields,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -248,7 +268,9 @@
reporter = #{record.reporter,jdbcType=VARCHAR},
lastmodify = #{record.lastmodify,jdbcType=VARCHAR},
platform = #{record.platform,jdbcType=VARCHAR},
description = #{record.description,jdbcType=LONGVARCHAR}
project_id = #{record.projectId,jdbcType=VARCHAR},
description = #{record.description,jdbcType=LONGVARCHAR},
custom_fields = #{record.customFields,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -262,12 +284,13 @@
update_time = #{record.updateTime,jdbcType=BIGINT},
reporter = #{record.reporter,jdbcType=VARCHAR},
lastmodify = #{record.lastmodify,jdbcType=VARCHAR},
platform = #{record.platform,jdbcType=VARCHAR}
platform = #{record.platform,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.Issues">
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.IssuesWithBLOBs">
update issues
<set>
<if test="title != null">
@ -291,13 +314,19 @@
<if test="platform != null">
platform = #{platform,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
project_id = #{projectId,jdbcType=VARCHAR},
</if>
<if test="description != null">
description = #{description,jdbcType=LONGVARCHAR},
</if>
<if test="customFields != null">
custom_fields = #{customFields,jdbcType=LONGVARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.Issues">
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.IssuesWithBLOBs">
update issues
set title = #{title,jdbcType=VARCHAR},
`status` = #{status,jdbcType=VARCHAR},
@ -306,7 +335,9 @@
reporter = #{reporter,jdbcType=VARCHAR},
lastmodify = #{lastmodify,jdbcType=VARCHAR},
platform = #{platform,jdbcType=VARCHAR},
description = #{description,jdbcType=LONGVARCHAR}
project_id = #{projectId,jdbcType=VARCHAR},
description = #{description,jdbcType=LONGVARCHAR},
custom_fields = #{customFields,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.Issues">
@ -317,7 +348,8 @@
update_time = #{updateTime,jdbcType=BIGINT},
reporter = #{reporter,jdbcType=VARCHAR},
lastmodify = #{lastmodify,jdbcType=VARCHAR},
platform = #{platform,jdbcType=VARCHAR}
platform = #{platform,jdbcType=VARCHAR},
project_id = #{projectId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -1,11 +1,14 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.IssuesDao;
import io.metersphere.track.request.testcase.IssuesRequest;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtIssuesMapper {
List<Issues> getIssues(@Param("caseId") String caseId, @Param("platform") String platform);
List<IssuesDao> getIssuesByCaseId(@Param("request") IssuesRequest issuesRequest);
List<IssuesDao> getIssuesByProjectId(@Param("request") IssuesRequest issuesRequest);
}

View File

@ -2,12 +2,61 @@
<!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.ExtIssuesMapper">
<select id="getIssues" resultType="io.metersphere.base.domain.Issues">
<select id="getIssuesByCaseId" resultType="io.metersphere.base.domain.IssuesDao">
select issues.*
from test_case_issues, issues
where test_case_issues.issues_id = issues.id
and test_case_issues.test_case_id = #{caseId}
and issues.platform = #{platform}
order by issues.create_time DESC
from issues
inner join test_case_issues
on test_case_issues.issues_id = issues.id
<include refid="queryWhereCondition"/>
<include refid="io.metersphere.base.mapper.ext.ExtBaseMapper.orders"/>
</select>
</mapper>
<select id="getIssuesByProjectId" resultType="io.metersphere.base.domain.IssuesDao">
select issues.*
from issues
<include refid="queryWhereCondition"/>
<include refid="io.metersphere.base.mapper.ext.ExtBaseMapper.orders"/>
</select>
<sql id="queryWhereCondition">
<where>
<if test="request.projectId != null and request.projectId != ''">
and issues.project_id = #{request.projectId}
</if>
<if test="request.testCaseId != null and request.testCaseId != ''">
and test_case_issues.test_case_id = #{request.testCaseId}
</if>
<if test="request.platform != null and request.platform != ''">
and issues.platform = #{request.platform}
</if>
<!-- <if test="request.ids != null and request.ids.size() > 0">-->
<!-- and issues.id in-->
<!-- <foreach collection="request.ids" item="id" separator="," open="(" close=")">-->
<!-- #{id}-->
<!-- </foreach>-->
<!-- </if>-->
<!-- <if test="request.filters != null and request.filters.size() > 0">-->
<!-- <foreach collection="request.filters.entrySet()" index="key" item="values">-->
<!-- <if test="values != null and values.size() > 0">-->
<!-- <choose>-->
<!-- <when test="key == 'scene'">-->
<!-- AND cf.scene IN-->
<!-- <foreach collection="values" item="value" separator="," open="(" close=")">-->
<!-- #{value}-->
<!-- </foreach>-->
<!-- </when>-->
<!-- </choose>-->
<!-- </if>-->
<!-- </foreach>-->
<!-- </if>-->
</where>
</sql>
</mapper>

View File

@ -32,6 +32,14 @@ public interface ExtTestCaseMapper {
*/
List<TestCase> getTestCaseByNotInPlan(@Param("request") QueryTestCaseRequest request);
/**
* 获取不在测试缺陷中的用例
*
* @param request
* @return
*/
List<TestCaseDTO> getTestCaseByNotInIssue(@Param("request") QueryTestCaseRequest request);
/**
* 获取不在评审范围中的用例
*
@ -78,4 +86,6 @@ public interface ExtTestCaseMapper {
List<TestCaseWithBLOBs> listForMinder(@Param("request") QueryTestCaseRequest request);
List<TestCaseDTO> getTestCaseByIds(@Param("ids")List<String> ids);
}

View File

@ -76,6 +76,12 @@
select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status from test_case
as test_case
left join test_case_review_test_case as T2 on test_case.id=T2.case_id and T2.review_id =#{request.reviewId}
and T2.case_id is null
<include refid="notInQueryWhereCondition"></include>
ORDER BY test_case.update_time DESC
</select>
<sql id="notInQueryWhereCondition">
<where>
<if test="request.combine != null">
<include refid="combine">
@ -84,11 +90,15 @@
<property name="objectKey" value="request.combine.tags"/>
</include>
</if>
and T2.case_id is null
<if test="request.testCaseContainIds != null and request.testCaseContainIds.size() > 0">
and test_case.id not in
<foreach collection="request.testCaseContainIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.projectId != null">
AND test_case.project_id = #{request.projectId}
</if>
@ -100,37 +110,14 @@
</if>
<include refid="filters"/>
</where>
ORDER BY test_case.update_time DESC
</select>
</sql>
<select id="getTestCaseByNotInPlan" resultType="io.metersphere.base.domain.TestCase">
select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status from test_case
as test_case
left join test_plan_test_case as T2 on test_case.id=T2.case_id and T2.plan_id =#{request.planId}
<where>
<if test="request.combine != null">
<include refid="combine">
<property name="condition" value="request.combine"/>
<property name="name" value="request.name"/>
<property name="objectKey" value="request.combine.tags"/>
</include>
</if>
and T2.case_id is null
<if test="request.name != null">
and test_case.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.projectId != null">
AND test_case.project_id = #{request.projectId}
</if>
<if test="request.nodeIds != null and request.nodeIds.size() > 0">
AND test_case.node_id IN
<foreach collection="request.nodeIds" open="(" close=")" separator="," item="nodeId">
#{nodeId}
</foreach>
</if>
<include refid="filters"/>
</where>
and T2.case_id is null
<include refid="notInQueryWhereCondition"></include>
ORDER BY test_case.update_time DESC
</select>
@ -428,5 +415,21 @@
from test_case
<include refid="queryWhereCondition"/>
</select>
<select id="getTestCaseByNotInIssue" resultType="io.metersphere.track.dto.TestCaseDTO">
select
<include refid="io.metersphere.base.mapper.TestCaseMapper.Base_Column_List"/>
from test_case
<include refid="notInQueryWhereCondition"></include>
</select>
<select id="getTestCaseByIds" resultType="io.metersphere.track.dto.TestCaseDTO">
select
<include refid="io.metersphere.base.mapper.TestCaseMapper.Base_Column_List"/>
from test_case
<where>
test_case.id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</where>
</select>
</mapper>

View File

@ -0,0 +1,14 @@
package io.metersphere.commons.constants;
public enum IssuesStatus {
NEW("new"), CLOSED("closed"), RESOLVED("resolved"), DELETE("delete");
private String value;
IssuesStatus(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}

View File

@ -8,6 +8,7 @@ import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.BaseQueryRequest;
import io.metersphere.controller.request.UpdateIssueTemplateRequest;
import io.metersphere.dto.IssueTemplateDao;
import io.metersphere.service.IssueTemplateService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -48,4 +49,9 @@ public class IssueTemplateController {
public List<IssueTemplate> list(@PathVariable String workspaceId) {
return issueTemplateService.getOption(workspaceId);
}
@GetMapping("/get/relate/{projectId}")
public IssueTemplateDao getTemplate(@PathVariable String projectId) {
return issueTemplateService.getTemplate(projectId);
}
}

View File

@ -0,0 +1,11 @@
package io.metersphere.dto;
import io.metersphere.base.domain.IssueTemplate;
import lombok.Data;
import java.util.List;
@Data
public class IssueTemplateDao extends IssueTemplate {
List<CustomFieldDao> customFields;
}

View File

@ -4,13 +4,16 @@ import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.CustomField;
import io.metersphere.base.domain.CustomFieldExample;
import io.metersphere.base.domain.CustomFieldTemplate;
import io.metersphere.base.mapper.CustomFieldMapper;
import io.metersphere.base.mapper.ext.ExtCustomFieldMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.controller.request.QueryCustomFieldRequest;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -19,10 +22,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
@Service
@ -102,6 +102,28 @@ public class CustomFieldService {
.andSceneEqualTo(scene);
return customFieldMapper.selectByExampleWithBLOBs(example);
}
public List<CustomFieldDao> getCustomFieldByTemplateId(String templateId) {
List<CustomFieldTemplate> customFields = customFieldTemplateService.getCustomFields(templateId);
List<String> fieldIds = customFields.stream()
.map(CustomFieldTemplate::getFieldId)
.collect(Collectors.toList());
List<CustomField> fields = getFieldByIds(fieldIds);
Map<String, CustomField> fieldMap = fields.stream()
.collect(Collectors.toMap(CustomField::getId, item -> item));
List<CustomFieldDao> result = new ArrayList<>();
customFields.forEach((item) -> {
CustomFieldDao customFieldDao = new CustomFieldDao();
CustomField customField = fieldMap.get(item.getFieldId());
BeanUtils.copyBean(customFieldDao, customField);
BeanUtils.copyBean(customFieldDao, item);
result.add(customFieldDao);
});
return result;
}
public List<CustomField> getFieldByIds(List<String> ids) {
if (CollectionUtils.isNotEmpty(ids)) {
CustomFieldExample example = new CustomFieldExample();

View File

@ -9,6 +9,8 @@ import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.controller.request.BaseQueryRequest;
import io.metersphere.controller.request.UpdateIssueTemplateRequest;
import io.metersphere.dto.CustomFieldDao;
import io.metersphere.dto.IssueTemplateDao;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -35,6 +37,9 @@ public class IssueTemplateService {
@Resource
CustomFieldService customFieldService;
@Resource
ProjectService projectService;
public void add(UpdateIssueTemplateRequest request) {
checkExist(request);
IssueTemplate template = new IssueTemplate();
@ -163,4 +168,23 @@ public class IssueTemplateService {
issueTemplates.add(getDefaultTemplate(workspaceId));
return issueTemplates;
}
public IssueTemplateDao getTemplate(String projectId) {
Project project = projectService.getProjectById(projectId);
String caseTemplateId = project.getCaseTemplateId();
IssueTemplate issueTemplate = null;
IssueTemplateDao issueTemplateDao = new IssueTemplateDao();
if (StringUtils.isNotBlank(caseTemplateId)) {
issueTemplate = issueTemplateMapper.selectByPrimaryKey(caseTemplateId);
if (issueTemplate == null) {
issueTemplate = getDefaultTemplate(project.getWorkspaceId());
}
} else {
issueTemplate = getDefaultTemplate(project.getWorkspaceId());
}
BeanUtils.copyBean(issueTemplateDao, issueTemplate);
List<CustomFieldDao> result = customFieldService.getCustomFieldByTemplateId(issueTemplate.getId());
issueTemplateDao.setCustomFields(result);
return issueTemplateDao;
}
}

View File

@ -89,6 +89,15 @@ public class ProjectService {
return extProjectMapper.getProjectWithWorkspace(request);
}
public List<Project> getProjectByIds(List<String> ids) {
if (!CollectionUtils.isEmpty(ids)) {
ProjectExample example = new ProjectExample();
example.createCriteria().andIdIn(ids);
return projectMapper.selectByExample(example);
}
return new ArrayList<>();
}
public void deleteProject(String projectId) {
// delete test
LoadTestExample loadTestExample = new LoadTestExample();

View File

@ -179,27 +179,14 @@ public class TestCaseTemplateService {
TestCaseTemplateDao caseTemplateDao = new TestCaseTemplateDao();
if (StringUtils.isNotBlank(caseTemplateId)) {
caseTemplate = testCaseTemplateMapper.selectByPrimaryKey(caseTemplateId);
if (caseTemplate == null) {
caseTemplate = getDefaultTemplate(project.getWorkspaceId());
}
} else {
caseTemplate = getDefaultTemplate(project.getWorkspaceId());
}
BeanUtils.copyBean(caseTemplateDao, caseTemplate);
List<CustomFieldTemplate> customFields = customFieldTemplateService.getCustomFields(caseTemplate.getId());
List<String> fieldIds = customFields.stream()
.map(CustomFieldTemplate::getFieldId)
.collect(Collectors.toList());
List<CustomField> fields = customFieldService.getFieldByIds(fieldIds);
Map<String, CustomField> fieldMap = fields.stream()
.collect(Collectors.toMap(CustomField::getId, item -> item));
List<CustomFieldDao> result = new ArrayList<>();
customFields.forEach((item) -> {
CustomFieldDao customFieldDao = new CustomFieldDao();
CustomField customField = fieldMap.get(item.getFieldId());
BeanUtils.copyBean(customFieldDao, customField);
BeanUtils.copyBean(customFieldDao, item);
result.add(customFieldDao);
});
List<CustomFieldDao> result = customFieldService.getCustomFieldByTemplateId(caseTemplate.getId());
caseTemplateDao.setCustomFields(result);
return caseTemplateDao;
}

View File

@ -0,0 +1,78 @@
package io.metersphere.track.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.IssuesDao;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.issue.domain.ZentaoBuild;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import io.metersphere.track.service.IssuesService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RequestMapping("issues")
@RestController
public class IssuesController {
@Resource
private IssuesService issuesService;
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<IssuesDao>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody IssuesRequest request) {
Page<List<Issues>> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, issuesService.list(request));
}
@PostMapping("/add")
public void addIssues(@RequestBody IssuesUpdateRequest issuesRequest) {
issuesService.addIssues(issuesRequest);
}
@PostMapping("/update")
public void updateIssues(@RequestBody IssuesUpdateRequest issuesRequest) {
issuesService.updateIssues(issuesRequest);
}
@GetMapping("/get/{id}")
public List<IssuesDao> getIssues(@PathVariable String id) {
return issuesService.getIssues(id);
}
@GetMapping("/auth/{platform}")
public void testAuth(@PathVariable String platform) {
issuesService.testAuth(platform);
}
@GetMapping("/close/{id}")
public void closeLocalIssue(@PathVariable String id) {
issuesService.closeLocalIssue(id);
}
@PostMapping("/delete")
public void deleteIssue(@RequestBody IssuesRequest request) {
issuesService.deleteIssue(request);
}
@GetMapping("/tapd/user/{caseId}")
public List<PlatformUser> getTapdUsers(@PathVariable String caseId) {
return issuesService.getTapdProjectUsers(caseId);
}
@GetMapping("/zentao/user/{caseId}")
public List<PlatformUser> getZentaoUsers(@PathVariable String caseId) {
return issuesService.getZentaoUsers(caseId);
}
@GetMapping("/zentao/builds/{caseId}")
public List<ZentaoBuild> getZentaoBuilds(@PathVariable String caseId) {
return issuesService.getZentaoBuilds(caseId);
}
}

View File

@ -92,10 +92,16 @@ public class TestCaseController {
return testCaseService.getTestCaseByNodeId(nodeIds);
}
@PostMapping("/name/{goPage}/{pageSize}")
public Pager<List<TestCase>> getTestCaseNames(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
@PostMapping("/relate/{goPage}/{pageSize}")
public Pager<List<TestCase>> getTestCaseRelateList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page,testCaseService.getTestCaseNames(request));
return PageUtils.setPageInfo(page,testCaseService.getTestCaseRelateList(request));
}
@PostMapping("/relate/issue/{goPage}/{pageSize}")
public Pager<List<TestCaseDTO>> getTestCaseIssueRelateList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page,testCaseService.getTestCaseIssueRelateList(request));
}
@PostMapping("/reviews/case/{goPage}/{pageSize}")

View File

@ -1,61 +1,25 @@
package io.metersphere.track.controller;
import io.metersphere.base.domain.Issues;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.issue.domain.ZentaoBuild;
import io.metersphere.track.service.IssuesService;
import io.metersphere.track.request.testcase.IssuesRequest;
import org.springframework.web.bind.annotation.*;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.issues.IssuesRelevanceRequest;
import io.metersphere.track.service.TestCaseIssueService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RequestMapping("issues")
@RequestMapping("test/case/issues")
@RestController
public class TestCaseIssuesController {
@Resource
private IssuesService issuesService;
private TestCaseIssueService testCaseIssueService;
@PostMapping("/add")
public void addIssues(@RequestBody IssuesRequest issuesRequest) {
issuesService.addIssues(issuesRequest);
@PostMapping("/list")
public List<TestCaseDTO> list(@RequestBody IssuesRelevanceRequest request) {
return testCaseIssueService.list(request);
}
@GetMapping("/get/{id}")
public List<Issues> getIssues(@PathVariable String id) {
return issuesService.getIssues(id);
}
@GetMapping("/auth/{platform}")
public void testAuth(@PathVariable String platform) {
issuesService.testAuth(platform);
}
@GetMapping("/close/{id}")
public void closeLocalIssue(@PathVariable String id) {
issuesService.closeLocalIssue(id);
}
@PostMapping("/delete")
public void deleteIssue(@RequestBody IssuesRequest request) {
issuesService.deleteIssue(request);
}
@GetMapping("/tapd/user/{caseId}")
public List<PlatformUser> getTapdUsers(@PathVariable String caseId) {
return issuesService.getTapdProjectUsers(caseId);
}
@GetMapping("/zentao/user/{caseId}")
public List<PlatformUser> getZentaoUsers(@PathVariable String caseId) {
return issuesService.getZentaoUsers(caseId);
}
@GetMapping("/zentao/builds/{caseId}")
public List<ZentaoBuild> getZentaoBuilds(@PathVariable String caseId) {
return issuesService.getZentaoBuilds(caseId);
}
}

View File

@ -37,6 +37,12 @@ public class TestCaseNodeController {
return testCaseNodeService.getAllNodeByPlanId(request);
}
/*模块列表列表*/
@PostMapping("/list/project")
public List<TestCaseNodeDTO> getAllNodeByProjectId(@RequestBody QueryNodeRequest request) {
return testCaseNodeService.getAllNodeByProjectId(request);
}
@PostMapping("/list/all/review")
public List<TestCaseNodeDTO> getAllNodeByReviewId(@RequestBody QueryNodeRequest request) {
return testCaseNodeService.getAllNodeByReviewId(request);

View File

@ -15,6 +15,7 @@ public class TestCaseDTO extends TestCaseWithBLOBs {
private String apiName;
private String performName;
private String lastResultId;
private String projectName;
private List<String> caseTags = new ArrayList<>();
}

View File

@ -1,6 +1,6 @@
package io.metersphere.track.dto;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.IssuesDao;
import lombok.Getter;
import lombok.Setter;
@ -15,7 +15,7 @@ public class TestCaseReportMetricDTO {
private List<TestCaseReportModuleResultDTO> moduleExecuteResult;
private FailureTestCasesAdvanceDTO failureTestCases;
// private List<TestPlanCaseDTO> failureTestCases;
private List<Issues> Issues;
private List<IssuesDao> Issues;
private List<String> executors;
private String principal;
private Long startTime;

View File

@ -1,6 +1,6 @@
package io.metersphere.track.issue;
import io.metersphere.base.domain.ServiceIntegration;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.IssuesMapper;
import io.metersphere.base.mapper.TestCaseIssuesMapper;
import io.metersphere.base.mapper.ext.ExtIssuesMapper;
@ -14,6 +14,7 @@ import io.metersphere.controller.request.IntegrationRequest;
import io.metersphere.service.IntegrationService;
import io.metersphere.service.ProjectService;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import io.metersphere.track.service.TestCaseService;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
@ -22,10 +23,13 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;
public abstract class AbstractIssuePlatform implements IssuesPlatform {
@ -41,6 +45,11 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
protected RestTemplate restTemplateIgnoreSSL;
protected String testCaseId;
protected String key;
public String getKey() {
return key;
}
static {
try {
@ -112,4 +121,45 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
return StringUtils.isNotBlank(integration.getId());
}
protected void insertTestCaseIssues(String issuesId, String caseId) {
if (StringUtils.isNotBlank(caseId)) {
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(issuesId);
testCaseIssues.setTestCaseId(caseId);
testCaseIssuesMapper.insert(testCaseIssues);
}
}
protected void handleIssueUpdate(IssuesUpdateRequest request) {
request.setUpdateTime(System.currentTimeMillis());
issuesMapper.updateByPrimaryKeySelective(request);
handleTestCaseIssues(request);
}
protected void handleTestCaseIssues(IssuesUpdateRequest issuesRequest) {
String issuesId = issuesRequest.getId();
if (StringUtils.isNotBlank(issuesRequest.getTestCaseId())) {
insertTestCaseIssues(issuesId, issuesRequest.getTestCaseId());
} else {
List<String> testCaseIds = issuesRequest.getTestCaseIds();
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andIssuesIdEqualTo(issuesId);
testCaseIssuesMapper.deleteByExample(example);
if (!CollectionUtils.isEmpty(testCaseIds)) {
testCaseIds.forEach(caseId -> {
insertTestCaseIssues(issuesId, caseId);
});
}
}
}
protected void insertIssuesWithoutContext(String id, IssuesUpdateRequest issuesRequest) {
IssuesWithBLOBs issues = new IssuesWithBLOBs();
issues.setId(id);
issues.setPlatform(issuesRequest.getPlatform());
issues.setProjectId(issuesRequest.getProjectId());
issues.setCustomFields(issuesRequest.getCustomFields());
issuesMapper.insert(issues);
}
}

View File

@ -5,7 +5,9 @@ import io.metersphere.track.request.testcase.IssuesRequest;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class IssueFactory {
public static AbstractIssuePlatform createPlatform(String platform, IssuesRequest addIssueRequest) {
@ -15,7 +17,7 @@ public class IssueFactory {
return new JiraPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.Zentao.toString(), platform)) {
return new ZentaoPlatform(addIssueRequest);
} else if (StringUtils.equals("LOCAL", platform)) {
} else if (StringUtils.equalsIgnoreCase(IssuesManagePlatform.Local.toString(), platform)) {
return new LocalPlatform(addIssueRequest);
}
return null;
@ -31,4 +33,15 @@ public class IssueFactory {
});
return platforms;
}
public static Map<String, AbstractIssuePlatform> createPlatformsForMap(List<String> types, IssuesRequest addIssueRequest) {
Map<String, AbstractIssuePlatform> platformMap = new HashMap<>();
types.forEach(type -> {
AbstractIssuePlatform abstractIssuePlatform = createPlatform(type, addIssueRequest);
if (abstractIssuePlatform != null) {
platformMap.put(type, abstractIssuePlatform);
}
});
return platformMap;
}
}

View File

@ -1,9 +1,10 @@
package io.metersphere.track.issue;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.IssuesDao;
import io.metersphere.track.dto.DemandDTO;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import java.util.List;
@ -14,7 +15,13 @@ public interface IssuesPlatform {
*
* @return platform issues list
*/
List<Issues> getIssue();
List<IssuesDao> getIssue(IssuesRequest request);
/**
* 过滤分页数据
* @param issues
*/
void filter(List<IssuesDao> issues);
/*获取平台相关需求*/
List<DemandDTO> getDemandList(String projectId);
@ -24,7 +31,13 @@ public interface IssuesPlatform {
*
* @param issuesRequest issueRequest
*/
void addIssue(IssuesRequest issuesRequest);
void addIssue(IssuesUpdateRequest issuesRequest);
/**
* 更新缺陷
* @param request
*/
void updateIssue(IssuesUpdateRequest request);
/**
* 删除缺陷平台缺陷

View File

@ -5,12 +5,14 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.constants.IssuesStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.dto.DemandDTO;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.lang3.StringUtils;
import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
@ -28,40 +30,31 @@ import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class JiraPlatform extends AbstractIssuePlatform {
protected String key = IssuesManagePlatform.Jira.toString();
public JiraPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
}
@Override
public List<Issues> getIssue() {
List<Issues> list = new ArrayList<>();
public List<IssuesDao> getIssue(IssuesRequest issuesRequest) {
List<IssuesDao> list = new ArrayList<>();
issuesRequest.setPlatform(IssuesManagePlatform.Jira.toString());
List<IssuesDao> issues = extIssuesMapper.getIssuesByCaseId(issuesRequest);
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString());
JSONObject object = JSON.parseObject(config);
if (object == null) {
MSException.throwException("tapd config is null");
}
String account = object.getString("account");
String password = object.getString("password");
HttpHeaders headers = getAuthHeader(object);
String url = object.getString("url");
HttpHeaders headers = auth(account, password);
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andTestCaseIdEqualTo(testCaseId);
List<Issues> issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Jira.toString());
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
issuesIds.forEach(issuesId -> {
Issues dto = getJiraIssues(headers, url, issuesId);
IssuesDao dto = getJiraIssues(headers, url, issuesId);
if (StringUtils.isBlank(dto.getId())) {
// 缺陷不存在解除用例和缺陷的关联
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
@ -80,6 +73,37 @@ public class JiraPlatform extends AbstractIssuePlatform {
return list;
}
public HttpHeaders getAuthHeader(JSONObject object) {
if (object == null) {
MSException.throwException("tapd config is null");
}
String account = object.getString("account");
String password = object.getString("password");
return auth(account, password);
}
@Override
public void filter(List<IssuesDao> issues) {
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString());
JSONObject object = JSON.parseObject(config);
HttpHeaders headers = getAuthHeader(object);
String url = object.getString("url");
issues.forEach((issuesDao) -> {
IssuesDao dto = getJiraIssues(headers, url, issuesDao.getId());
if (StringUtils.isBlank(dto.getId())) {
// 标记成删除
issuesDao.setStatus(IssuesStatus.DELETE.toString());
} else {
// 缺陷状态为 完成则不显示
if (!StringUtils.equals("done", dto.getStatus())) {
issuesDao.setStatus(IssuesStatus.RESOLVED.toString());
}
}
});
}
@Override
public List<DemandDTO> getDemandList(String projectId) {
List<DemandDTO> list = new ArrayList<>();
@ -134,8 +158,9 @@ public class JiraPlatform extends AbstractIssuePlatform {
}
@Override
public void addIssue(IssuesRequest issuesRequest) {
public void addIssue(IssuesUpdateRequest issuesRequest) {
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString());
issuesRequest.setPlatform(IssuesManagePlatform.Jira.toString());
JSONObject object = JSON.parseObject(config);
if (object == null) {
@ -151,7 +176,6 @@ public class JiraPlatform extends AbstractIssuePlatform {
}
String auth = EncryptUtils.base64Encoding(account + ":" + password);
String testCaseId = issuesRequest.getTestCaseId();
String jiraKey = getProjectId(null);
@ -159,7 +183,7 @@ public class JiraPlatform extends AbstractIssuePlatform {
MSException.throwException("未关联Jira 项目Key");
}
String content = issuesRequest.getContent();
String content = issuesRequest.getDescription();
Document document = Jsoup.parse(content);
document.outputSettings(new Document.OutputSettings().prettyPrint(false));
@ -187,18 +211,19 @@ public class JiraPlatform extends AbstractIssuePlatform {
JSONObject jsonObject = JSON.parseObject(result);
String id = jsonObject.getString("key");
issuesRequest.setId(id);
// 用例与第三方缺陷平台中的缺陷关联
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(id);
testCaseIssues.setTestCaseId(testCaseId);
testCaseIssuesMapper.insert(testCaseIssues);
handleTestCaseIssues(issuesRequest);
// 插入缺陷表
Issues issues = new Issues();
issues.setId(id);
issues.setPlatform(IssuesManagePlatform.Jira.toString());
issuesMapper.insert(issues);
insertIssuesWithoutContext(id, issuesRequest);
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
// todo 调用接口
request.setDescription(null);
handleIssueUpdate(request);
}
private String addJiraIssue(String url, String auth, String json) {
@ -258,12 +283,12 @@ public class JiraPlatform extends AbstractIssuePlatform {
return project.getJiraKey();
}
private Issues getJiraIssues(HttpHeaders headers, String url, String issuesId) {
private IssuesDao getJiraIssues(HttpHeaders headers, String url, String issuesId) {
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
//post
ResponseEntity<String> responseEntity;
Issues issues = new Issues();
IssuesDao issues = new IssuesDao();
try {
responseEntity = restTemplate.exchange(url + "/rest/api/2/issue/" + issuesId, HttpMethod.GET, requestEntity, String.class);
String body = responseEntity.getBody();
@ -307,7 +332,7 @@ public class JiraPlatform extends AbstractIssuePlatform {
issues.setPlatform(IssuesManagePlatform.Jira.toString());
} catch (HttpClientErrorException.NotFound e) {
LogUtil.error(e.getStackTrace(), e);
return new Issues();
return new IssuesDao();
} catch (HttpClientErrorException.Unauthorized e) {
LogUtil.error(e.getStackTrace(), e);
MSException.throwException("获取Jira缺陷失败检查Jira配置信息");

View File

@ -1,26 +1,35 @@
package io.metersphere.track.issue;
import io.metersphere.base.domain.Issues;
import io.metersphere.base.domain.TestCaseIssues;
import io.metersphere.base.domain.IssuesDao;
import io.metersphere.base.domain.IssuesWithBLOBs;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.track.dto.DemandDTO;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import java.util.List;
import java.util.UUID;
public class LocalPlatform extends AbstractIssuePlatform {
protected String key = IssuesManagePlatform.Local.toString();
public LocalPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
}
@Override
public List<Issues> getIssue() {
return extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Local.toString());
public List<IssuesDao> getIssue(IssuesRequest issuesRequest) {
issuesRequest.setPlatform(IssuesManagePlatform.Local.toString());
return extIssuesMapper.getIssuesByCaseId(issuesRequest);
}
@Override
public void filter(List<IssuesDao> issues) {
}
@Override
@ -29,25 +38,26 @@ public class LocalPlatform extends AbstractIssuePlatform {
}
@Override
public void addIssue(IssuesRequest issuesRequest) {
public void addIssue(IssuesUpdateRequest issuesRequest) {
SessionUser user = SessionUtils.getUser();
String id = UUID.randomUUID().toString();
Issues issues = new Issues();
IssuesWithBLOBs issues = new IssuesWithBLOBs();
BeanUtils.copyBean(issues, issuesRequest);
issues.setId(id);
issues.setStatus("new");
issues.setReporter(user.getId());
issues.setTitle(issuesRequest.getTitle());
issues.setDescription(issuesRequest.getContent());
issues.setCreateTime(System.currentTimeMillis());
issues.setUpdateTime(System.currentTimeMillis());
issues.setPlatform(IssuesManagePlatform.Local.toString());
issues.setPlatform(IssuesManagePlatform.Local.toString());;
issuesMapper.insert(issues);
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(id);
testCaseIssues.setTestCaseId(issuesRequest.getTestCaseId());
testCaseIssuesMapper.insert(testCaseIssues);
issuesRequest.setId(id);
handleTestCaseIssues(issuesRequest);
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
handleIssueUpdate(request);
}
@Override
@ -71,7 +81,7 @@ public class LocalPlatform extends AbstractIssuePlatform {
}
public void closeIssue(String issueId) {
Issues issues = new Issues();
IssuesWithBLOBs issues = new IssuesWithBLOBs();
issues.setId(issueId);
issues.setStatus("closed");
issuesMapper.updateByPrimaryKeySelective(issues);

View File

@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.constants.IssuesStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
@ -12,6 +13,7 @@ import io.metersphere.controller.ResultHolder;
import io.metersphere.track.dto.DemandDTO;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@ -23,29 +25,30 @@ import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
public class TapdPlatform extends AbstractIssuePlatform {
protected String key = IssuesManagePlatform.Tapd.toString();
public TapdPlatform(IssuesRequest issueRequest) {
super(issueRequest);
}
@Override
public List<Issues> getIssue() {
List<Issues> list = new ArrayList<>();
public List<IssuesDao> getIssue(IssuesRequest issuesRequest) {
List<IssuesDao> list = new ArrayList<>();
String tapdId = getProjectId("");
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andTestCaseIdEqualTo(testCaseId);
List<Issues> issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Tapd.toString());
issuesRequest.setPlatform(IssuesManagePlatform.Tapd.toString());
List<IssuesDao> issues = extIssuesMapper.getIssuesByCaseId(issuesRequest);
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
issuesIds.forEach(issuesId -> {
Issues dto = getTapdIssues(tapdId, issuesId);
IssuesDao dto = getTapdIssues(tapdId, issuesId);
if (StringUtils.isBlank(dto.getId())) {
// 缺陷不存在解除用例和缺陷的关联
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
@ -57,7 +60,7 @@ public class TapdPlatform extends AbstractIssuePlatform {
} else {
dto.setPlatform(IssuesManagePlatform.Tapd.toString());
// 缺陷状态为 关闭则不显示
if (!StringUtils.equals("closed", dto.getStatus())) {
if (!StringUtils.equals(IssuesStatus.CLOSED.toString(), dto.getStatus())) {
list.add(dto);
}
}
@ -65,6 +68,30 @@ public class TapdPlatform extends AbstractIssuePlatform {
return list;
}
@Override
public void filter(List<IssuesDao> issues) {
String tapdId = "";
for (IssuesDao item : issues) {
if (StringUtils.isNotBlank(item.getProjectId())) {
tapdId = getProjectId(issues.get(0).getProjectId());
break;
}
}
for (IssuesDao item : issues) {
IssuesDao dto = getTapdIssues(tapdId, item.getId());
if (StringUtils.isBlank(dto.getId())) {
// 标记成删除
item.setStatus(IssuesStatus.DELETE.toString());
} else {
// 缺陷状态为 完成则不显示
if (!StringUtils.equals(IssuesStatus.CLOSED.toString(), dto.getStatus())) {
item.setStatus(IssuesStatus.CLOSED.toString());
}
}
}
}
@Override
public List<DemandDTO> getDemandList(String projectId) {
List<DemandDTO> demandList = new ArrayList<>();
@ -86,17 +113,17 @@ public class TapdPlatform extends AbstractIssuePlatform {
return demandList;
}
private Issues getTapdIssues(String projectId, String issuesId) {
private IssuesDao getTapdIssues(String projectId, String issuesId) {
String url = "https://api.tapd.cn/bugs?workspace_id=" + projectId + "&id=" + issuesId;
ResultHolder call = call(url);
String listJson = JSON.toJSONString(call.getData());
if (StringUtils.equals(Boolean.FALSE.toString(), listJson)) {
return new Issues();
return new IssuesDao();
}
JSONObject jsonObject = JSONObject.parseObject(listJson);
JSONObject bug = jsonObject.getJSONObject("Bug");
Long created = bug.getLong("created");
Issues issues = jsonObject.getObject("Bug", Issues.class);
IssuesDao issues = jsonObject.getObject("Bug", IssuesDao.class);
// 获取工作流中缺陷状态名称
String workflow = "https://api.tapd.cn/workflows/status_map?workspace_id=" + projectId + "&system=bug";
@ -112,7 +139,9 @@ public class TapdPlatform extends AbstractIssuePlatform {
}
@Override
public void addIssue(IssuesRequest issuesRequest) {
public void addIssue(IssuesUpdateRequest issuesRequest) {
issuesRequest.setPlatform(IssuesManagePlatform.Tapd.toString());
String url = "https://api.tapd.cn/bugs";
String testCaseId = issuesRequest.getTestCaseId();
String tapdId = getProjectId("");
@ -129,7 +158,7 @@ public class TapdPlatform extends AbstractIssuePlatform {
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("title", issuesRequest.getTitle());
paramMap.add("workspace_id", tapdId);
paramMap.add("description", issuesRequest.getContent());
paramMap.add("description", issuesRequest.getDescription());
paramMap.add("reporter", username);
paramMap.add("current_owner", usersStr);
@ -139,18 +168,19 @@ public class TapdPlatform extends AbstractIssuePlatform {
JSONObject jsonObject = JSONObject.parseObject(listJson);
String issuesId = jsonObject.getObject("Bug", Issues.class).getId();
issuesRequest.setId(issuesId);
// 用例与第三方缺陷平台中的缺陷关联
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(issuesId);
testCaseIssues.setTestCaseId(testCaseId);
testCaseIssuesMapper.insert(testCaseIssues);
handleTestCaseIssues(issuesRequest);
// 插入缺陷表
Issues issues = new Issues();
issues.setId(issuesId);
issues.setPlatform(IssuesManagePlatform.Tapd.toString());
issuesMapper.insert(issues);
insertIssuesWithoutContext(issuesId, issuesRequest);
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
// todo 调用接口
request.setDescription(null);
handleIssueUpdate(request);
}
@Override

View File

@ -5,12 +5,14 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.constants.IssuesStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.dto.DemandDTO;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.issue.domain.ZentaoBuild;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@ -38,6 +40,8 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
*/
private final String url;
protected String key = IssuesManagePlatform.Zentao.toString();
public ZentaoPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
String config = getPlatformConfig(IssuesManagePlatform.Zentao.toString());
@ -58,17 +62,19 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
}
@Override
public List<Issues> getIssue() {
List<Issues> list = new ArrayList<>();
public List<IssuesDao> getIssue(IssuesRequest issuesRequest) {
List<IssuesDao> list = new ArrayList<>();
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andTestCaseIdEqualTo(testCaseId);
List<Issues> issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Zentao.toString());
issuesRequest.setPlatform(IssuesManagePlatform.Zentao.toString());
List<IssuesDao> issues = extIssuesMapper.getIssuesByCaseId(issuesRequest);
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
issuesIds.forEach(issuesId -> {
Issues dto = getZentaoIssues(issuesId);
IssuesDao dto = getZentaoIssues(issuesId);
if (StringUtils.isBlank(dto.getId())) {
// 缺陷不存在解除用例和缺陷的关联
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
@ -89,6 +95,22 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
}
@Override
public void filter(List<IssuesDao> issues) {
issues.forEach((issuesDao) -> {
IssuesDao dto = getZentaoIssues(issuesDao.getId());
if (StringUtils.isBlank(dto.getId())) {
// 标记成删除
issuesDao.setStatus(IssuesStatus.DELETE.toString());
} else {
// 缺陷状态为 完成则不显示
if (!StringUtils.equals("done", dto.getStatus())) {
issuesDao.setStatus(IssuesStatus.RESOLVED.toString());
}
}
});
}
@Override
public List<DemandDTO> getDemandList(String projectId) {
//getTestStories
@ -126,7 +148,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
return list;
}
private Issues getZentaoIssues(String bugId) {
private IssuesDao getZentaoIssues(String bugId) {
String session = login();
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
@ -148,9 +170,9 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
String reporter = bug.getString("openedBy");
int deleted = bug.getInteger("deleted");
if (deleted == 1) {
return new Issues();
return new IssuesDao();
}
Issues issues = new Issues();
IssuesDao issues = new IssuesDao();
issues.setId(id);
issues.setTitle(title);
issues.setDescription(description);
@ -163,11 +185,12 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
LogUtil.error("get zentao bug fail " + e.getMessage());
}
return new Issues();
return new IssuesDao();
}
@Override
public void addIssue(IssuesRequest issuesRequest) {
public void addIssue(IssuesUpdateRequest issuesRequest) {
issuesRequest.setPlatform(IssuesManagePlatform.Zentao.toString());
String session = login();
String projectId = getProjectId(null);
@ -183,7 +206,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("product", projectId);
paramMap.add("title", issuesRequest.getTitle());
paramMap.add("steps", issuesRequest.getContent());
paramMap.add("steps", issuesRequest.getDescription());
if (!CollectionUtils.isEmpty(issuesRequest.getZentaoBuilds())) {
List<String> builds = issuesRequest.getZentaoBuilds();
builds.forEach(build -> {
@ -208,27 +231,28 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
JSONObject data = obj.getJSONObject("data");
String id = data.getString("id");
if (StringUtils.isNotBlank(id)) {
issuesRequest.setId(id);
// 用例与第三方缺陷平台中的缺陷关联
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(id);
testCaseIssues.setTestCaseId(testCaseId);
testCaseIssuesMapper.insert(testCaseIssues);
handleTestCaseIssues(issuesRequest);
IssuesExample issuesExample = new IssuesExample();
issuesExample.createCriteria().andIdEqualTo(id)
.andPlatformEqualTo(IssuesManagePlatform.Zentao.toString());
if (issuesMapper.selectByExample(issuesExample).size() <= 0) {
// 插入缺陷表
Issues issues = new Issues();
issues.setId(id);
issues.setPlatform(IssuesManagePlatform.Zentao.toString());
issuesMapper.insert(issues);
insertIssuesWithoutContext(id, issuesRequest);
}
}
}
}
@Override
public void updateIssue(IssuesUpdateRequest request) {
// todo 调用接口
request.setDescription(null);
handleIssueUpdate(request);
}
@Override
public void deleteIssue(String id) {

View File

@ -0,0 +1,29 @@
package io.metersphere.track.request.issues;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class IssuesRelevanceRequest {
/**
* 缺陷ID
*/
private String issuesId;
/**
* 当选择关联全部用例时把加载条件送到后台从后台查询
*/
private QueryTestCaseRequest request;
/**
* 具体要关联的用例
*/
private List<String> testCaseIds = new ArrayList<>();
private Boolean checked;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.track.request.testcase;
import io.metersphere.controller.request.BaseQueryRequest;
import lombok.Getter;
import lombok.Setter;
@ -7,7 +8,7 @@ import java.util.List;
@Getter
@Setter
public class IssuesRequest {
public class IssuesRequest extends BaseQueryRequest {
private String title;
private String content;
private String projectId;
@ -27,4 +28,7 @@ public class IssuesRequest {
*/
private String id;
private String caseId;
private String platform;
private String customFields;
private List<String> testCaseIds;
}

View File

@ -0,0 +1,24 @@
package io.metersphere.track.request.testcase;
import io.metersphere.base.domain.IssuesWithBLOBs;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class IssuesUpdateRequest extends IssuesWithBLOBs {
private String content;
private String testCaseId;
private List<String> tapdUsers;
/**
* zentao bug 处理人
*/
private String zentaoUser;
/**
* zentao bug 影响版本
*/
private List<String> zentaoBuilds;
private List<String> testCaseIds;
}

View File

@ -19,6 +19,8 @@ public class QueryTestCaseRequest extends BaseQueryRequest {
private String planId;
private String issuesId;
private String userId;
private String reviewId;
@ -30,4 +32,5 @@ public class QueryTestCaseRequest extends BaseQueryRequest {
private long createTime = 0;
private long relevanceCreateTime = 0;
private List<String> testCaseContainIds;
}

View File

@ -3,9 +3,11 @@ package io.metersphere.track.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.IssuesMapper;
import io.metersphere.base.mapper.TestCaseIssuesMapper;
import io.metersphere.base.mapper.ext.ExtIssuesMapper;
import io.metersphere.commons.constants.IssuesManagePlatform;
import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.IntegrationRequest;
import io.metersphere.i18n.Translator;
@ -13,11 +15,17 @@ import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.IntegrationService;
import io.metersphere.service.ProjectService;
import io.metersphere.track.issue.*;
import io.metersphere.track.issue.AbstractIssuePlatform;
import io.metersphere.track.issue.IssueFactory;
import io.metersphere.track.issue.ZentaoPlatform;
import io.metersphere.track.issue.domain.PlatformUser;
import io.metersphere.track.issue.domain.ZentaoBuild;
import io.metersphere.track.request.testcase.IssuesRequest;
import io.metersphere.track.request.testcase.IssuesUpdateRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -35,6 +43,7 @@ public class IssuesService {
private IntegrationService integrationService;
@Resource
private ProjectService projectService;
@Lazy
@Resource
private TestCaseService testCaseService;
@Resource
@ -43,53 +52,27 @@ public class IssuesService {
private NoticeSendService noticeSendService;
@Resource
private TestCaseIssuesMapper testCaseIssuesMapper;
@Resource
private SqlSessionFactory sqlSessionFactory;
@Resource
private ExtIssuesMapper extIssuesMapper;
public void testAuth(String platform) {
AbstractIssuePlatform abstractPlatform = IssueFactory.createPlatform(platform, new IssuesRequest());
abstractPlatform.testAuth();
}
public void addIssues(IssuesRequest issuesRequest) {
SessionUser user = SessionUtils.getUser();
String orgId = user.getLastOrganizationId();
boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString());
boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString());
boolean zentao = isIntegratedPlatform(orgId, IssuesManagePlatform.Zentao.toString());
String tapdId = getTapdProjectId(issuesRequest.getTestCaseId());
String jiraKey = getJiraProjectKey(issuesRequest.getTestCaseId());
String zentaoId = getZentaoProjectId(issuesRequest.getTestCaseId());
List<String> platforms = new ArrayList<>();
if (tapd) {
// 是否关联了项目
if (StringUtils.isNotBlank(tapdId)) {
platforms.add(IssuesManagePlatform.Tapd.name());
}
}
if (jira) {
if (StringUtils.isNotBlank(jiraKey)) {
platforms.add(IssuesManagePlatform.Jira.name());
}
}
if (zentao) {
if (StringUtils.isNotBlank(zentaoId)) {
platforms.add(IssuesManagePlatform.Zentao.name());
}
}
if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey) && StringUtils.isBlank(zentaoId)) {
platforms.add("LOCAL");
}
List<AbstractIssuePlatform> platformList = IssueFactory.createPlatforms(platforms, issuesRequest);
public void addIssues(IssuesUpdateRequest issuesRequest) {
List<AbstractIssuePlatform> platformList = getUpdatePlatforms(issuesRequest);
platformList.forEach(platform -> {
platform.addIssue(issuesRequest);
});
noticeIssueEven(issuesRequest, "IssuesCreate");
}
public void noticeIssueEven(IssuesUpdateRequest issuesRequest, String type) {
SessionUser user = SessionUtils.getUser();
String orgId = user.getLastOrganizationId();
List<String> userIds = new ArrayList<>();
userIds.add(orgId);
String context = getIssuesContext(user, issuesRequest, NoticeConstants.Event.CREATE);
@ -100,18 +83,75 @@ public class IssuesService {
.context(context)
.relatedUsers(userIds)
.subject(Translator.get("task_defect_notification"))
.mailTemplate("IssuesCreate")
.mailTemplate(type)
.paramMap(paramMap)
.event(NoticeConstants.Event.CREATE)
.build();
noticeSendService.send(NoticeConstants.TaskType.DEFECT_TASK, noticeModel);
}
public List<Issues> getIssues(String caseId) {
List<Issues> list = new ArrayList<>();
public void updateIssues(IssuesUpdateRequest issuesRequest) {
List<AbstractIssuePlatform> platformList = getUpdatePlatforms(issuesRequest);
platformList.forEach(platform -> {
platform.updateIssue(issuesRequest);
});
// todo 缺陷更新事件
}
public List<AbstractIssuePlatform> getUpdatePlatforms(IssuesUpdateRequest updateRequest) {
List<String> platforms = null;
if (StringUtils.isNotBlank(updateRequest.getTestCaseId())) {
// 测试计划关联
platforms = getPlatformsByCaseId(updateRequest.getTestCaseId());
} else {
// 缺陷管理关联
platforms = getPlatformsByProjectId(updateRequest.getProjectId());
}
if (CollectionUtils.isEmpty(platforms)) {
platforms.add("LOCAL");
}
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setTestCaseId(updateRequest.getTestCaseId());
return IssueFactory.createPlatforms(platforms, issuesRequest);
}
public List<IssuesDao> getIssues(String caseId) {
IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setTestCaseId(caseId);
ServiceUtils.getDefaultOrder(issueRequest.getOrders());
Project project = getProjectByCaseId(caseId);
return getIssuesByProject(issueRequest, project);
}
public List<IssuesDao> getIssuesByProject(IssuesRequest issueRequest, Project project) {
List<IssuesDao> list = new ArrayList<>();
List<String> platforms = getPlatforms(project);
platforms.add(IssuesManagePlatform.Local.toString());
List<AbstractIssuePlatform> platformList = IssueFactory.createPlatforms(platforms, issueRequest);
platformList.forEach(platform -> {
List<IssuesDao> issue = platform.getIssue(issueRequest);
list.addAll(issue);
});
return list;
}
public List<String> getPlatformsByProjectId(String projectId) {
return getPlatforms(projectService.getProjectById(projectId));
}
public List<String> getPlatformsByCaseId(String caseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(caseId);
Project project = projectService.getProjectById(testCase.getProjectId());
List<String> platforms = getPlatforms(project);
platforms.add("LOCAL");
return getPlatforms(project);
}
public List<String> getPlatforms(Project project) {
SessionUser user = SessionUtils.getUser();
String orgId = user.getLastOrganizationId();
boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString());
boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString());
boolean zentao = isIntegratedPlatform(orgId, IssuesManagePlatform.Zentao.toString());
@ -119,7 +159,7 @@ public class IssuesService {
List<String> platforms = new ArrayList<>();
if (tapd) {
// 是否关联了项目
String tapdId = getTapdProjectId(caseId);
String tapdId = project.getTapdId();
if (StringUtils.isNotBlank(tapdId)) {
platforms.add(IssuesManagePlatform.Tapd.name());
}
@ -127,29 +167,25 @@ public class IssuesService {
}
if (jira) {
String jiraKey = getJiraProjectKey(caseId);
String jiraKey = project.getJiraKey();
if (StringUtils.isNotBlank(jiraKey)) {
platforms.add(IssuesManagePlatform.Jira.name());
}
}
if (zentao) {
String zentaoId = getZentaoProjectId(caseId);
String zentaoId = project.getZentaoId();
if (StringUtils.isNotBlank(zentaoId)) {
platforms.add(IssuesManagePlatform.Zentao.name());
}
}
return platforms;
}
platforms.add("LOCAL");
IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setTestCaseId(caseId);
List<AbstractIssuePlatform> platformList = IssueFactory.createPlatforms(platforms, issueRequest);
platformList.forEach(platform -> {
List<Issues> issue = platform.getIssue();
list.addAll(issue);
});
return list;
private Project getProjectByCaseId(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project;
}
private String getTapdProjectId(String testCaseId) {
@ -182,7 +218,7 @@ public class IssuesService {
}
public void closeLocalIssue(String issueId) {
Issues issues = new Issues();
IssuesWithBLOBs issues = new IssuesWithBLOBs();
issues.setId(issueId);
issues.setStatus("closed");
issuesMapper.updateByPrimaryKeySelective(issues);
@ -212,7 +248,7 @@ public class IssuesService {
testCaseIssuesMapper.deleteByExample(example);
}
private static String getIssuesContext(SessionUser user, IssuesRequest issuesRequest, String type) {
private static String getIssuesContext(SessionUser user, IssuesUpdateRequest issuesRequest, String type) {
String context = "";
if (StringUtils.equals(NoticeConstants.Event.CREATE, type)) {
context = "缺陷任务通知:" + user.getName() + "发起了一个缺陷" + "'" + issuesRequest.getTitle() + "'" + "请跟进";
@ -226,4 +262,41 @@ public class IssuesService {
ZentaoPlatform platform = (ZentaoPlatform) IssueFactory.createPlatform(IssuesManagePlatform.Zentao.name(), issueRequest);
return platform.getBuilds();
}
public List<IssuesDao> list(IssuesRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
List<IssuesDao> issues = extIssuesMapper.getIssuesByProjectId(request);
Map<String, List<IssuesDao>> issueMap = getIssueMap(issues);
Map<String, AbstractIssuePlatform> platformMap = getPlatformMap(request);
issueMap.forEach((platformName, data) -> {
AbstractIssuePlatform platform = platformMap.get(platformName);
platform.filter(data);
});
return issues;
}
public Map<String, List<IssuesDao>> getIssueMap(List<IssuesDao> issues) {
Map<String, List<IssuesDao>> issueMap = new HashMap<>();
issues.forEach(item -> {
String platForm = item.getPlatform();
if (StringUtils.equalsIgnoreCase(IssuesManagePlatform.Local.toString(), item.getPlatform())) {
// 可能有大小写的问题
platForm = IssuesManagePlatform.Local.toString();
}
List<IssuesDao> issuesDao = issueMap.get(platForm);
if (issuesDao == null) {
issuesDao = new ArrayList<>();
}
issuesDao.add(item);
issueMap.put(platForm, issuesDao);
});
return issueMap;
}
public Map<String, AbstractIssuePlatform> getPlatformMap(IssuesRequest request) {
Project project = projectService.getProjectById(request.getProjectId());
List<String> platforms = getPlatforms(project);
platforms.add(IssuesManagePlatform.Local.toString());
return IssueFactory.createPlatformsForMap(platforms, request);
}
}

View File

@ -4,6 +4,9 @@ import io.metersphere.base.domain.TestCaseIssues;
import io.metersphere.base.domain.TestCaseIssuesExample;
import io.metersphere.base.mapper.IssuesMapper;
import io.metersphere.base.mapper.TestCaseIssuesMapper;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.issues.IssuesRelevanceRequest;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@ -18,6 +21,9 @@ public class TestCaseIssueService {
@Resource
private TestCaseIssuesMapper testCaseIssuesMapper;
@Lazy
@Resource
private TestCaseService testCaseService;
@Resource
private IssuesMapper issuesMapper;
@ -33,4 +39,23 @@ public class TestCaseIssueService {
}
testCaseIssuesMapper.deleteByExample(example);
}
public List<TestCaseDTO> list(IssuesRelevanceRequest request) {
List<String> testCaseIds = getTestCaseIdsByIssuesId(request.getIssuesId());
List<TestCaseDTO> list = testCaseService.getTestCaseByIds(testCaseIds);
testCaseService.addProjectName(list);
return list;
}
public List<TestCaseIssues> getTestCaseIssuesByIssuesId(String issuesId) {
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andIssuesIdEqualTo(issuesId);
return testCaseIssuesMapper.selectByExample(example);
}
public List<String> getTestCaseIdsByIssuesId(String issuesId) {
return getTestCaseIssuesByIssuesId(issuesId).stream()
.map(TestCaseIssues::getTestCaseId)
.collect(Collectors.toList());
}
}

View File

@ -337,13 +337,15 @@ public class TestCaseNodeService extends NodeTreeService<TestCaseNodeDTO> {
public List<TestCaseNodeDTO> getAllNodeByPlanId(QueryNodeRequest request) {
String planId = request.getTestPlanId();
String projectId = request.getProjectId();
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
if (testPlan == null) {
return Collections.emptyList();
}
return getAllNodeByProjectId(request);
}
return getNodeTreeByProjectId(projectId);
public List<TestCaseNodeDTO> getAllNodeByProjectId(QueryNodeRequest request) {
return getNodeTreeByProjectId(request.getProjectId());
}
public List<TestCaseNodeDTO> getAllNodeByReviewId(QueryNodeRequest request) {

View File

@ -23,6 +23,7 @@ import io.metersphere.excel.listener.TestCaseDataListener;
import io.metersphere.excel.utils.EasyExcelExporter;
import io.metersphere.i18n.Translator;
import io.metersphere.service.FileService;
import io.metersphere.service.ProjectService;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.testcase.EditTestCaseRequest;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
@ -35,6 +36,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -70,6 +72,10 @@ public class TestCaseService {
@Resource
ProjectMapper projectMapper;
@Lazy
@Resource
ProjectService projectService;
@Resource
SqlSessionFactory sqlSessionFactory;
@ -278,16 +284,41 @@ public class TestCaseService {
* @param request
* @return
*/
public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) {
public List<TestCase> getTestCaseRelateList(QueryTestCaseRequest request) {
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
OrderRequest order = new OrderRequest();
order.setName("sort");
order.setType("desc");
orderList.add(order);
request.setOrders(orderList);
return getTestCaseByNotInPlan(request);
}
public List<TestCase> getTestCaseByNotInPlan(QueryTestCaseRequest request) {
return extTestCaseMapper.getTestCaseByNotInPlan(request);
}
public List<TestCaseDTO> getTestCaseByNotInIssue(QueryTestCaseRequest request) {
List<TestCaseDTO> list = extTestCaseMapper.getTestCaseByNotInIssue(request);
addProjectName(list);
return list;
}
public void addProjectName(List<TestCaseDTO> list) {
List<String> projectIds = list.stream()
.map(TestCase::getProjectId)
.collect(Collectors.toList());
List<Project> projects = projectService.getProjectByIds(projectIds);
Map<String, String> projectMap = projects.stream()
.collect(Collectors.toMap(Project::getId, Project::getName));
list.forEach(item -> {
String projectName = projectMap.get(item.getProjectId());
if (StringUtils.isNotBlank(projectName)) {
item.setProjectName(projectName);
}
});
}
public List<TestCase> getReviewCase(QueryTestCaseRequest request) {
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
OrderRequest order = new OrderRequest();
@ -925,4 +956,17 @@ public class TestCaseService {
public List<TestCaseWithBLOBs> listTestCaseForMinder(QueryTestCaseRequest request) {
return extTestCaseMapper.listForMinder(request);
}
public List<TestCaseDTO> getTestCaseByIds(List<String> testCaseIds) {
if (CollectionUtils.isNotEmpty(testCaseIds)) {
return extTestCaseMapper.getTestCaseByIds(testCaseIds);
} else {
return new ArrayList<>();
}
}
public List<TestCaseDTO> getTestCaseIssueRelateList(QueryTestCaseRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
return getTestCaseByNotInIssue(request);
}
}

View File

@ -271,7 +271,7 @@ public class TestPlanReportService {
testPlanService.buildLoadCaseReport(testPlanReport.getTestPlanId(), components);
if (StringUtils.equals(ReportTriggerMode.MANUAL.name(), triggerMode)) {
List<Issues> issues = testPlanService.buildFunctionalCaseReport(testPlanReport.getTestPlanId(), components);
List<IssuesDao> issues = testPlanService.buildFunctionalCaseReport(testPlanReport.getTestPlanId(), components);
issuesInfo = JSONArray.toJSONString(issues);
}

View File

@ -133,6 +133,9 @@ public class TestPlanService {
private ApiScenarioReportMapper apiScenarioReportMapper;
@Resource
private TestPlanReportMapper testPlanReportMapper;
@Lazy
@Resource
private IssuesService issuesService;
public synchronized String addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
@ -620,7 +623,7 @@ public class TestPlanService {
JSONArray componentIds = content.getJSONArray("components");
List<ReportComponent> components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan);
List<Issues> issues = buildFunctionalCaseReport(planId, components);
List<IssuesDao> issues = buildFunctionalCaseReport(planId, components);
buildApiCaseReport(planId, components);
buildScenarioCaseReport(planId, components);
buildLoadCaseReport(planId, components);
@ -780,7 +783,7 @@ public class TestPlanService {
JSONArray componentIds = content.getJSONArray("components");
List<ReportComponent> components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan);
List<Issues> issues = buildFunctionalCaseReport(planId, components);
List<IssuesDao> issues = buildFunctionalCaseReport(planId, components);
buildApiCaseReport(planId, components);
buildScenarioCaseReport(planId, components);
buildLoadCaseReport(planId, components);
@ -826,14 +829,13 @@ public class TestPlanService {
}
}
public List<Issues> buildFunctionalCaseReport(String planId, List<ReportComponent> components) {
IssuesService issuesService = (IssuesService) CommonBeanFactory.getBean("issuesService");
public List<IssuesDao> buildFunctionalCaseReport(String planId, List<ReportComponent> components) {
List<TestPlanCaseDTO> testPlanTestCases = listTestCaseByPlanId(planId);
List<Issues> issues = new ArrayList<>();
List<IssuesDao> issues = new ArrayList<>();
for (TestPlanCaseDTO testCase : testPlanTestCases) {
List<Issues> issue = issuesService.getIssues(testCase.getCaseId());
List<IssuesDao> issue = issuesService.getIssues(testCase.getCaseId());
if (issue.size() > 0) {
for (Issues i : issue) {
for (IssuesDao i : issue) {
i.setModel(testCase.getNodePath());
i.setProjectName(testCase.getProjectName());
String des = i.getDescription().replaceAll("<p>", "").replaceAll("</p>", "");

View File

@ -37,7 +37,7 @@ VALUES ('09642424-7b1b-4004-867e-ff9c798a1933','i43sf4_issueCreator','ISSUE','me
INSERT INTO custom_field (id,name,scene,`type`,remark,`options`,`system`,`global`,workspace_id,create_time,update_time)
VALUES ('a577bc60-75fe-47ec-8aa6-32dca23bf3d6','i43sf4_issueProcessor','ISSUE','member','','[]',1,1,'global',unix_timestamp() * 1000,unix_timestamp() * 1000);
INSERT INTO custom_field (id,name,scene,`type`,remark,`options`,`system`,`global`,workspace_id,create_time,update_time)
VALUES ('beb57501-19c8-4ca3-8dfb-2cef7c0ea087','i43sf4_issueStatus','ISSUE','select','','[{"text":"test_track.issue.status_new","value":"NEW","system": true},{"text":"test_track.issue.status_resolved","value":"RESOLVED","system": true},{"text":"test_track.issue.status_closed","value":"CLOSED","system": true}]',1,1,'global',unix_timestamp() * 1000,unix_timestamp() * 1000);
VALUES ('beb57501-19c8-4ca3-8dfb-2cef7c0ea087','i43sf4_issueStatus','ISSUE','select','','[{"text":"test_track.issue.status_new","value":"new","system": true},{"text":"test_track.issue.status_resolved","value":"resolved","system": true},{"text":"test_track.issue.status_closed","value":"closed","system": true}]',1,1,'global',unix_timestamp() * 1000,unix_timestamp() * 1000);
INSERT INTO custom_field (id,name,scene,`type`,remark,`options`,`system`,`global`,workspace_id,create_time,update_time)
VALUES ('d392af07-fdfe-4475-a459-87d59f0b1626','i43sf4_issueSeverity','ISSUE','select','','[{"text":"P0","value":"P0","system": true},{"text":"P1","value":"P1","system": true},{"text":"P2","value":"P2","system": true},{"text":"P3","value":"P3","system": true}]',1,1,'global',unix_timestamp() * 1000,unix_timestamp() * 1000);
@ -119,7 +119,7 @@ VALUES ('657e70c3-0d1b-4bbe-b52f-bfadee05148a','09642424-7b1b-4004-867e-ff9c798a
INSERT INTO custom_field_template (id,field_id,template_id,scene,required,default_value)
VALUES ('b8921cbc-05b3-4d8f-a96e-d1e4ae9d8664','a577bc60-75fe-47ec-8aa6-32dca23bf3d6','5d7c87d2-f405-4ec1-9a3d-71b514cdfda3','ISSUE',1,'');
INSERT INTO custom_field_template (id,field_id,template_id,scene,required,default_value)
VALUES ('d5908553-1b29-4868-9001-e938822b92ef','beb57501-19c8-4ca3-8dfb-2cef7c0ea087','5d7c87d2-f405-4ec1-9a3d-71b514cdfda3','ISSUE',1,'"NEW"');
VALUES ('d5908553-1b29-4868-9001-e938822b92ef','beb57501-19c8-4ca3-8dfb-2cef7c0ea087','5d7c87d2-f405-4ec1-9a3d-71b514cdfda3','ISSUE',1,'"new"');
ALTER TABLE project
ADD case_template_id varchar(50) NULL COMMENT 'Relate test case template id';
@ -192,6 +192,29 @@ VALUES ('metersphere.module.reportStat', 'ENABLE', 'text', 1);
INSERT INTO system_parameter (param_key, param_value, type, sort)
VALUES ('metersphere.module.testTrack', 'ENABLE', 'text', 1);
-- issue表添加自定义字段和项目id列
ALTER TABLE issues ADD custom_fields TEXT NULL COMMENT 'CustomField';
ALTER TABLE issues ADD project_id varchar(50) NULL;
-- 兼容旧数据初始化issue表的project_id
update issues i
inner join
(
select tc.project_id, tci.issues_id
from test_case_issues tci
inner join test_case tc
on tci.test_case_id = tc.id
) as tmp
on i.id = tmp.issues_id
set i.project_id = tmp.project_id;
-- 修改issue表主键
alter table issues drop primary key;
alter table issues
add constraint issues_pk
primary key (id);
-- init prometheus host
INSERT INTO system_parameter (param_key, param_value, type, sort)
VALUES ('prometheus.host', 'http://ms-prometheus:9090', 'text', 1);
@ -206,3 +229,4 @@ alter table load_test_report
alter table load_test_report
add tps VARCHAR(10) null;

View File

@ -80,7 +80,7 @@
<!-- <table tableName="test_case_template"></table>-->
<!-- <table tableName="custom_field"></table>-->
<!-- <table tableName="test_case"></table>-->
<table tableName="test_plan_test_case"></table>
<table tableName="issues"></table>
<!-- <table tableName="custom_field_template"></table>-->
</context>

View File

@ -1,5 +1,5 @@
<template>
<div class="card-container" >
<div class="card-container">
<el-table
border
@ -11,13 +11,14 @@
@header-dragend="headerDragend"
@cell-mouse-enter="showPopover"
row-key="id"
class="test-content adjust-table ms-select-all-fixed"
class="test-content adjust-table"
:class="{'ms-select-all-fixed':showSelectAll}"
:height="screenHeight"
ref="table" @row-click="handleRowClick">
<el-table-column v-if="enableSelection" width="50" type="selection"/>
<ms-table-header-select-popover v-if="enableSelection" ref="selectPopover"
<ms-table-header-select-popover v-if="enableSelection && showSelectAll" ref="selectPopover"
:page-size="pageSize > total ? total : pageSize"
:total="total"
@selectPageAll="isSelectDataAll(false)"
@ -139,6 +140,12 @@ export default {
default() {
return true;
}
}, //
showSelectAll: {
type: Boolean,
default() {
return true;
}
}
},
mounted() {
@ -221,12 +228,15 @@ export default {
this.$emit("handleRowClick");
},
handleRefresh() {
this.selectRows.clear();
this.clear();
this.$emit('refresh');
},
handlePageChange() {
this.$emit('pageChange');
},
clear() {
this.selectRows.clear();
this.selectDataCounts = 0;
checkTableRowIsSelect() {
checkTableRowIsSelect(this, this.condition, this.data, this.$refs.table, this.selectRows);
}

View File

@ -26,10 +26,10 @@
<el-form-item :label="'用例名称'" prop="caseName">
<el-input v-model="form.caseName" autocomplete="off"></el-input>
</el-form-item>
<test-case-r-ich-text-item :title="$t('test_track.case.prerequisite')" :data="form" prop="prerequisite"/>
<test-case-r-ich-text-item :title="$t('test_track.case.step_desc')" :data="form" prop="stepDescription"/>
<test-case-r-ich-text-item :title="$t('test_track.case.expected_results')" :data="form" prop="expectedResult"/>
<test-case-r-ich-text-item :title="$t('test_track.plan_view.actual_result')" :data="form" prop="actualResult"/>
<form-rich-text-item :title="$t('test_track.case.prerequisite')" :data="form" prop="prerequisite"/>
<form-rich-text-item :title="$t('test_track.case.step_desc')" :data="form" prop="stepDescription"/>
<form-rich-text-item :title="$t('test_track.case.expected_results')" :data="form" prop="expectedResult"/>
<form-rich-text-item :title="$t('test_track.plan_view.actual_result')" :data="form" prop="actualResult"/>
</template>
</field-template-edit>
@ -46,12 +46,12 @@ import {CASE_TYPE_OPTION} from "@/common/js/table-constants";
import CustomFieldFormList from "@/business/components/settings/workspace/template/CustomFieldFormList";
import CustomFieldRelateList from "@/business/components/settings/workspace/template/CustomFieldRelateList";
import FieldTemplateEdit from "@/business/components/settings/workspace/template/FieldTemplateEdit";
import TestCaseRIchTextItem from "@/business/components/track/case/components/FormRichTextItem";
import FormRichTextItem from "@/business/components/track/case/components/FormRichTextItem";
export default {
name: "TestCaseTemplateEdit",
components: {
TestCaseRIchTextItem,
FormRichTextItem,
FieldTemplateEdit,
CustomFieldRelateList,
CustomFieldFormList,

View File

@ -1,9 +1,7 @@
<template>
<el-form>
<el-form-item :disable="true" :label="title">
<test-case-rich-text :disabled="disabled" :content="data[prop]" @updateRichText="updateData"/>
</el-form-item>
</el-form>
<el-form-item :disable="true" :label="title" :prop="prop">
<test-case-rich-text :disabled="disabled" :content="data[prop]" @updateRichText="updateData"/>
</el-form-item>
</template>
<script>

View File

@ -63,9 +63,9 @@
<ms-form-divider :title="$t('步骤信息')"/>
<div class="step-info">
<test-case-r-ich-text-item :title="$t('test_track.case.prerequisite')" :data="form" prop="prerequisite"/>
<test-case-r-ich-text-item :title="$t('test_track.case.step_desc')" :data="form" prop="stepDesc"/>
<test-case-r-ich-text-item :title="$t('test_track.case.expected_results')" :data="form" prop="stepResult"/>
<form-rich-text-item :title="$t('test_track.case.prerequisite')" :data="form" prop="prerequisite"/>
<form-rich-text-item :title="$t('test_track.case.step_desc')" :data="form" prop="stepDesc"/>
<form-rich-text-item :title="$t('test_track.case.expected_results')" :data="form" prop="stepResult"/>
</div>
<ms-form-divider :title="$t('其他信息')"/>
@ -109,7 +109,6 @@
import {TokenKey, WORKSPACE_ID} from '@/common/js/constants';
import MsDialogFooter from '../../../common/components/MsDialogFooter'
import {getCurrentUser, getNodePath, handleCtrlSEvent, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {Message} from "element-ui";
import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment";
import CaseComment from "@/business/components/track/case/components/CaseComment";
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
@ -130,16 +129,16 @@ import {
parseCustomField
} from "@/common/js/custom_field";
import {SYSTEM_FIELD_NAME_MAP} from "@/common/js/table-constants";
import TestCaseRIchTextItem from "@/business/components/track/case/components/FormRichTextItem";
import MsFormDivider from "@/business/components/common/components/MsFormDivider";
import TestCaseEditOtherInfo from "@/business/components/track/case/components/TestCaseEditOtherInfo";
import FormRichTextItem from "@/business/components/track/case/components/FormRichTextItem";
export default {
name: "TestCaseEdit",
components: {
FormRichTextItem,
TestCaseEditOtherInfo,
MsFormDivider,
TestCaseRIchTextItem,
CustomFiledComponent,
MsTableButton,
MsSelectTree,
@ -594,7 +593,7 @@ export default {
if (this.$refs.otherInfo && this.$refs.otherInfo.fileList) {
if (param.isCopy) {
// copyID
param.fileIds = this.$refs.fileList.map(f => f.id);
param.fileIds = this.$refs.otherInfo.fileList.map(f => f.id);
}
param.updatedFileList = this.$refs.otherInfo.fileList;
} else {

View File

@ -44,7 +44,11 @@
</el-col>
</el-tab-pane>
<el-tab-pane v-if="!isTestPlan" label="关联缺陷" name="bug">关联缺陷</el-tab-pane>
<el-tab-pane label="关联缺陷" name="bug">
<test-case-issue-relate
:case-id="caseId" ref="issue"/>
</el-tab-pane>
<el-tab-pane :label="$t('test_track.case.attachment')" name="attachment">
<el-row>
<el-col :span="20" :offset="1">
@ -81,10 +85,11 @@ import TestCaseRichText from "@/business/components/track/case/components/MsRich
import MsRichText from "@/business/components/track/case/components/MsRichText";
import {TEST} from "@/business/components/api/definition/model/JsonData";
import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment";
import TestCaseIssueRelate from "@/business/components/track/case/components/TestCaseIssueRelate";
export default {
name: "TestCaseEditOtherInfo",
components: {TestCaseAttachment, MsRichText, TestCaseRichText},
components: {TestCaseIssueRelate, TestCaseAttachment, MsRichText, TestCaseRichText},
props: ['form', 'labelWidth', 'caseId', 'readOnly', 'projectId', 'isTestPlan'],
data() {
return {
@ -109,7 +114,7 @@ export default {
} else if (this.tabActiveName === 'demand') {
this.getDemandOptions();
} else if (this.tabActiveName === 'bug') {
//remark..
this.$refs.issue.getIssues();
} else if (this.tabActiveName === 'attachment') {
this.getFileMetaData();
}

View File

@ -0,0 +1,122 @@
<template>
<div>
<el-button class="add-btn" type="primary" size="mini" @click="appIssue">添加缺陷</el-button>
<el-tooltip class="item" effect="dark"
:content="$t('test_track.issue.platform_tip')"
placement="right">
<i class="el-icon-info"/>
</el-tooltip>
<ms-table
v-loading="result.loading"
:show-select-all="false"
:data="issues"
:enable-selection="false"
@refresh="getIssues">
<ms-table-column
:label="$t('test_track.issue.id')"
prop="id">
</ms-table-column>
<ms-table-column
:label="$t('test_track.issue.title')"
prop="title">
</ms-table-column>
<ms-table-column
:label="$t('test_track.issue.status')"
prop="status">
<template v-slot="scope">
<span>{{ issueStatusMap[scope.row.status] }}</span>
</template>
</ms-table-column>
<ms-table-column
:label="$t('test_track.issue.platform')"
prop="platform">
</ms-table-column>
<issue-description-table-item/>
<el-table-column :label="$t('test_track.issue.operate')">
<template v-slot:default="scope">
<el-tooltip :content="$t('test_track.issue.close')"
placement="top" :enterable="false">
<el-button type="danger" icon="el-icon-circle-close" size="mini"
circle v-if="scope.row.platform === 'Local'"
@click="closeIssue(scope.row)"
/>
</el-tooltip>
<el-tooltip :content="$t('test_track.issue.delete')"
placement="top" :enterable="false">
<el-button type="danger" icon="el-icon-delete" size="mini"
circle v-if="scope.row.platform === 'Local'"
@click="deleteIssue(scope.row)"
/>
</el-tooltip>
</template>
</el-table-column>
</ms-table>
<test-plan-issue-edit :case-id="caseId" @refresh="getIssues" ref="issueEdit"/>
</div>
</template>
<script>
import TestPlanIssueEdit from "@/business/components/track/case/components/TestPlanIssueEdit";
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/Ms-table-column";
import IssueDescriptionTableItem from "@/business/components/track/issue/IssueDescriptionTableItem";
import {ISSUE_STATUS_MAP} from "@/common/js/table-constants";
export default {
name: "TestCaseIssueRelate",
components: {IssueDescriptionTableItem, MsTableColumn, MsTable, TestPlanIssueEdit},
data() {
return {
issues: [],
result: {},
}
},
props: ['caseId'],
computed: {
issueStatusMap() {
return ISSUE_STATUS_MAP;
},
},
methods: {
getIssues() {
this.result = this.$get("/issues/get/" + this.caseId, (response) => {
this.issues = response.data;
});
},
appIssue() {
this.$refs.issueEdit.open();
},
closeIssue(row) {
if (row.status === 'closed') {
this.$success(this.$t('test_track.issue.close_success'));
} else {
this.result = this.$get("/issues/close/" + row.id, () => {
this.getIssues();
this.$success(this.$t('test_track.issue.close_success'));
});
}
},
deleteIssue(row) {
this.result = this.$post("/issues/delete", {id: row.id, caseId: this.caseId}, () => {
this.getIssues();
this.$success(this.$t('commons.delete_success'));
})
},
}
}
</script>
<style scoped>
.add-btn {
display: inline-block;
margin-right: 5px;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<ms-edit-dialog
width="60%"
:visible.sync="visible"
@confirm="confirm"
:title="'创建字段'"
append-to-body
ref="msEditDialog">
<template v-slot:default="scope">
<issue-edit-detail :case-id="caseId" :is-plan="true" @refresh="$emit('refresh')" @close="handleClose" ref="issueEditDetail"/>
</template>
</ms-edit-dialog>
</template>
<script>
import TemplateComponentEditHeader
from "@/business/components/track/plan/view/comonents/report/TemplateComponentEditHeader";
import IssueEditDetail from "@/business/components/track/issue/IssueEditDetail";
import MsEditDialog from "@/business/components/common/components/MsEditDialog";
export default {
name: "TestPlanIssueEdit",
components: {MsEditDialog, IssueEditDetail, TemplateComponentEditHeader},
data() {
return {
visible: false
}
},
computed: {
projectId() {
return this.$store.state.projectId;
}
},
props: ['caseId'],
methods: {
open(data) {
this.visible = true;
this.$nextTick(() => {
this.$refs.issueEditDetail.open(data);
})
},
handleClose() {
this.visible = false;
},
confirm() {
this.$refs.issueEditDetail.save();
}
}
};
</script>
<style scoped>
</style>

View File

@ -46,6 +46,10 @@
:title="$t('test_track.plan.create_plan')"/>
</el-submenu>
<el-menu-item :index="'/track/issue'">
{{ $t("缺陷管理") }}
</el-menu-item>
<el-menu-item :index="'/track/testPlan/reportList'">
{{ $t("commons.report") }}
</el-menu-item>

View File

@ -0,0 +1,37 @@
<template>
<ms-table-column
:label="$t('test_track.issue.description')"
prop="description">
<template v-slot:default="scope">
<el-popover
placement="right"
width="500"
trigger="hover"
popper-class="issues-popover"
>
<ckeditor :editor="editor" disabled :config="readConfig"
v-model="scope.row.description"/>
<el-button slot="reference" type="text">{{ $t('test_track.issue.preview') }}</el-button>
</el-popover>
</template>
</ms-table-column>
</template>
<script>
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import MsTableColumn from "@/business/components/common/components/table/Ms-table-column";
export default {
name: "IssueDescriptionTableItem",
components: {MsTableColumn},
data() {
return {
editor: ClassicEditor,
readConfig: {toolbar: []},
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,50 @@
<template>
<el-drawer
class="field-template-edit"
:before-close="handleClose"
:visible.sync="visible"
:with-header="false"
size="100%"
:modal-append-to-body="false"
ref="drawer"
>
<template v-slot:default="scope">
<template-component-edit-header :show-edit="false" :template="{}" prop="title" @cancel="handleClose" @save="save"/>
<issue-edit-detail @refresh="$emit('refresh')" @close="handleClose" ref="issueEditDetail"/>
</template>
</el-drawer>
</template>
<script>
import TemplateComponentEditHeader
from "@/business/components/track/plan/view/comonents/report/TemplateComponentEditHeader";
import IssueEditDetail from "@/business/components/track/issue/IssueEditDetail";
export default {
name: "IssueEdit",
components: {IssueEditDetail, TemplateComponentEditHeader},
data() {
return {
visible: false
}
},
methods: {
open(data) {
this.visible = true;
this.$nextTick(() => {
this.$refs.issueEditDetail.open(data);
})
},
handleClose() {
this.visible = false;
},
save() {
this.$refs.issueEditDetail.save();
}
}
};
</script>
<style scoped>
</style>

View File

@ -0,0 +1,271 @@
<template>
<el-main v-loading="result.loading" class="container">
<el-scrollbar>
<el-form :model="form" :rules="rules" label-position="right" label-width="140px" ref="form">
<el-form-item :label="'标题'" prop="title">
<el-input v-model="form.title" autocomplete="off"></el-input>
</el-form-item>
<el-row class="custom-field-row">
<el-col :span="8" v-if="hasTapdId">
<el-form-item :label="$t('test_track.issue.tapd_current_owner')" prop="tapdUsers">
<el-select v-model="form.tapdUsers" multiple filterable
:placeholder="$t('test_track.issue.please_choose_current_owner')">
<el-option v-for="(userInfo, index) in tapdUsers" :key="index" :label="userInfo.user"
:value="userInfo.user"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="hasZentaoId">
<el-form-item :label="$t('test_track.issue.zentao_bug_build')" prop="zentaoBuilds">
<el-select v-model="form.zentaoBuilds" multiple filterable
:placeholder="$t('test_track.issue.zentao_bug_build')">
<el-option v-for="(build, index) in Builds" :key="index" :label="build.name"
:value="build.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8" v-if="hasZentaoId">
<el-form-item :label="$t('test_track.issue.zentao_bug_assigned')" prop="zentaoAssigned">
<el-select v-model="form.zentaoAssigned" filterable
:placeholder="$t('test_track.issue.please_choose_current_owner')">
<el-option v-for="(userInfo, index) in zentaoUsers" :key="index" :label="userInfo.name"
:value="userInfo.user"/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 自定义字段 -->
<el-form v-if="isFormAlive" :model="customFieldForm" :rules="customFieldRules" ref="customFieldForm"
class="case-form">
<el-row class="custom-field-row">
<el-col :span="8" v-for="(item, index) in issueTemplate.customFields" :key="index">
<el-form-item :label="item.system ? $t(systemNameMap[item.name]) : item.name" :prop="item.name"
:label-width="formLabelWidth">
<custom-filed-component @reload="reloadForm" :data="item" :form="customFieldForm" prop="defaultValue"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<form-rich-text-item :title="$t('缺陷内容')" :data="form" prop="description"/>
<el-form-item v-if="!isPlan">
<test-case-issue-list :test-case-contain-ids="testCaseContainIds" :issues-id="form.id" ref="testCaseIssueList"/>
</el-form-item>
</el-form>
</el-scrollbar>
</el-main>
</template>
<script>
import TemplateComponentEditHeader
from "@/business/components/track/plan/view/comonents/report/TemplateComponentEditHeader";
import MsFormDivider from "@/business/components/common/components/MsFormDivider";
import CustomFieldFormList from "@/business/components/settings/workspace/template/CustomFieldFormList";
import CustomFieldRelateList from "@/business/components/settings/workspace/template/CustomFieldRelateList";
import FormRichTextItem from "@/business/components/track/case/components/FormRichTextItem";
import {SYSTEM_FIELD_NAME_MAP} from "@/common/js/table-constants";
import {buildCustomFields, getTemplate, parseCustomField} from "@/common/js/custom_field";
import CustomFiledComponent from "@/business/components/settings/workspace/template/CustomFiledComponent";
import TestCaseIssueList from "@/business/components/track/issue/TestCaseIssueList";
import IssueEditDetail from "@/business/components/track/issue/IssueEditDetail";
export default {
name: "IssueEditDetail",
components: {
IssueEditDetail,
TestCaseIssueList,
CustomFiledComponent,
FormRichTextItem,
CustomFieldRelateList,
CustomFieldFormList,
MsFormDivider,
TemplateComponentEditHeader
},
data() {
return {
result: {},
relateFields: [],
isFormAlive: true,
formLabelWidth: "120px",
issueTemplate: {},
customFieldForm: {},
customFieldRules: {},
rules: {
title: [
{required: true, message: this.$t('请填写标题'), trigger: 'blur'},
{max: 64, message: this.$t('test_track.length_less_than') + '64', trigger: 'blur'}
],
description: [
{required: true, message: this.$t('请填写内容'), trigger: 'blur'},
]
},
testCaseContainIds: new Set(),
url: '',
form:{
title: '',
description: ''
},
tapdUsers: [],
zentaoUsers: [],
Builds: [],
hasTapdId: false,
hasZentaoId: false
};
},
props: {
isPlan: {
type: Boolean,
default() {
return false;
}
},
caseId: String
},
computed: {
isSystem() {
return this.form.system;
},
systemNameMap() {
return SYSTEM_FIELD_NAME_MAP;
},
projectId() {
return this.$store.state.projectId
}
},
methods: {
open(data) {
let initAddFuc = this.initEdit;
getTemplate('field/template/issue/get/relate/', this)
.then((template) => {
this.issueTemplate = template;
initAddFuc(data);
});
},
getThirdPartyInfo() {
let url = '/project/get/' + this.projectId;
if (this.isPlan) {
url = '/test/case/project/' + this.caseId;
}
this.$get(url, res => {
let project = res.data;
if (project.tapdId) {
this.hasTapdId = true;
this.result = this.$get("/issues/tapd/user/" + this.caseId, (response) => {
this.tapdUsers = response.data;
});
}
if (project.zentaoId) {
this.hasZentaoId = true;
this.result = this.$get("/issues/zentao/builds/" + this.caseId,response => {
this.Builds = response.data;
});
this.result = this.$get("/issues/zentao/user/" + this.caseId, response => {
this.zentaoUsers = response.data;
});
}
})
},
initEdit(data) {
this.testCaseContainIds = new Set();
if (data) {
Object.assign(this.form, data);
if (!(data.options instanceof Array)) {
this.form.options = data.options ? JSON.parse(data.options) : [];
}
if (data.id) {
this.url = 'issues/update';
} else {
//copy
this.url = 'issues/add';
}
} else {
this.form = {
title: '',
description: ''
}
this.url = 'issues/add';
}
parseCustomField(this.form, this.issueTemplate, this.customFieldForm, this.customFieldRules, null);
this.$nextTick(() => {
if (this.$refs.testCaseIssueList) {
this.$refs.testCaseIssueList.initTableData();
}
});
},
reloadForm() {
this.isFormAlive = false;
this.$nextTick(() => (this.isFormAlive = true));
},
save() {
let isValidate = true;
this.$refs['form'].validate((valid) => {
if (!valid) {
isValidate = false;
return false;
}
});
this.$refs['customFieldForm'].validate((valid) => {
if (!valid) {
isValidate = false;
return false;
}
});
if (isValidate) {
this._save();
}
},
buildPram() {
let param = {};
Object.assign(param, this.form);
param.projectId = this.projectId;
buildCustomFields(this.form, param, this.issueTemplate);
if (this.isPlan) {
param.testCaseIds = [this.caseId];
} else {
param.testCaseIds = Array.from(this.testCaseContainIds);
}
return param;
},
_save() {
let param = this.buildPram();
this.parseOldFields(param);
this.result = this.$post(this.url, param, () => {
this.$emit('close');
this.$success(this.$t('commons.save_success'));
this.$emit('refresh');
});
},
parseOldFields(param) {
let customFieldsStr = param.customFields;
if (customFieldsStr) {
let customFields = JSON.parse(customFieldsStr);
if (customFields['i43sf4_issueStatus']) {
param.status = JSON.parse(customFields['i43sf4_issueStatus']);
}
}
},
}
};
</script>
<style scoped>
.container {
height: calc(100vh - 62px);
}
.filed-list {
margin-top: 30px;
}
.custom-field-row {
padding-left: 18px;
}
</style>

View File

@ -0,0 +1,161 @@
<template>
<el-main>
<el-card>
<template v-slot:header>
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="getIssues" @create="handleCreate"
:create-tip="'创建缺陷'" :title="'缺陷列表'"/>
</template>
<ms-table
v-loading="result.loading"
:data="tableData"
:condition="condition"
:total="total"
:page-size.sync="pageSize"
:operators="operators"
:show-select-all="false"
@handlePageChange="getIssues"
@refresh="getIssues">
<ms-table-column
:label="$t('test_track.issue.id')"
prop="id">
</ms-table-column>
<ms-table-column
:label="$t('test_track.issue.title')"
prop="title">
</ms-table-column>
<ms-table-column
:label="$t('test_track.issue.status')"
prop="status">
<template v-slot="scope">
<span>{{ issueStatusMap[scope.row.status] }}</span>
</template>
</ms-table-column>
<ms-table-column
:label="$t('test_track.issue.platform')"
prop="platform">
</ms-table-column>
<issue-description-table-item/>
</ms-table>
<ms-table-pagination :change="getIssues" :current-page.sync="currentPage" :page-size.sync="pageSize" :total="total"/>
<issue-edit @refresh="getIssues" ref="issueEdit"/>
</el-card>
</el-main>
</template>
<script>
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/Ms-table-column";
import MsTableOperators from "@/business/components/common/components/MsTableOperators";
import MsTableButton from "@/business/components/common/components/MsTableButton";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import {
CUSTOM_FIELD_SCENE_OPTION,
CUSTOM_FIELD_TYPE_OPTION,
FIELD_TYPE_MAP, ISSUE_STATUS_MAP,
SYSTEM_FIELD_NAME_MAP
} from "@/common/js/table-constants";
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import IssueDescriptionTableItem from "@/business/components/track/issue/IssueDescriptionTableItem";
import IssueEdit from "@/business/components/track/issue/IssueEdit";
export default {
name: "CustomFieldList",
components: {
IssueEdit,
IssueDescriptionTableItem,
MsTableHeader,
MsTablePagination, MsTableButton, MsTableOperators, MsTableColumn, MsTable},
data() {
return {
tableData: [],
condition: {},
total: 0,
pageSize: 10,
currentPage: 1,
result: {},
operators: [
{
tip: this.$t('commons.edit'), icon: "el-icon-edit",
exec: this.handleEdit
}, {
tip: this.$t('commons.copy'), icon: "el-icon-copy-document", type: "success",
exec: this.handleCopy,
isDisable: this.systemDisable
}, {
tip: this.$t('commons.delete'), icon: "el-icon-delete", type: "danger",
exec: this.handleDelete,
isDisable: this.systemDisable
}
],
};
},
activated() {
this.getIssues();
},
computed: {
fieldFilters() {
return CUSTOM_FIELD_TYPE_OPTION;
},
sceneFilters() {
return CUSTOM_FIELD_SCENE_OPTION;
},
fieldTypeMap() {
return FIELD_TYPE_MAP;
},
issueStatusMap() {
return ISSUE_STATUS_MAP;
},
systemNameMap() {
return SYSTEM_FIELD_NAME_MAP;
},
projectId() {
return this.$store.state.projectId;
}
},
methods: {
getIssues() {
this.condition.projectId = this.projectId;
this.result = this.$post('issues/list/' + this.currentPage + '/' + this.pageSize,
this.condition, (response) => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
});
},
handleEdit(data) {
this.$refs.issueEdit.open(data);
},
handleCreate() {
this.$refs.issueEdit.open();
},
handleCopy(data) {
let copyData = {};
Object.assign(copyData, data);
copyData.id = null;
copyData.name = data.name + '_copy';
this.$refs.issueEdit.open(copyData);
},
handleDelete(data) {
this.result = this.$get('custom/field/delete/' + data.id, () => {
this.$success(this.$t('commons.delete_success'));
this.getIssues();
});
},
}
};
</script>
<style scoped>
</style>

View File

@ -0,0 +1,122 @@
<template>
<div>
<el-button type="primary" @click="relateTestCase">关联用例</el-button>
<ms-table
v-loading="result.loading"
:enable-selection="false"
:operators="operators"
:data="tableData"
@refresh="initTableData"
ref="table">
<ms-table-column
:label="$t('commons.id')"
prop="num">
</ms-table-column>
<ms-table-column
:label="$t('commons.name')"
prop="name">
</ms-table-column>
<ms-table-column
:label="$t('test_track.case.priority')"
prop="name">
<template v-slot:default="scope">
<priority-table-item :value="scope.row.priority" ref="priority"/>
</template>
</ms-table-column>
<ms-table-column
:label="$t('test_track.case.type')"
prop="type">
<template v-slot:default="scope">
<type-table-item :value="scope.row.type"/>
</template>
</ms-table-column>
<ms-table-column
:label="$t('test_track.case.module')"
prop="nodePath">
</ms-table-column>
<ms-table-column
:label="$t('test_track.plan.plan_project')"
prop="projectName">
</ms-table-column>
</ms-table>
<test-case-relate-list
@refresh="initTableData"
@save="handleRelate"
:test-case-contain-ids="testCaseContainIds"
ref="testCaseRelevance"/>
</div>
</template>
<script>
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/Ms-table-column";
import PriorityTableItem from "@/business/components/track/common/tableItems/planview/PriorityTableItem";
import TypeTableItem from "@/business/components/track/common/tableItems/planview/TypeTableItem";
import TestCaseRelateList from "@/business/components/track/issue/TestCaseRelateList";
export default {
name: "TestCaseIssueList",
components: {TestCaseRelateList, TypeTableItem, PriorityTableItem, MsTableColumn, MsTable},
data() {
return {
result: {},
tableData: [],
operators: [
{
tip: this.$t('commons.delete'), icon: "el-icon-delete", type: "danger",
exec: this.handleDelete
}
],
};
},
props: {
issuesId: String,
testCaseContainIds: Set,
},
methods: {
handleDelete(item, index) {
this.testCaseContainIds.delete(item.id);
this.tableData.splice(index, 1);
},
initTableData() {
this.tableData = [];
let condition = {
issuesId: this.issuesId
};
if (this.issuesId) {
this.result = this.$post('test/case/issues/list', condition, response => {
this.tableData = response.data;
this.tableData.forEach(item => {
this.testCaseContainIds.add(item.id);
});
});
}
},
relateTestCase() {
this.$refs.testCaseRelevance.open();
},
handleRelate(selectRows) {
let selectData = Array.from(selectRows);
selectData.forEach(item => {
if (item.id) {
this.testCaseContainIds.add(item.id);
}
});
this.tableData.push(...selectData);
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,178 @@
<template>
<test-case-relevance-base
@setProject="setProject"
@save="save"
ref="baseRelevance">
<template v-slot:aside>
<ms-node-tree class="node-tree"
v-loading="result.loading"
@nodeSelectEvent="nodeChange"
:tree-nodes="treeNodes"
ref="nodeTree"/>
</template>
<el-card>
<ms-table-header :condition="condition" @search="initTableData" title="" :show-create="false"/>
<ms-table
v-loading="result.loading"
:data="tableData"
:condition="condition"
:total="total"
:page-size.sync="pageSize"
:show-select-all="false"
@handlePageChange="initTableData"
@refresh="initTableData"
ref="table">
<ms-table-column
:label="$t('commons.id')"
prop="num">
</ms-table-column>
<ms-table-column
:label="$t('commons.name')"
prop="name">
</ms-table-column>
<ms-table-column
:label="$t('test_track.case.priority')"
prop="name">
<template v-slot:default="scope">
<priority-table-item :value="scope.row.priority" ref="priority"/>
</template>
</ms-table-column>
<ms-table-column
:label="$t('test_track.case.type')"
prop="type">
<template v-slot:default="scope">
<type-table-item :value="scope.row.type"/>
</template>
</ms-table-column>
<ms-table-column
:label="$t('test_track.case.module')"
prop="nodePath">
</ms-table-column>
<ms-table-column
:label="$t('test_track.plan.plan_project')"
prop="projectName">
</ms-table-column>
</ms-table>
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize" :total="total"/>
</el-card>
</test-case-relevance-base>
</template>
<script>
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/Ms-table-column";
import {CUSTOM_FIELD_LIST} from "@/common/js/default-table-header";
import MsTableButton from "@/business/components/common/components/MsTableButton";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import MsTableHeader from "@/business/components/common/components/MsTableHeader";
import MsEditDialog from "@/business/components/common/components/MsEditDialog";
import TestCaseRelevanceBase from "@/business/components/track/plan/view/comonents/base/TestCaseRelevanceBase";
import MsNodeTree from "@/business/components/track/common/NodeTree";
import PriorityTableItem from "@/business/components/track/common/tableItems/planview/PriorityTableItem";
import TypeTableItem from "@/business/components/track/common/tableItems/planview/TypeTableItem";
export default {
name: "TestCaseRelateList",
components: {
TypeTableItem,
PriorityTableItem,
MsNodeTree,
TestCaseRelevanceBase,
MsEditDialog,
MsTableHeader,
MsTablePagination, MsTableButton, MsTableColumn, MsTable},
data() {
return {
tableData: [],
condition: {},
visible: false,
total: 0,
pageSize: 10,
currentPage: 1,
projectId: '',
result: {},
treeNodes: [],
projects: [],
selectNodeIds: [],
};
},
props: [
'testCaseContainIds'
],
watch: {
selectNodeIds() {
this.initTableData();
},
projectId() {
this.getProjectNode();
this.initTableData();
},
},
computed: {
fields() {
return CUSTOM_FIELD_LIST;
},
},
methods: {
initTableData() {
this.condition.projectId = this.projectId;
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
this.condition.nodeIds = this.selectNodeIds;
} else {
this.condition.nodeIds = [];
}
if (this.projectId) {
this.condition.projectId = this.projectId;
this.condition.testCaseContainIds = Array.from(this.testCaseContainIds)
this.result = this.$post('/test/case/relate/issue/' + +this.currentPage + '/' + this.pageSize, this.condition, response => {
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
});
}
},
nodeChange(node, nodeIds, nodeNames) {
this.selectNodeIds = nodeIds;
this.selectNodeNames = nodeNames;
},
getProjectNode(projectId) {
if (projectId) {
this.projectId = projectId;
}
this.$refs.nodeTree.result = this.$post("/case/node/list/project",
{projectId: this.projectId}, response => {
this.treeNodes = response.data;
});
this.selectNodeIds = [];
},
open() {
this.$refs.baseRelevance.open();
this.initTableData();
},
save() {
this.$emit('save', this.$refs.table.selectRows);
this.$refs.table.clear();
this.$refs.baseRelevance.close();
},
setProject(projectId) {
this.projectId = projectId;
}
}
};
</script>
<style scoped>
</style>

View File

@ -42,169 +42,61 @@
</el-header>
<div class="case_container">
<el-row>
<el-col :span="4" :offset="1">
<span class="cast_label">{{ $t('test_track.case.priority') }}</span>
<span class="cast_item">{{ testCase.priority }}</span>
</el-col>
<el-col :span="5">
<span class="cast_label">{{ $t('test_track.case.module') }}</span>
<span class="cast_item">{{ testCase.nodePath }}</span>
</el-col>
<el-col :span="10">
<test-plan-test-case-status-button class="status-button"
@statusChange="statusChange"
:is-read-only="isReadOnly"
:status="testCase.status"/>
</el-col>
</el-row>
<el-row>
<el-col :span="4" :offset="1">
<span class="cast_label">{{ $t('test_track.plan.plan_project') }}</span>
<span class="cast_item">{{ testCase.projectName }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="4" :offset="1" v-if="testCase.testId == 'other'">
<span class="cast_label">{{ $t('test_track.case.test_name') }}</span>
<span class="cast_item">{{ testCase.otherTestName }}</span>
</el-col>
</el-row>
<el-form ref="customFieldForm"
class="case-form">
<el-form>
<el-row>
<el-col :span="7" v-for="(item, index) in testCaseTemplate.customFields" :key="index">
<el-form-item :label="item.system ? $t(systemNameMap[item.name]) : item.name" :prop="item.name"
label-width="120px">
<custom-filed-component :disabled="true" :data="item" :form="{}" prop="defaultValue"/>
</el-form-item>
<el-col :span="4" :offset="1">
<span class="cast_label">{{ $t('test_track.case.priority') }}</span>
<span class="cast_item">{{ testCase.priority }}</span>
</el-col>
<el-col :span="5">
<span class="cast_label">{{ $t('test_track.case.module') }}</span>
<span class="cast_item">{{ testCase.nodePath }}</span>
</el-col>
<el-col :span="10">
<test-plan-test-case-status-button class="status-button"
@statusChange="statusChange"
:is-read-only="isReadOnly"
:status="testCase.status"/>
</el-col>
</el-row>
</el-form>
<el-row>
<el-col :span="22" :offset="1">
<div class="step-info">
<form-rich-text-item :disabled="true" :title="$t('test_track.case.prerequisite')" :data="testCase" prop="prerequisite"/>
<form-rich-text-item :disabled="true" :title="$t('test_track.case.step_desc')" :data="testCase" prop="stepDesc"/>
<form-rich-text-item :disabled="true" :title="$t('test_track.case.expected_results')" :data="testCase" prop="stepResult"/>
<form-rich-text-item :title="$t('test_track.plan_view.actual_result')" :data="testCase" prop="actualResult"/>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span="4" :offset="1">
<span class="cast_label">{{ $t('test_track.plan.plan_project') }}</span>
<span class="cast_item">{{ testCase.projectName }}</span>
</el-col>
</el-row>
<el-row>
<el-col :span="5" :offset="1">
<el-switch
:disabled="isReadOnly"
v-model="issuesSwitch"
@change="issuesChange"
:active-text="$t('test_track.plan_view.submit_issues')">
</el-switch>
<el-tooltip class="item" effect="dark"
:content="$t('test_track.issue.platform_tip')"
placement="right">
<i class="el-icon-info"/>
</el-tooltip>
</el-col>
</el-row>
<el-row>
<el-col :span="4" :offset="1" v-if="testCase.testId == 'other'">
<span class="cast_label">{{ $t('test_track.case.test_name') }}</span>
<span class="cast_item">{{ testCase.otherTestName }}</span>
</el-col>
</el-row>
<el-row v-if="issuesSwitch">
<el-col :span="20" :offset="1" class="issues-edit">
<el-input
type="text"
:placeholder="$t('test_track.issue.input_title')"
v-model="testCase.issues.title"
maxlength="60"
show-word-limit
/>
<ckeditor :editor="editor" :disabled="isReadOnly" :config="editorConfig"
v-model="testCase.issues.content"/>
<el-row v-if="hasTapdId">
{{ $t('test_track.issue.tapd_current_owner') }}
<el-select v-model="testCase.tapdUsers"
multiple
filterable
style="width: 20%"
:placeholder="$t('test_track.issue.please_choose_current_owner')"
collapse-tags size="small">
<el-option v-for="(userInfo, index) in users" :key="index" :label="userInfo.user"
:value="userInfo.user"/>
</el-select>
<el-form ref="customFieldForm"
class="case-form">
<el-row>
<el-col :span="7" v-for="(item, index) in testCaseTemplate.customFields" :key="index">
<el-form-item :label="item.system ? $t(systemNameMap[item.name]) : item.name" :prop="item.name"
label-width="120px">
<custom-filed-component :disabled="true" :data="item" :form="{}" prop="defaultValue"/>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="hasZentaoId">
{{ $t('test_track.issue.zentao_bug_build') }}
<el-select v-model="testCase.zentaoBuilds"
multiple
filterable
style="width: 20%"
:placeholder="$t('test_track.issue.zentao_bug_build')"
collapse-tags size="small">
<el-option v-for="(build, index) in Builds" :key="index" :label="build.name"
:value="build.id"/>
</el-select>
{{ $t('test_track.issue.zentao_bug_assigned') }}
<el-select v-model="testCase.zentaoAssigned"
filterable
style="width: 20%"
:placeholder="$t('test_track.issue.please_choose_current_owner')"
collapse-tags size="small">
<el-option v-for="(userInfo, index) in zentaoUsers" :key="index" :label="userInfo.name"
:value="userInfo.user"/>
</el-select>
</el-row>
<el-button type="primary" size="small" @click="saveIssues">{{ $t('commons.save') }}</el-button>
<el-button size="small" @click="issuesSwitch=false">{{ $t('commons.cancel') }}</el-button>
</el-col>
</el-row>
</el-form>
<el-row>
<el-col :span="20" :offset="1" class="issues-edit">
<el-table border class="adjust-table" :data="issues" style="width: 100%">
<el-table-column prop="id" :label="$t('test_track.issue.id')" show-overflow-tooltip/>
<el-table-column prop="title" :label="$t('test_track.issue.title')" show-overflow-tooltip/>
<el-table-column prop="description" :label="$t('test_track.issue.description')">
<template v-slot:default="scope">
<el-popover
placement="right"
width="500"
trigger="hover"
popper-class="issues-popover"
>
<ckeditor :editor="editor" disabled :config="readConfig"
v-model="scope.row.description"/>
<el-button slot="reference" type="text">{{ $t('test_track.issue.preview') }}</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('test_track.issue.status')"/>
<el-table-column prop="platform" :label="$t('test_track.issue.platform')"/>
<el-table-column :label="$t('test_track.issue.operate')">
<template v-slot:default="scope">
<el-tooltip :content="$t('test_track.issue.close')"
placement="top" :enterable="false">
<el-button type="danger" icon="el-icon-circle-close" size="mini"
circle v-if="scope.row.platform === 'Local'"
@click="closeIssue(scope.row)"
/>
</el-tooltip>
<el-tooltip :content="$t('test_track.issue.delete')"
placement="top" :enterable="false">
<el-button type="danger" icon="el-icon-delete" size="mini"
circle v-if="scope.row.platform === 'Local'"
@click="deleteIssue(scope.row)"
/>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-row>
<el-col :span="22" :offset="1">
<div class="step-info">
<form-rich-text-item :disabled="true" :title="$t('test_track.case.prerequisite')" :data="testCase" prop="prerequisite"/>
<form-rich-text-item :disabled="true" :title="$t('test_track.case.step_desc')" :data="testCase" prop="stepDesc"/>
<form-rich-text-item :disabled="true" :title="$t('test_track.case.expected_results')" :data="testCase" prop="stepResult"/>
<form-rich-text-item :title="$t('test_track.plan_view.actual_result')" :data="testCase" prop="actualResult"/>
</div>
</el-col>
</el-row>
<el-form>
<test-case-edit-other-info @openTest="openTest" :read-only="true" :is-test-plan="true" :project-id="projectId" :form="testCase" :case-id="testCase.caseId" ref="otherInfo"/>
</el-form>
</div>
@ -255,10 +147,12 @@ import MsFormDivider from "@/business/components/common/components/MsFormDivider
import TestCaseEditOtherInfo from "@/business/components/track/case/components/TestCaseEditOtherInfo";
import CustomFiledComponent from "@/business/components/settings/workspace/template/CustomFiledComponent";
import {SYSTEM_FIELD_NAME_MAP} from "@/common/js/table-constants";
import IssueDescriptionTableItem from "@/business/components/track/issue/IssueDescriptionTableItem";
export default {
name: "FunctionalTestCaseEdit",
components: {
IssueDescriptionTableItem,
CustomFiledComponent,
TestCaseEditOtherInfo,
MsFormDivider,
@ -279,9 +173,7 @@ export default {
showDialog: false,
testCase: {},
index: 0,
issuesSwitch: false,
testCases: [],
issues: [],
editor: ClassicEditor,
editorConfig: {
toolbar: ['heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'insertTable', '|', 'undo', 'redo'],
@ -443,13 +335,11 @@ export default {
// ,使
this.testCase.actualResult = this.testCaseTemplate.actualResult;
}
this.getIssues(item.caseId);
this.getComments(item);
});
},
openTestCaseEdit(testCase) {
this.showDialog = true;
this.issuesSwitch = false;
this.activeTab = 'detail';
this.hasTapdId = false;
this.hasZentaoId = false;
@ -509,101 +399,12 @@ export default {
}
}
},
issuesChange() {
if (this.issuesSwitch) {
let desc = this.addPLabel('[' + this.$t('test_track.plan_view.operate_step') + ']');
let result = this.addPLabel('[' + this.$t('test_track.case.expected_results') + ']');
let actualResult = this.addPLabel('[' + this.$t('test_track.plan_view.actual_result') + ']');
this.testCase.steps.forEach(step => {
let stepPrefix = this.$t('test_track.plan_view.step') + step.num + ':';
desc += this.addPLabel(stepPrefix + (step.desc === undefined ? '' : step.desc));
result += this.addPLabel(stepPrefix + (step.result === undefined ? '' : step.result));
actualResult += this.addPLabel(stepPrefix + (step.actualResult === undefined ? '' : step.actualResult));
});
this.testCase.issues.content = desc + this.addPLabel('') + result + this.addPLabel('') + actualResult + this.addPLabel('');
this.$get("/test/case/project/" + this.testCase.caseId, res => {
const project = res.data;
if (project.tapdId) {
this.hasTapdId = true;
this.result = this.$get("/issues/tapd/user/" + this.testCase.caseId).then(response => {
this.users = response.data.data;
}).catch(() => {
console.log("get tapd user error.");
})
}
if (project.zentaoId) {
this.hasZentaoId = true;
this.result = this.$get("/issues/zentao/builds/" + this.testCase.caseId).then(response => {
this.Builds = response.data.data;
}).catch(() => {
console.log("get zentao builds error.");
})
this.result = this.$get("/issues/zentao/user/" + this.testCase.caseId).then(response => {
this.zentaoUsers = response.data.data;
}).catch(() => {
console.log("get zentao user error.");
})
}
})
}
},
addPLabel(str) {
return "<p>" + str + "</p>";
},
setPlanStatus(planId) {
this.$post('/test/plan/edit/status/' + planId);
},
saveIssues() {
if (!this.testCase.issues.title || !this.testCase.issues.content) {
this.$warning(this.$t('test_track.issue.title_description_required'));
return;
}
let param = {};
param.title = this.testCase.issues.title;
param.content = this.testCase.issues.content;
param.testCaseId = this.testCase.caseId;
param.tapdUsers = this.testCase.tapdUsers;
param.zentaoBuilds = this.testCase.zentaoBuilds;
param.zentaoUser = this.testCase.zentaoAssigned;
this.result = this.$post("/issues/add", param, () => {
this.$success(this.$t('commons.save_success'));
this.getIssues(param.testCaseId);
});
this.issuesSwitch = false;
this.testCase.issues.title = "";
this.testCase.issues.content = "";
this.testCase.tapdUsers = [];
this.testCase.zentaoBuilds = [];
this.testCase.zentaoAssigned = "";
},
getIssues(caseId) {
this.result = this.$get("/issues/get/" + caseId).then(response => {
this.issues = response.data.data;
}).catch(() => {
console.log("get issues error")
})
},
closeIssue(row) {
if (row.status === 'closed') {
this.$success(this.$t('test_track.issue.close_success'));
} else {
this.result = this.$get("/issues/close/" + row.id, () => {
this.getIssues(this.testCase.caseId);
this.$success(this.$t('test_track.issue.close_success'));
});
}
},
deleteIssue(row) {
let caseId = this.testCase.caseId;
this.result = this.$post("/issues/delete", {id: row.id, caseId: caseId}, () => {
this.getIssues(caseId);
this.$success(this.$t('commons.delete_success'));
})
},
}
}
}
</script>

View File

@ -2,10 +2,10 @@
<el-row type="flex" class="head-bar">
<el-col :span="12">
<div class="name-edit">
<el-input :placeholder="$t('test_track.plan_view.input_template_name')" v-model="template.name" @change="change"/>
<span v-if="template.name" class="title">{{template.name}}</span>
<span class="name-tip" v-if="!template.name">{{$t('test_track.plan_view.input_template_name')}}</span>
<div v-if="showEdit" class="name-edit">
<el-input :placeholder="$t('test_track.plan_view.input_template_name')" v-model="template[prop]"/>
<span v-if="template[prop]" class="title">{{template[prop]}}</span>
<span class="name-tip" v-else>{{$t('test_track.plan_view.input_template_name')}}</span>
</div>
</el-col>
<el-col :span="12" class="head-right">
@ -32,6 +32,18 @@
return {}
}
},
prop: {
type: String,
default() {
return 'name';
}
},
showEdit: {
type: Boolean,
default() {
return true;
}
},
},
methods: {
handleCancel() {
@ -39,9 +51,6 @@
},
handleSave() {
this.$emit('save');
},
change() {
this.$emit('update:template', this.templateName);
}
}
}

View File

@ -7,6 +7,7 @@ const TestCaseReview = () => import('@/business/components/track/review/TestCase
const TestCaseReviewView = () => import('@/business/components/track/review/view/TestCaseReviewView')
const TestPlanView = () => import('@/business/components/track/plan/view/TestPlanView')
const reportListView = () => import('@/business/components/track/report/TestPlanReport')
const issueList = () => import('@/business/components/track/issue/IssueList.vue')
// const reportListView = () => import('@/business/components/track/plan/TestPlan')
export default {
@ -42,6 +43,11 @@ export default {
name: 'testPlanReportList',
component: reportListView,
},
{
path: 'issue',
name: 'issueManagement',
component: issueList,
},
{
path: "plan/:type",
name: "testPlan",

View File

@ -58,6 +58,7 @@ export function parseCustomField(data, template, customFieldForm, rules, oldFiel
});
}
// 将template的属性值设置给customFields
export function buildCustomFields(data, param, template) {
if (template.customFields) {
let customFields = data.customFields;

View File

@ -58,3 +58,10 @@ export const SYSTEM_FIELD_NAME_MAP = {
i43sf4_issueStatus: 'custom_field.issue_status',
i43sf4_issueSeverity: 'custom_field.issue_severity',
}
export const ISSUE_STATUS_MAP = {
'new': '新建',
'closed': '已关闭',
'resolved': '已解决'
}

View File

@ -45,12 +45,14 @@ export function setUnSelectIds(tableData, condition, selectRows) {
let thisUnSelectIds = allIDs.filter(function (val) {
return ids.indexOf(val) === -1;
});
let needPushIds = thisUnSelectIds.filter(function (val) {
return condition.unSelectIds.indexOf(val) === -1;
});
needPushIds.forEach(id => {
condition.unSelectIds.push(id);
});
if (condition.unSelectIds) {
let needPushIds = thisUnSelectIds.filter(function (val) {
return condition.unSelectIds.indexOf(val) === -1;
});
needPushIds.forEach(id => {
condition.unSelectIds.push(id);
});
}
}
export function getSelectDataCounts(condition, total, selectRows) {