Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
ce222275fb
103
README.md
103
README.md
|
@ -76,7 +76,7 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan="10">测试跟踪</td>
|
||||
<td rowspan="17">测试跟踪</td>
|
||||
<td>项目管理</td>
|
||||
<td>多项目支持,测试用例、测试计划与项目关联</td>
|
||||
</tr>
|
||||
|
@ -92,9 +92,22 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
</tr>
|
||||
<tr>
|
||||
<td>快速导入用例到系统</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">测试用例评审</td>
|
||||
<td>基于已有用例发起评审</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="5">测试计划跟踪</td>
|
||||
<td>在线更新评审结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持多人在线添加评审评论</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>灵活的评审人分配形式</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="8">测试计划跟踪</td>
|
||||
<td>基于已有用例发起测试计划</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -110,8 +123,17 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
<td>与平台中的接口测试、性能测试功能结合,自动更新关联用例的结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="7">接口测试</td>
|
||||
<td rowspan="5">测试脚本</td>
|
||||
<td>记录测试用例关联的缺陷</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>缺陷记录支持关联到 Jira/TAPD 平台</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试报告支持分享、导出</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="19">接口测试</td>
|
||||
<td rowspan="12">测试脚本</td>
|
||||
<td>在线编辑接口测试内容</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -123,19 +145,56 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
<tr>
|
||||
<td>支持多接口的场景化测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试场景复用</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试场景支持引用已有环境信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试环境信息管理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过浏览器插件快速录制测试脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">测试报告</td>
|
||||
<td>测试执行后自动生成测试报告</td>
|
||||
<td>支持前后置 BeanShell/Python 脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>上传并引用自定义 Jar 包</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多协议支持,支持 HTTP、Dubbo、SQL、TCP 类型请求</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持等待时间、条件判断等逻辑控制功能</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">测试执行</td>
|
||||
<td>内置定时任务支持</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过 Jenkins 插件触发测试执行</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多个接口测试一键合并执行</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>一键创建性能测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="3">测试报告</td>
|
||||
<td>测试执行后自动生成动态实时测试报告</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试报告导出</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="9">性能测试</td>
|
||||
<td rowspan="5">测试脚本</td>
|
||||
<td>通过邮件、IM 工具等通知执行结果</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="12">性能测试</td>
|
||||
<td rowspan="6">测试脚本</td>
|
||||
<td>完全兼容 JMeter 脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -150,6 +209,16 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
<tr>
|
||||
<td>通过浏览器插件快速录制测试脚本</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多协议支持</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">测试执行</td>
|
||||
<td>内置定时任务支持</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>通过 Jenkins 插件触发测试执行</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">测试报告</td>
|
||||
<td>测试执行后自动生成测试报告</td>
|
||||
|
@ -164,27 +233,37 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
<td>查看测试日志详情</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="6">系统管理</td>
|
||||
<td rowspan="2">租户管理</td>
|
||||
<td rowspan="9">系统管理</td>
|
||||
<td rowspan="3">用户租户管理</td>
|
||||
<td>支持多级租户体系</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持多种租户角色</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">测试资源管理</td>
|
||||
<td>LDAP 认证对接</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试资源管理</td>
|
||||
<td>性能测试资源池管理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">消息通知配置</td>
|
||||
<td>IM 工具通知(如企业微信、钉钉)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>邮件通知配置</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">集成与扩展</td>
|
||||
<td rowspan="3">集成与扩展</td>
|
||||
<td>完善的 API 列表</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持对接 Jenkins 等持续集成工具</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持对接 Jira/TAPD 等缺陷管理工具</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -507,6 +507,15 @@
|
|||
<outputDirectory>src/main/resources/jmeter/lib/ext</outputDirectory>
|
||||
<destFileName>ApacheJMeter_functions.jar</destFileName>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>org.python</groupId>
|
||||
<artifactId>jython-standalone</artifactId>
|
||||
<version>2.7.0</version>
|
||||
<type>jar</type>
|
||||
<overWrite>true</overWrite>
|
||||
<outputDirectory>src/main/resources/jmeter/lib/ext</outputDirectory>
|
||||
<destFileName>jython-standalone.jar</destFileName>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<outputDirectory>${project.build.directory}/wars</outputDirectory>
|
||||
<overWriteReleases>false</overWriteReleases>
|
||||
|
|
|
@ -75,6 +75,11 @@ public class APITestController {
|
|||
apiTestService.create(request, file, bodyFiles);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/create/merge", consumes = {"multipart/form-data"})
|
||||
public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List<String> selectIds) {
|
||||
apiTestService.mergeCreate(request, file, selectIds);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
|
||||
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
checkownerService.checkApiTestOwner(request.getId());
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package io.metersphere.api.controller;
|
||||
|
||||
import io.metersphere.api.dto.scenario.DatabaseConfig;
|
||||
import io.metersphere.api.service.APIDatabaseService;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/database")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER}, logical = Logical.OR)
|
||||
public class ApiDatabaseController {
|
||||
|
||||
@Resource
|
||||
APIDatabaseService apiDatabaseService;
|
||||
|
||||
@PostMapping("/validate")
|
||||
public void validate(@RequestBody DatabaseConfig databaseConfig) {
|
||||
apiDatabaseService.validate(databaseConfig);
|
||||
}
|
||||
|
||||
}
|
|
@ -35,4 +35,6 @@ public class HttpRequest extends Request {
|
|||
private Long responseTimeout;
|
||||
@JSONField(ordinal = 16)
|
||||
private Boolean followRedirects;
|
||||
@JSONField(ordinal = 17)
|
||||
private Boolean doMultipartPost;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ package io.metersphere.api.dto.scenario.request;
|
|||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@JSONType(typeName = RequestType.SQL)
|
||||
|
@ -25,4 +28,6 @@ public class SqlRequest extends Request {
|
|||
private String resultVariable;
|
||||
@JSONField(ordinal = 14)
|
||||
private String variableNames;
|
||||
@JSONField(ordinal = 15)
|
||||
private List<KeyValue> variables;
|
||||
}
|
||||
|
|
|
@ -221,7 +221,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult();
|
||||
responseAssertionResult.setMessage(assertionResult.getFailureMessage());
|
||||
responseAssertionResult.setName(assertionResult.getName());
|
||||
responseAssertionResult.setPass(!assertionResult.isFailure());
|
||||
responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError());
|
||||
return responseAssertionResult;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import org.apache.jmeter.save.SaveService;
|
|||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jmeter.visualizers.backend.BackendListener;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.python.core.Options;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -34,10 +34,6 @@ public class JMeterService {
|
|||
JMeterUtils.setJMeterHome(JMETER_HOME);
|
||||
JMeterUtils.setLocale(LocaleContextHolder.getLocale());
|
||||
|
||||
|
||||
//解决无法加载 PyScriptEngineFactory
|
||||
Options.importSite = false;
|
||||
|
||||
try {
|
||||
Object scriptWrapper = SaveService.loadElement(is);
|
||||
HashTree testPlan = getHashTree(scriptWrapper);
|
||||
|
@ -51,7 +47,7 @@ public class JMeterService {
|
|||
}
|
||||
}
|
||||
|
||||
private String getJmeterHome() {
|
||||
public String getJmeterHome() {
|
||||
String home = getClass().getResource("/").getPath() + "jmeter";
|
||||
try {
|
||||
File file = new File(home);
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import io.metersphere.api.dto.scenario.DatabaseConfig;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.sql.DriverManager;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class APIDatabaseService {
|
||||
|
||||
public void validate(DatabaseConfig databaseConfig) {
|
||||
try {
|
||||
DriverManager.getConnection(databaseConfig.getDbUrl(), databaseConfig.getUsername(), databaseConfig.getPassword());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -83,15 +83,19 @@ public class APITestService {
|
|||
}
|
||||
|
||||
public void create(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
|
||||
ApiTest test = createTest(request, file);
|
||||
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||
}
|
||||
private ApiTest createTest(SaveAPITestRequest request, MultipartFile file) {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
||||
}
|
||||
checkQuota();
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
|
||||
request.setBodyUploadIds(null);
|
||||
ApiTest test = createTest(request);
|
||||
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||
saveFile(test.getId(), file);
|
||||
return test;
|
||||
}
|
||||
|
||||
public void update(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||
|
@ -108,6 +112,9 @@ public class APITestService {
|
|||
}
|
||||
|
||||
private void createBodyFiles(ApiTest test, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
|
||||
if (bodyUploadIds.size() <= 0) {
|
||||
return;
|
||||
}
|
||||
String dir = BODY_FILE_DIR + "/" + test.getId();
|
||||
File testDir = new File(dir);
|
||||
if (!testDir.exists()) {
|
||||
|
@ -436,4 +443,11 @@ public class APITestService {
|
|||
quotaService.checkAPITestQuota();
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeCreate(SaveAPITestRequest request, MultipartFile file, List<String> selectIds) {
|
||||
ApiTest test = createTest(request, file);
|
||||
selectIds.forEach(sourceId -> {
|
||||
copyBodyFiles(test.getId(), sourceId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.base.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TestCaseFile implements Serializable {
|
||||
private String caseId;
|
||||
|
||||
private String fileId;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -0,0 +1,340 @@
|
|||
package io.metersphere.base.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TestCaseFileExample {
|
||||
protected String orderByClause;
|
||||
|
||||
protected boolean distinct;
|
||||
|
||||
protected List<Criteria> oredCriteria;
|
||||
|
||||
public TestCaseFileExample() {
|
||||
oredCriteria = new ArrayList<Criteria>();
|
||||
}
|
||||
|
||||
public void setOrderByClause(String orderByClause) {
|
||||
this.orderByClause = orderByClause;
|
||||
}
|
||||
|
||||
public String getOrderByClause() {
|
||||
return orderByClause;
|
||||
}
|
||||
|
||||
public void setDistinct(boolean distinct) {
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
public boolean isDistinct() {
|
||||
return distinct;
|
||||
}
|
||||
|
||||
public List<Criteria> getOredCriteria() {
|
||||
return oredCriteria;
|
||||
}
|
||||
|
||||
public void or(Criteria criteria) {
|
||||
oredCriteria.add(criteria);
|
||||
}
|
||||
|
||||
public Criteria or() {
|
||||
Criteria criteria = createCriteriaInternal();
|
||||
oredCriteria.add(criteria);
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public Criteria createCriteria() {
|
||||
Criteria criteria = createCriteriaInternal();
|
||||
if (oredCriteria.size() == 0) {
|
||||
oredCriteria.add(criteria);
|
||||
}
|
||||
return criteria;
|
||||
}
|
||||
|
||||
protected Criteria createCriteriaInternal() {
|
||||
Criteria criteria = new Criteria();
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
oredCriteria.clear();
|
||||
orderByClause = null;
|
||||
distinct = false;
|
||||
}
|
||||
|
||||
protected abstract static class GeneratedCriteria {
|
||||
protected List<Criterion> criteria;
|
||||
|
||||
protected GeneratedCriteria() {
|
||||
super();
|
||||
criteria = new ArrayList<Criterion>();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return criteria.size() > 0;
|
||||
}
|
||||
|
||||
public List<Criterion> getAllCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public List<Criterion> getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
protected void addCriterion(String condition) {
|
||||
if (condition == null) {
|
||||
throw new RuntimeException("Value for condition cannot be null");
|
||||
}
|
||||
criteria.add(new Criterion(condition));
|
||||
}
|
||||
|
||||
protected void addCriterion(String condition, Object value, String property) {
|
||||
if (value == null) {
|
||||
throw new RuntimeException("Value for " + property + " cannot be null");
|
||||
}
|
||||
criteria.add(new Criterion(condition, value));
|
||||
}
|
||||
|
||||
protected void addCriterion(String condition, Object value1, Object value2, String property) {
|
||||
if (value1 == null || value2 == null) {
|
||||
throw new RuntimeException("Between values for " + property + " cannot be null");
|
||||
}
|
||||
criteria.add(new Criterion(condition, value1, value2));
|
||||
}
|
||||
|
||||
public Criteria andCaseIdIsNull() {
|
||||
addCriterion("case_id is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdIsNotNull() {
|
||||
addCriterion("case_id is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdEqualTo(String value) {
|
||||
addCriterion("case_id =", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdNotEqualTo(String value) {
|
||||
addCriterion("case_id <>", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdGreaterThan(String value) {
|
||||
addCriterion("case_id >", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("case_id >=", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdLessThan(String value) {
|
||||
addCriterion("case_id <", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdLessThanOrEqualTo(String value) {
|
||||
addCriterion("case_id <=", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdLike(String value) {
|
||||
addCriterion("case_id like", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdNotLike(String value) {
|
||||
addCriterion("case_id not like", value, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdIn(List<String> values) {
|
||||
addCriterion("case_id in", values, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdNotIn(List<String> values) {
|
||||
addCriterion("case_id not in", values, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdBetween(String value1, String value2) {
|
||||
addCriterion("case_id between", value1, value2, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCaseIdNotBetween(String value1, String value2) {
|
||||
addCriterion("case_id not between", value1, value2, "caseId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdIsNull() {
|
||||
addCriterion("file_id is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdIsNotNull() {
|
||||
addCriterion("file_id is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdEqualTo(String value) {
|
||||
addCriterion("file_id =", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotEqualTo(String value) {
|
||||
addCriterion("file_id <>", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdGreaterThan(String value) {
|
||||
addCriterion("file_id >", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("file_id >=", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdLessThan(String value) {
|
||||
addCriterion("file_id <", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdLessThanOrEqualTo(String value) {
|
||||
addCriterion("file_id <=", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdLike(String value) {
|
||||
addCriterion("file_id like", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotLike(String value) {
|
||||
addCriterion("file_id not like", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdIn(List<String> values) {
|
||||
addCriterion("file_id in", values, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotIn(List<String> values) {
|
||||
addCriterion("file_id not in", values, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdBetween(String value1, String value2) {
|
||||
addCriterion("file_id between", value1, value2, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotBetween(String value1, String value2) {
|
||||
addCriterion("file_id not between", value1, value2, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Criteria extends GeneratedCriteria {
|
||||
|
||||
protected Criteria() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Criterion {
|
||||
private String condition;
|
||||
|
||||
private Object value;
|
||||
|
||||
private Object secondValue;
|
||||
|
||||
private boolean noValue;
|
||||
|
||||
private boolean singleValue;
|
||||
|
||||
private boolean betweenValue;
|
||||
|
||||
private boolean listValue;
|
||||
|
||||
private String typeHandler;
|
||||
|
||||
public String getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Object getSecondValue() {
|
||||
return secondValue;
|
||||
}
|
||||
|
||||
public boolean isNoValue() {
|
||||
return noValue;
|
||||
}
|
||||
|
||||
public boolean isSingleValue() {
|
||||
return singleValue;
|
||||
}
|
||||
|
||||
public boolean isBetweenValue() {
|
||||
return betweenValue;
|
||||
}
|
||||
|
||||
public boolean isListValue() {
|
||||
return listValue;
|
||||
}
|
||||
|
||||
public String getTypeHandler() {
|
||||
return typeHandler;
|
||||
}
|
||||
|
||||
protected Criterion(String condition) {
|
||||
super();
|
||||
this.condition = condition;
|
||||
this.typeHandler = null;
|
||||
this.noValue = true;
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value, String typeHandler) {
|
||||
super();
|
||||
this.condition = condition;
|
||||
this.value = value;
|
||||
this.typeHandler = typeHandler;
|
||||
if (value instanceof List<?>) {
|
||||
this.listValue = true;
|
||||
} else {
|
||||
this.singleValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value) {
|
||||
this(condition, value, null);
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
|
||||
super();
|
||||
this.condition = condition;
|
||||
this.value = value;
|
||||
this.secondValue = secondValue;
|
||||
this.typeHandler = typeHandler;
|
||||
this.betweenValue = true;
|
||||
}
|
||||
|
||||
protected Criterion(String condition, Object value, Object secondValue) {
|
||||
this(condition, value, secondValue, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package io.metersphere.base.mapper;
|
||||
|
||||
import io.metersphere.base.domain.TestCaseFile;
|
||||
import io.metersphere.base.domain.TestCaseFileExample;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface TestCaseFileMapper {
|
||||
long countByExample(TestCaseFileExample example);
|
||||
|
||||
int deleteByExample(TestCaseFileExample example);
|
||||
|
||||
int insert(TestCaseFile record);
|
||||
|
||||
int insertSelective(TestCaseFile record);
|
||||
|
||||
List<TestCaseFile> selectByExample(TestCaseFileExample example);
|
||||
|
||||
int updateByExampleSelective(@Param("record") TestCaseFile record, @Param("example") TestCaseFileExample example);
|
||||
|
||||
int updateByExample(@Param("record") TestCaseFile record, @Param("example") TestCaseFileExample example);
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.metersphere.base.mapper.TestCaseFileMapper">
|
||||
<resultMap id="BaseResultMap" type="io.metersphere.base.domain.TestCaseFile">
|
||||
<result column="case_id" jdbcType="VARCHAR" property="caseId" />
|
||||
<result column="file_id" jdbcType="VARCHAR" property="fileId" />
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
<where>
|
||||
<foreach collection="oredCriteria" item="criteria" separator="or">
|
||||
<if test="criteria.valid">
|
||||
<trim prefix="(" prefixOverrides="and" suffix=")">
|
||||
<foreach collection="criteria.criteria" item="criterion">
|
||||
<choose>
|
||||
<when test="criterion.noValue">
|
||||
and ${criterion.condition}
|
||||
</when>
|
||||
<when test="criterion.singleValue">
|
||||
and ${criterion.condition} #{criterion.value}
|
||||
</when>
|
||||
<when test="criterion.betweenValue">
|
||||
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
|
||||
</when>
|
||||
<when test="criterion.listValue">
|
||||
and ${criterion.condition}
|
||||
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
|
||||
#{listItem}
|
||||
</foreach>
|
||||
</when>
|
||||
</choose>
|
||||
</foreach>
|
||||
</trim>
|
||||
</if>
|
||||
</foreach>
|
||||
</where>
|
||||
</sql>
|
||||
<sql id="Update_By_Example_Where_Clause">
|
||||
<where>
|
||||
<foreach collection="example.oredCriteria" item="criteria" separator="or">
|
||||
<if test="criteria.valid">
|
||||
<trim prefix="(" prefixOverrides="and" suffix=")">
|
||||
<foreach collection="criteria.criteria" item="criterion">
|
||||
<choose>
|
||||
<when test="criterion.noValue">
|
||||
and ${criterion.condition}
|
||||
</when>
|
||||
<when test="criterion.singleValue">
|
||||
and ${criterion.condition} #{criterion.value}
|
||||
</when>
|
||||
<when test="criterion.betweenValue">
|
||||
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
|
||||
</when>
|
||||
<when test="criterion.listValue">
|
||||
and ${criterion.condition}
|
||||
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
|
||||
#{listItem}
|
||||
</foreach>
|
||||
</when>
|
||||
</choose>
|
||||
</foreach>
|
||||
</trim>
|
||||
</if>
|
||||
</foreach>
|
||||
</where>
|
||||
</sql>
|
||||
<sql id="Base_Column_List">
|
||||
case_id, file_id
|
||||
</sql>
|
||||
<select id="selectByExample" parameterType="io.metersphere.base.domain.TestCaseFileExample" resultMap="BaseResultMap">
|
||||
select
|
||||
<if test="distinct">
|
||||
distinct
|
||||
</if>
|
||||
<include refid="Base_Column_List" />
|
||||
from test_case_file
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
<if test="orderByClause != null">
|
||||
order by ${orderByClause}
|
||||
</if>
|
||||
</select>
|
||||
<delete id="deleteByExample" parameterType="io.metersphere.base.domain.TestCaseFileExample">
|
||||
delete from test_case_file
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
</delete>
|
||||
<insert id="insert" parameterType="io.metersphere.base.domain.TestCaseFile">
|
||||
insert into test_case_file (case_id, file_id)
|
||||
values (#{caseId,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR})
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestCaseFile">
|
||||
insert into test_case_file
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="caseId != null">
|
||||
case_id,
|
||||
</if>
|
||||
<if test="fileId != null">
|
||||
file_id,
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="caseId != null">
|
||||
#{caseId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="fileId != null">
|
||||
#{fileId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
<select id="countByExample" parameterType="io.metersphere.base.domain.TestCaseFileExample" resultType="java.lang.Long">
|
||||
select count(*) from test_case_file
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause" />
|
||||
</if>
|
||||
</select>
|
||||
<update id="updateByExampleSelective" parameterType="map">
|
||||
update test_case_file
|
||||
<set>
|
||||
<if test="record.caseId != null">
|
||||
case_id = #{record.caseId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.fileId != null">
|
||||
file_id = #{record.fileId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
</update>
|
||||
<update id="updateByExample" parameterType="map">
|
||||
update test_case_file
|
||||
set case_id = #{record.caseId,jdbcType=VARCHAR},
|
||||
file_id = #{record.fileId,jdbcType=VARCHAR}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
</update>
|
||||
</mapper>
|
|
@ -20,8 +20,13 @@ public interface ExtTestCaseMapper {
|
|||
|
||||
TestCase getMaxNumByProjectId(@Param("projectId") String projectId);
|
||||
|
||||
List<TestCase> getTestCaseByNotInPlan(@Param("request") QueryTestCaseRequest request);
|
||||
|
||||
List<TestCase> getTestCaseByNotInReview(@Param("request") QueryTestCaseRequest request);
|
||||
|
||||
/**
|
||||
* 检查某工作空间下是否有某用例
|
||||
*
|
||||
* @param caseId
|
||||
* @param workspaceId
|
||||
* @return TestCase ID
|
||||
|
|
|
@ -97,6 +97,113 @@
|
|||
</if>
|
||||
</sql>
|
||||
|
||||
<select id="getTestCaseByNotInReview" 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
|
||||
<where>
|
||||
<if test="request.combine != null">
|
||||
<include refid="combine">
|
||||
<property name="condition" value="request.combine"/>
|
||||
<property name="name" value="request.name"/>
|
||||
</include>
|
||||
</if>
|
||||
and test_case.id not in (select case_id from test_case_review_test_case where review_id =#{request.reviewId})
|
||||
<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>
|
||||
<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=='priority'">
|
||||
and test_case.priority in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</when>
|
||||
<when test="key=='status'">
|
||||
and test_case.review_status in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</when>
|
||||
<otherwise>
|
||||
and test_case.type in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY test_case.update_time DESC
|
||||
</select>
|
||||
|
||||
<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
|
||||
<where>
|
||||
<if test="request.combine != null">
|
||||
<include refid="combine">
|
||||
<property name="condition" value="request.combine"/>
|
||||
<property name="name" value="request.name"/>
|
||||
</include>
|
||||
</if>
|
||||
and test_case.id not in (select case_id from test_plan_test_case where plan_id =#{request.planId})
|
||||
<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>
|
||||
<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=='priority'">
|
||||
and test_case.priority in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</when>
|
||||
<when test="key=='status'">
|
||||
and test_case.review_status in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</when>
|
||||
<otherwise>
|
||||
and test_case.type in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
</foreach>
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY test_case.update_time DESC
|
||||
</select>
|
||||
|
||||
<select id="getTestCaseNames" resultType="io.metersphere.base.domain.TestCase">
|
||||
select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status
|
||||
|
|
|
@ -173,6 +173,12 @@
|
|||
#{value}
|
||||
</foreach>
|
||||
</when>
|
||||
<when test="key=='executor'">
|
||||
and test_plan_test_case.executor in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
#{value}
|
||||
</foreach>
|
||||
</when>
|
||||
<otherwise>
|
||||
and test_plan_test_case.status in
|
||||
<foreach collection="values" item="value" separator="," open="(" close=")">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.metersphere.commons.constants;
|
||||
|
||||
public enum FileType {
|
||||
JMX(".jmx"), CSV(".csv"), JSON(".json");
|
||||
JMX(".jmx"), CSV(".csv"), JSON(".json"), PDF(".pdf"), JPG(".jpg"), PNG(".png");
|
||||
|
||||
// 保存后缀
|
||||
private String suffix;
|
||||
|
|
|
@ -25,6 +25,15 @@ public class TestController {
|
|||
return jsonObject;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/multipart", consumes = {"multipart/form-data"})
|
||||
public Object testMultipart(@RequestPart(value = "id") String id, @RequestPart(value = "user") User user, @RequestParam(value = "name") String name) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", id);
|
||||
jsonObject.put("user", user.getName());
|
||||
jsonObject.put("name", name);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{str}")
|
||||
public Object getString(@PathVariable String str) throws InterruptedException {
|
||||
if (StringUtils.equals("error", str)) {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package io.metersphere.listener;
|
||||
|
||||
import io.metersphere.api.jmeter.JMeterService;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.service.ScheduleService;
|
||||
import org.python.core.Options;
|
||||
import org.python.util.PythonInterpreter;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -12,12 +16,16 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
|
|||
|
||||
@Resource
|
||||
private ScheduleService scheduleService;
|
||||
@Resource
|
||||
private JMeterService jMeterService;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
||||
|
||||
System.out.println("================= 应用启动 =================");
|
||||
|
||||
initPythonEnv();
|
||||
|
||||
try {
|
||||
Thread.sleep(3 * 60 * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
|
@ -25,6 +33,24 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
|
|||
}
|
||||
|
||||
scheduleService.startEnableSchedules();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决接口测试-无法导入内置python包
|
||||
*/
|
||||
private void initPythonEnv() {
|
||||
//解决无法加载 PyScriptEngineFactory
|
||||
Options.importSite = false;
|
||||
try {
|
||||
PythonInterpreter interp = new PythonInterpreter();
|
||||
String path = jMeterService.getJmeterHome();
|
||||
System.out.println("sys.path: " + path);
|
||||
path += "/lib/ext/jython-standalone.jar/Lib";
|
||||
interp.exec("import sys");
|
||||
interp.exec("sys.path.append(\"" + path + "\")");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.metersphere.base.domain.*;
|
|||
import io.metersphere.base.mapper.FileContentMapper;
|
||||
import io.metersphere.base.mapper.FileMetadataMapper;
|
||||
import io.metersphere.base.mapper.LoadTestFileMapper;
|
||||
import io.metersphere.base.mapper.TestCaseFileMapper;
|
||||
import io.metersphere.commons.constants.FileType;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -24,6 +25,8 @@ public class FileService {
|
|||
private LoadTestFileMapper loadTestFileMapper;
|
||||
@Resource
|
||||
private FileContentMapper fileContentMapper;
|
||||
@Resource
|
||||
private TestCaseFileMapper testCaseFileMapper;
|
||||
|
||||
public byte[] loadFileAsBytes(String id) {
|
||||
FileContent fileContent = fileContentMapper.selectByPrimaryKey(id);
|
||||
|
@ -66,6 +69,19 @@ public class FileService {
|
|||
loadTestFileMapper.deleteByExample(example3);
|
||||
}
|
||||
|
||||
public void deleteFileRelatedByIds(List<String> ids) {
|
||||
if (CollectionUtils.isEmpty(ids)) {
|
||||
return;
|
||||
}
|
||||
FileMetadataExample example = new FileMetadataExample();
|
||||
example.createCriteria().andIdIn(ids);
|
||||
fileMetadataMapper.deleteByExample(example);
|
||||
|
||||
FileContentExample example2 = new FileContentExample();
|
||||
example2.createCriteria().andFileIdIn(ids);
|
||||
fileContentMapper.deleteByExample(example2);
|
||||
}
|
||||
|
||||
public FileMetadata saveFile(MultipartFile file) {
|
||||
final FileMetadata fileMetadata = new FileMetadata();
|
||||
fileMetadata.setId(UUID.randomUUID().toString());
|
||||
|
@ -109,4 +125,19 @@ public class FileService {
|
|||
String type = filename.substring(s);
|
||||
return FileType.valueOf(type.toUpperCase());
|
||||
}
|
||||
|
||||
public List<FileMetadata> getFileMetadataByCaseId(String caseId) {
|
||||
TestCaseFileExample testCaseFileExample = new TestCaseFileExample();
|
||||
testCaseFileExample.createCriteria().andCaseIdEqualTo(caseId);
|
||||
final List<TestCaseFile> testCaseFiles = testCaseFileMapper.selectByExample(testCaseFileExample);
|
||||
|
||||
if (CollectionUtils.isEmpty(testCaseFiles)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> fileIds = testCaseFiles.stream().map(TestCaseFile::getFileId).collect(Collectors.toList());
|
||||
FileMetadataExample example = new FileMetadataExample();
|
||||
example.createCriteria().andIdIn(fileIds);
|
||||
return fileMetadataMapper.selectByExample(example);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package io.metersphere.track.controller;
|
|||
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.base.domain.FileMetadata;
|
||||
import io.metersphere.base.domain.Project;
|
||||
import io.metersphere.base.domain.TestCase;
|
||||
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
||||
|
@ -11,12 +12,18 @@ import io.metersphere.commons.utils.Pager;
|
|||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.excel.domain.ExcelResponse;
|
||||
import io.metersphere.service.CheckOwnerService;
|
||||
import io.metersphere.service.FileService;
|
||||
import io.metersphere.track.dto.TestCaseDTO;
|
||||
import io.metersphere.track.request.testcase.EditTestCaseRequest;
|
||||
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.track.request.testcase.TestCaseBatchRequest;
|
||||
import io.metersphere.track.request.testplan.FileOperationRequest;
|
||||
import io.metersphere.track.service.TestCaseService;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
|
@ -33,6 +40,8 @@ public class TestCaseController {
|
|||
TestCaseService testCaseService;
|
||||
@Resource
|
||||
private CheckOwnerService checkOwnerService;
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
@PostMapping("/list/{goPage}/{pageSize}")
|
||||
public Pager<List<TestCaseDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
|
||||
|
@ -72,14 +81,16 @@ public class TestCaseController {
|
|||
return testCaseService.getTestCaseByNodeId(nodeIds);
|
||||
}
|
||||
|
||||
@PostMapping("/name")
|
||||
public List<TestCase> getTestCaseNames(@RequestBody QueryTestCaseRequest request) {
|
||||
return testCaseService.getTestCaseNames(request);
|
||||
@PostMapping("/name/{goPage}/{pageSize}")
|
||||
public Pager<List<TestCase>> getTestCaseNames(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
return PageUtils.setPageInfo(page,testCaseService.getTestCaseNames(request));
|
||||
}
|
||||
|
||||
@PostMapping("/reviews/case")
|
||||
public List<TestCase> getReviewCase(@RequestBody QueryTestCaseRequest request) {
|
||||
return testCaseService.getReviewCase(request);
|
||||
@PostMapping("/reviews/case/{goPage}/{pageSize}")
|
||||
public Pager<List<TestCase>> getReviewCase(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request) {
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
return PageUtils.setPageInfo(page, testCaseService.getReviewCase(request));
|
||||
}
|
||||
|
||||
@GetMapping("/get/{testCaseId}")
|
||||
|
@ -94,16 +105,16 @@ public class TestCaseController {
|
|||
return testCaseService.getProjectByTestCaseId(testCaseId);
|
||||
}
|
||||
|
||||
@PostMapping("/add")
|
||||
@PostMapping(value = "/add", consumes = {"multipart/form-data"})
|
||||
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
public void addTestCase(@RequestBody TestCaseWithBLOBs testCase) {
|
||||
testCaseService.addTestCase(testCase);
|
||||
public void addTestCase(@RequestPart("request") TestCaseWithBLOBs testCase, @RequestPart(value = "file") List<MultipartFile> files) {
|
||||
testCaseService.save(testCase, files);
|
||||
}
|
||||
|
||||
@PostMapping("/edit")
|
||||
@PostMapping(value = "/edit", consumes = {"multipart/form-data"})
|
||||
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
public void editTestCase(@RequestBody TestCaseWithBLOBs testCase) {
|
||||
testCaseService.editTestCase(testCase);
|
||||
public void editTestCase(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file") List<MultipartFile> files) {
|
||||
testCaseService.edit(request, files);
|
||||
}
|
||||
|
||||
@PostMapping("/delete/{testCaseId}")
|
||||
|
@ -150,4 +161,18 @@ public class TestCaseController {
|
|||
testCaseService.deleteTestCaseBath(request);
|
||||
}
|
||||
|
||||
@GetMapping("/file/metadata/{caseId}")
|
||||
public List<FileMetadata> getFileMetadata(@PathVariable String caseId) {
|
||||
return fileService.getFileMetadataByCaseId(caseId);
|
||||
}
|
||||
|
||||
@PostMapping("/file/download")
|
||||
public ResponseEntity<byte[]> downloadJmx(@RequestBody FileOperationRequest fileOperationRequest) {
|
||||
byte[] bytes = fileService.loadFileAsBytes(fileOperationRequest.getId());
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType("application/octet-stream"))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileOperationRequest.getName() + "\"")
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.metersphere.track.controller;
|
||||
|
||||
import io.metersphere.base.domain.Issues;
|
||||
import io.metersphere.track.domain.TapdUser;
|
||||
import io.metersphere.track.issue.PlatformUser;
|
||||
import io.metersphere.track.service.IssuesService;
|
||||
import io.metersphere.track.request.testcase.IssuesRequest;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -42,7 +42,7 @@ public class TestCaseIssuesController {
|
|||
}
|
||||
|
||||
@GetMapping("/tapd/user/{caseId}")
|
||||
public List<TapdUser> getTapdUsers(@PathVariable String caseId) {
|
||||
public List<PlatformUser> getPlatformUsers(@PathVariable String caseId) {
|
||||
return issuesService.getTapdProjectUsers(caseId);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package io.metersphere.track.issue;
|
||||
|
||||
import io.metersphere.base.domain.ServiceIntegration;
|
||||
import io.metersphere.base.mapper.IssuesMapper;
|
||||
import io.metersphere.base.mapper.TestCaseIssuesMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtIssuesMapper;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.EncryptUtils;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
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.service.TestCaseService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
public abstract class AbstractIssuePlatform implements IssuesPlatform {
|
||||
|
||||
protected IntegrationService integrationService;
|
||||
protected TestCaseIssuesMapper testCaseIssuesMapper;
|
||||
protected ProjectService projectService;
|
||||
protected TestCaseService testCaseService;
|
||||
protected IssuesMapper issuesMapper;
|
||||
protected ExtIssuesMapper extIssuesMapper;
|
||||
|
||||
protected String testCaseId;
|
||||
|
||||
public AbstractIssuePlatform(IssuesRequest issuesRequest) {
|
||||
this.integrationService = CommonBeanFactory.getBean(IntegrationService.class);
|
||||
this.testCaseIssuesMapper = CommonBeanFactory.getBean(TestCaseIssuesMapper.class);
|
||||
this.projectService = CommonBeanFactory.getBean(ProjectService.class);
|
||||
this.testCaseService = CommonBeanFactory.getBean(TestCaseService.class);
|
||||
this.issuesMapper = CommonBeanFactory.getBean(IssuesMapper.class);
|
||||
this.extIssuesMapper = CommonBeanFactory.getBean(ExtIssuesMapper.class);
|
||||
this.testCaseId = issuesRequest.getTestCaseId();
|
||||
}
|
||||
|
||||
protected String getPlatformConfig(String platform) {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
String orgId = user.getLastOrganizationId();
|
||||
|
||||
IntegrationRequest request = new IntegrationRequest();
|
||||
if (StringUtils.isBlank(orgId)) {
|
||||
MSException.throwException("organization id is null");
|
||||
}
|
||||
request.setOrgId(orgId);
|
||||
request.setPlatform(platform);
|
||||
|
||||
ServiceIntegration integration = integrationService.get(request);
|
||||
return integration.getConfiguration();
|
||||
}
|
||||
|
||||
protected HttpHeaders auth(String apiUser, String password) {
|
||||
String authKey = EncryptUtils.base64Encoding(apiUser + ":" + password);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Authorization", "Basic " + authKey);
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平台与项目相关的属性
|
||||
* @return
|
||||
*/
|
||||
abstract String getProjectId();
|
||||
|
||||
protected boolean isIntegratedPlatform(String orgId, String platform) {
|
||||
IntegrationRequest request = new IntegrationRequest();
|
||||
request.setPlatform(platform);
|
||||
request.setOrgId(orgId);
|
||||
ServiceIntegration integration = integrationService.get(request);
|
||||
return StringUtils.isNotBlank(integration.getId());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package io.metersphere.track.issue;
|
||||
|
||||
import io.metersphere.commons.constants.IssuesManagePlatform;
|
||||
import io.metersphere.track.request.testcase.IssuesRequest;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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);
|
||||
} else if (StringUtils.equals(IssuesManagePlatform.Jira.toString(), platform)) {
|
||||
return new JiraIssue(addIssueRequest);
|
||||
} else if (StringUtils.equals("LOCAL", platform)) {
|
||||
return new LocalIssue(addIssueRequest);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<AbstractIssuePlatform> createPlatforms(List<String> types, IssuesRequest addIssueRequest) {
|
||||
List<AbstractIssuePlatform> platforms = new ArrayList<>();
|
||||
types.forEach(type -> {
|
||||
AbstractIssuePlatform abstractIssuePlatform = createPlatform(type, addIssueRequest);
|
||||
if (abstractIssuePlatform != null) {
|
||||
platforms.add(abstractIssuePlatform);
|
||||
}
|
||||
});
|
||||
return platforms;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package io.metersphere.track.issue;
|
||||
|
||||
import io.metersphere.base.domain.Issues;
|
||||
import io.metersphere.track.request.testcase.IssuesRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IssuesPlatform {
|
||||
|
||||
/**
|
||||
* 获取平台相关联的缺陷
|
||||
* @return
|
||||
*/
|
||||
List<Issues> getIssue();
|
||||
|
||||
/**
|
||||
* 添加缺陷到缺陷平台
|
||||
* @param issuesRequest
|
||||
*/
|
||||
void addIssue(IssuesRequest issuesRequest);
|
||||
|
||||
/**
|
||||
* 删除缺陷平台缺陷
|
||||
* @param id
|
||||
*/
|
||||
void deleteIssue(String id);
|
||||
|
||||
/**
|
||||
* 测试缺陷平台连通性
|
||||
* @param
|
||||
*/
|
||||
void testAuth();
|
||||
|
||||
/**
|
||||
* 获取缺陷平台项目下的相关人员
|
||||
* @return
|
||||
*/
|
||||
List<PlatformUser> getPlatformUser();
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
package io.metersphere.track.issue;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
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.EncryptUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.track.request.testcase.IssuesRequest;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.safety.Whitelist;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
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 JiraIssue extends AbstractIssuePlatform {
|
||||
|
||||
|
||||
public JiraIssue(IssuesRequest issuesRequest) {
|
||||
super(issuesRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Issues> getIssue() {
|
||||
List<Issues> list = new ArrayList<>();
|
||||
|
||||
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");
|
||||
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);
|
||||
if (StringUtils.isBlank(dto.getId())) {
|
||||
// 缺陷不存在,解除用例和缺陷的关联
|
||||
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
|
||||
issuesExample.createCriteria()
|
||||
.andTestCaseIdEqualTo(testCaseId)
|
||||
.andIssuesIdEqualTo(issuesId);
|
||||
testCaseIssuesMapper.deleteByExample(issuesExample);
|
||||
issuesMapper.deleteByPrimaryKey(issuesId);
|
||||
} else {
|
||||
// 缺陷状态为 完成,则不显示
|
||||
if (!StringUtils.equals("done", dto.getStatus())) {
|
||||
list.add(dto);
|
||||
}
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIssue(IssuesRequest issuesRequest) {
|
||||
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString());
|
||||
JSONObject object = JSON.parseObject(config);
|
||||
|
||||
if (object == null) {
|
||||
MSException.throwException("jira config is null");
|
||||
}
|
||||
|
||||
String account = object.getString("account");
|
||||
String password = object.getString("password");
|
||||
String url = object.getString("url");
|
||||
String issuetype = object.getString("issuetype");
|
||||
if (StringUtils.isBlank(issuetype)) {
|
||||
MSException.throwException("Jira 问题类型为空");
|
||||
}
|
||||
String auth = EncryptUtils.base64Encoding(account + ":" + password);
|
||||
|
||||
String testCaseId = issuesRequest.getTestCaseId();
|
||||
String jiraKey = getProjectId();
|
||||
|
||||
|
||||
if (StringUtils.isBlank(jiraKey)) {
|
||||
MSException.throwException("未关联Jira 项目Key");
|
||||
}
|
||||
|
||||
String content = issuesRequest.getContent();
|
||||
|
||||
Document document = Jsoup.parse(content);
|
||||
document.outputSettings(new Document.OutputSettings().prettyPrint(false));
|
||||
document.select("br").append("\\n");
|
||||
document.select("p").prepend("\\n\\n");
|
||||
String s = document.html().replaceAll("\\\\n", "\n");
|
||||
String desc = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false));
|
||||
desc = desc.replace(" ", "");
|
||||
|
||||
String json = "{\n" +
|
||||
" \"fields\":{\n" +
|
||||
" \"project\":{\n" +
|
||||
" \"key\":\"" + jiraKey + "\"\n" +
|
||||
" },\n" +
|
||||
" \"summary\":\"" + issuesRequest.getTitle() + "\",\n" +
|
||||
" \"description\": " + JSON.toJSONString(desc) + ",\n" +
|
||||
" \"issuetype\":{\n" +
|
||||
" \"name\":\"" + issuetype + "\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
String result = addJiraIssue(url, auth, json);
|
||||
|
||||
JSONObject jsonObject = JSON.parseObject(result);
|
||||
String id = jsonObject.getString("key");
|
||||
|
||||
// 用例与第三方缺陷平台中的缺陷关联
|
||||
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.Jira.toString());
|
||||
issuesMapper.insert(issues);
|
||||
}
|
||||
|
||||
private String addJiraIssue(String url, String auth, String json) {
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
requestHeaders.add("Authorization", "Basic " + auth);
|
||||
requestHeaders.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
|
||||
//HttpEntity
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(json, requestHeaders);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//post
|
||||
ResponseEntity<String> responseEntity = null;
|
||||
try {
|
||||
responseEntity = restTemplate.exchange(url + "/rest/api/2/issue", HttpMethod.POST, requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("调用Jira接口创建缺陷失败");
|
||||
}
|
||||
|
||||
return responseEntity.getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteIssue(String id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testAuth() {
|
||||
try {
|
||||
String config = getPlatformConfig(IssuesManagePlatform.Jira.toString());
|
||||
JSONObject object = JSON.parseObject(config);
|
||||
String account = object.getString("account");
|
||||
String password = object.getString("password");
|
||||
String url = object.getString("url");
|
||||
HttpHeaders headers = auth(account, password);
|
||||
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.exchange(url + "rest/api/2/issue/createmeta", HttpMethod.GET, requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("验证失败!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlatformUser> getPlatformUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getProjectId() {
|
||||
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
|
||||
Project project = projectService.getProjectById(testCase.getProjectId());
|
||||
return project.getJiraKey();
|
||||
}
|
||||
|
||||
private Issues 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();
|
||||
try {
|
||||
responseEntity = restTemplate.exchange(url + "/rest/api/2/issue/" + issuesId, HttpMethod.GET, requestEntity, String.class);
|
||||
String body = responseEntity.getBody();
|
||||
|
||||
JSONObject obj = JSONObject.parseObject(body);
|
||||
LogUtil.info(obj);
|
||||
|
||||
String lastmodify = "";
|
||||
String status = "";
|
||||
|
||||
JSONObject fields = (JSONObject) obj.get("fields");
|
||||
JSONObject statusObj = (JSONObject) fields.get("status");
|
||||
JSONObject assignee = (JSONObject) fields.get("assignee");
|
||||
|
||||
if (statusObj != null) {
|
||||
JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory");
|
||||
status = statusCategory.getString("key");
|
||||
}
|
||||
|
||||
String id = obj.getString("key");
|
||||
String title = fields.getString("summary");
|
||||
String description = fields.getString("description");
|
||||
|
||||
Parser parser = Parser.builder().build();
|
||||
Node document = parser.parse(description);
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
description = renderer.render(document);
|
||||
|
||||
Long createTime = fields.getLong("created");
|
||||
|
||||
if (assignee != null) {
|
||||
lastmodify = assignee.getString("displayName");
|
||||
}
|
||||
|
||||
issues.setId(id);
|
||||
issues.setTitle(title);
|
||||
issues.setCreateTime(createTime);
|
||||
issues.setLastmodify(lastmodify);
|
||||
issues.setDescription(description);
|
||||
issues.setStatus(status);
|
||||
issues.setPlatform(IssuesManagePlatform.Jira.toString());
|
||||
} catch (HttpClientErrorException.NotFound e) {
|
||||
LogUtil.error(e.getStackTrace(), e);
|
||||
return new Issues();
|
||||
} catch (HttpClientErrorException.Unauthorized e) {
|
||||
LogUtil.error(e.getStackTrace(), e);
|
||||
MSException.throwException("获取Jira缺陷失败,检查Jira配置信息");
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("调用Jira接口获取缺陷失败");
|
||||
}
|
||||
|
||||
return issues;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package io.metersphere.track.issue;
|
||||
|
||||
import io.metersphere.base.domain.Issues;
|
||||
import io.metersphere.base.domain.TestCaseIssues;
|
||||
import io.metersphere.commons.constants.IssuesManagePlatform;
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.track.request.testcase.IssuesRequest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LocalIssue extends AbstractIssuePlatform {
|
||||
|
||||
public LocalIssue(IssuesRequest issuesRequest) {
|
||||
super(issuesRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Issues> getIssue() {
|
||||
return extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Local.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIssue(IssuesRequest issuesRequest) {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
String id = UUID.randomUUID().toString();
|
||||
Issues issues = new Issues();
|
||||
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());
|
||||
issuesMapper.insert(issues);
|
||||
|
||||
TestCaseIssues testCaseIssues = new TestCaseIssues();
|
||||
testCaseIssues.setId(UUID.randomUUID().toString());
|
||||
testCaseIssues.setIssuesId(id);
|
||||
testCaseIssues.setTestCaseId(issuesRequest.getTestCaseId());
|
||||
testCaseIssuesMapper.insert(testCaseIssues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteIssue(String id) {
|
||||
issuesMapper.deleteByPrimaryKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testAuth() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlatformUser> getPlatformUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getProjectId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void closeIssue(String issueId) {
|
||||
Issues issues = new Issues();
|
||||
issues.setId(issueId);
|
||||
issues.setStatus("closed");
|
||||
issuesMapper.updateByPrimaryKeySelective(issues);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.track.issue;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PlatformUser {
|
||||
private List<String> roleId;
|
||||
private String name;
|
||||
private String user;
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
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.commons.utils.RestTemplateUtils;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.controller.ResultHolder;
|
||||
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.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
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 TapdIssue extends AbstractIssuePlatform {
|
||||
|
||||
|
||||
public TapdIssue(IssuesRequest issueRequest) {
|
||||
super(issueRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Issues> getIssue() {
|
||||
List<Issues> list = new ArrayList<>();
|
||||
String tapdId = getProjectId();
|
||||
|
||||
TestCaseIssuesExample example = new TestCaseIssuesExample();
|
||||
example.createCriteria().andTestCaseIdEqualTo(testCaseId);
|
||||
|
||||
List<Issues> issues = extIssuesMapper.getIssues(testCaseId, IssuesManagePlatform.Tapd.toString());
|
||||
|
||||
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
|
||||
issuesIds.forEach(issuesId -> {
|
||||
Issues dto = getTapdIssues(tapdId, 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.Tapd.toString());
|
||||
// 缺陷状态为 关闭,则不显示
|
||||
if (!StringUtils.equals("closed", dto.getStatus())) {
|
||||
list.add(dto);
|
||||
}
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
private Issues 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();
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(listJson);
|
||||
JSONObject bug = jsonObject.getJSONObject("Bug");
|
||||
Long created = bug.getLong("created");
|
||||
Issues issues = jsonObject.getObject("Bug", Issues.class);
|
||||
issues.setCreateTime(created);
|
||||
return issues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIssue(IssuesRequest issuesRequest) {
|
||||
String url = "https://api.tapd.cn/bugs";
|
||||
String testCaseId = issuesRequest.getTestCaseId();
|
||||
String tapdId = getProjectId();
|
||||
|
||||
if (StringUtils.isBlank(tapdId)) {
|
||||
MSException.throwException("未关联Tapd 项目ID");
|
||||
}
|
||||
|
||||
List<String> PlatformUsers = issuesRequest.getTapdUsers();
|
||||
String usersStr = String.join(";", PlatformUsers);
|
||||
|
||||
String username = SessionUtils.getUser().getName();
|
||||
|
||||
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
|
||||
paramMap.add("title", issuesRequest.getTitle());
|
||||
paramMap.add("workspace_id", tapdId);
|
||||
paramMap.add("description", issuesRequest.getContent());
|
||||
paramMap.add("reporter", username);
|
||||
paramMap.add("current_owner", usersStr);
|
||||
|
||||
ResultHolder result = call(url, HttpMethod.POST, paramMap);
|
||||
|
||||
String listJson = JSON.toJSONString(result.getData());
|
||||
JSONObject jsonObject = JSONObject.parseObject(listJson);
|
||||
String issuesId = jsonObject.getObject("Bug", Issues.class).getId();
|
||||
|
||||
// 用例与第三方缺陷平台中的缺陷关联
|
||||
TestCaseIssues testCaseIssues = new TestCaseIssues();
|
||||
testCaseIssues.setId(UUID.randomUUID().toString());
|
||||
testCaseIssues.setIssuesId(issuesId);
|
||||
testCaseIssues.setTestCaseId(testCaseId);
|
||||
testCaseIssuesMapper.insert(testCaseIssues);
|
||||
|
||||
// 插入缺陷表
|
||||
Issues issues = new Issues();
|
||||
issues.setId(issuesId);
|
||||
issues.setPlatform(IssuesManagePlatform.Tapd.toString());
|
||||
issuesMapper.insert(issues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteIssue(String id) {}
|
||||
|
||||
@Override
|
||||
public void testAuth() {
|
||||
try {
|
||||
String tapdConfig = getPlatformConfig(IssuesManagePlatform.Tapd.toString());
|
||||
JSONObject object = JSON.parseObject(tapdConfig);
|
||||
String account = object.getString("account");
|
||||
String password = object.getString("password");
|
||||
HttpHeaders headers = auth(account, password);
|
||||
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.exchange("https://api.tapd.cn/quickstart/testauth", HttpMethod.GET, requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("验证失败!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlatformUser> getPlatformUser() {
|
||||
List<PlatformUser> users = new ArrayList<>();
|
||||
String projectId = getProjectId();
|
||||
String url = "https://api.tapd.cn/workspaces/users?workspace_id=" + projectId;
|
||||
ResultHolder call = call(url);
|
||||
String listJson = JSON.toJSONString(call.getData());
|
||||
JSONArray jsonArray = JSON.parseArray(listJson);
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
JSONObject o = jsonArray.getJSONObject(i);
|
||||
PlatformUser user = o.getObject("UserWorkspace", PlatformUser.class);
|
||||
users.add(user);
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getProjectId() {
|
||||
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
|
||||
Project project = projectService.getProjectById(testCase.getProjectId());
|
||||
return project.getTapdId();
|
||||
}
|
||||
|
||||
private ResultHolder call(String url) {
|
||||
return call(url, HttpMethod.GET, null);
|
||||
}
|
||||
|
||||
private ResultHolder call(String url, HttpMethod httpMethod, Object params) {
|
||||
String responseJson;
|
||||
|
||||
String config = getPlatformConfig(IssuesManagePlatform.Tapd.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 header = auth(account, password);
|
||||
|
||||
if (httpMethod.equals(HttpMethod.GET)) {
|
||||
responseJson = RestTemplateUtils.get(url, header);
|
||||
} else {
|
||||
responseJson = RestTemplateUtils.post(url, params, header);
|
||||
}
|
||||
|
||||
ResultHolder result = JSON.parseObject(responseJson, ResultHolder.class);
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
MSException.throwException(result.getMessage());
|
||||
}
|
||||
return JSON.parseObject(responseJson, ResultHolder.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.metersphere.track.request.testcase;
|
||||
|
||||
import io.metersphere.base.domain.FileMetadata;
|
||||
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class EditTestCaseRequest extends TestCaseWithBLOBs {
|
||||
private List<FileMetadata> updatedFileList;
|
||||
}
|
|
@ -10,5 +10,6 @@ import java.util.List;
|
|||
@Setter
|
||||
public class PlanCaseRelevanceRequest {
|
||||
private String planId;
|
||||
private String projectId;
|
||||
private List<String> testCaseIds = new ArrayList<>();
|
||||
}
|
||||
|
|
|
@ -10,5 +10,6 @@ import java.util.List;
|
|||
@Setter
|
||||
public class ReviewRelevanceRequest {
|
||||
private String reviewId;
|
||||
private String projectId;
|
||||
private List<String> testCaseIds = new ArrayList<>();
|
||||
}
|
||||
|
|
|
@ -1,45 +1,23 @@
|
|||
package io.metersphere.track.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
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.exception.MSException;
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
import io.metersphere.commons.utils.EncryptUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.RestTemplateUtils;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.controller.ResultHolder;
|
||||
import io.metersphere.controller.request.IntegrationRequest;
|
||||
import io.metersphere.service.IntegrationService;
|
||||
import io.metersphere.service.ProjectService;
|
||||
import io.metersphere.track.domain.TapdUser;
|
||||
import io.metersphere.track.issue.AbstractIssuePlatform;
|
||||
import io.metersphere.track.issue.IssueFactory;
|
||||
import io.metersphere.track.issue.PlatformUser;
|
||||
import io.metersphere.track.request.testcase.IssuesRequest;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.safety.Whitelist;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.commonmark.node.*;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
|
@ -48,108 +26,16 @@ public class IssuesService {
|
|||
@Resource
|
||||
private IntegrationService integrationService;
|
||||
@Resource
|
||||
private TestCaseIssuesMapper testCaseIssuesMapper;
|
||||
@Resource
|
||||
private ProjectService projectService;
|
||||
@Resource
|
||||
private TestCaseService testCaseService;
|
||||
@Resource
|
||||
private IssuesMapper issuesMapper;
|
||||
@Resource
|
||||
private ExtIssuesMapper extIssuesMapper;
|
||||
|
||||
|
||||
public void testAuth(String platform) {
|
||||
if (StringUtils.equals(platform, IssuesManagePlatform.Tapd.toString())) {
|
||||
|
||||
try {
|
||||
String tapdConfig = platformConfig(IssuesManagePlatform.Tapd.toString());
|
||||
JSONObject object = JSON.parseObject(tapdConfig);
|
||||
String account = object.getString("account");
|
||||
String password = object.getString("password");
|
||||
HttpHeaders headers = auth(account, password);
|
||||
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.exchange("https://api.tapd.cn/quickstart/testauth", HttpMethod.GET, requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("验证失败!");
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
try {
|
||||
String config = platformConfig(IssuesManagePlatform.Jira.toString());
|
||||
JSONObject object = JSON.parseObject(config);
|
||||
String account = object.getString("account");
|
||||
String password = object.getString("password");
|
||||
String url = object.getString("url");
|
||||
HttpHeaders headers = auth(account, password);
|
||||
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(headers);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.exchange(url + "rest/api/2/issue/createmeta", HttpMethod.GET, requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("验证失败!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ResultHolder call(String url) {
|
||||
return call(url, HttpMethod.GET, null);
|
||||
}
|
||||
|
||||
private ResultHolder call(String url, HttpMethod httpMethod, Object params) {
|
||||
String responseJson;
|
||||
|
||||
String config = platformConfig(IssuesManagePlatform.Tapd.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 header = auth(account, password);
|
||||
|
||||
if (httpMethod.equals(HttpMethod.GET)) {
|
||||
responseJson = RestTemplateUtils.get(url, header);
|
||||
} else {
|
||||
responseJson = RestTemplateUtils.post(url, params, header);
|
||||
}
|
||||
|
||||
ResultHolder result = JSON.parseObject(responseJson, ResultHolder.class);
|
||||
|
||||
if (!result.isSuccess()) {
|
||||
MSException.throwException(result.getMessage());
|
||||
}
|
||||
return JSON.parseObject(responseJson, ResultHolder.class);
|
||||
|
||||
}
|
||||
|
||||
private String platformConfig(String platform) {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
String orgId = user.getLastOrganizationId();
|
||||
|
||||
IntegrationRequest request = new IntegrationRequest();
|
||||
if (StringUtils.isBlank(orgId)) {
|
||||
MSException.throwException("organization id is null");
|
||||
}
|
||||
request.setOrgId(orgId);
|
||||
request.setPlatform(platform);
|
||||
|
||||
ServiceIntegration integration = integrationService.get(request);
|
||||
return integration.getConfiguration();
|
||||
}
|
||||
|
||||
private HttpHeaders auth(String apiUser, String password) {
|
||||
String authKey = EncryptUtils.base64Encoding(apiUser + ":" + password);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Authorization", "Basic " + authKey);
|
||||
return headers;
|
||||
AbstractIssuePlatform abstractPlatform = IssueFactory.createPlatform(platform, new IssuesRequest());
|
||||
abstractPlatform.testAuth();
|
||||
}
|
||||
|
||||
public void addIssues(IssuesRequest issuesRequest) {
|
||||
|
@ -162,247 +48,29 @@ public class IssuesService {
|
|||
String tapdId = getTapdProjectId(issuesRequest.getTestCaseId());
|
||||
String jiraKey = getJiraProjectKey(issuesRequest.getTestCaseId());
|
||||
|
||||
List<String> platforms = new ArrayList<>();
|
||||
|
||||
if (tapd) {
|
||||
// 是否关联了项目
|
||||
if (StringUtils.isNotBlank(tapdId)) {
|
||||
addTapdIssues(issuesRequest);
|
||||
platforms.add(IssuesManagePlatform.Tapd.name());
|
||||
}
|
||||
}
|
||||
|
||||
if (jira) {
|
||||
if (StringUtils.isNotBlank(jiraKey)) {
|
||||
addJiraIssues(issuesRequest);
|
||||
platforms.add(IssuesManagePlatform.Jira.name());
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(tapdId) && StringUtils.isBlank(jiraKey)) {
|
||||
addLocalIssues(issuesRequest);
|
||||
platforms.add("LOCAL");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void addTapdIssues(IssuesRequest issuesRequest) {
|
||||
String url = "https://api.tapd.cn/bugs";
|
||||
String testCaseId = issuesRequest.getTestCaseId();
|
||||
String tapdId = getTapdProjectId(testCaseId);
|
||||
|
||||
if (StringUtils.isBlank(tapdId)) {
|
||||
MSException.throwException("未关联Tapd 项目ID");
|
||||
}
|
||||
|
||||
List<String> tapdUsers = issuesRequest.getTapdUsers();
|
||||
String usersStr = String.join(";", tapdUsers);
|
||||
|
||||
String username = SessionUtils.getUser().getName();
|
||||
|
||||
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
|
||||
paramMap.add("title", issuesRequest.getTitle());
|
||||
paramMap.add("workspace_id", tapdId);
|
||||
paramMap.add("description", issuesRequest.getContent());
|
||||
paramMap.add("reporter", username);
|
||||
paramMap.add("current_owner", usersStr);
|
||||
|
||||
ResultHolder result = call(url, HttpMethod.POST, paramMap);
|
||||
|
||||
String listJson = JSON.toJSONString(result.getData());
|
||||
JSONObject jsonObject = JSONObject.parseObject(listJson);
|
||||
String issuesId = jsonObject.getObject("Bug", Issues.class).getId();
|
||||
|
||||
// 用例与第三方缺陷平台中的缺陷关联
|
||||
TestCaseIssues testCaseIssues = new TestCaseIssues();
|
||||
testCaseIssues.setId(UUID.randomUUID().toString());
|
||||
testCaseIssues.setIssuesId(issuesId);
|
||||
testCaseIssues.setTestCaseId(testCaseId);
|
||||
testCaseIssuesMapper.insert(testCaseIssues);
|
||||
|
||||
// 插入缺陷表
|
||||
Issues issues = new Issues();
|
||||
issues.setId(issuesId);
|
||||
issues.setPlatform(IssuesManagePlatform.Tapd.toString());
|
||||
issuesMapper.insert(issues);
|
||||
}
|
||||
|
||||
public void addJiraIssues(IssuesRequest issuesRequest) {
|
||||
String config = platformConfig(IssuesManagePlatform.Jira.toString());
|
||||
JSONObject object = JSON.parseObject(config);
|
||||
|
||||
if (object == null) {
|
||||
MSException.throwException("jira config is null");
|
||||
}
|
||||
|
||||
String account = object.getString("account");
|
||||
String password = object.getString("password");
|
||||
String url = object.getString("url");
|
||||
String issuetype = object.getString("issuetype");
|
||||
if (StringUtils.isBlank(issuetype)) {
|
||||
MSException.throwException("Jira 问题类型为空");
|
||||
}
|
||||
String auth = EncryptUtils.base64Encoding(account + ":" + password);
|
||||
|
||||
String testCaseId = issuesRequest.getTestCaseId();
|
||||
String jiraKey = getJiraProjectKey(testCaseId);
|
||||
|
||||
|
||||
if (StringUtils.isBlank(jiraKey)) {
|
||||
MSException.throwException("未关联Jira 项目Key");
|
||||
}
|
||||
|
||||
String content = issuesRequest.getContent();
|
||||
|
||||
Document document = Jsoup.parse(content);
|
||||
document.outputSettings(new Document.OutputSettings().prettyPrint(false));
|
||||
document.select("br").append("\\n");
|
||||
document.select("p").prepend("\\n\\n");
|
||||
String s = document.html().replaceAll("\\\\n", "\n");
|
||||
String desc = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false));
|
||||
desc = desc.replace(" ", "");
|
||||
|
||||
String json = "{\n" +
|
||||
" \"fields\":{\n" +
|
||||
" \"project\":{\n" +
|
||||
" \"key\":\"" + jiraKey + "\"\n" +
|
||||
" },\n" +
|
||||
" \"summary\":\"" + issuesRequest.getTitle() + "\",\n" +
|
||||
" \"description\": " + JSON.toJSONString(desc) + ",\n" +
|
||||
" \"issuetype\":{\n" +
|
||||
" \"name\":\"" + issuetype + "\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
|
||||
String result = addJiraIssue(url, auth, json);
|
||||
|
||||
JSONObject jsonObject = JSON.parseObject(result);
|
||||
String id = jsonObject.getString("key");
|
||||
|
||||
// 用例与第三方缺陷平台中的缺陷关联
|
||||
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.Jira.toString());
|
||||
issuesMapper.insert(issues);
|
||||
}
|
||||
|
||||
private String addJiraIssue(String url, String auth, String json) {
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
requestHeaders.add("Authorization", "Basic " + auth);
|
||||
requestHeaders.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
|
||||
//HttpEntity
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(json, requestHeaders);
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
//post
|
||||
ResponseEntity<String> responseEntity = null;
|
||||
try {
|
||||
responseEntity = restTemplate.exchange(url + "/rest/api/2/issue", HttpMethod.POST, requestEntity, String.class);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("调用Jira接口创建缺陷失败");
|
||||
}
|
||||
|
||||
return responseEntity.getBody();
|
||||
}
|
||||
|
||||
public void addLocalIssues(IssuesRequest request) {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
String id = UUID.randomUUID().toString();
|
||||
Issues issues = new Issues();
|
||||
issues.setId(id);
|
||||
issues.setStatus("new");
|
||||
issues.setReporter(user.getId());
|
||||
issues.setTitle(request.getTitle());
|
||||
issues.setDescription(request.getContent());
|
||||
issues.setCreateTime(System.currentTimeMillis());
|
||||
issues.setUpdateTime(System.currentTimeMillis());
|
||||
issues.setPlatform(IssuesManagePlatform.Local.toString());
|
||||
issuesMapper.insert(issues);
|
||||
|
||||
TestCaseIssues testCaseIssues = new TestCaseIssues();
|
||||
testCaseIssues.setId(UUID.randomUUID().toString());
|
||||
testCaseIssues.setIssuesId(id);
|
||||
testCaseIssues.setTestCaseId(request.getTestCaseId());
|
||||
testCaseIssuesMapper.insert(testCaseIssues);
|
||||
}
|
||||
|
||||
public Issues 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();
|
||||
}
|
||||
JSONObject jsonObject = JSONObject.parseObject(listJson);
|
||||
JSONObject bug = jsonObject.getJSONObject("Bug");
|
||||
Long created = bug.getLong("created");
|
||||
Issues issues = jsonObject.getObject("Bug", Issues.class);
|
||||
issues.setCreateTime(created);
|
||||
return issues;
|
||||
}
|
||||
|
||||
public Issues 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();
|
||||
try {
|
||||
responseEntity = restTemplate.exchange(url + "/rest/api/2/issue/" + issuesId, HttpMethod.GET, requestEntity, String.class);
|
||||
String body = responseEntity.getBody();
|
||||
|
||||
JSONObject obj = JSONObject.parseObject(body);
|
||||
LogUtil.info(obj);
|
||||
|
||||
String lastmodify = "";
|
||||
String status = "";
|
||||
|
||||
JSONObject fields = (JSONObject) obj.get("fields");
|
||||
JSONObject statusObj = (JSONObject) fields.get("status");
|
||||
JSONObject assignee = (JSONObject) fields.get("assignee");
|
||||
|
||||
if (statusObj != null) {
|
||||
JSONObject statusCategory = (JSONObject) statusObj.get("statusCategory");
|
||||
status = statusCategory.getString("key");
|
||||
}
|
||||
|
||||
String id = obj.getString("key");
|
||||
String title = fields.getString("summary");
|
||||
String description = fields.getString("description");
|
||||
|
||||
Parser parser = Parser.builder().build();
|
||||
Node document = parser.parse(description);
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
description = renderer.render(document);
|
||||
|
||||
Long createTime = fields.getLong("created");
|
||||
|
||||
if (assignee != null) {
|
||||
lastmodify = assignee.getString("displayName");
|
||||
}
|
||||
|
||||
issues.setId(id);
|
||||
issues.setTitle(title);
|
||||
issues.setCreateTime(createTime);
|
||||
issues.setLastmodify(lastmodify);
|
||||
issues.setDescription(description);
|
||||
issues.setStatus(status);
|
||||
issues.setPlatform(IssuesManagePlatform.Jira.toString());
|
||||
} catch (HttpClientErrorException.NotFound e) {
|
||||
LogUtil.error(e.getStackTrace(), e);
|
||||
return new Issues();
|
||||
} catch (HttpClientErrorException.Unauthorized e) {
|
||||
LogUtil.error(e.getStackTrace(), e);
|
||||
MSException.throwException("获取Jira缺陷失败,检查Jira配置信息");
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException("调用Jira接口获取缺陷失败");
|
||||
}
|
||||
|
||||
return issues;
|
||||
List<AbstractIssuePlatform> platformList = IssueFactory.createPlatforms(platforms, issuesRequest);
|
||||
platformList.forEach(platform -> {
|
||||
platform.addIssue(issuesRequest);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
@ -414,11 +82,12 @@ public class IssuesService {
|
|||
boolean tapd = isIntegratedPlatform(orgId, IssuesManagePlatform.Tapd.toString());
|
||||
boolean jira = isIntegratedPlatform(orgId, IssuesManagePlatform.Jira.toString());
|
||||
|
||||
List<String> platforms = new ArrayList<>();
|
||||
if (tapd) {
|
||||
// 是否关联了项目
|
||||
String tapdId = getTapdProjectId(caseId);
|
||||
if (StringUtils.isNotBlank(tapdId)) {
|
||||
list.addAll(getTapdIssues(caseId));
|
||||
platforms.add(IssuesManagePlatform.Tapd.name());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -426,90 +95,22 @@ public class IssuesService {
|
|||
if (jira) {
|
||||
String jiraKey = getJiraProjectKey(caseId);
|
||||
if (StringUtils.isNotBlank(jiraKey)) {
|
||||
list.addAll(getJiraIssues(caseId));
|
||||
platforms.add(IssuesManagePlatform.Jira.name());
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(getLocalIssues(caseId));
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<Issues> getTapdIssues(String caseId) {
|
||||
List<Issues> list = new ArrayList<>();
|
||||
String tapdId = getTapdProjectId(caseId);
|
||||
|
||||
TestCaseIssuesExample example = new TestCaseIssuesExample();
|
||||
example.createCriteria().andTestCaseIdEqualTo(caseId);
|
||||
|
||||
List<Issues> issues = extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Tapd.toString());
|
||||
|
||||
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
|
||||
issuesIds.forEach(issuesId -> {
|
||||
Issues dto = getTapdIssues(tapdId, issuesId);
|
||||
if (StringUtils.isBlank(dto.getId())) {
|
||||
// 缺陷不存在,解除用例和缺陷的关联
|
||||
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
|
||||
issuesExample.createCriteria()
|
||||
.andTestCaseIdEqualTo(caseId)
|
||||
.andIssuesIdEqualTo(issuesId);
|
||||
testCaseIssuesMapper.deleteByExample(issuesExample);
|
||||
issuesMapper.deleteByPrimaryKey(issuesId);
|
||||
} else {
|
||||
dto.setPlatform(IssuesManagePlatform.Tapd.toString());
|
||||
// 缺陷状态为 关闭,则不显示
|
||||
if (!StringUtils.equals("closed", dto.getStatus())) {
|
||||
list.add(dto);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public List<Issues> getJiraIssues(String caseId) {
|
||||
List<Issues> list = new ArrayList<>();
|
||||
|
||||
String config = platformConfig(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");
|
||||
String url = object.getString("url");
|
||||
HttpHeaders headers = auth(account, password);
|
||||
|
||||
TestCaseIssuesExample example = new TestCaseIssuesExample();
|
||||
example.createCriteria().andTestCaseIdEqualTo(caseId);
|
||||
|
||||
List<Issues> issues = extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Jira.toString());
|
||||
|
||||
List<String> issuesIds = issues.stream().map(Issues::getId).collect(Collectors.toList());
|
||||
issuesIds.forEach(issuesId -> {
|
||||
Issues dto = getJiraIssues(headers, url, issuesId);
|
||||
if (StringUtils.isBlank(dto.getId())) {
|
||||
// 缺陷不存在,解除用例和缺陷的关联
|
||||
TestCaseIssuesExample issuesExample = new TestCaseIssuesExample();
|
||||
issuesExample.createCriteria()
|
||||
.andTestCaseIdEqualTo(caseId)
|
||||
.andIssuesIdEqualTo(issuesId);
|
||||
testCaseIssuesMapper.deleteByExample(issuesExample);
|
||||
issuesMapper.deleteByPrimaryKey(issuesId);
|
||||
} else {
|
||||
// 缺陷状态为 完成,则不显示
|
||||
if (!StringUtils.equals("done", dto.getStatus())) {
|
||||
list.add(dto);
|
||||
}
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<Issues> getLocalIssues(String caseId) {
|
||||
return extIssuesMapper.getIssues(caseId, IssuesManagePlatform.Local.toString());
|
||||
}
|
||||
|
||||
public String getTapdProjectId(String testCaseId) {
|
||||
TestCaseWithBLOBs testCase = testCaseService.getTestCase(testCaseId);
|
||||
Project project = projectService.getProjectById(testCase.getProjectId());
|
||||
|
@ -540,19 +141,11 @@ public class IssuesService {
|
|||
issuesMapper.updateByPrimaryKeySelective(issues);
|
||||
}
|
||||
|
||||
public List<TapdUser> getTapdProjectUsers(String caseId) {
|
||||
List<TapdUser> users = new ArrayList<>();
|
||||
String projectId = getTapdProjectId(caseId);
|
||||
String url = "https://api.tapd.cn/workspaces/users?workspace_id=" + projectId;
|
||||
ResultHolder call = call(url);
|
||||
String listJson = JSON.toJSONString(call.getData());
|
||||
JSONArray jsonArray = JSON.parseArray(listJson);
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
JSONObject o = jsonArray.getJSONObject(i);
|
||||
TapdUser user = o.getObject("UserWorkspace", TapdUser.class);
|
||||
users.add(user);
|
||||
}
|
||||
return users;
|
||||
public List<PlatformUser> getTapdProjectUsers(String caseId) {
|
||||
IssuesRequest issueRequest = new IssuesRequest();
|
||||
issueRequest.setTestCaseId(caseId);
|
||||
AbstractIssuePlatform platform = IssueFactory.createPlatform(IssuesManagePlatform.Tapd.name(), issueRequest);
|
||||
return platform.getPlatformUser();
|
||||
}
|
||||
|
||||
public void deleteIssue(String id) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.track.service;
|
|||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.*;
|
||||
import io.metersphere.base.mapper.ext.ExtProjectMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestCaseReviewMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper;
|
||||
import io.metersphere.commons.constants.TestCaseReviewStatus;
|
||||
|
@ -18,6 +19,7 @@ import io.metersphere.service.UserService;
|
|||
import io.metersphere.track.dto.TestCaseReviewDTO;
|
||||
import io.metersphere.track.dto.TestReviewCaseDTO;
|
||||
import io.metersphere.track.dto.TestReviewDTOWithMetric;
|
||||
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.track.request.testreview.QueryCaseReviewRequest;
|
||||
import io.metersphere.track.request.testreview.QueryTestReviewRequest;
|
||||
import io.metersphere.track.request.testreview.ReviewRelevanceRequest;
|
||||
|
@ -65,7 +67,8 @@ public class TestCaseReviewService {
|
|||
TestCaseReviewTestCaseMapper testCaseReviewTestCaseMapper;
|
||||
@Resource
|
||||
MailService mailService;
|
||||
|
||||
@Resource
|
||||
ExtTestCaseMapper extTestCaseMapper;
|
||||
|
||||
public void saveTestCaseReview(SaveTestCaseReviewRequest reviewRequest) {
|
||||
checkCaseReviewExist(reviewRequest);
|
||||
|
@ -300,6 +303,16 @@ public class TestCaseReviewService {
|
|||
if (testCaseIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 如果是关联全部指令则从新查询未关联的案例
|
||||
if (testCaseIds.get(0).equals("all")) {
|
||||
QueryTestCaseRequest req = new QueryTestCaseRequest();
|
||||
req.setReviewId(request.getReviewId());
|
||||
req.setProjectId(request.getProjectId());
|
||||
List<TestCase> testCases = extTestCaseMapper.getTestCaseByNotInReview(req);
|
||||
if (!testCases.isEmpty()) {
|
||||
testCaseIds = testCases.stream().map(testCase -> testCase.getId()).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
|
||||
TestCaseReviewTestCaseMapper batchMapper = sqlSession.getMapper(TestCaseReviewTestCaseMapper.class);
|
||||
|
|
|
@ -26,10 +26,13 @@ import io.metersphere.excel.listener.EasyExcelListener;
|
|||
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.track.dto.TestCaseDTO;
|
||||
import io.metersphere.track.request.testcase.EditTestCaseRequest;
|
||||
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.track.request.testcase.TestCaseBatchRequest;
|
||||
import io.metersphere.xmind.XmindCaseParser;
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.ibatis.session.ExecutorType;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
|
@ -83,8 +86,12 @@ public class TestCaseService {
|
|||
TestCaseReviewTestCaseMapper testCaseReviewTestCaseMapper;
|
||||
@Resource
|
||||
TestCaseCommentService testCaseCommentService;
|
||||
@Resource
|
||||
FileService fileService;
|
||||
@Resource
|
||||
TestCaseFileMapper testCaseFileMapper;
|
||||
|
||||
public void addTestCase(TestCaseWithBLOBs testCase) {
|
||||
private TestCaseWithBLOBs addTestCase(TestCaseWithBLOBs testCase) {
|
||||
testCase.setName(testCase.getName());
|
||||
checkTestCaseExist(testCase);
|
||||
testCase.setId(UUID.randomUUID().toString());
|
||||
|
@ -93,6 +100,7 @@ public class TestCaseService {
|
|||
testCase.setNum(getNextNum(testCase.getProjectId()));
|
||||
testCase.setReviewStatus(TestCaseReviewStatus.Prepare.name());
|
||||
testCaseMapper.insert(testCase);
|
||||
return testCase;
|
||||
}
|
||||
|
||||
public List<TestCase> getTestCaseByNodeId(List<String> nodeIds) {
|
||||
|
@ -190,46 +198,24 @@ public class TestCaseService {
|
|||
* @return
|
||||
*/
|
||||
public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) {
|
||||
if (StringUtils.isNotBlank(request.getPlanId())) {
|
||||
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getPlanId());
|
||||
// request 传入要查询的 projectId 切换的项目ID
|
||||
}
|
||||
|
||||
List<TestCase> testCaseNames = extTestCaseMapper.getTestCaseNames(request);
|
||||
|
||||
if (StringUtils.isNotBlank(request.getPlanId())) {
|
||||
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
|
||||
testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(request.getPlanId());
|
||||
List<String> relevanceIds = testPlanTestCaseMapper.selectByExample(testPlanTestCaseExample).stream()
|
||||
.map(TestPlanTestCase::getCaseId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return testCaseNames.stream()
|
||||
.filter(testcase -> !relevanceIds.contains(testcase.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return testCaseNames;
|
||||
|
||||
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
|
||||
OrderRequest order = new OrderRequest();
|
||||
order.setName("sort");
|
||||
order.setType("desc");
|
||||
orderList.add(order);
|
||||
request.setOrders(orderList);
|
||||
return extTestCaseMapper.getTestCaseByNotInPlan(request);
|
||||
}
|
||||
|
||||
public List<TestCase> getReviewCase(QueryTestCaseRequest request) {
|
||||
|
||||
List<TestCase> testCases = extTestCaseMapper.getTestCaseNames(request);
|
||||
|
||||
if (StringUtils.isNotBlank(request.getReviewId())) {
|
||||
TestCaseReviewTestCaseExample testCaseReviewTestCaseExample = new TestCaseReviewTestCaseExample();
|
||||
testCaseReviewTestCaseExample.createCriteria().andReviewIdEqualTo(request.getReviewId());
|
||||
List<String> relevanceIds = testCaseReviewTestCaseMapper.selectByExample(testCaseReviewTestCaseExample).stream()
|
||||
.map(TestCaseReviewTestCase::getCaseId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return testCases.stream()
|
||||
.filter(testcase -> !relevanceIds.contains(testcase.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return testCases;
|
||||
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
|
||||
OrderRequest order = new OrderRequest();
|
||||
// 对模板导入的测试用例排序
|
||||
order.setName("sort");
|
||||
order.setType("desc");
|
||||
orderList.add(order);
|
||||
request.setOrders(orderList);
|
||||
return extTestCaseMapper.getTestCaseByNotInReview(request);
|
||||
}
|
||||
|
||||
|
||||
|
@ -596,4 +582,55 @@ public class TestCaseService {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String save(TestCaseWithBLOBs testCase, List<MultipartFile> files) {
|
||||
if (files == null) {
|
||||
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
||||
}
|
||||
final TestCaseWithBLOBs testCaseWithBLOBs = addTestCase(testCase);
|
||||
files.forEach(file -> {
|
||||
final FileMetadata fileMetadata = fileService.saveFile(file);
|
||||
TestCaseFile testCaseFile = new TestCaseFile();
|
||||
testCaseFile.setCaseId(testCaseWithBLOBs.getId());
|
||||
testCaseFile.setFileId(fileMetadata.getId());
|
||||
testCaseFileMapper.insert(testCaseFile);
|
||||
});
|
||||
return testCaseWithBLOBs.getId();
|
||||
}
|
||||
|
||||
public String edit(EditTestCaseRequest request, List<MultipartFile> files) {
|
||||
TestCaseWithBLOBs testCaseWithBLOBs = testCaseMapper.selectByPrimaryKey(request.getId());
|
||||
if (testCaseWithBLOBs == null) {
|
||||
MSException.throwException(Translator.get("edit_load_test_not_found") + request.getId());
|
||||
}
|
||||
|
||||
// 新选择了一个文件,删除原来的文件
|
||||
List<FileMetadata> updatedFiles = request.getUpdatedFileList();
|
||||
List<FileMetadata> originFiles = fileService.getFileMetadataByCaseId(request.getId());
|
||||
List<String> updatedFileIds = updatedFiles.stream().map(FileMetadata::getId).collect(Collectors.toList());
|
||||
List<String> originFileIds = originFiles.stream().map(FileMetadata::getId).collect(Collectors.toList());
|
||||
// 相减
|
||||
List<String> deleteFileIds = ListUtils.subtract(originFileIds, updatedFileIds);
|
||||
fileService.deleteFileRelatedByIds(deleteFileIds);
|
||||
|
||||
if (!CollectionUtils.isEmpty(deleteFileIds)) {
|
||||
TestCaseFileExample testCaseFileExample = new TestCaseFileExample();
|
||||
testCaseFileExample.createCriteria().andFileIdIn(deleteFileIds);
|
||||
testCaseFileMapper.deleteByExample(testCaseFileExample);
|
||||
}
|
||||
|
||||
|
||||
if (files != null) {
|
||||
files.forEach(file -> {
|
||||
final FileMetadata fileMetadata = fileService.saveFile(file);
|
||||
TestCaseFile testCaseFile = new TestCaseFile();
|
||||
testCaseFile.setFileId(fileMetadata.getId());
|
||||
testCaseFile.setCaseId(request.getId());
|
||||
testCaseFileMapper.insert(testCaseFile);
|
||||
});
|
||||
}
|
||||
|
||||
editTestCase(request);
|
||||
return request.getId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
|
|||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.*;
|
||||
import io.metersphere.base.mapper.ext.ExtProjectMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
|
||||
import io.metersphere.commons.constants.TestPlanStatus;
|
||||
|
@ -24,6 +25,7 @@ import io.metersphere.track.dto.TestPlanCaseDTO;
|
|||
import io.metersphere.track.dto.TestPlanDTO;
|
||||
import io.metersphere.track.dto.TestPlanDTOWithMetric;
|
||||
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
|
||||
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
|
||||
import io.metersphere.track.request.testplan.AddTestPlanRequest;
|
||||
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
|
||||
|
@ -78,6 +80,8 @@ public class TestPlanService {
|
|||
TestPlanProjectService testPlanProjectService;
|
||||
@Resource
|
||||
ProjectMapper projectMapper;
|
||||
@Resource
|
||||
ExtTestCaseMapper extTestCaseMapper;
|
||||
|
||||
public void addTestPlan(AddTestPlanRequest testPlan) {
|
||||
if (getTestPlanByName(testPlan.getName()).size() > 0) {
|
||||
|
@ -207,6 +211,16 @@ public class TestPlanService {
|
|||
return;
|
||||
}
|
||||
|
||||
// 如果是关联全部指令则从新查询未关联的案例
|
||||
if (testCaseIds.get(0).equals("all")) {
|
||||
QueryTestCaseRequest req = new QueryTestCaseRequest();
|
||||
req.setPlanId(request.getPlanId());
|
||||
req.setProjectId(request.getProjectId());
|
||||
List<TestCase> testCases = extTestCaseMapper.getTestCaseByNotInPlan(req);
|
||||
if (!testCases.isEmpty()) {
|
||||
testCaseIds = testCases.stream().map(testCase -> testCase.getId()).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
TestCaseExample testCaseExample = new TestCaseExample();
|
||||
testCaseExample.createCriteria().andIdIn(testCaseIds);
|
||||
|
||||
|
|
|
@ -248,6 +248,10 @@ public class XmindCaseParser {
|
|||
String nodePath = data.getNodePath();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
if (data.getName().length() > 50) {
|
||||
stringBuilder.append(data.getName() + ":" + Translator.get("test_case") + Translator.get("test_track.length_less_than") + "50 ;");
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(nodePath)) {
|
||||
String[] nodes = nodePath.split("/");
|
||||
if (nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
|
||||
|
@ -259,7 +263,7 @@ public class XmindCaseParser {
|
|||
stringBuilder.append(Translator.get("module_not_null") + "; ");
|
||||
break;
|
||||
} else if (nodes[i].trim().length() > 30) {
|
||||
stringBuilder.append(nodes[i].trim() + ":" + Translator.get("test_track.length_less_than") + "30 ;");
|
||||
stringBuilder.append(nodes[i].trim() + ":" + Translator.get("module") + Translator.get("test_track.length_less_than") + "30 ;");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
create table if not exists test_case_file
|
||||
(
|
||||
case_id varchar(64) null,
|
||||
file_id varchar(64) null,
|
||||
constraint test_case_file_unique_key
|
||||
unique (case_id, file_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
|
@ -34,7 +34,8 @@
|
|||
"html2canvas": "^1.0.0-rc.7",
|
||||
"jspdf": "^2.1.1",
|
||||
"yan-progress": "^1.0.3",
|
||||
"nprogress": "^0.2.0"
|
||||
"nprogress": "^0.2.0",
|
||||
"el-table-infinite-scroll": "^1.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.0",
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<el-input :disabled="isReadOnly" class="test-name" v-model="test.name" maxlength="60"
|
||||
:placeholder="$t('api_test.input_name')"
|
||||
show-word-limit>
|
||||
<el-select :disabled="isReadOnly" class="test-project" v-model="test.projectId" slot="prepend"
|
||||
<el-select filterable class="test-project" v-model="test.projectId" slot="prepend"
|
||||
:placeholder="$t('api_test.select_project')">
|
||||
<el-option v-for="project in projects" :key="project.id" :label="project.name" :value="project.id"/>
|
||||
</el-select>
|
||||
|
|
|
@ -156,37 +156,11 @@
|
|||
},
|
||||
save(callback) {
|
||||
this.change = false;
|
||||
let url = "/api/create";
|
||||
let bodyFiles = this.getBodyUploadFiles();
|
||||
this.result = this.$request(this.getOptions(url, bodyFiles), () => {
|
||||
let url = "/api/create/merge";
|
||||
this.result = this.$request(this.getOptions(url, this.selectIds), () => {
|
||||
if (callback) callback();
|
||||
});
|
||||
},
|
||||
getBodyUploadFiles() {
|
||||
let bodyUploadFiles = [];
|
||||
this.test.bodyUploadIds = [];
|
||||
this.test.scenarioDefinition.forEach(scenario => {
|
||||
scenario.requests.forEach(request => {
|
||||
if (request.body) {
|
||||
request.body.kvs.forEach(param => {
|
||||
if (param.files) {
|
||||
param.files.forEach(item => {
|
||||
if (item.file) {
|
||||
let fileId = getUUID().substring(0, 8);
|
||||
item.name = item.file.name;
|
||||
item.id = fileId;
|
||||
this.test.bodyUploadIds.push(fileId);
|
||||
bodyUploadFiles.push(item.file);
|
||||
// item.file = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return bodyUploadFiles;
|
||||
},
|
||||
runTest() {
|
||||
this.result = this.$post("/api/run", {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
|
||||
this.$success(this.$t('api_test.running'));
|
||||
|
@ -196,16 +170,14 @@
|
|||
this.test = ""
|
||||
});
|
||||
},
|
||||
getOptions(url, bodyFiles) {
|
||||
getOptions(url, selectIds) {
|
||||
|
||||
let formData = new FormData();
|
||||
if (bodyFiles) {
|
||||
bodyFiles.forEach(f => {
|
||||
formData.append("files", f);
|
||||
})
|
||||
}
|
||||
let requestJson = JSON.stringify(this.test);
|
||||
formData.append('request', new Blob([requestJson], {
|
||||
formData.append('request', new Blob([JSON.stringify(this.test)], {
|
||||
type: "application/json"
|
||||
}));
|
||||
|
||||
formData.append('selectIds', new Blob([JSON.stringify(Array.from(selectIds))], {
|
||||
type: "application/json"
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="$t('api_test.environment.environment_config')"
|
||||
:visible.sync="visible" class="environment-dialog"
|
||||
:visible.sync="visible" class="environment-dialog" width="60%"
|
||||
@close="close" append-to-body ref="environmentConfig">
|
||||
<el-container v-loading="result.loading">
|
||||
<ms-aside-item :enable-aside-hidden="false" :title="$t('api_test.environment.environment_list')"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<ms-api-variable-input :show-variable="showVariable" :is-read-only="isReadOnly" v-model="item.name" size="small" maxlength="200" @change="change"
|
||||
<ms-api-variable-input :show-copy="showCopy" :show-variable="showVariable" :is-read-only="isReadOnly" v-model="item.name" size="small" maxlength="200" @change="change"
|
||||
:placeholder="$t('api_test.variable_name')" show-word-limit/>
|
||||
</el-col>
|
||||
<el-col>
|
||||
|
@ -45,6 +45,10 @@
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showCopy: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="variable-input">
|
||||
<el-input :disabled="isReadOnly" :value="value" v-bind="$attrs" :size="size" @change="change" @input="input"/>
|
||||
<el-input class="el-input__inner_pd" :disabled="isReadOnly" :value="value" v-bind="$attrs" :size="size" @change="change" @input="input"/>
|
||||
<div :class="{'hidden': !showVariable}" class="variable-combine" v-if="value">
|
||||
<div class="variable">{{variable}}</div>
|
||||
<el-tooltip :content="$t('api_test.copied')" manual v-model="visible" placement="top" :visible-arrow="false">
|
||||
<div v-if="showCopy" class="variable">{{variable}}</div>
|
||||
<el-tooltip v-if="showCopy" :content="$t('api_test.copied')" manual v-model="visible" placement="top" :visible-arrow="false">
|
||||
<i class="el-icon-copy-document copy" @click="copy"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
@ -25,6 +25,10 @@
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showCopy: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
@ -70,6 +74,9 @@
|
|||
.variable-input {
|
||||
position: relative;
|
||||
}
|
||||
.el-input__inner_pd >>> .el-input__inner {
|
||||
padding-right: 135px;
|
||||
}
|
||||
|
||||
.variable-combine {
|
||||
color: #7F7F7F;
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
handleRemove(file) {
|
||||
this.$refs.upload.handleRemove(file);
|
||||
for (let i = 0; i < this.parameter.files.length; i++) {
|
||||
if (file.name === this.parameter.files[i].name) {
|
||||
if (file.file.name === this.parameter.files[i].file.name) {
|
||||
this.parameter.files.splice(i, 1);
|
||||
this.$refs.upload.handleRemove(file);
|
||||
break;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<el-form :model="commonConfig" :rules="rules" ref="commonConfig">
|
||||
|
||||
<span>{{$t('api_test.environment.globalVariable')}}</span>
|
||||
<ms-api-scenario-variables :items="commonConfig.variables"/>
|
||||
<ms-api-scenario-variables :show-copy="false" :items="commonConfig.variables"/>
|
||||
|
||||
<el-form-item>
|
||||
<el-switch v-model="commonConfig.enableHost" active-text="Hosts"/>
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
:active-text="$t('api_test.request.refer_to_environment')" @change="useEnvironmentChange">
|
||||
</el-switch>
|
||||
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox>
|
||||
<el-checkbox class="do-multipart-post" v-model="request.doMultipartPost">{{$t('api_test.request.do_multipart_post')}}</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small"
|
||||
|
@ -200,31 +201,36 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-tag {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.environment-display {
|
||||
font-size: 14px;
|
||||
}
|
||||
.el-tag {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.environment-name {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
.environment-display {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.adjust-margin-bottom {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.environment-name {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.environment-url-tip {
|
||||
color: #F56C6C;
|
||||
}
|
||||
.adjust-margin-bottom {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.follow-redirects-item {
|
||||
margin-left: 30px;
|
||||
}
|
||||
.environment-url-tip {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.follow-redirects-item {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.do-multipart-post {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="request-type">
|
||||
{{ request.showType() }}
|
||||
</div>
|
||||
<div class="request-method">
|
||||
<div class="request-method" :style="{'color': getColor(request.enable, request.showMethod())}">
|
||||
{{ request.showMethod() }}
|
||||
</div>
|
||||
<div class="request-name">
|
||||
|
@ -85,7 +85,13 @@ export default {
|
|||
selected: 0,
|
||||
visible: false,
|
||||
types: RequestFactory.TYPES,
|
||||
type: ""
|
||||
type: "",
|
||||
methodColorMap: new Map([
|
||||
['GET', "#61AFFE"], ['POST', '#49CC90'], ['PUT', '#fca130'],
|
||||
['PATCH', '#E2EE11'], ['DELETE', '#f93e3d'], ['OPTIONS', '#0EF5DA'],
|
||||
['HEAD', '#8E58E7'], ['CONNECT', '#90AFAE'],
|
||||
['DUBBO', '#C36EEF'],['SQL', '#0AEAD4'],['TCP', '#0A52DF'],
|
||||
])
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -156,6 +162,11 @@ export default {
|
|||
break;
|
||||
}
|
||||
},
|
||||
getColor(enable, method) {
|
||||
if (enable) {
|
||||
return this.methodColorMap.get(method);
|
||||
}
|
||||
},
|
||||
select(request) {
|
||||
if (!request) {
|
||||
return;
|
||||
|
|
|
@ -41,6 +41,10 @@
|
|||
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" type="primary" @click="runDebug">{{$t('api_test.request.debug')}}</el-button>
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane :label="$t('api_test.scenario.variables')" name="variables">
|
||||
<ms-api-scenario-variables :is-read-only="isReadOnly" :items="request.variables"
|
||||
:description="$t('api_test.scenario.kv_description')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.sql.sql_script')" name="sql">
|
||||
<div class="sql-content" >
|
||||
<ms-code-edit mode="sql" :read-only="isReadOnly" :modes="['sql']" :data.sync="request.query" theme="eclipse" ref="codeEdit"/>
|
||||
|
@ -74,10 +78,12 @@
|
|||
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
|
||||
import MsJsr233Processor from "../processor/Jsr233Processor";
|
||||
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
|
||||
import MsApiScenarioVariables from "../ApiScenarioVariables";
|
||||
|
||||
export default {
|
||||
name: "MsApiSqlRequestForm",
|
||||
components: {
|
||||
MsApiScenarioVariables,
|
||||
MsCodeEdit,
|
||||
MsJsr233Processor,
|
||||
MsDubboConsumerService,
|
||||
|
@ -96,7 +102,7 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
activeName: "sql",
|
||||
activeName: "variables",
|
||||
databaseConfigsOptions: [],
|
||||
rules: {
|
||||
name: [
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
currentConfig: new DatabaseConfig()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
configs() {
|
||||
this.currentConfig = new DatabaseConfig();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveConfig(config) {
|
||||
for (let item of this.configs) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="database-from">
|
||||
<div class="database-from" v-loading="result.loading">
|
||||
<el-form :model="currentConfig" :rules="rules" label-width="150px" size="small" :disabled="isReadOnly" ref="databaseFrom">
|
||||
|
||||
<el-form-item :label="$t('api_test.request.sql.dataSource')" prop="name">
|
||||
|
@ -39,6 +39,7 @@
|
|||
|
||||
<el-form-item>
|
||||
<div class="buttons">
|
||||
<el-button type="primary" v-show="currentConfig.id" size="small" @click="validate()">{{$t('校验')}}</el-button>
|
||||
<el-button type="primary" v-show="currentConfig.id" size="small" @click="save('update')">{{$t('commons.update')}}</el-button>
|
||||
<el-button type="primary" size="small" @click="save('add')">{{$t('commons.add')}}</el-button>
|
||||
</div>
|
||||
|
@ -81,6 +82,7 @@
|
|||
data() {
|
||||
return {
|
||||
drivers: DatabaseConfig.DRIVER_CLASS,
|
||||
result: {},
|
||||
currentConfig: new DatabaseConfig(),
|
||||
rules: {
|
||||
name: [
|
||||
|
@ -124,6 +126,11 @@
|
|||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
validate() {
|
||||
this.result = this.$post('/api/database/validate', this.currentConfig, () => {
|
||||
this.$success(this.$t('commons.connection_successful'));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -337,6 +337,7 @@ export class HTTPSamplerProxy extends DefaultTestElement {
|
|||
}
|
||||
|
||||
this.boolProp("HTTPSampler.use_keepalive", options.keepalive, true);
|
||||
this.boolProp("HTTPSampler.DO_MULTIPART_POST", options.doMultipartPost, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -346,6 +346,7 @@ export class HttpRequest extends Request {
|
|||
this.environment = options.environment;
|
||||
this.useEnvironment = options.useEnvironment;
|
||||
this.debugReport = undefined;
|
||||
this.doMultipartPost = options.doMultipartPost;
|
||||
this.connectTimeout = options.connectTimeout || 60 * 1000;
|
||||
this.responseTimeout = options.responseTimeout;
|
||||
this.followRedirects = options.followRedirects === undefined ? true : options.followRedirects;
|
||||
|
@ -476,13 +477,14 @@ export class SqlRequest extends Request {
|
|||
this.useEnvironment = options.useEnvironment;
|
||||
this.resultVariable = options.resultVariable;
|
||||
this.variableNames = options.variableNames;
|
||||
this.variables = [];
|
||||
this.debugReport = undefined;
|
||||
this.dataSource = options.dataSource;
|
||||
this.query = options.query;
|
||||
// this.queryType = options.queryType;
|
||||
this.queryTimeout = options.queryTimeout || 60000;
|
||||
|
||||
this.sets({args: KeyValue, attachmentArgs: KeyValue}, options);
|
||||
this.sets({args: KeyValue, attachmentArgs: KeyValue, variables: KeyValue}, options);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
|
@ -987,7 +989,7 @@ class JMXHttpRequest {
|
|||
this.connectTimeout = request.connectTimeout;
|
||||
this.responseTimeout = request.responseTimeout;
|
||||
this.followRedirects = request.followRedirects;
|
||||
|
||||
this.doMultipartPost = request.doMultipartPost;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1052,10 +1054,21 @@ class JMXTCPRequest {
|
|||
obj.set(scenario.environment.config.tcpConfig, true);
|
||||
return obj;
|
||||
}
|
||||
obj.set(scenario.tcpConfig, true);
|
||||
|
||||
this.copy(this, scenario.tcpConfig);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
copy(target, source) {
|
||||
for (let key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (source[key] !== undefined && !target[key]) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JMeterTestPlan extends Element {
|
||||
|
@ -1104,7 +1117,6 @@ class JMXGenerator {
|
|||
if (request.enable) {
|
||||
if (!request.isValid()) return;
|
||||
let sampler;
|
||||
|
||||
if (request instanceof DubboRequest) {
|
||||
sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig));
|
||||
} else if (request instanceof HttpRequest) {
|
||||
|
@ -1115,6 +1127,7 @@ class JMXGenerator {
|
|||
} else if (request instanceof SqlRequest) {
|
||||
request.dataSource = scenario.databaseConfigMap.get(request.dataSource);
|
||||
sampler = new JDBCSampler(request.name || "", request);
|
||||
this.addRequestVariables(sampler, request);
|
||||
} else if (request instanceof TCPRequest) {
|
||||
sampler = new TCPSampler(request.name || "", new JMXTCPRequest(request, scenario));
|
||||
}
|
||||
|
@ -1176,6 +1189,14 @@ class JMXGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
addRequestVariables(httpSamplerProxy, request) {
|
||||
let name = request.name + " Variables";
|
||||
let variables = this.filterKV(request.variables);
|
||||
if (variables && variables.length > 0) {
|
||||
httpSamplerProxy.put(new Arguments(name, variables));
|
||||
}
|
||||
}
|
||||
|
||||
addScenarioCookieManager(threadGroup, scenario) {
|
||||
if (scenario.enableCookieShare) {
|
||||
threadGroup.put(new CookieManager(scenario.name));
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
maxlength="30" show-word-limit
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<el-select :disabled="isReadOnly" v-model="testPlan.projectId"
|
||||
<el-select filterable v-model="testPlan.projectId"
|
||||
:placeholder="$t('load_test.select_project')">
|
||||
<el-option
|
||||
v-for="item in projects"
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
<el-form :model="memberForm" ref="form" :rules="orgMemberRule" label-position="right" label-width="100px"
|
||||
size="small">
|
||||
<el-form-item :label="$t('commons.member')" prop="userIds">
|
||||
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
|
||||
<el-select filterable v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in memberForm.userList"
|
||||
|
@ -114,7 +114,7 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.role')" prop="roleIds">
|
||||
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in memberForm.roles"
|
||||
|
@ -151,7 +151,7 @@
|
|||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.role')" prop="roleIds"
|
||||
:rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}">
|
||||
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in memberForm.allroles"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<el-input type="textarea" v-model="form.description"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('workspace.organization_name')" prop="organizationId">
|
||||
<el-select v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
|
||||
<el-select filterable v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in form.orgList"
|
||||
|
@ -65,7 +65,7 @@
|
|||
<el-input type="textarea" v-model="form.description"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('workspace.organization_name')" prop="organizationId">
|
||||
<el-select v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
|
||||
<el-select filterable v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in form.orgList1"
|
||||
|
@ -117,7 +117,7 @@
|
|||
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px"
|
||||
size="small">
|
||||
<el-form-item :label="$t('commons.member')" prop="userIds">
|
||||
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
|
||||
<el-select filterable v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in memberForm.userList"
|
||||
|
@ -130,7 +130,7 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.role')" prop="roleIds">
|
||||
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in memberForm.roles"
|
||||
|
@ -166,7 +166,7 @@
|
|||
<el-input v-model="memberForm.phone" autocomplete="off" :disabled="true"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.role')" prop="roleIds" :rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}">
|
||||
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
|
||||
class="select-width">
|
||||
<el-option
|
||||
v-for="item in memberForm.allroles"
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
:prop="'roles.' + index + '.id'"
|
||||
:rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.id" :placeholder="$t('role.please_choose_role')">
|
||||
<el-select filterable v-model="role.id" :placeholder="$t('role.please_choose_role')">
|
||||
<el-option
|
||||
v-for="item in activeRole(role)"
|
||||
:key="item.id"
|
||||
|
@ -93,7 +93,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('organization.select_organization'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.orgList"
|
||||
:key="item.id"
|
||||
|
@ -108,7 +108,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.wsList"
|
||||
:key="item.id"
|
||||
|
@ -123,7 +123,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.wsList"
|
||||
:key="item.id"
|
||||
|
@ -138,7 +138,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.wsList"
|
||||
:key="item.id"
|
||||
|
@ -187,7 +187,7 @@
|
|||
:prop="'roles.' + index + '.id'"
|
||||
:rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.id" :placeholder="$t('role.please_choose_role')" :disabled="!!role.id">
|
||||
<el-select filterable v-model="role.id" :placeholder="$t('role.please_choose_role')" :disabled="!!role.id">
|
||||
<el-option
|
||||
v-for="item in activeRole(role)"
|
||||
:key="item.id"
|
||||
|
@ -204,7 +204,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('organization.select_organization'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.orgList"
|
||||
:key="item.id"
|
||||
|
@ -219,7 +219,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.wsList"
|
||||
:key="item.id"
|
||||
|
@ -234,7 +234,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.wsList"
|
||||
:key="item.id"
|
||||
|
@ -249,7 +249,7 @@
|
|||
:prop="'roles.' + index + '.ids'"
|
||||
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
|
||||
>
|
||||
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
|
||||
<el-option
|
||||
v-for="item in form.wsList"
|
||||
:key="item.id"
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<span>
|
||||
<ms-tag v-if="value === 'Prepare'" type="info" :content="$t('test_track.case.status_prepare')"/>
|
||||
<ms-tag v-if="value === 'Pass'" type="success" :content="$t('test_track.case.status_pass')"/>
|
||||
<ms-tag v-if="value === 'UnPass'" type="danger" :content="$t('test_track.case.status_un_pass')"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsTag from "@/business/components/common/components/MsTag";
|
||||
|
||||
export default {
|
||||
name: "ReviewStatus",
|
||||
components: {MsTag},
|
||||
props: {
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -45,7 +45,6 @@
|
|||
width: 20px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.show-more-btn-title {
|
||||
|
|
|
@ -209,6 +209,59 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row style="margin-top: 15px;margin-bottom: 10px">
|
||||
<el-col :offset="2" :span="20">附件:
|
||||
<el-upload
|
||||
action=""
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
:http-request="handleUpload"
|
||||
:on-exceed="handleExceed"
|
||||
multiple
|
||||
:limit="3"
|
||||
:file-list="fileList">
|
||||
<el-button icon="el-icon-plus" size="mini"></el-button>
|
||||
<!-- <span slot="tip" class="el-upload__tip"></span>-->
|
||||
</el-upload>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="20">
|
||||
<el-table class="basic-config" :data="tableData">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('load_test.file_name')">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="size"
|
||||
:label="$t('load_test.file_size')">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
:label="$t('load_test.file_type')">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('load_test.last_modify_time')">
|
||||
<template v-slot:default="scope">
|
||||
<i class="el-icon-time"/>
|
||||
<span class="last-modified">{{ scope.row.updateTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button @click="handleDownload(scope.row)" :disabled="!scope.row.id || readOnly" type="primary"
|
||||
icon="el-icon-download"
|
||||
size="mini" circle/>
|
||||
<el-button :disabled="readOnly" @click="handleDelete(scope.row, scope.$index)" type="danger"
|
||||
icon="el-icon-delete" size="mini"
|
||||
circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template v-slot:footer>
|
||||
|
@ -234,6 +287,7 @@ import {TokenKey, WORKSPACE_ID} from '../../../../../common/js/constants';
|
|||
import MsDialogFooter from '../../../common/components/MsDialogFooter'
|
||||
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
|
||||
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
|
||||
import {Message} from "element-ui";
|
||||
|
||||
export default {
|
||||
name: "TestCaseEdit",
|
||||
|
@ -263,6 +317,9 @@ export default {
|
|||
maintainerOptions: [],
|
||||
testOptions: [],
|
||||
workspaceId: '',
|
||||
fileList: [],
|
||||
tableData: [],
|
||||
uploadList: [],
|
||||
rules: {
|
||||
name: [
|
||||
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
|
||||
|
@ -339,6 +396,7 @@ export default {
|
|||
tmp.steps = JSON.parse(testCase.steps);
|
||||
Object.assign(this.form, tmp);
|
||||
this.form.module = testCase.nodeId;
|
||||
this.getFileMetaData(testCase);
|
||||
} else {
|
||||
if (this.selectNode.data) {
|
||||
this.form.module = this.selectNode.data.id;
|
||||
|
@ -358,6 +416,24 @@ export default {
|
|||
this.reload();
|
||||
this.dialogFormVisible = true;
|
||||
},
|
||||
getFileMetaData(testCase) {
|
||||
this.fileList = [];
|
||||
this.tableData = [];
|
||||
this.uploadList = [];
|
||||
this.result = this.$get("test/case/file/metadata/" + testCase.id, response => {
|
||||
let files = response.data;
|
||||
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
// deep copy
|
||||
this.fileList = JSON.parse(JSON.stringify(files));
|
||||
this.tableData = JSON.parse(JSON.stringify(files));
|
||||
this.tableData.map(f => {
|
||||
f.size = f.size + ' Bytes';
|
||||
});
|
||||
})
|
||||
},
|
||||
handleAddStep(index, data) {
|
||||
let step = {};
|
||||
step.num = data.num + 1;
|
||||
|
@ -400,7 +476,8 @@ export default {
|
|||
if (valid) {
|
||||
let param = this.buildParam();
|
||||
if (this.validate(param)) {
|
||||
this.result = this.$post('/test/case/' + this.operationType, param, () => {
|
||||
let option = this.getOption(param);
|
||||
this.result = this.$request(option, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
if (this.operationType == 'add' && this.isCreateContinue) {
|
||||
this.form.name = '';
|
||||
|
@ -411,6 +488,7 @@ export default {
|
|||
result: ''
|
||||
}];
|
||||
this.form.remark = '';
|
||||
this.form.uploadList = [];
|
||||
this.$emit("refresh");
|
||||
return;
|
||||
}
|
||||
|
@ -444,6 +522,32 @@ export default {
|
|||
}
|
||||
return param;
|
||||
},
|
||||
getOption(param) {
|
||||
let formData = new FormData();
|
||||
let url = '/test/case/' + this.operationType;
|
||||
this.uploadList.forEach(f => {
|
||||
formData.append("file", f);
|
||||
});
|
||||
|
||||
param.updatedFileList = this.fileList;
|
||||
|
||||
let requestJson = JSON.stringify(param, function (key, value) {
|
||||
return key === "file" ? undefined : value
|
||||
});
|
||||
|
||||
formData.append('request', new Blob([requestJson], {
|
||||
type: "application/json"
|
||||
}));
|
||||
|
||||
return {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
validate(param) {
|
||||
for (let i = 0; i < param.steps.length; i++) {
|
||||
if ((param.steps[i].desc && param.steps[i].desc.length > 300) ||
|
||||
|
@ -527,7 +631,88 @@ export default {
|
|||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
handleExceed() {
|
||||
this.$error(this.$t('load_test.file_size_limit'));
|
||||
},
|
||||
beforeUpload(file) {
|
||||
if (!this.fileValidator(file)) {
|
||||
/// todo: 显示错误信息
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.tableData.filter(f => f.name === file.name).length > 0) {
|
||||
this.$error(this.$t('load_test.delete_file'));
|
||||
return false;
|
||||
}
|
||||
|
||||
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
|
||||
|
||||
this.tableData.push({
|
||||
name: file.name,
|
||||
size: file.size + ' Bytes', /// todo: 按照大小显示Byte、KB、MB等
|
||||
type: type.toUpperCase(),
|
||||
updateTime: file.lastModified,
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
handleUpload(uploadResources) {
|
||||
this.uploadList.push(uploadResources.file);
|
||||
},
|
||||
handleDownload(file) {
|
||||
let data = {
|
||||
name: file.name,
|
||||
id: file.id,
|
||||
};
|
||||
let config = {
|
||||
url: '/test/case/file/download',
|
||||
method: 'post',
|
||||
data: data,
|
||||
responseType: 'blob'
|
||||
};
|
||||
this.result = this.$request(config).then(response => {
|
||||
const content = response.data;
|
||||
const blob = new Blob([content]);
|
||||
if ("download" in document.createElement("a")) {
|
||||
// 非IE下载
|
||||
// chrome/firefox
|
||||
let aTag = document.createElement('a');
|
||||
aTag.download = file.name;
|
||||
aTag.href = URL.createObjectURL(blob);
|
||||
aTag.click();
|
||||
URL.revokeObjectURL(aTag.href)
|
||||
} else {
|
||||
// IE10+下载
|
||||
navigator.msSaveBlob(blob, this.filename)
|
||||
}
|
||||
}).catch(e => {
|
||||
Message.error({message: e.message, showClose: true});
|
||||
});
|
||||
},
|
||||
handleDelete(file, index) {
|
||||
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(file, index);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleDelete(file, index) {
|
||||
this.fileList.splice(index, 1);
|
||||
this.tableData.splice(index, 1);
|
||||
let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
|
||||
if (i > -1) {
|
||||
this.uploadList.splice(i, 1);
|
||||
}
|
||||
},
|
||||
fileValidator(file) {
|
||||
/// todo: 是否需要对文件内容和大小做限制
|
||||
return file.size > 0;
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
trigger="hover"
|
||||
>
|
||||
<test-case-detail :test-case="scope.row"/>
|
||||
<p slot="reference">{{ scope.row.name }}</p>
|
||||
<span slot="reference">{{ scope.row.name }}</span>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -107,7 +107,7 @@
|
|||
:label="$t('test_track.case.status')">
|
||||
<template v-slot:default="scope">
|
||||
<span class="el-dropdown-link">
|
||||
<status-table-item :value="scope.row.reviewStatus"/>
|
||||
<review-status :value="scope.row.reviewStatus"/>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -174,6 +174,7 @@
|
|||
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
|
||||
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
|
||||
import TestCaseDetail from "./TestCaseDetail";
|
||||
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
||||
export default {
|
||||
name: "TestCaseList",
|
||||
components: {
|
||||
|
@ -192,7 +193,8 @@
|
|||
ShowMoreBtn,
|
||||
BatchEdit,
|
||||
StatusTableItem,
|
||||
TestCaseDetail
|
||||
TestCaseDetail,
|
||||
ReviewStatus
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -222,9 +224,9 @@
|
|||
{text: this.$t('commons.api'), value: 'api'}
|
||||
],
|
||||
statusFilters: [
|
||||
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
|
||||
{text: this.$t('test_track.plan_view.pass'), value: 'Pass'},
|
||||
{text: '未通过', value: 'UnPass'},
|
||||
{text: this.$t('test_track.case.status_prepare'), value: 'Prepare'},
|
||||
{text: this.$t('test_track.case.status_pass'), value: 'Pass'},
|
||||
{text: this.$t('test_track.case.status_un_pass'), value: 'UnPass'},
|
||||
],
|
||||
showMore: false,
|
||||
buttons: [
|
||||
|
|
|
@ -1,53 +1,56 @@
|
|||
<template>
|
||||
<el-menu :unique-opened="true" mode="horizontal" active-text-color="write"
|
||||
class="project_menu">
|
||||
<el-submenu index="1" popper-class="submenu">
|
||||
<template v-slot:title>
|
||||
<span class="menu-title">{{'[' + title + ']'}}</span>
|
||||
<span> {{currentData == null ? '' : currentData.name}} </span>
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<div style="height:400px;">
|
||||
<el-scrollbar style="height:100%">
|
||||
<label v-for="(item,index) in data" :key="index">
|
||||
<el-menu-item @click="changeData(item)">
|
||||
{{item.name}}
|
||||
<i class="el-icon-check" v-if="currentData && item.id === currentData.id"></i>
|
||||
</el-menu-item>
|
||||
</label>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
<div>
|
||||
<span class="menu-title">{{'[' + title + ']'}}</span>
|
||||
<el-select filterable slot="prepend" v-model="value" @change="changeData" class="project_menu"
|
||||
size="small">
|
||||
<el-option v-for="(item,index) in data" :key="index" :label="item.name" :value="index"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SelectMenu",
|
||||
props: {
|
||||
data: {
|
||||
type: Array
|
||||
},
|
||||
currentData: {
|
||||
type: Object
|
||||
},
|
||||
title: {
|
||||
type: String
|
||||
}
|
||||
export default {
|
||||
name: "SelectMenu",
|
||||
props: {
|
||||
data: {
|
||||
type: Array
|
||||
},
|
||||
methods: {
|
||||
changeData(data) {
|
||||
this.$emit("dataChange", data);
|
||||
currentData: {
|
||||
type: Object
|
||||
},
|
||||
title: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentData(data) {
|
||||
if (data != undefined && data != null) {
|
||||
this.value = data.name;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeData(index) {
|
||||
this.$emit("dataChange", this.data[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.project_menu {
|
||||
width: 214px;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
color: darkgrey;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
refresh() {
|
||||
this.selectNodeIds = [];
|
||||
this.selectParentNodes = [];
|
||||
this.$refs.testCaseRelevance.search();
|
||||
this.getNodeTreeByPlanId();
|
||||
},
|
||||
initData() {
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
|
||||
<el-container class="main-content">
|
||||
<el-aside class="tree-aside" width="250px">
|
||||
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName : $t('test_track.switch_project') }}</el-link>
|
||||
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :
|
||||
$t('test_track.switch_project') }}
|
||||
</el-link>
|
||||
<node-tree class="node-tree"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
@refresh="refresh"
|
||||
|
@ -21,11 +23,13 @@
|
|||
|
||||
<el-container>
|
||||
<el-main class="case-content">
|
||||
<ms-table-header :condition.sync="condition" @search="getCaseNames" title="" :show-create="false"/>
|
||||
<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>
|
||||
<el-table
|
||||
:data="testCases"
|
||||
@filter-change="filter"
|
||||
row-key="id"
|
||||
@mouseleave.passive="leave"
|
||||
v-el-table-infinite-scroll="loadData"
|
||||
@select-all="handleSelectAll"
|
||||
@select="handleSelectionChange"
|
||||
height="50vh"
|
||||
|
@ -63,7 +67,9 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="text-align: center">共 {{testCases.length}} 条</div>
|
||||
|
||||
<div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>
|
||||
<div style="text-align: center">共 {{total}} 条</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
@ -91,6 +97,7 @@
|
|||
import MsTableHeader from "../../../../common/components/MsTableHeader";
|
||||
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
|
||||
import SwitchProject from "../../../case/components/SwitchProject";
|
||||
import elTableInfiniteScroll from 'el-table-infinite-scroll';
|
||||
|
||||
export default {
|
||||
name: "TestCaseRelevance",
|
||||
|
@ -104,6 +111,9 @@
|
|||
MsTableHeader,
|
||||
SwitchProject
|
||||
},
|
||||
directives: {
|
||||
'el-table-infinite-scroll': elTableInfiniteScroll
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
|
@ -117,6 +127,10 @@
|
|||
projectId: '',
|
||||
projectName: '',
|
||||
projects: [],
|
||||
pageSize: 50,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
lineStatus: true,
|
||||
condition: {
|
||||
components: TEST_CASE_CONFIGS
|
||||
},
|
||||
|
@ -140,12 +154,15 @@
|
|||
},
|
||||
watch: {
|
||||
planId() {
|
||||
this.initData();
|
||||
this.condition.planId = this.planId;
|
||||
},
|
||||
selectNodeIds() {
|
||||
this.getCaseNames();
|
||||
if (this.dialogFormVisible) {
|
||||
this.search();
|
||||
}
|
||||
},
|
||||
projectId() {
|
||||
this.condition.projectId = this.projectId;
|
||||
this.getProjectNode();
|
||||
}
|
||||
},
|
||||
|
@ -155,13 +172,17 @@
|
|||
methods: {
|
||||
openTestCaseRelevanceDialog() {
|
||||
this.getProject();
|
||||
this.initData();
|
||||
this.dialogFormVisible = true;
|
||||
},
|
||||
saveCaseRelevance() {
|
||||
let param = {};
|
||||
param.planId = this.planId;
|
||||
param.testCaseIds = [...this.selectIds];
|
||||
param.projectId = this.projectId;
|
||||
// 选择全选则全部加入到评审,无论是否加载完全部
|
||||
if (this.testCases.length === param.testCaseIds.length) {
|
||||
param.testCaseIds = ['all'];
|
||||
}
|
||||
this.result = this.$post('/test/plan/relevance', param, () => {
|
||||
this.selectIds.clear();
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
|
@ -169,25 +190,34 @@
|
|||
this.$emit('refresh');
|
||||
});
|
||||
},
|
||||
getCaseNames() {
|
||||
buildPagePath(path) {
|
||||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
search() {
|
||||
this.currentPage = 1;
|
||||
this.testCases = [];
|
||||
this.getTestCases();
|
||||
},
|
||||
getTestCases() {
|
||||
if (this.planId) {
|
||||
// param.planId = this.planId;
|
||||
this.condition.planId = this.planId;
|
||||
}
|
||||
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
||||
// param.nodeIds = this.selectNodeIds;
|
||||
this.condition.nodeIds = this.selectNodeIds;
|
||||
} else {
|
||||
this.condition.nodeIds = [];
|
||||
}
|
||||
|
||||
if (this.projectId) {
|
||||
this.condition.projectId = this.projectId;
|
||||
this.result = this.$post('/test/case/name', this.condition, response => {
|
||||
this.testCases = response.data;
|
||||
this.testCases.forEach(item => {
|
||||
this.result = this.$post(this.buildPagePath('/test/case/name'), this.condition, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
let tableData = data.listObject;
|
||||
tableData.forEach(item => {
|
||||
item.checked = false;
|
||||
});
|
||||
this.testCases = this.testCases.concat(tableData);
|
||||
this.lineStatus = tableData.length === 50 && this.testCases.length < this.total;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -198,7 +228,6 @@
|
|||
this.selectIds.add(item.id);
|
||||
});
|
||||
} else {
|
||||
// this.selectIds.clear();
|
||||
this.testCases.forEach(item => {
|
||||
if (this.selectIds.has(item.id)) {
|
||||
this.selectIds.delete(item.id);
|
||||
|
@ -217,32 +246,37 @@
|
|||
this.selectNodeIds = nodeIds;
|
||||
this.selectNodeNames = nodeNames;
|
||||
},
|
||||
initData() {
|
||||
this.getCaseNames();
|
||||
this.getAllNodeTreeByPlanId();
|
||||
},
|
||||
refresh() {
|
||||
this.close();
|
||||
},
|
||||
loadData() {
|
||||
if (this.dialogFormVisible) {
|
||||
if (this.lineStatus) {
|
||||
this.currentPage += 1;
|
||||
this.getTestCases();
|
||||
}
|
||||
}
|
||||
},
|
||||
getAllNodeTreeByPlanId() {
|
||||
if (this.planId) {
|
||||
let param = {
|
||||
testPlanId: this.planId,
|
||||
projectId: this.projectId
|
||||
};
|
||||
this.result = this.$post("/case/node/list/all/plan", param , response => {
|
||||
this.result = this.$post("/case/node/list/all/plan", param, response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.lineStatus = false;
|
||||
this.selectIds.clear();
|
||||
this.selectNodeIds = [];
|
||||
this.selectNodeNames = [];
|
||||
},
|
||||
filter(filters) {
|
||||
_filter(filters, this.condition);
|
||||
this.initData();
|
||||
this.search();
|
||||
},
|
||||
toggleSelection(rows) {
|
||||
rows.forEach(row => {
|
||||
|
@ -256,7 +290,7 @@
|
|||
},
|
||||
getProject() {
|
||||
if (this.planId) {
|
||||
this.$post("/test/plan/project/", {planId: this.planId},res => {
|
||||
this.$post("/test/plan/project/", {planId: this.planId}, res => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
this.projects = data;
|
||||
|
@ -267,7 +301,7 @@
|
|||
}
|
||||
},
|
||||
switchProject() {
|
||||
this.$refs.switchProject.open({id: this.planId, url: '/test/plan/project/',type: 'plan'});
|
||||
this.$refs.switchProject.open({id: this.planId, url: '/test/plan/project/', type: 'plan'});
|
||||
},
|
||||
getProjectNode(projectId) {
|
||||
const index = this.projects.findIndex(project => project.id === projectId);
|
||||
|
@ -278,9 +312,9 @@
|
|||
this.projectId = projectId;
|
||||
}
|
||||
this.result = this.$post("/case/node/list/all/plan",
|
||||
{testPlanId: this.planId, projectId: this.projectId} , response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
{testPlanId: this.planId, projectId: this.projectId}, response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
|
||||
this.selectNodeIds = [];
|
||||
}
|
||||
|
|
|
@ -180,6 +180,7 @@
|
|||
:disabled="isReadOnly"
|
||||
v-model="scope.row.executeResult"
|
||||
@change="stepResultChange()"
|
||||
filterable
|
||||
size="mini">
|
||||
<el-option :label="$t('test_track.plan_view.pass')" value="Pass"
|
||||
style="color: #7ebf50;"></el-option>
|
||||
|
@ -227,9 +228,10 @@
|
|||
{{ $t('test_track.issue.please_choose_current_owner') }}
|
||||
<el-select v-model="testCase.tapdUsers"
|
||||
multiple
|
||||
filterable
|
||||
style="width: 20%"
|
||||
:placeholder="$t('test_track.issue.please_choose_current_owner')"
|
||||
collapse-tags>
|
||||
collapse-tags size="small">
|
||||
<el-option v-for="(userInfo, index) in users" :key="index" :label="userInfo.user"
|
||||
:value="userInfo.user"/>
|
||||
</el-select>
|
||||
|
|
|
@ -122,13 +122,13 @@
|
|||
>
|
||||
<ckeditor :editor="editor" disabled :config="editorConfig"
|
||||
v-model="scope.row.description"/>
|
||||
<el-button slot="reference" type="text">{{$t('test_track.issue.preview')}}</el-button>
|
||||
<el-button slot="reference" type="text">{{ $t('test_track.issue.preview') }}</el-button>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="platform" :label="$t('test_track.issue.platform')"/>
|
||||
</el-table>
|
||||
<el-button slot="reference" type="text">{{scope.row.issuesSize}}</el-button>
|
||||
<el-button slot="reference" type="text">{{ scope.row.issuesSize }}</el-button>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -136,6 +136,8 @@
|
|||
|
||||
<el-table-column
|
||||
prop="executorName"
|
||||
:filters="executorFilters"
|
||||
column-key="executor"
|
||||
:label="$t('test_track.plan_view.executor')">
|
||||
</el-table-column>
|
||||
|
||||
|
@ -152,18 +154,18 @@
|
|||
</span>
|
||||
<el-dropdown-menu slot="dropdown" chang>
|
||||
<el-dropdown-item :disabled="!isTestManagerOrTestUser" :command="{id: scope.row.id, status: 'Pass'}">
|
||||
{{$t('test_track.plan_view.pass')}}
|
||||
{{ $t('test_track.plan_view.pass') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="!isTestManagerOrTestUser"
|
||||
:command="{id: scope.row.id, status: 'Failure'}">
|
||||
{{$t('test_track.plan_view.failure')}}
|
||||
{{ $t('test_track.plan_view.failure') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="!isTestManagerOrTestUser"
|
||||
:command="{id: scope.row.id, status: 'Blocking'}">
|
||||
{{$t('test_track.plan_view.blocking')}}
|
||||
{{ $t('test_track.plan_view.blocking') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :disabled="!isTestManagerOrTestUser" :command="{id: scope.row.id, status: 'Skip'}">
|
||||
{{$t('test_track.plan_view.skip')}}
|
||||
{{ $t('test_track.plan_view.skip') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
@ -210,386 +212,391 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ExecutorEdit from './ExecutorEdit';
|
||||
import StatusEdit from './StatusEdit';
|
||||
import TestPlanTestCaseEdit from "./TestPlanTestCaseEdit";
|
||||
import MsTipButton from '../../../../common/components/MsTipButton';
|
||||
import MsTablePagination from '../../../../common/pagination/TablePagination';
|
||||
import MsTableHeader from '../../../../common/components/MsTableHeader';
|
||||
import MsTableButton from '../../../../common/components/MsTableButton';
|
||||
import NodeBreadcrumb from '../../../common/NodeBreadcrumb';
|
||||
import ExecutorEdit from './ExecutorEdit';
|
||||
import StatusEdit from './StatusEdit';
|
||||
import TestPlanTestCaseEdit from "./TestPlanTestCaseEdit";
|
||||
import MsTipButton from '../../../../common/components/MsTipButton';
|
||||
import MsTablePagination from '../../../../common/pagination/TablePagination';
|
||||
import MsTableHeader from '../../../../common/components/MsTableHeader';
|
||||
import MsTableButton from '../../../../common/components/MsTableButton';
|
||||
import NodeBreadcrumb from '../../../common/NodeBreadcrumb';
|
||||
|
||||
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, TokenKey, WORKSPACE_ID} from '../../../../../../common/js/constants';
|
||||
import {_filter, _sort, checkoutTestManagerOrTestUser, hasRoles} from '../../../../../../common/js/utils';
|
||||
import PriorityTableItem from "../../../common/tableItems/planview/PriorityTableItem";
|
||||
import StatusTableItem from "../../../common/tableItems/planview/StatusTableItem";
|
||||
import TypeTableItem from "../../../common/tableItems/planview/TypeTableItem";
|
||||
import MethodTableItem from "../../../common/tableItems/planview/MethodTableItem";
|
||||
import MsTableOperator from "../../../../common/components/MsTableOperator";
|
||||
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
|
||||
import TestReportTemplateList from "./TestReportTemplateList";
|
||||
import TestCaseReportView from "./report/TestCaseReportView";
|
||||
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
|
||||
import ShowMoreBtn from "../../../case/components/ShowMoreBtn";
|
||||
import BatchEdit from "../../../case/components/BatchEdit";
|
||||
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
|
||||
import {hub} from "@/business/components/track/plan/event-bus";
|
||||
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, TokenKey, WORKSPACE_ID} from "@/common/js/constants";
|
||||
import {_filter, _sort, checkoutTestManagerOrTestUser, hasRoles} from "@/common/js/utils";
|
||||
import PriorityTableItem from "../../../common/tableItems/planview/PriorityTableItem";
|
||||
import StatusTableItem from "../../../common/tableItems/planview/StatusTableItem";
|
||||
import TypeTableItem from "../../../common/tableItems/planview/TypeTableItem";
|
||||
import MethodTableItem from "../../../common/tableItems/planview/MethodTableItem";
|
||||
import MsTableOperator from "../../../../common/components/MsTableOperator";
|
||||
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
|
||||
import TestReportTemplateList from "./TestReportTemplateList";
|
||||
import TestCaseReportView from "./report/TestCaseReportView";
|
||||
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
|
||||
import ShowMoreBtn from "../../../case/components/ShowMoreBtn";
|
||||
import BatchEdit from "../../../case/components/BatchEdit";
|
||||
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
|
||||
import {hub} from "@/business/components/track/plan/event-bus";
|
||||
|
||||
export default {
|
||||
name: "TestPlanTestCaseList",
|
||||
components: {
|
||||
TestCaseReportView,
|
||||
TestReportTemplateList,
|
||||
MsTableOperatorButton,
|
||||
MsTableOperator,
|
||||
MethodTableItem,
|
||||
TypeTableItem,
|
||||
StatusTableItem,
|
||||
PriorityTableItem, StatusEdit, ExecutorEdit, MsTipButton, MsTablePagination,
|
||||
TestPlanTestCaseEdit, MsTableHeader, NodeBreadcrumb, MsTableButton, ShowMoreBtn,
|
||||
BatchEdit
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
deletePath: "/test/case/delete",
|
||||
condition: {
|
||||
components: TEST_CASE_CONFIGS
|
||||
},
|
||||
showMyTestCase: false,
|
||||
tableData: [],
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
selectRows: new Set(),
|
||||
testPlan: {},
|
||||
isReadOnly: false,
|
||||
isTestManagerOrTestUser: false,
|
||||
priorityFilters: [
|
||||
{text: 'P0', value: 'P0'},
|
||||
{text: 'P1', value: 'P1'},
|
||||
{text: 'P2', value: 'P2'},
|
||||
{text: 'P3', value: 'P3'}
|
||||
],
|
||||
methodFilters: [
|
||||
{text: this.$t('test_track.case.manual'), value: 'manual'},
|
||||
{text: this.$t('test_track.case.auto'), value: 'auto'}
|
||||
],
|
||||
typeFilters: [
|
||||
{text: this.$t('commons.functional'), value: 'functional'},
|
||||
{text: this.$t('commons.performance'), value: 'performance'},
|
||||
{text: this.$t('commons.api'), value: 'api'}
|
||||
],
|
||||
statusFilters: [
|
||||
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
|
||||
{text: this.$t('test_track.plan_view.pass'), value: 'Pass'},
|
||||
{text: this.$t('test_track.plan_view.failure'), value: 'Failure'},
|
||||
{text: this.$t('test_track.plan_view.blocking'), value: 'Blocking'},
|
||||
{text: this.$t('test_track.plan_view.skip'), value: 'Skip'},
|
||||
{text: this.$t('test_track.plan.plan_status_running'), value: 'Underway'},
|
||||
],
|
||||
showMore: false,
|
||||
buttons: [
|
||||
{
|
||||
name: this.$t('test_track.case.batch_edit_case'), handleClick: this.handleBatchEdit
|
||||
},
|
||||
{
|
||||
name: this.$t('test_track.case.batch_unlink'), handleClick: this.handleDeleteBatch
|
||||
}
|
||||
],
|
||||
typeArr: [
|
||||
{id: 'status', name: this.$t('test_track.plan_view.execute_result')},
|
||||
{id: 'executor', name: this.$t('test_track.plan_view.executor')},
|
||||
],
|
||||
valueArr: {
|
||||
executor: [],
|
||||
status: [
|
||||
{name: this.$t('test_track.plan_view.pass'), id: 'Pass'},
|
||||
{name: this.$t('test_track.plan_view.failure'), id: 'Failure'},
|
||||
{name: this.$t('test_track.plan_view.blocking'), id: 'Blocking'},
|
||||
{name: this.$t('test_track.plan_view.skip'), id: 'Skip'}
|
||||
]
|
||||
},
|
||||
editor: ClassicEditor,
|
||||
editorConfig: {
|
||||
// 'increaseIndent','decreaseIndent'
|
||||
toolbar: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
props: {
|
||||
planId: {
|
||||
type: String
|
||||
export default {
|
||||
name: "TestPlanTestCaseList",
|
||||
components: {
|
||||
TestCaseReportView,
|
||||
TestReportTemplateList,
|
||||
MsTableOperatorButton,
|
||||
MsTableOperator,
|
||||
MethodTableItem,
|
||||
TypeTableItem,
|
||||
StatusTableItem,
|
||||
PriorityTableItem, StatusEdit, ExecutorEdit, MsTipButton, MsTablePagination,
|
||||
TestPlanTestCaseEdit, MsTableHeader, NodeBreadcrumb, MsTableButton, ShowMoreBtn,
|
||||
BatchEdit
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
deletePath: "/test/case/delete",
|
||||
condition: {
|
||||
components: TEST_CASE_CONFIGS
|
||||
},
|
||||
selectNodeIds: {
|
||||
type: Array
|
||||
showMyTestCase: false,
|
||||
tableData: [],
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
selectRows: new Set(),
|
||||
testPlan: {},
|
||||
isReadOnly: false,
|
||||
isTestManagerOrTestUser: false,
|
||||
priorityFilters: [
|
||||
{text: 'P0', value: 'P0'},
|
||||
{text: 'P1', value: 'P1'},
|
||||
{text: 'P2', value: 'P2'},
|
||||
{text: 'P3', value: 'P3'}
|
||||
],
|
||||
methodFilters: [
|
||||
{text: this.$t('test_track.case.manual'), value: 'manual'},
|
||||
{text: this.$t('test_track.case.auto'), value: 'auto'}
|
||||
],
|
||||
typeFilters: [
|
||||
{text: this.$t('commons.functional'), value: 'functional'},
|
||||
{text: this.$t('commons.performance'), value: 'performance'},
|
||||
{text: this.$t('commons.api'), value: 'api'}
|
||||
],
|
||||
statusFilters: [
|
||||
{text: this.$t('test_track.plan.plan_status_prepare'), value: 'Prepare'},
|
||||
{text: this.$t('test_track.plan_view.pass'), value: 'Pass'},
|
||||
{text: this.$t('test_track.plan_view.failure'), value: 'Failure'},
|
||||
{text: this.$t('test_track.plan_view.blocking'), value: 'Blocking'},
|
||||
{text: this.$t('test_track.plan_view.skip'), value: 'Skip'},
|
||||
{text: this.$t('test_track.plan.plan_status_running'), value: 'Underway'},
|
||||
],
|
||||
executorFilters: [],
|
||||
showMore: false,
|
||||
buttons: [
|
||||
{
|
||||
name: this.$t('test_track.case.batch_edit_case'), handleClick: this.handleBatchEdit
|
||||
},
|
||||
{
|
||||
name: this.$t('test_track.case.batch_unlink'), handleClick: this.handleDeleteBatch
|
||||
}
|
||||
],
|
||||
typeArr: [
|
||||
{id: 'status', name: this.$t('test_track.plan_view.execute_result')},
|
||||
{id: 'executor', name: this.$t('test_track.plan_view.executor')},
|
||||
],
|
||||
valueArr: {
|
||||
executor: [],
|
||||
status: [
|
||||
{name: this.$t('test_track.plan_view.pass'), id: 'Pass'},
|
||||
{name: this.$t('test_track.plan_view.failure'), id: 'Failure'},
|
||||
{name: this.$t('test_track.plan_view.blocking'), id: 'Blocking'},
|
||||
{name: this.$t('test_track.plan_view.skip'), id: 'Skip'}
|
||||
]
|
||||
},
|
||||
selectParentNodes: {
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
planId() {
|
||||
this.refreshTableAndPlan();
|
||||
editor: ClassicEditor,
|
||||
editorConfig: {
|
||||
// 'increaseIndent','decreaseIndent'
|
||||
toolbar: [],
|
||||
},
|
||||
selectNodeIds() {
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
planId: {
|
||||
type: String
|
||||
},
|
||||
mounted() {
|
||||
hub.$on("openFailureTestCase", row => {
|
||||
this.isReadOnly = true;
|
||||
this.condition.status = 'Failure';
|
||||
this.$refs.testPlanTestCaseEdit.openTestCaseEdit(row);
|
||||
});
|
||||
selectNodeIds: {
|
||||
type: Array
|
||||
},
|
||||
selectParentNodes: {
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
planId() {
|
||||
this.refreshTableAndPlan();
|
||||
this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser();
|
||||
},
|
||||
beforeDestroy() {
|
||||
hub.$off("openFailureTestCase");
|
||||
},
|
||||
methods: {
|
||||
initTableData() {
|
||||
if (this.planId) {
|
||||
// param.planId = this.planId;
|
||||
this.condition.planId = this.planId;
|
||||
}
|
||||
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
||||
// param.nodeIds = this.selectNodeIds;
|
||||
this.condition.nodeIds = this.selectNodeIds;
|
||||
}
|
||||
if (this.planId) {
|
||||
this.result = this.$post(this.buildPagePath('/test/plan/case/list'), this.condition, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
for (let i = 0; i < this.tableData.length; i++) {
|
||||
if (this.tableData[i]) {
|
||||
this.$set(this.tableData[i], "issuesSize", 0);
|
||||
this.$get("/issues/get/" + this.tableData[i].caseId, response => {
|
||||
let issues = response.data;
|
||||
if (this.tableData[i]) {
|
||||
this.$set(this.tableData[i], "issuesSize", issues.length);
|
||||
this.$set(this.tableData[i], "issuesContent", issues);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
this.selectRows.clear();
|
||||
});
|
||||
}
|
||||
},
|
||||
showDetail(row, event, column) {
|
||||
this.isReadOnly = true;
|
||||
this.$refs.testPlanTestCaseEdit.openTestCaseEdit(row);
|
||||
},
|
||||
refresh() {
|
||||
this.condition = {components: TEST_CASE_CONFIGS};
|
||||
this.selectRows.clear();
|
||||
this.$emit('refresh');
|
||||
},
|
||||
refreshTableAndPlan() {
|
||||
this.getTestPlanById();
|
||||
this.initTableData();
|
||||
},
|
||||
refreshTestPlanRecent() {
|
||||
if (hasRoles(ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
|
||||
let param = {};
|
||||
param.id = this.planId;
|
||||
param.updateTime = Date.now();
|
||||
this.$post('/test/plan/edit', param);
|
||||
}
|
||||
},
|
||||
search() {
|
||||
this.initTableData();
|
||||
},
|
||||
buildPagePath(path) {
|
||||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
handleEdit(testCase, index) {
|
||||
this.isReadOnly = false;
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.isReadOnly = true;
|
||||
}
|
||||
this.$refs.testPlanTestCaseEdit.openTestCaseEdit(testCase);
|
||||
},
|
||||
handleDelete(testCase) {
|
||||
this.$alert(this.$t('test_track.plan_view.confirm_cancel_relevance') + ' ' + testCase.name + " ?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(testCase);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
handleDeleteBatch() {
|
||||
if (this.tableData.length < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.no_case_relevance'));
|
||||
return;
|
||||
}
|
||||
this.$alert(this.$t('test_track.plan_view.confirm_cancel_relevance') + " ?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
if (this.selectRows.size > 0) {
|
||||
let ids = Array.from(this.selectRows).map(row => row.id);
|
||||
this._handleBatchDelete(ids);
|
||||
} else {
|
||||
if (this.planId) {
|
||||
this.condition.planId = this.planId;
|
||||
}
|
||||
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
||||
this.condition.nodeIds = this.selectNodeIds;
|
||||
}
|
||||
// 根据条件查询计划下所有的关联用例
|
||||
this.$post('/test/plan/case/list/all', this.condition, res => {
|
||||
let data = res.data;
|
||||
let ids = data.map(d => d.id);
|
||||
this._handleBatchDelete(ids);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleBatchDelete(ids) {
|
||||
this.result = this.$post('/test/plan/case/batch/delete', {ids:ids}, () => {
|
||||
this.selectRows.clear();
|
||||
this.$emit("refresh");
|
||||
this.$success(this.$t('test_track.cancel_relevance_success'));
|
||||
});
|
||||
},
|
||||
_handleDelete(testCase) {
|
||||
let testCaseId = testCase.id;
|
||||
this.result = this.$post('/test/plan/case/delete/' + testCaseId, {}, () => {
|
||||
this.$emit("refresh");
|
||||
this.$success(this.$t('test_track.cancel_relevance_success'));
|
||||
});
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
this.tableData.forEach(item => {
|
||||
this.$set(item, "showMore", true);
|
||||
this.selectRows.add(item);
|
||||
});
|
||||
} else {
|
||||
this.selectRows.clear();
|
||||
this.tableData.forEach(row => {
|
||||
this.$set(row, "showMore", false);
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSelectionChange(selection, row) {
|
||||
if (this.selectRows.has(row)) {
|
||||
this.$set(row, "showMore", false);
|
||||
this.selectRows.delete(row);
|
||||
} else {
|
||||
this.$set(row, "showMore", true);
|
||||
this.selectRows.add(row);
|
||||
}
|
||||
},
|
||||
handleBatch(type) {
|
||||
if (this.selectRows.size < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.select_manipulate'));
|
||||
return;
|
||||
}
|
||||
if (type === 'executor') {
|
||||
this.$refs.executorEdit.openExecutorEdit();
|
||||
} else if (type === 'status') {
|
||||
this.$refs.statusEdit.openStatusEdit();
|
||||
} else if (type === 'delete') {
|
||||
this.handleDeleteBatch();
|
||||
}
|
||||
},
|
||||
searchMyTestCase() {
|
||||
this.showMyTestCase = !this.showMyTestCase;
|
||||
if (this.showMyTestCase) {
|
||||
let user = JSON.parse(localStorage.getItem(TokenKey));
|
||||
this.condition.executor = user.id;
|
||||
} else {
|
||||
this.condition.executor = null;
|
||||
}
|
||||
this.initTableData();
|
||||
},
|
||||
openTestReport() {
|
||||
this.$refs.testReportTemplateList.open(this.planId);
|
||||
},
|
||||
statusChange(param) {
|
||||
this.$post('/test/plan/case/edit', param, () => {
|
||||
selectNodeIds() {
|
||||
this.search();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
hub.$on("openFailureTestCase", row => {
|
||||
this.isReadOnly = true;
|
||||
this.condition.status = 'Failure';
|
||||
this.$refs.testPlanTestCaseEdit.openTestCaseEdit(row);
|
||||
});
|
||||
this.refreshTableAndPlan();
|
||||
this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser();
|
||||
this.getMaintainerOptions();
|
||||
},
|
||||
beforeDestroy() {
|
||||
hub.$off("openFailureTestCase");
|
||||
},
|
||||
methods: {
|
||||
initTableData() {
|
||||
if (this.planId) {
|
||||
// param.planId = this.planId;
|
||||
this.condition.planId = this.planId;
|
||||
}
|
||||
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
||||
// param.nodeIds = this.selectNodeIds;
|
||||
this.condition.nodeIds = this.selectNodeIds;
|
||||
}
|
||||
if (this.planId) {
|
||||
this.result = this.$post(this.buildPagePath('/test/plan/case/list'), this.condition, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
for (let i = 0; i < this.tableData.length; i++) {
|
||||
if (this.tableData[i].id == param.id) {
|
||||
this.tableData[i].status = param.status;
|
||||
break;
|
||||
if (this.tableData[i]) {
|
||||
this.$set(this.tableData[i], "issuesSize", 0);
|
||||
this.$get("/issues/get/" + this.tableData[i].caseId, response => {
|
||||
let issues = response.data;
|
||||
if (this.tableData[i]) {
|
||||
this.$set(this.tableData[i], "issuesSize", issues.length);
|
||||
this.$set(this.tableData[i], "issuesContent", issues);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
getTestPlanById() {
|
||||
if (this.planId) {
|
||||
this.$post('/test/plan/get/' + this.planId, {}, response => {
|
||||
this.testPlan = response.data;
|
||||
this.refreshTestPlanRecent();
|
||||
});
|
||||
}
|
||||
},
|
||||
openReport(planId, id) {
|
||||
this.getTestPlanById();
|
||||
if (!id) {
|
||||
id = this.testPlan.reportId;
|
||||
}
|
||||
if (!planId) {
|
||||
planId = this.planId;
|
||||
}
|
||||
this.$refs.testCaseReportView.open(planId, id);
|
||||
},
|
||||
filter(filters) {
|
||||
_filter(filters, this.condition);
|
||||
this.initTableData();
|
||||
},
|
||||
sort(column) {
|
||||
// 每次只对一个字段排序
|
||||
if (this.condition.orders) {
|
||||
this.condition.orders = [];
|
||||
}
|
||||
_sort(column, this.condition);
|
||||
this.initTableData();
|
||||
},
|
||||
batchEdit(form) {
|
||||
let param = {};
|
||||
param[form.type] = form.value;
|
||||
param.ids = Array.from(this.selectRows).map(row => row.id);
|
||||
this.$post('/test/plan/case/batch/edit', param, () => {
|
||||
this.selectRows.clear();
|
||||
this.status = '';
|
||||
this.$post('/test/plan/edit/status/' + this.planId);
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$emit('refresh');
|
||||
});
|
||||
},
|
||||
handleBatchEdit() {
|
||||
this.getMaintainerOptions();
|
||||
this.$refs.batchEdit.open();
|
||||
},
|
||||
getMaintainerOptions() {
|
||||
let workspaceId = localStorage.getItem(WORKSPACE_ID);
|
||||
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
|
||||
this.valueArr.executor = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
showDetail(row, event, column) {
|
||||
this.isReadOnly = true;
|
||||
this.$refs.testPlanTestCaseEdit.openTestCaseEdit(row);
|
||||
},
|
||||
refresh() {
|
||||
this.condition = {components: TEST_CASE_CONFIGS};
|
||||
this.selectRows.clear();
|
||||
this.$emit('refresh');
|
||||
},
|
||||
refreshTableAndPlan() {
|
||||
this.getTestPlanById();
|
||||
this.initTableData();
|
||||
},
|
||||
refreshTestPlanRecent() {
|
||||
if (hasRoles(ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
|
||||
let param = {};
|
||||
param.id = this.planId;
|
||||
param.updateTime = Date.now();
|
||||
this.$post('/test/plan/edit', param);
|
||||
}
|
||||
},
|
||||
search() {
|
||||
this.initTableData();
|
||||
},
|
||||
buildPagePath(path) {
|
||||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
handleEdit(testCase, index) {
|
||||
this.isReadOnly = false;
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.isReadOnly = true;
|
||||
}
|
||||
this.$refs.testPlanTestCaseEdit.openTestCaseEdit(testCase);
|
||||
},
|
||||
handleDelete(testCase) {
|
||||
this.$alert(this.$t('test_track.plan_view.confirm_cancel_relevance') + ' ' + testCase.name + " ?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(testCase);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
handleDeleteBatch() {
|
||||
if (this.tableData.length < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.no_case_relevance'));
|
||||
return;
|
||||
}
|
||||
this.$alert(this.$t('test_track.plan_view.confirm_cancel_relevance') + " ?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
if (this.selectRows.size > 0) {
|
||||
let ids = Array.from(this.selectRows).map(row => row.id);
|
||||
this._handleBatchDelete(ids);
|
||||
} else {
|
||||
if (this.planId) {
|
||||
this.condition.planId = this.planId;
|
||||
}
|
||||
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
||||
this.condition.nodeIds = this.selectNodeIds;
|
||||
}
|
||||
// 根据条件查询计划下所有的关联用例
|
||||
this.$post('/test/plan/case/list/all', this.condition, res => {
|
||||
let data = res.data;
|
||||
let ids = data.map(d => d.id);
|
||||
this._handleBatchDelete(ids);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleBatchDelete(ids) {
|
||||
this.result = this.$post('/test/plan/case/batch/delete', {ids: ids}, () => {
|
||||
this.selectRows.clear();
|
||||
this.$emit("refresh");
|
||||
this.$success(this.$t('test_track.cancel_relevance_success'));
|
||||
});
|
||||
},
|
||||
_handleDelete(testCase) {
|
||||
let testCaseId = testCase.id;
|
||||
this.result = this.$post('/test/plan/case/delete/' + testCaseId, {}, () => {
|
||||
this.$emit("refresh");
|
||||
this.$success(this.$t('test_track.cancel_relevance_success'));
|
||||
});
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
this.tableData.forEach(item => {
|
||||
this.$set(item, "showMore", true);
|
||||
this.selectRows.add(item);
|
||||
});
|
||||
} else {
|
||||
this.selectRows.clear();
|
||||
this.tableData.forEach(row => {
|
||||
this.$set(row, "showMore", false);
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSelectionChange(selection, row) {
|
||||
if (this.selectRows.has(row)) {
|
||||
this.$set(row, "showMore", false);
|
||||
this.selectRows.delete(row);
|
||||
} else {
|
||||
this.$set(row, "showMore", true);
|
||||
this.selectRows.add(row);
|
||||
}
|
||||
},
|
||||
handleBatch(type) {
|
||||
if (this.selectRows.size < 1) {
|
||||
this.$warning(this.$t('test_track.plan_view.select_manipulate'));
|
||||
return;
|
||||
}
|
||||
if (type === 'executor') {
|
||||
this.$refs.executorEdit.openExecutorEdit();
|
||||
} else if (type === 'status') {
|
||||
this.$refs.statusEdit.openStatusEdit();
|
||||
} else if (type === 'delete') {
|
||||
this.handleDeleteBatch();
|
||||
}
|
||||
},
|
||||
searchMyTestCase() {
|
||||
this.showMyTestCase = !this.showMyTestCase;
|
||||
if (this.showMyTestCase) {
|
||||
let user = JSON.parse(localStorage.getItem(TokenKey));
|
||||
this.condition.executor = user.id;
|
||||
} else {
|
||||
this.condition.executor = null;
|
||||
}
|
||||
this.initTableData();
|
||||
},
|
||||
openTestReport() {
|
||||
this.$refs.testReportTemplateList.open(this.planId);
|
||||
},
|
||||
statusChange(param) {
|
||||
this.$post('/test/plan/case/edit', param, () => {
|
||||
for (let i = 0; i < this.tableData.length; i++) {
|
||||
if (this.tableData[i].id == param.id) {
|
||||
this.tableData[i].status = param.status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
getTestPlanById() {
|
||||
if (this.planId) {
|
||||
this.$post('/test/plan/get/' + this.planId, {}, response => {
|
||||
this.testPlan = response.data;
|
||||
this.refreshTestPlanRecent();
|
||||
});
|
||||
}
|
||||
},
|
||||
openReport(planId, id) {
|
||||
this.getTestPlanById();
|
||||
if (!id) {
|
||||
id = this.testPlan.reportId;
|
||||
}
|
||||
if (!planId) {
|
||||
planId = this.planId;
|
||||
}
|
||||
this.$refs.testCaseReportView.open(planId, id);
|
||||
},
|
||||
filter(filters) {
|
||||
_filter(filters, this.condition);
|
||||
this.initTableData();
|
||||
},
|
||||
sort(column) {
|
||||
// 每次只对一个字段排序
|
||||
if (this.condition.orders) {
|
||||
this.condition.orders = [];
|
||||
}
|
||||
_sort(column, this.condition);
|
||||
this.initTableData();
|
||||
},
|
||||
batchEdit(form) {
|
||||
let param = {};
|
||||
param[form.type] = form.value;
|
||||
param.ids = Array.from(this.selectRows).map(row => row.id);
|
||||
this.$post('/test/plan/case/batch/edit', param, () => {
|
||||
this.selectRows.clear();
|
||||
this.status = '';
|
||||
this.$post('/test/plan/edit/status/' + this.planId);
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$emit('refresh');
|
||||
});
|
||||
},
|
||||
handleBatchEdit() {
|
||||
this.getMaintainerOptions();
|
||||
this.$refs.batchEdit.open();
|
||||
},
|
||||
getMaintainerOptions() {
|
||||
let workspaceId = localStorage.getItem(WORKSPACE_ID);
|
||||
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
|
||||
this.valueArr.executor = response.data;
|
||||
this.executorFilters = response.data.map(u => {
|
||||
return {text: u.name, value: u.id}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.search {
|
||||
margin-left: 10px;
|
||||
width: 240px;
|
||||
}
|
||||
.search {
|
||||
margin-left: 10px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.test-case-status, .el-table {
|
||||
cursor: pointer;
|
||||
}
|
||||
.test-case-status, .el-table {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -88,6 +88,7 @@ export default {
|
|||
refresh() {
|
||||
this.selectNodeIds = [];
|
||||
this.selectParentNodes = [];
|
||||
this.$refs.testReviewRelevance.search();
|
||||
this.getNodeTreeByReviewId();
|
||||
},
|
||||
initData() {
|
||||
|
|
|
@ -2,38 +2,31 @@
|
|||
|
||||
<div>
|
||||
|
||||
<el-dialog :title="$t('test_track.review_view.relevance_case')"
|
||||
:visible.sync="dialogFormVisible"
|
||||
@close="close"
|
||||
<el-dialog :title="$t('test_track.review_view.relevance_case')" :visible.sync="dialogFormVisible" @close="close"
|
||||
width="60%" v-loading="result.loading"
|
||||
:close-on-click-modal="false"
|
||||
top="50px">
|
||||
|
||||
<el-container class="main-content">
|
||||
<el-aside class="tree-aside" width="250px">
|
||||
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName : $t('test_track.switch_project') }}</el-link>
|
||||
<node-tree class="node-tree"
|
||||
@nodeSelectEvent="nodeChange"
|
||||
@refresh="refresh"
|
||||
:tree-nodes="treeNodes"
|
||||
<el-link type="primary" class="project-link" @click="switchProject">{{projectName ? projectName :
|
||||
$t('test_track.switch_project') }}
|
||||
</el-link>
|
||||
<node-tree class="node-tree" @nodeSelectEvent="nodeChange" @refresh="refresh" :tree-nodes="treeNodes"
|
||||
ref="nodeTree"/>
|
||||
</el-aside>
|
||||
|
||||
<el-container>
|
||||
<el-main class="case-content">
|
||||
<ms-table-header :condition.sync="condition" @search="getReviews" title="" :show-create="false"/>
|
||||
<el-table
|
||||
:data="testReviews"
|
||||
@filter-change="filter"
|
||||
row-key="id"
|
||||
@select-all="handleSelectAll"
|
||||
@select="handleSelectionChange"
|
||||
height="50vh"
|
||||
ref="table">
|
||||
|
||||
<el-table-column
|
||||
type="selection"/>
|
||||
<ms-table-header :condition.sync="condition" @search="search" title="" :show-create="false"/>
|
||||
<el-table :data="testReviews" @mouseleave.passive="leave" v-el-table-infinite-scroll="loadData"
|
||||
@filter-change="filter" row-key="id"
|
||||
@select-all="handleSelectAll"
|
||||
@select="handleSelectionChange"
|
||||
height="50vh"
|
||||
ref="table">
|
||||
|
||||
<el-table-column type="selection"/>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('test_track.case.name')"
|
||||
|
@ -42,6 +35,7 @@
|
|||
{{scope.row.name}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="priority"
|
||||
:filters="priorityFilters"
|
||||
|
@ -52,6 +46,7 @@
|
|||
<priority-table-item :value="scope.row.priority"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="type"
|
||||
:filters="typeFilters"
|
||||
|
@ -62,17 +57,20 @@
|
|||
<type-table-item :value="scope.row.type"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
:filters="statusFilters"
|
||||
column-key="status"
|
||||
:label="$t('test_track.case.status')"
|
||||
show-overflow-tooltip>
|
||||
<template v-slot:default="scope">
|
||||
<status-table-item :value="scope.row.reviewStatus"/>
|
||||
<review-status :value="scope.row.reviewStatus"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div style="text-align: center">共 {{testReviews.length}} 条</div>
|
||||
<div v-if="!lineStatus" style="text-align: center">{{$t('test_track.review_view.last_page')}}</div>
|
||||
<div style="text-align: center">共 {{total}} 条</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
@ -90,216 +88,250 @@
|
|||
|
||||
<script>
|
||||
|
||||
import NodeTree from "../../../common/NodeTree";
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import PriorityTableItem from "../../../common/tableItems/planview/PriorityTableItem";
|
||||
import TypeTableItem from "../../../common/tableItems/planview/TypeTableItem";
|
||||
import MsTableSearchBar from "../../../../common/components/MsTableSearchBar";
|
||||
import MsTableAdvSearchBar from "../../../../common/components/search/MsTableAdvSearchBar";
|
||||
import MsTableHeader from "../../../../common/components/MsTableHeader";
|
||||
import SwitchProject from "../../../case/components/SwitchProject";
|
||||
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
|
||||
import {_filter} from "../../../../../../common/js/utils";
|
||||
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
|
||||
import NodeTree from "../../../common/NodeTree";
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import PriorityTableItem from "../../../common/tableItems/planview/PriorityTableItem";
|
||||
import TypeTableItem from "../../../common/tableItems/planview/TypeTableItem";
|
||||
import MsTableSearchBar from "../../../../common/components/MsTableSearchBar";
|
||||
import MsTableAdvSearchBar from "../../../../common/components/search/MsTableAdvSearchBar";
|
||||
import MsTableHeader from "../../../../common/components/MsTableHeader";
|
||||
import SwitchProject from "../../../case/components/SwitchProject";
|
||||
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
|
||||
import {_filter} from "../../../../../../common/js/utils";
|
||||
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
||||
import elTableInfiniteScroll from 'el-table-infinite-scroll';
|
||||
|
||||
export default {
|
||||
name: "TestReviewRelevance",
|
||||
components: {
|
||||
NodeTree,
|
||||
MsDialogFooter,
|
||||
PriorityTableItem,
|
||||
TypeTableItem,
|
||||
MsTableSearchBar,
|
||||
MsTableAdvSearchBar,
|
||||
MsTableHeader,
|
||||
SwitchProject,
|
||||
ReviewStatus
|
||||
|
||||
export default {
|
||||
name: "TestReviewRelevance",
|
||||
components: {
|
||||
NodeTree,
|
||||
MsDialogFooter,
|
||||
PriorityTableItem,
|
||||
TypeTableItem,
|
||||
MsTableSearchBar,
|
||||
MsTableAdvSearchBar,
|
||||
MsTableHeader,
|
||||
SwitchProject,
|
||||
StatusTableItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
dialogFormVisible: false,
|
||||
isCheckAll: false,
|
||||
testReviews: [],
|
||||
selectIds: new Set(),
|
||||
treeNodes: [],
|
||||
selectNodeIds: [],
|
||||
selectNodeNames: [],
|
||||
projectId: '',
|
||||
projectName: '',
|
||||
projects: [],
|
||||
condition: {
|
||||
components: TEST_CASE_CONFIGS
|
||||
},
|
||||
priorityFilters: [
|
||||
{text: 'P0', value: 'P0'},
|
||||
{text: 'P1', value: 'P1'},
|
||||
{text: 'P2', value: 'P2'},
|
||||
{text: 'P3', value: 'P3'}
|
||||
],
|
||||
typeFilters: [
|
||||
{text: this.$t('commons.functional'), value: 'functional'},
|
||||
{text: this.$t('commons.performance'), value: 'performance'},
|
||||
{text: this.$t('commons.api'), value: 'api'}
|
||||
],
|
||||
statusFilters: [
|
||||
{text: this.$t('test_track.case.status_prepare'), value: 'Prepare'},
|
||||
{text: this.$t('test_track.case.status_pass'), value: 'Pass'},
|
||||
{text: this.$t('test_track.case.status_un_pass'), value: 'UnPass'},
|
||||
],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
reviewId: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
reviewId() {
|
||||
this.initData();
|
||||
},
|
||||
selectNodeIds() {
|
||||
this.getReviews();
|
||||
directives: {
|
||||
'el-table-infinite-scroll': elTableInfiniteScroll
|
||||
},
|
||||
projectId() {
|
||||
this.getProjectNode();
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
this.toggleSelection(this.testReviews);
|
||||
},
|
||||
methods: {
|
||||
openTestReviewRelevanceDialog() {
|
||||
this.getProject();
|
||||
this.initData();
|
||||
this.dialogFormVisible = true;
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
dialogFormVisible: false,
|
||||
isCheckAll: false,
|
||||
testReviews: [],
|
||||
selectIds: new Set(),
|
||||
treeNodes: [],
|
||||
selectNodeIds: [],
|
||||
selectNodeNames: [],
|
||||
projectId: '',
|
||||
projectName: '',
|
||||
projects: [],
|
||||
pageSize: 50,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
lineStatus: true,
|
||||
condition: {
|
||||
components: TEST_CASE_CONFIGS
|
||||
},
|
||||
priorityFilters: [
|
||||
{text: 'P0', value: 'P0'},
|
||||
{text: 'P1', value: 'P1'},
|
||||
{text: 'P2', value: 'P2'},
|
||||
{text: 'P3', value: 'P3'}
|
||||
],
|
||||
typeFilters: [
|
||||
{text: this.$t('commons.functional'), value: 'functional'},
|
||||
{text: this.$t('commons.performance'), value: 'performance'},
|
||||
{text: this.$t('commons.api'), value: 'api'}
|
||||
],
|
||||
statusFilters: [
|
||||
{text: this.$t('test_track.case.status_prepare'), value: 'Prepare'},
|
||||
{text: this.$t('test_track.case.status_pass'), value: 'Pass'},
|
||||
{text: this.$t('test_track.case.status_un_pass'), value: 'UnPass'},
|
||||
],
|
||||
};
|
||||
},
|
||||
saveReviewRelevance() {
|
||||
let param = {};
|
||||
param.reviewId = this.reviewId;
|
||||
param.testCaseIds = [...this.selectIds];
|
||||
this.result = this.$post('/test/case/review/relevance', param, () => {
|
||||
this.selectIds.clear();
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.dialogFormVisible = false;
|
||||
this.$emit('refresh');
|
||||
});
|
||||
props: {
|
||||
reviewId: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
getReviews() {
|
||||
if (this.reviewId) {
|
||||
watch: {
|
||||
reviewId() {
|
||||
this.condition.reviewId = this.reviewId;
|
||||
}
|
||||
if (this.selectNodeIds && this.selectNodeIds.length > 0) {
|
||||
this.condition.nodeIds = this.selectNodeIds;
|
||||
} else {
|
||||
this.condition.nodeIds = [];
|
||||
}
|
||||
|
||||
if (this.projectId) {
|
||||
},
|
||||
selectNodeIds() {
|
||||
if (this.dialogFormVisible) {
|
||||
this.search();
|
||||
}
|
||||
},
|
||||
projectId() {
|
||||
this.condition.projectId = this.projectId;
|
||||
this.result = this.$post('/test/case/reviews/case', this.condition, response => {
|
||||
this.testReviews = response.data;
|
||||
this.testReviews.forEach(item => {
|
||||
item.checked = false;
|
||||
this.getProjectNode();
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
this.toggleSelection(this.testReviews);
|
||||
},
|
||||
methods: {
|
||||
openTestReviewRelevanceDialog() {
|
||||
this.getProject();
|
||||
this.dialogFormVisible = true;
|
||||
},
|
||||
saveReviewRelevance() {
|
||||
let param = {};
|
||||
param.reviewId = this.reviewId;
|
||||
param.testCaseIds = [...this.selectIds];
|
||||
param.projectId = this.projectId;
|
||||
// 选择全选则全部加入到评审,无论是否加载完全部
|
||||
if (this.testReviews.length === param.testCaseIds.length) {
|
||||
param.testCaseIds = ['all'];
|
||||
}
|
||||
this.result = this.$post('/test/case/review/relevance', param, () => {
|
||||
this.selectIds.clear();
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.dialogFormVisible = false;
|
||||
this.$emit('refresh');
|
||||
});
|
||||
},
|
||||
buildPagePath(path) {
|
||||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
getReviews() {
|
||||
if (this.reviewId) {
|
||||
this.condition.reviewId = this.reviewId;
|
||||
}
|
||||
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.result = this.$post(this.buildPagePath('/test/case/reviews/case'), this.condition, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
let tableData = data.listObject;
|
||||
tableData.forEach(item => {
|
||||
item.checked = false;
|
||||
});
|
||||
this.testReviews = this.testReviews.concat(tableData);
|
||||
this.lineStatus = tableData.length === 50 && this.testReviews.length < this.total;
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
this.testReviews.forEach(item => {
|
||||
this.selectIds.add(item.id);
|
||||
});
|
||||
} else {
|
||||
// this.selectIds.clear();
|
||||
this.testReviews.forEach(item => {
|
||||
if (this.selectIds.has(item.id)) {
|
||||
this.selectIds.delete(item.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
handleSelectionChange(selection, row) {
|
||||
if (this.selectIds.has(row.id)) {
|
||||
this.selectIds.delete(row.id);
|
||||
} else {
|
||||
this.selectIds.add(row.id);
|
||||
}
|
||||
},
|
||||
nodeChange(nodeIds, nodeNames) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
this.selectNodeNames = nodeNames;
|
||||
},
|
||||
initData() {
|
||||
this.getReviews();
|
||||
this.getAllNodeTreeByPlanId();
|
||||
},
|
||||
refresh() {
|
||||
this.close();
|
||||
},
|
||||
getAllNodeTreeByPlanId() {
|
||||
if (this.reviewId) {
|
||||
let param = {
|
||||
reviewId: this.reviewId,
|
||||
projectId: this.projectId
|
||||
};
|
||||
this.result = this.$post("/case/node/list/all/review", param , response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.selectIds.clear();
|
||||
this.selectNodeIds = [];
|
||||
this.selectNodeNames = [];
|
||||
},
|
||||
filter(filters) {
|
||||
_filter(filters, this.condition);
|
||||
this.initData();
|
||||
},
|
||||
toggleSelection(rows) {
|
||||
rows.forEach(row => {
|
||||
this.selectIds.forEach(id => {
|
||||
if (row.id === id) {
|
||||
// true 是为选中
|
||||
this.$refs.table.toggleRowSelection(row, true)
|
||||
}
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
this.testReviews.forEach(item => {
|
||||
this.selectIds.add(item.id);
|
||||
});
|
||||
} else {
|
||||
// this.selectIds.clear();
|
||||
this.testReviews.forEach(item => {
|
||||
if (this.selectIds.has(item.id)) {
|
||||
this.selectIds.delete(item.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
handleSelectionChange(selection, row) {
|
||||
if (this.selectIds.has(row.id)) {
|
||||
this.selectIds.delete(row.id);
|
||||
} else {
|
||||
this.selectIds.add(row.id);
|
||||
}
|
||||
},
|
||||
nodeChange(nodeIds, nodeNames) {
|
||||
this.selectNodeIds = nodeIds;
|
||||
this.selectNodeNames = nodeNames;
|
||||
},
|
||||
refresh() {
|
||||
this.close();
|
||||
},
|
||||
getAllNodeTreeByPlanId() {
|
||||
if (this.reviewId) {
|
||||
let param = {
|
||||
reviewId: this.reviewId,
|
||||
projectId: this.projectId
|
||||
};
|
||||
this.result = this.$post("/case/node/list/all/review", param, response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.lineStatus = false;
|
||||
this.selectIds.clear();
|
||||
this.selectNodeIds = [];
|
||||
this.selectNodeNames = [];
|
||||
},
|
||||
filter(filters) {
|
||||
_filter(filters, this.condition);
|
||||
this.search();
|
||||
},
|
||||
toggleSelection(rows) {
|
||||
rows.forEach(row => {
|
||||
this.selectIds.forEach(id => {
|
||||
if (row.id === id) {
|
||||
// true 是为选中
|
||||
this.$refs.table.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
getProject() {
|
||||
if (this.reviewId) {
|
||||
this.$post("/test/case/review/projects", {reviewId: this.reviewId},res => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
this.projects = data;
|
||||
this.projectId = data[0].id;
|
||||
this.projectName = data[0].name;
|
||||
},
|
||||
getProject() {
|
||||
if (this.reviewId) {
|
||||
this.$post("/test/case/review/projects", {reviewId: this.reviewId}, res => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
this.projects = data;
|
||||
this.projectId = data[0].id;
|
||||
this.projectName = data[0].name;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
switchProject() {
|
||||
this.$refs.switchProject.open({id: this.reviewId, url: '/test/case/review/project/', type: 'review'});
|
||||
},
|
||||
loadData() {
|
||||
if (this.dialogFormVisible) {
|
||||
if (this.lineStatus) {
|
||||
this.currentPage += 1;
|
||||
this.getReviews();
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
switchProject() {
|
||||
this.$refs.switchProject.open({id: this.reviewId, url : '/test/case/review/project/', type: 'review'});
|
||||
},
|
||||
getProjectNode(projectId) {
|
||||
const index = this.projects.findIndex(project => project.id === projectId);
|
||||
if (index !== -1) {
|
||||
this.projectName = this.projects[index].name;
|
||||
}
|
||||
if (projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
this.result = this.$post("/case/node/list/all/review",
|
||||
{reviewId: this.reviewId, projectId: this.projectId} , response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
search() {
|
||||
this.currentPage = 1;
|
||||
this.testReviews = [];
|
||||
this.getReviews();
|
||||
},
|
||||
|
||||
this.selectNodeIds = [];
|
||||
getProjectNode(projectId) {
|
||||
const index = this.projects.findIndex(project => project.id === projectId);
|
||||
if (index !== -1) {
|
||||
this.projectName = this.projects[index].name;
|
||||
}
|
||||
if (projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
this.result = this.$post("/case/node/list/all/review",
|
||||
{reviewId: this.reviewId, projectId: this.projectId}, response => {
|
||||
this.treeNodes = response.data;
|
||||
});
|
||||
|
||||
this.selectNodeIds = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
:label="$t('test_track.review_view.execute_result')">
|
||||
<template v-slot:default="scope">
|
||||
<span class="el-dropdown-link">
|
||||
<status-table-item :value="scope.row.reviewStatus"/>
|
||||
<review-status :value="scope.row.reviewStatus"/>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -169,6 +169,7 @@ import {_filter, _sort, checkoutTestManagerOrTestUser, hasRoles} from "../../../
|
|||
import {TEST_CASE_CONFIGS} from "../../../../common/components/search/search-components";
|
||||
import {ROLE_TEST_MANAGER, ROLE_TEST_USER} from "../../../../../../common/js/constants";
|
||||
import TestReviewTestCaseEdit from "./TestReviewTestCaseEdit";
|
||||
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
||||
|
||||
export default {
|
||||
name: "TestReviewTestCaseList",
|
||||
|
@ -176,7 +177,7 @@ export default {
|
|||
MsTableOperatorButton, MsTableOperator, MethodTableItem, TypeTableItem,
|
||||
StatusTableItem, PriorityTableItem, StatusEdit,
|
||||
ExecutorEdit, MsTipButton, TestReviewTestCaseEdit, MsTableHeader,
|
||||
NodeBreadcrumb, MsTableButton, ShowMoreBtn, BatchEdit, MsTablePagination
|
||||
NodeBreadcrumb, MsTableButton, ShowMoreBtn, BatchEdit, MsTablePagination, ReviewStatus
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -61,7 +61,7 @@ export default {
|
|||
window.console.error(error.response || error.message);
|
||||
if (error.response && error.response.data) {
|
||||
if (error.response.headers["authentication-status"] !== "invalid") {
|
||||
Message.error({message: error.response.data.message, showClose: true});
|
||||
Message.error({message: error.response.data.message || error.response.data, showClose: true});
|
||||
}
|
||||
} else {
|
||||
Message.error({message: error.message, showClose: true});
|
||||
|
|
|
@ -518,6 +518,7 @@ export default {
|
|||
connect_timeout: "Connect Timeout",
|
||||
response_timeout: "Response Timeout",
|
||||
follow_redirects: "Follow Redirects",
|
||||
do_multipart_post: "Use multipart/form-data for POST",
|
||||
body_upload_limit_size: "The file size does not exceed 500 MB",
|
||||
condition: "condition",
|
||||
condition_variable: "Variable, e.g: ${var}",
|
||||
|
@ -727,8 +728,8 @@ export default {
|
|||
batch_delete_case: 'Batch delete',
|
||||
batch_unlink: 'Batch Unlink',
|
||||
project_name: "Project",
|
||||
status: 'Status',
|
||||
status_prepare: 'Prepare',
|
||||
status: 'Review Status',
|
||||
status_prepare: 'Not reviewed',
|
||||
status_pass: 'Pass',
|
||||
status_un_pass: 'UnPass',
|
||||
cancel_relevance_project: "Disassociating the project will also cancel the associated test cases under the project",
|
||||
|
@ -821,6 +822,7 @@ export default {
|
|||
all_case: "All case",
|
||||
start_review: "Start Review",
|
||||
relevance_case: "Relevance Case",
|
||||
last_page: "It's the end!",
|
||||
execute_result: "Result",
|
||||
},
|
||||
module: {
|
||||
|
|
|
@ -521,6 +521,7 @@ export default {
|
|||
connect_timeout: "连接超时",
|
||||
response_timeout: "响应超时",
|
||||
follow_redirects: "跟随重定向",
|
||||
do_multipart_post: "对 POST 使用 multipart/form-data",
|
||||
body_upload_limit_size: "上传文件大小不能超过 500 MB!",
|
||||
condition: "条件",
|
||||
condition_variable: "变量,例如: ${var}",
|
||||
|
@ -731,8 +732,8 @@ export default {
|
|||
batch_delete_case: '批量删除用例',
|
||||
batch_unlink: '批量取消关联',
|
||||
project_name: '所属项目',
|
||||
status: '状态',
|
||||
status_prepare: '未开始',
|
||||
status: '评审状态',
|
||||
status_prepare: '未评审',
|
||||
status_pass: '通过',
|
||||
status_un_pass: '未通过',
|
||||
cancel_relevance_project: "取消项目关联会同时取消该项目下已关联的测试用例",
|
||||
|
@ -825,6 +826,7 @@ export default {
|
|||
all_case: "全部用例",
|
||||
start_review: "开始评审",
|
||||
relevance_case: "关联用例",
|
||||
last_page: "已经到底了!",
|
||||
execute_result: "评审结果",
|
||||
},
|
||||
module: {
|
||||
|
|
|
@ -521,6 +521,7 @@ export default {
|
|||
connect_timeout: "連接超時",
|
||||
response_timeout: "響應超時",
|
||||
follow_redirects: "跟隨重定向",
|
||||
do_multipart_post: "對 POST 使用 multipart/form-data",
|
||||
body_upload_limit_size: "上傳文件大小不能超過 500 MB!",
|
||||
condition: "條件",
|
||||
condition_variable: "變量,例如: ${var}",
|
||||
|
@ -731,8 +732,8 @@ export default {
|
|||
batch_delete_case: '批量刪除用例',
|
||||
batch_unlink: '批量取消關聯',
|
||||
project_name: '所屬項目',
|
||||
status: '狀態',
|
||||
status_prepare: '未開始',
|
||||
status: '評審狀態',
|
||||
status_prepare: '未評審',
|
||||
status_pass: '通過',
|
||||
status_un_pass: '未通過',
|
||||
cancel_relevance_project: "取消項目關聯會同時取消該項目下已關聯的測試用例",
|
||||
|
@ -825,6 +826,7 @@ export default {
|
|||
all_case: "全部用例",
|
||||
start_review: "開始評審",
|
||||
relevance_case: "關聯用例",
|
||||
last_page: "已經到底了!",
|
||||
execute_result: "評審結果",
|
||||
},
|
||||
module: {
|
||||
|
|
Loading…
Reference in New Issue