This commit is contained in:
chenjianxing 2020-11-16 22:06:57 +08:00
commit 62f32e5d38
23 changed files with 502 additions and 105 deletions

View File

@ -13,20 +13,19 @@
MeterSphere 是一站式的开源企业级持续测试平台涵盖测试跟踪、接口测试、性能测试、团队协作等功能兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。
- 测试跟踪: 远超 TestLink 的使用体验;
- 接口测试: 类似 Postman 的体验;
- 性能测试: 兼容 JMeter支持 Kubernetes 和云环境,轻松支持高并发、分布式的性能测试;
- 团队协作: 两级租户体系,天然支持团队协作。
- 测试跟踪: 远超 TestLink 的使用体验;
- 接口测试: 类似 Postman 的体验;
- 性能测试: 兼容 JMeter支持 Kubernetes 和云环境,轻松支持高并发、分布式的性能测试;
- 团队协作: 两级租户体系,天然支持团队协作。
![产品定位](https://metersphere.oss-cn-hangzhou.aliyuncs.com/img/ct-devops.png)
> 如需进一步了解 MeterSphere 开源项目,推荐阅读 [MeterSphere 的初心和使命](https://mp.weixin.qq.com/s/DpCt3BNgBTlV3sJ5qtPmZw)
## 在线体验
- 环境地址https://demo.metersphere.com/
- 用户名demo
- 密码P@ssw0rd123..
- 环境地址https://demo.metersphere.com/
- 用户名demo
- 密码P@ssw0rd123..
| :warning: 注意 |
|:---------------------------|
@ -38,8 +37,8 @@ MeterSphere 是一站式的开源企业级持续测试平台,涵盖测试跟
仅需两步快速安装 MeterSphere
1. 准备一台不小于 8 G内存的 64位 Linux 主机;
2. 以 root 用户执行如下命令一键安装 MeterSphere。
1. 准备一台不小于 8 G内存的 64位 Linux 主机;
2. 以 root 用户执行如下命令一键安装 MeterSphere。
```sh
curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/quick_start.sh | sh
@ -47,8 +46,8 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
文档和演示视频:
- [完整文档](https://metersphere.io/docs/)
- [演示视频](http://video.fit2cloud.com/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91202006%20MeterSphere%20v1.0%20%E5%8A%9F%E8%83%BD%E6%BC%94%E7%A4%BA.mp4)
- [完整文档](https://metersphere.io/docs/)
- [演示视频](http://video.fit2cloud.com/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91202006%20MeterSphere%20v1.0%20%E5%8A%9F%E8%83%BD%E6%BC%94%E7%A4%BA.mp4)
## MeterSphere 企业版
[申请企业版使用](https://jinshuju.net/f/CzzAOe)
@ -63,7 +62,7 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
MeterSphere 版本号命名规则为v大版本.功能版本.Bug修复版本。比如
```
```text
v1.0.1 是 v1.0.0 之后的Bug修复版本
v1.1.0 是 v1.0.0 之后的功能版本。
```
@ -71,10 +70,10 @@ v1.1.0 是 v1.0.0 之后的功能版本。
## 技术优势
- 全生命周期: 能够覆盖从测试计划到测试执行、测试报告分析的不同阶段;
- 自动化 & 扩展性: 支持接口和性能的自动化测试,可以充分利用云弹性实现超大规模的性能测试;
- 持续测试: 能够与持续集成工具无缝集成,支撑企业实现测试左移;
- 团队协作: 支持不同规模的测试团队,小到几个人的测试团队、大到数百人的测试中心。
- 全生命周期: 能够覆盖从测试计划到测试执行、测试报告分析的不同阶段;
- 自动化 & 扩展性: 支持接口和性能的自动化测试,可以充分利用云弹性实现超大规模的性能测试;
- 持续测试: 能够与持续集成工具无缝集成,支撑企业实现测试左移;
- 团队协作: 支持不同规模的测试团队,小到几个人的测试团队、大到数百人的测试中心。
## 功能列表
@ -276,11 +275,11 @@ v1.1.0 是 v1.0.0 之后的功能版本。
## 技术栈
- 后端: [Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_introduction.htm)
- 前端: [Vue.js](https://vuejs.org/)
- 中间件: [MySQL](https://www.mysql.com/), [Kafka](https://kafka.apache.org/)
- 基础设施: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/)
- 测试引擎: [JMeter](https://jmeter.apache.org/)
- 后端: [Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_introduction.htm)
- 前端: [Vue.js](https://vuejs.org/)
- 中间件: [MySQL](https://www.mysql.com/), [Kafka](https://kafka.apache.org/)
- 基础设施: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/)
- 测试引擎: [JMeter](https://jmeter.apache.org/)
## 致谢

View File

@ -1,61 +1,61 @@
## v1.1 (已发布)
## v1.1 (已发布)
- [x] 浏览器插件支持编辑录制后的内容
- [x] 插件录制的脚本支持用作接口测试
- [x] 动态展示性能测试报告
- [x] 优化性能测试稳定性及资源使用效率
- [x] 提供 Jenkins 插件
- [x] 提供 Swagger API 文档
- [x] 增加针对项目的环境配置、全局变量管理功能
- [x] 支持 JMX/Swagger 等方式导入接口测试
- [x] 支持 LDAP 登录
- [ ] 推出在线体验环境
- [x] 浏览器插件支持编辑录制后的内容
- [x] 插件录制的脚本支持用作接口测试
- [x] 动态展示性能测试报告
- [x] 优化性能测试稳定性及资源使用效率
- [x] 提供 Jenkins 插件
- [x] 提供 Swagger API 文档
- [x] 增加针对项目的环境配置、全局变量管理功能
- [x] 支持 JMX/Swagger 等方式导入接口测试
- [x] 支持 LDAP 登录
- [ ] 推出在线体验环境
## v1.2 (已发布)
- [x] 接口测试支持前后置脚本
- [x] 接口测试支持常用函数
- [x] 接口测试批量执行
- [x] 接口测试单接口调试功能
- [ ] 测试用例增加用例评审功能
- [x] 测试用例与缺陷管理工具的集成
- [x] 测试用例增加批量操作类型
- [ ] 增加消息通知
- [ ] 测试报告导出
- [ ] 优化性能测试压力配置模式
- [x] 接口测试支持前后置脚本
- [x] 接口测试支持常用函数
- [x] 接口测试批量执行
- [x] 接口测试单接口调试功能
- [ ] 测试用例增加用例评审功能
- [x] 测试用例与缺陷管理工具的集成
- [x] 测试用例增加批量操作类型
- [ ] 增加消息通知
- [ ] 测试报告导出
- [ ] 优化性能测试压力配置模式
## v1.3 (已发布)
- [x] 测试跟踪:用例评审机制
- [x] 测试跟踪:测试计划关联用例支持跨项目
- [ ] 测试跟踪:测试用例支持贴图
- [x] 测试跟踪:支持思维导图格式导入用例
- [x] 接口测试:增加逻辑控制环节
- [x] 接口测试:支持场景拼接
- [x] 接口测试:支持 SQL 类型的请求
- [x] 接口测试:支持自定义 Hosts
- [x] 接口测试:参数增加启用禁用
- [ ] 性能测试:增加压测模式
- [x] 其他:消息通知
- [x] 其他:报告导出
- [x] 测试跟踪:用例评审机制
- [x] 测试跟踪:测试计划关联用例支持跨项目
- [ ] 测试跟踪:测试用例支持贴图
- [x] 测试跟踪:支持思维导图格式导入用例
- [x] 接口测试:增加逻辑控制环节
- [x] 接口测试:支持场景拼接
- [x] 接口测试:支持 SQL 类型的请求
- [x] 接口测试:支持自定义 Hosts
- [x] 接口测试:参数增加启用禁用
- [ ] 性能测试:增加压测模式
- [x] 其他:消息通知
- [x] 其他:报告导出
## v1.4 (已发布)
- [x] 测试跟踪模块编辑测试用例支持上传附件
- [x] 支持上传并引用自定义Jar包
- [x] 接口测试支持TCP协议请求
- [x] 全新的消息通知设置,支持企业微信、钉钉机器人通知
- [x] 测试跟踪模块编辑测试用例支持上传附件
- [x] 支持上传并引用自定义Jar包
- [x] 接口测试支持TCP协议请求
- [x] 全新的消息通知设置,支持企业微信、钉钉机器人通知
## v1.5 (开发中)
- [ ] 性能测试:优化并发数、持续时间等压力配置方式
- [ ] 性能测试:支持使用了额外插件的 JMX 文件
- [ ] 性能测试:自动修改 csv 等数据文件引用路径
- [ ] 性能测试:优化性能测试报告展示
- [ ] 测试跟踪:支持对接禅道同步缺陷
- [ ] 其他Jenkins 插件支持 pipeline 方式调用
- [ ] 性能测试:优化并发数、持续时间等压力配置方式
- [ ] 性能测试:支持使用了额外插件的 JMX 文件
- [ ] 性能测试:自动修改 csv 等数据文件引用路径
- [ ] 性能测试:优化性能测试报告展示
- [ ] 测试跟踪:支持对接禅道同步缺陷
- [ ] 其他Jenkins 插件支持 pipeline 方式调用
## 规划中
- [ ] 接口测试支持添加 WebSocket 协议请求
- [ ] 接口管理功能
- [ ] 集成云平台动态管理测试资源池
- [ ] 支持 K8s 集群作为测试资源池
- [ ] 移动端测试支持
- [ ] UI 功能测试支持
- [ ] 接口测试支持添加 WebSocket 协议请求
- [ ] 接口管理功能
- [ ] 集成云平台动态管理测试资源池
- [ ] 支持 K8s 集群作为测试资源池
- [ ] 移动端测试支持
- [ ] UI 功能测试支持

View File

@ -21,5 +21,7 @@ public class Project implements Serializable {
private String jiraKey;
private String zentaoId;
private static final long serialVersionUID = 1L;
}

View File

@ -643,6 +643,76 @@ public class ProjectExample {
addCriterion("jira_key not between", value1, value2, "jiraKey");
return (Criteria) this;
}
public Criteria andZentaoIdIsNull() {
addCriterion("zentao_id is null");
return (Criteria) this;
}
public Criteria andZentaoIdIsNotNull() {
addCriterion("zentao_id is not null");
return (Criteria) this;
}
public Criteria andZentaoIdEqualTo(String value) {
addCriterion("zentao_id =", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotEqualTo(String value) {
addCriterion("zentao_id <>", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdGreaterThan(String value) {
addCriterion("zentao_id >", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdGreaterThanOrEqualTo(String value) {
addCriterion("zentao_id >=", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdLessThan(String value) {
addCriterion("zentao_id <", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdLessThanOrEqualTo(String value) {
addCriterion("zentao_id <=", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdLike(String value) {
addCriterion("zentao_id like", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotLike(String value) {
addCriterion("zentao_id not like", value, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdIn(List<String> values) {
addCriterion("zentao_id in", values, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotIn(List<String> values) {
addCriterion("zentao_id not in", values, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdBetween(String value1, String value2) {
addCriterion("zentao_id between", value1, value2, "zentaoId");
return (Criteria) this;
}
public Criteria andZentaoIdNotBetween(String value1, String value2) {
addCriterion("zentao_id not between", value1, value2, "zentaoId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -10,6 +10,7 @@
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="tapd_id" jdbcType="VARCHAR" property="tapdId" />
<result column="jira_key" jdbcType="VARCHAR" property="jiraKey" />
<result column="zentao_id" jdbcType="VARCHAR" property="zentaoId" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -70,7 +71,8 @@
</where>
</sql>
<sql id="Base_Column_List">
id, workspace_id, `name`, description, create_time, update_time, tapd_id, jira_key
id, workspace_id, `name`, description, create_time, update_time, tapd_id, jira_key,
zentao_id
</sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultMap="BaseResultMap">
select
@ -105,10 +107,12 @@
<insert id="insert" parameterType="io.metersphere.base.domain.Project">
insert into project (id, workspace_id, `name`,
description, create_time, update_time,
tapd_id, jira_key)
tapd_id, jira_key, zentao_id
)
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR})
#{tapdId,jdbcType=VARCHAR}, #{jiraKey,jdbcType=VARCHAR}, #{zentaoId,jdbcType=VARCHAR}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.Project">
insert into project
@ -137,6 +141,9 @@
<if test="jiraKey != null">
jira_key,
</if>
<if test="zentaoId != null">
zentao_id,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -163,6 +170,9 @@
<if test="jiraKey != null">
#{jiraKey,jdbcType=VARCHAR},
</if>
<if test="zentaoId != null">
#{zentaoId,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.ProjectExample" resultType="java.lang.Long">
@ -198,6 +208,9 @@
<if test="record.jiraKey != null">
jira_key = #{record.jiraKey,jdbcType=VARCHAR},
</if>
<if test="record.zentaoId != null">
zentao_id = #{record.zentaoId,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -212,7 +225,8 @@
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
tapd_id = #{record.tapdId,jdbcType=VARCHAR},
jira_key = #{record.jiraKey,jdbcType=VARCHAR}
jira_key = #{record.jiraKey,jdbcType=VARCHAR},
zentao_id = #{record.zentaoId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -241,6 +255,9 @@
<if test="jiraKey != null">
jira_key = #{jiraKey,jdbcType=VARCHAR},
</if>
<if test="zentaoId != null">
zentao_id = #{zentaoId,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -252,7 +269,8 @@
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
tapd_id = #{tapdId,jdbcType=VARCHAR},
jira_key = #{jiraKey,jdbcType=VARCHAR}
jira_key = #{jiraKey,jdbcType=VARCHAR},
zentao_id = #{zentaoId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -4,7 +4,7 @@
<select id="getProjectWithWorkspace" resultType="io.metersphere.dto.ProjectDTO">
select p.id, p.workspace_id, p.name, p.description, p.update_time,
p.create_time, w.id as workspaceId, w.name as workspaceName, p.tapd_id, p.jira_key
p.create_time, w.id as workspaceId, w.name as workspaceName, p.tapd_id, p.jira_key, p.zentao_id
from project p
join workspace w on p.workspace_id = w.id
<where>

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum IssuesManagePlatform {
Tapd, Jira, Local
Tapd, Jira, Local, Zentao
}

View File

@ -16,5 +16,5 @@ public class ProjectDTO {
private Long updateTime;
private String tapdId;
private String jiraKey;
private String zentaoId;
}

View File

@ -10,11 +10,13 @@ import java.util.List;
public class IssueFactory {
public static AbstractIssuePlatform createPlatform(String platform, IssuesRequest addIssueRequest) {
if (StringUtils.equals(IssuesManagePlatform.Tapd.toString(), platform)) {
return new TapdIssue(addIssueRequest);
return new TapdPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.Jira.toString(), platform)) {
return new JiraIssue(addIssueRequest);
} else if (StringUtils.equals("LOCAL", platform)) {
return new LocalIssue(addIssueRequest);
return new JiraPlatform(addIssueRequest);
} else if (StringUtils.equals(IssuesManagePlatform.Zentao.toString(), platform)) {
return new ZentaoPlatform(addIssueRequest);
} else if (StringUtils.equals("LOCAL", platform)) {
return new LocalPlatform(addIssueRequest);
}
return null;
}

View File

@ -28,10 +28,10 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class JiraIssue extends AbstractIssuePlatform {
public class JiraPlatform extends AbstractIssuePlatform {
public JiraIssue(IssuesRequest issuesRequest) {
public JiraPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
}

View File

@ -10,9 +10,9 @@ import io.metersphere.track.request.testcase.IssuesRequest;
import java.util.List;
import java.util.UUID;
public class LocalIssue extends AbstractIssuePlatform {
public class LocalPlatform extends AbstractIssuePlatform {
public LocalIssue(IssuesRequest issuesRequest) {
public LocalPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
}

View File

@ -24,10 +24,10 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class TapdIssue extends AbstractIssuePlatform {
public class TapdPlatform extends AbstractIssuePlatform {
public TapdIssue(IssuesRequest issueRequest) {
public TapdPlatform(IssuesRequest issueRequest) {
super(issueRequest);
}

View File

@ -0,0 +1,221 @@
package io.metersphere.track.issue;
import com.alibaba.fastjson.JSON;
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.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.request.testcase.IssuesRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.*;
import java.util.stream.Collectors;
public class ZentaoPlatform extends AbstractIssuePlatform {
/**
* zentao account
*/
private final String account;
/**
* zentao password
*/
private final String password;
/**
* zentao url eg:http://x.x.x.x/zentao
*/
private final String url;
public ZentaoPlatform(IssuesRequest issuesRequest) {
super(issuesRequest);
String config = getPlatformConfig(IssuesManagePlatform.Zentao.toString());
JSONObject object = JSON.parseObject(config);
this.account = object.getString("account");
this.password = object.getString("password");
this.url = object.getString("url");
}
@Override
String getProjectId() {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project.getZentaoId();
}
@Override
public List<Issues> getIssue() {
List<Issues> list = new ArrayList<>();
TestCaseIssuesExample example = new TestCaseIssuesExample();
example.createCriteria().andTestCaseIdEqualTo(testCaseId);
List<Issues> issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Zentao.toString());
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
issuesIds.forEach(issuesId -> {
Issues dto = getZentaoIssues(issuesId);
if (StringUtils.isBlank(dto.getId())) {
// 缺陷不存在解除用例和缺陷的关联
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
issuesExample.createCriteria()
.andTestCaseIdEqualTo(testCaseId)
.andIssuesIdEqualTo(issuesId);
testCaseIssuesMapper.deleteByExample(issuesExample);
issuesMapper.deleteByPrimaryKey(issuesId);
} else {
dto.setPlatform(IssuesManagePlatform.Zentao.toString());
// 缺陷状态为 关闭则不显示
if (!StringUtils.equals("closed", dto.getStatus())) {
list.add(dto);
}
}
});
return list;
}
private Issues getZentaoIssues(String bugId) {
String session = login();
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getModel-bug-getById-bugID={bugId}?zentaosid=" + session,
HttpMethod.POST, requestEntity, String.class, bugId);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
if (obj != null) {
JSONObject bug = obj.getJSONObject("data");
String id = bug.getString("id");
String title = bug.getString("title");
String description = bug.getString("steps");
Long createTime = bug.getLong("openedDate");
String status = bug.getString("status");
String reporter = bug.getString("openedBy");
int deleted = bug.getInteger("deleted");
if (deleted == 1) {
return new Issues();
}
Issues issues = new Issues();
issues.setId(id);
issues.setTitle(title);
issues.setDescription(description);
issues.setCreateTime(createTime);
issues.setStatus(status);
issues.setReporter(reporter);
return issues;
}
return new Issues();
}
@Override
public void addIssue(IssuesRequest issuesRequest) {
String session = login();
String projectId = getProjectId();
if (StringUtils.isBlank(projectId)) {
MSException.throwException("add zentao bug fail, project zentao id is null");
}
if (StringUtils.isBlank(session)) {
MSException.throwException("session is null");
}
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("product", projectId);
paramMap.add("title", issuesRequest.getTitle());
paramMap.add("openedBuild", "123");
paramMap.add("steps", issuesRequest.getContent());
paramMap.add("assignedTo", "admin");
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(paramMap, new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getModel-bug-create.json?zentaosid=" + session, HttpMethod.POST, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
if (obj != null) {
JSONObject data = obj.getJSONObject("data");
String id = data.getString("id");
if (StringUtils.isNotBlank(id)) {
// 用例与第三方缺陷平台中的缺陷关联
TestCaseIssues testCaseIssues = new TestCaseIssues();
testCaseIssues.setId(UUID.randomUUID().toString());
testCaseIssues.setIssuesId(id);
testCaseIssues.setTestCaseId(testCaseId);
testCaseIssuesMapper.insert(testCaseIssues);
// 插入缺陷表
Issues issues = new Issues();
issues.setId(id);
issues.setPlatform(IssuesManagePlatform.Zentao.toString());
issuesMapper.insert(issues);
}
}
}
@Override
public void deleteIssue(String id) {
}
@Override
public void testAuth() {
try {
login();
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException("验证失败!");
}
}
private String login() {
String session = getSession();
String loginUrl = url + "user-login.json?zentaosid=" + session;
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("account", account);
paramMap.add("password", password);
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(paramMap, new HttpHeaders());
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(loginUrl, HttpMethod.POST, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
JSONObject user = obj.getJSONObject("user");
if (user == null) {
LogUtil.error("login fail");
LogUtil.error(obj);
// 登录失败获取的session无效置空session
MSException.throwException("zentao login fail");
}
String username = user.getString("account");
if (!StringUtils.equals(username, account)) {
LogUtil.error("login failinconsistent users");
MSException.throwException("zentao login fail");
}
return session;
}
private String getSession() {
RestTemplate restTemplate = new RestTemplate();
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(new HttpHeaders());
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "api-getsessionid.json", HttpMethod.GET, requestEntity, String.class);
String body = responseEntity.getBody();
JSONObject obj = JSONObject.parseObject(body);
JSONObject data = obj.getJSONObject("data");
String session = data.getString("sessionID");
return session;
}
@Override
public List<PlatformUser> getPlatformUser() {
return null;
}
}

View File

@ -0,0 +1,26 @@
package io.metersphere.track.issue;
import io.metersphere.commons.utils.EncryptUtils;
public class ZentaoUtils {
/**
* @param code Zentao 应用代号
* @param key Zentao 密钥
* @return token
*/
public static String getToken(String code, String key, String time) {
return (String) EncryptUtils.md5Encrypt(code + key + time);
}
/**
* @param url Zentao url
* @param code Zentao 应用代号
* @param key Zentao 密钥
* @return url
*/
public static String getUrl(String url, String code, String key) {
String time = String.valueOf(System.currentTimeMillis());;
return url + "api.php?" + "code=" + code + "&time=" + time + "&token=" + getToken(code, key, time);
}
}

View File

@ -63,9 +63,11 @@ public class IssuesService {
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<>();
@ -82,7 +84,13 @@ public class IssuesService {
}
}
if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey)) {
if (zentao) {
if (StringUtils.isNotBlank(zentaoId)) {
platforms.add(IssuesManagePlatform.Zentao.name());
}
}
if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey) && StringUtils.isBlank(zentaoId)) {
platforms.add("LOCAL");
}
@ -122,6 +130,7 @@ public class IssuesService {
boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString());
boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString());
boolean zentao = isIntegratedPlatform(orgId, IssuesManagePlatform.Zentao.toString());
List<String> platforms = new ArrayList<>();
if (tapd) {
@ -140,6 +149,13 @@ public class IssuesService {
}
}
if (zentao) {
String zentaoId = getZentaoProjectId(caseId);
if (StringUtils.isNotBlank(zentaoId)) {
platforms.add(IssuesManagePlatform.Zentao.name());
}
}
platforms.add("LOCAL");
IssuesRequest issueRequest = new IssuesRequest();
issueRequest.setTestCaseId(caseId);
@ -152,18 +168,24 @@ public class IssuesService {
return list;
}
public String getTapdProjectId(String testCaseId) {
private String getTapdProjectId(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project.getTapdId();
}
public String getJiraProjectKey(String testCaseId) {
private String getJiraProjectKey(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project.getJiraKey();
}
private String getZentaoProjectId(String testCaseId) {
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
Project project = projectService.getProjectById(testCase.getProjectId());
return project.getZentaoId();
}
/**
* 是否关联平台
*/

View File

@ -0,0 +1 @@
alter table project add zentao_id varchar(50) null;

View File

@ -57,9 +57,9 @@ export default {
if (header.default !== undefined) {
this.licenseHeader = "LicenseMessage";
}
//
if (display.default !== undefined) {
display.default.valid(this);
display.default.showHome(this);
}
} else {
window.location.href = "/login"

View File

@ -63,6 +63,9 @@
<el-form-item :label="$t('project.jira_key')" v-if="jira">
<el-input v-model="form.jiraKey" autocomplete="off"></el-input>
</el-form-item>
<el-form-item :label="$t('project.zentao_id')" v-if="zentao">
<el-input v-model="form.zentaoId" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<template v-slot:footer>
<div class="dialog-footer">
@ -116,6 +119,7 @@ export default {
items: [],
tapd: false,
jira: false,
zentao: false,
form: {},
currentPage: 1,
pageSize: 5,
@ -194,6 +198,9 @@ export default {
if (platforms.indexOf("Jira") !== -1) {
this.jira = true;
}
if (platforms.indexOf("Zentao") !== -1) {
this.zentao = true;
}
});
}
},

View File

@ -10,6 +10,9 @@
<el-input v-model="form.password" auto-complete="new-password"
:placeholder="$t('organization.integration.input_app_key')" show-password/>
</el-form-item>
<el-form-item :label="$t('organization.integration.zentao_url')" prop="url">
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_zentao_url')"/>
</el-form-item>
</el-form>
</div>
@ -63,7 +66,12 @@ export default {
required: true,
message: this.$t('organization.integration.input_app_key'),
trigger: ['change', 'blur']
}
},
url: {
required: true,
message: this.$t('organization.integration.input_zentao_url'),
trigger: ['change', 'blur']
},
},
}
},
@ -71,12 +79,16 @@ export default {
save() {
this.$refs['form'].validate(valid => {
if (valid) {
let formatUrl = this.form.url.trim();
if (!formatUrl.endsWith('/')) {
formatUrl = formatUrl + '/';
}
const {lastOrganizationId} = getCurrentUser();
let param = {};
let auth = {
account: this.form.account,
password: this.form.password,
url: formatUrl,
};
param.organizationId = lastOrganizationId;
param.platform = ZEN_TAO;
@ -106,6 +118,7 @@ export default {
let config = JSON.parse(data.configuration);
this.$set(this.form, 'account', config.account);
this.$set(this.form, 'password', config.password);
this.$set(this.form, 'url', config.url);
} else {
this.clear();
}
@ -114,19 +127,26 @@ export default {
clear() {
this.$set(this.form, 'account', '');
this.$set(this.form, 'password', '');
this.$set(this.form, 'url', '');
this.$nextTick(() => {
this.$refs.form.clearValidate();
});
},
testConnection() {
if (this.form.account && this.form.password) {
this.$parent.result = this.$get("issues/auth/" + ZEN_TAO, () => {
this.$success(this.$t('organization.integration.verified'));
});
} else {
this.$warning(this.$t('organization.integration.not_integrated'));
return false;
}
this.$refs['form'].validate(valid => {
if (valid) {
if (this.form.account && this.form.password) {
this.$parent.result = this.$get("issues/auth/" + ZEN_TAO, () => {
this.$success(this.$t('organization.integration.verified'));
});
} else {
this.$warning(this.$t('organization.integration.not_integrated'));
return false;
}
} else {
return false;
}
})
},
cancelIntegration() {
if (this.form.account && this.form.password) {

View File

@ -262,6 +262,8 @@ export default {
input_app_key: 'Please enter the key',
input_jira_url: 'Please enter Jira address, for example: https://metersphere.atlassian.net/',
input_jira_issuetype: 'Please enter the question type',
zentao_url: 'Zentao url',
input_zentao_url: 'Please enter Zentao address, for example: http://xx.xx.xx.xx/zentao/',
use_tip: 'Usage guidelines:',
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
@ -292,6 +294,7 @@ export default {
special_characters_are_not_supported: 'Incorrect format (special characters are not supported and cannot end with \'-\')',
tapd_id: 'TAPD Project ID',
jira_key: 'JIRA Project key',
zentao_id: 'Zentao Project ID',
},
member: {
create: 'Create',

View File

@ -262,6 +262,8 @@ export default {
input_app_key: '请输入密钥',
input_jira_url: '请输入Jira地址https://metersphere.atlassian.net/',
input_jira_issuetype: '请输入问题类型',
zentao_url: 'Zentao 地址',
input_zentao_url: '请输入Zentao地址http://xx.xx.xx.xx/zentao/',
use_tip: '使用指引:',
use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
use_tip_jira: 'Jira software server 认证信息为 账号密码Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)',
@ -291,6 +293,7 @@ export default {
special_characters_are_not_supported: '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)',
tapd_id: 'TAPD项目ID',
jira_key: 'JIRA项目key',
zentao_id: 'Zentao项目ID',
},
member: {
create: '添加成员',

View File

@ -264,6 +264,8 @@ export default {
input_app_key: '請輸入密鑰',
input_jira_url: '請輸入Jira地址https://metersphere.atlassian.net/',
input_jira_issuetype: '請輸入問題類型',
zentao_url: 'Zentao 地址',
input_zentao_url: '請輸入Zentao地址http://xx.xx.xx.xx/zentao/',
use_tip: '使用指引:',
use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢',
use_tip_jira: 'Jira software server 認證信息為 賬號密碼Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
@ -293,6 +295,7 @@ export default {
special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)',
tapd_id: 'TAPD項目ID',
jira_key: 'JIRA項目key',
zentao_id: 'Zentao項目ID',
},
member: {
create: '添加成員',

View File

@ -95,7 +95,7 @@ export default {
this.result = this.$get("/isLogin").then(response => {
if (display.default !== undefined) {
display.default.valid(this);
display.default.showLogin(this);
}
if (!response.data.success) {