提交 3f73ecd5 编写于 作者: W wenyann

Merge remote-tracking branch 'origin/master'

......@@ -171,6 +171,13 @@
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_tcp</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
......
......@@ -34,6 +34,7 @@ public class APIReportController {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryAPIReportRequest request = new QueryAPIReportRequest();
request.setWorkspaceId(currentWorkspaceId);
request.setUserId(SessionUtils.getUserId());
PageHelper.startPage(1, count, true);
return apiReportService.recentTest(request);
}
......
......@@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
@RestController
......@@ -36,6 +37,7 @@ public class APITestController {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryAPITestRequest request = new QueryAPITestRequest();
request.setWorkspaceId(currentWorkspaceId);
request.setUserId(SessionUtils.getUserId());
PageHelper.startPage(1, count, true);
return apiTestService.recentTest(request);
}
......
......@@ -15,6 +15,7 @@ public class QueryAPIReportRequest {
private String projectId;
private String name;
private String workspaceId;
private String userId;
private boolean recent = false;
private List<OrderRequest> orders;
private Map<String, List<String>> filters;
......
......@@ -16,6 +16,7 @@ public class QueryAPITestRequest {
private String projectId;
private String name;
private String workspaceId;
private String userId;
private boolean recent = false;
private List<OrderRequest> orders;
private Map<String, List<String>> filters;
......
......@@ -16,6 +16,7 @@ public class Scenario {
private List<KeyValue> headers;
private List<Request> requests;
private DubboConfig dubboConfig;
private TCPConfig tcpConfig;
private List<DatabaseConfig> databaseConfigs;
private Boolean enable;
}
package io.metersphere.api.dto.scenario;
import lombok.Data;
@Data
public class TCPConfig {
private String classname;
private String server;
private Integer port;
private Integer ctimeout;
private Integer timeout;
private Boolean reUseConnection;
private Boolean nodelay;
private Boolean closeConnection;
private String soLinger;
private String eolByte;
private String username;
private String password;
}
......@@ -4,8 +4,6 @@ import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.processor.BeanShellPostProcessor;
import io.metersphere.api.dto.scenario.processor.BeanShellPreProcessor;
import io.metersphere.api.dto.scenario.request.dubbo.ConfigCenter;
import io.metersphere.api.dto.scenario.request.dubbo.ConsumerAndService;
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
......@@ -39,8 +37,4 @@ public class DubboRequest extends Request {
private List<KeyValue> args;
@JSONField(ordinal = 9)
private List<KeyValue> attachmentArgs;
@JSONField(ordinal = 12)
private BeanShellPreProcessor beanShellPreProcessor;
@JSONField(ordinal = 13)
private BeanShellPostProcessor beanShellPostProcessor;
}
......@@ -4,8 +4,6 @@ import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.processor.BeanShellPostProcessor;
import io.metersphere.api.dto.scenario.processor.BeanShellPreProcessor;
import lombok.Data;
import lombok.EqualsAndHashCode;
......@@ -31,10 +29,6 @@ public class HttpRequest extends Request {
private List<KeyValue> headers;
@JSONField(ordinal = 8)
private Body body;
@JSONField(ordinal = 11)
private BeanShellPreProcessor beanShellPreProcessor;
@JSONField(ordinal = 12)
private BeanShellPostProcessor beanShellPostProcessor;
@JSONField(ordinal = 14)
private Long connectTimeout;
@JSONField(ordinal = 15)
......
......@@ -16,7 +16,8 @@ import lombok.Data;
@JsonSubTypes({
@JsonSubTypes.Type(value = HttpRequest.class, name = RequestType.HTTP),
@JsonSubTypes.Type(value = DubboRequest.class, name = RequestType.DUBBO),
@JsonSubTypes.Type(value = SqlRequest.class, name = RequestType.SQL)
@JsonSubTypes.Type(value = SqlRequest.class, name = RequestType.SQL),
@JsonSubTypes.Type(value = TCPRequest.class, name = RequestType.TCP)
})
@JSONType(seeAlso = {HttpRequest.class, DubboRequest.class, SqlRequest.class}, typeKey = "type")
@Data
......
......@@ -7,4 +7,6 @@ public class RequestType {
public static final String DUBBO = "DUBBO";
public static final String SQL = "SQL";
public static final String TCP = "TCP";
}
package io.metersphere.api.dto.scenario.request;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@JSONType(typeName = RequestType.TCP)
public class TCPRequest extends Request {
// type 必须放最前面,以便能够转换正确的类
private String type = RequestType.TCP;
@JSONField(ordinal = 50)
private Boolean useEnvironment;
@JSONField(ordinal = 51)
private String classname;
@JSONField(ordinal = 52)
private String server;
@JSONField(ordinal = 53)
private Integer port;
@JSONField(ordinal = 54)
private Integer ctimeout;
@JSONField(ordinal = 55)
private Integer timeout;
@JSONField(ordinal = 56)
private Boolean reUseConnection;
@JSONField(ordinal = 57)
private Boolean nodelay;
@JSONField(ordinal = 58)
private Boolean closeConnection;
@JSONField(ordinal = 59)
private String soLinger;
@JSONField(ordinal = 60)
private String eolByte;
@JSONField(ordinal = 61)
private String request;
@JSONField(ordinal = 62)
private String username;
@JSONField(ordinal = 63)
private String password;
}
......@@ -18,10 +18,18 @@ public class JMeterVars {
private JMeterVars() {
}
// 数据和线程变量保持一致
/**
* 数据和线程变量保持一致
*/
private static Map<Integer, JMeterVariables> variables = new HashMap<>();
// 线程执行过程调用提取变量值
/**
* 线程执行过程调用提取变量值
*
* @param testId
* @param vars
* @param extract
*/
public static void addVars(Integer testId, JMeterVariables vars, String extract) {
JMeterVariables vs = new JMeterVariables();
......@@ -36,7 +44,11 @@ public class JMeterVars {
variables.put(testId, vs);
}
// 处理所有请求,有提取变量的请求增加后置脚本提取变量值
/**
* 处理所有请求,有提取变量的请求增加后置脚本提取变量值
*
* @param tree
*/
public static void addJSR223PostProcessor(HashTree tree) {
for (Object key : tree.keySet()) {
HashTree node = tree.get(key);
......
......@@ -120,6 +120,9 @@
<if test="request.id != null">
AND api_test.id = #{request.id}
</if>
<if test="request.userId != null">
AND api_test.user_id = #{request.userId}
</if>
<if test="request.filters != null and request.filters.size() > 0">
<foreach collection="request.filters.entrySet()" index="key" item="values">
......
......@@ -85,6 +85,9 @@
<if test="request.name != null">
and r.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.userId != null">
AND r.user_id = #{request.userId,jdbcType=VARCHAR}
</if>
<if test="request.projectId != null">
AND project.id = #{request.projectId}
</if>
......
......@@ -67,6 +67,9 @@
<if test="request.name != null">
and load_test.name like CONCAT('%', #{request.name},'%')
</if>
<if test="request.userId != null">
and load_test.user_id= #{request.userId}
</if>
<if test="request.workspaceId != null">
AND project.workspace_id = #{request.workspaceId}
</if>
......
......@@ -84,6 +84,9 @@
<if test="reportRequest.name != null">
AND ltr.name like CONCAT('%', #{reportRequest.name},'%')
</if>
<if test="reportRequest.userId != null">
AND ltr.user_id = #{reportRequest.userId,jdbcType=VARCHAR}
</if>
<if test="reportRequest.workspaceId != null">
AND workspace_id = #{reportRequest.workspaceId,jdbcType=VARCHAR}
</if>
......
......@@ -4,7 +4,8 @@
<resultMap id="BaseResultMap" type="io.metersphere.track.dto.TestPlanDTO"
extends="io.metersphere.base.mapper.TestPlanMapper.BaseResultMap">
<result column="project_name" property="projectName"/>
<result column="project_name" property="projectName" jdbcType="VARCHAR"/>
<result column="user_name" property="userName" jdbcType="VARCHAR"/>
</resultMap>
<sql id="condition">
<choose>
......@@ -97,7 +98,8 @@
<select id="list" resultMap="BaseResultMap"
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
select test_plan.* from test_plan
select test_plan.*, user.name as user_name from test_plan
LEFT JOIN user ON user.id = test_plan.principal
<where>
<if test="request.combine != null">
<include refid="combine">
......
......@@ -35,7 +35,6 @@ public class ShiroConfig implements EnvironmentAware {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager sessionManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// shiroFilterFactoryBean.getFilters().put("authc", new LoginFilter());
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSecurityManager(sessionManager);
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
......@@ -44,7 +43,6 @@ public class ShiroConfig implements EnvironmentAware {
shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
// filterChainDefinitionMap.put("/**", "apikey, authc");
filterChainDefinitionMap.put("/**", "apikey");
return shiroFilterFactoryBean;
}
......
......@@ -35,6 +35,7 @@ public class PerformanceReportController {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
ReportRequest request = new ReportRequest();
request.setWorkspaceId(currentWorkspaceId);
request.setUserId(SessionUtils.getUserId());
// 最近 `count` 个项目
PageHelper.startPage(1, count);
return reportService.getRecentReportList(request);
......
......@@ -44,6 +44,7 @@ public class PerformanceTestController {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setWorkspaceId(currentWorkspaceId);
request.setUserId(SessionUtils.getUserId());
PageHelper.startPage(1, count, true);
return performanceTestService.recentTestPlans(request);
}
......
......@@ -12,6 +12,7 @@ import java.util.Map;
public class ReportRequest {
private String name;
private String workspaceId;
private String userId;
private List<OrderRequest> orders;
private Map<String, List<String>> filters;
private Map<String, Object> combine;
......
package io.metersphere.security;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.controller.ResultHolder;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginFilter extends FormAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getServletPath().endsWith("login")) {
return super.onAccessDenied(request, response);
}
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.setHeader("authentication-status", "invalid");
ResultHolder result = ResultHolder.error("Authentication Status Invalid");
httpServletResponse.getWriter().write(JSONObject.toJSON(result).toString());
return true;
}
}
......@@ -63,6 +63,7 @@ public class TestCaseController {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryTestCaseRequest request = new QueryTestCaseRequest();
request.setWorkspaceId(currentWorkspaceId);
request.setUserId(SessionUtils.getUserId());
return testCaseService.recentTestPlans(request, count);
}
......
......@@ -10,5 +10,6 @@ import java.util.List;
@Setter
public class TestPlanDTO extends TestPlan {
private String projectName;
private String userName;
private List<String> projectIds;
}
......@@ -26,6 +26,8 @@ public class QueryTestCaseRequest extends TestCase {
private String workspaceId;
private String userId;
private Map<String, Object> combine;
private String reviewId;
......
......@@ -11,6 +11,7 @@ import java.util.Map;
@Setter
public class QueryTestPlanRequest extends TestPlanRequest {
private String workspaceId;
private String userId;
private List<OrderRequest> orders;
private Map<String, List<String>> filters;
private Map<String, Object> combine;
......
......@@ -252,7 +252,7 @@ public class TestCaseService {
PageHelper.startPage(1, count, true);
TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andProjectIdIn(projectIds);
testCaseExample.createCriteria().andProjectIdIn(projectIds).andMaintainerEqualTo(request.getUserId());
testCaseExample.setOrderByClause("update_time desc, sort desc");
return testCaseMapper.selectByExample(testCaseExample);
}
......
......@@ -37,6 +37,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
......@@ -119,7 +120,7 @@ public class TestPlanService {
if (TestPlanStatus.Underway.name().equals(testPlan.getStatus())) {
testPlan.setActualStartTime(System.currentTimeMillis());
} else if(TestPlanStatus.Completed.name().equals(testPlan.getStatus())){
} else if (TestPlanStatus.Completed.name().equals(testPlan.getStatus())) {
//已完成,写入实际完成时间
testPlan.setActualEndTime(System.currentTimeMillis());
}
......@@ -248,7 +249,8 @@ public class TestPlanService {
return null;
}
TestPlanExample testPlanTestCaseExample = new TestPlanExample();
testPlanTestCaseExample.createCriteria().andWorkspaceIdEqualTo(currentWorkspaceId);
testPlanTestCaseExample.createCriteria().andWorkspaceIdEqualTo(currentWorkspaceId)
.andPrincipalEqualTo(SessionUtils.getUserId());
testPlanTestCaseExample.setOrderByClause("update_time desc");
return testPlanMapper.selectByExample(testPlanTestCaseExample);
}
......
......@@ -30,14 +30,25 @@ public class XmindCaseParser {
private TestCaseService testCaseService;
private String maintainer;
private String projectId;
private StringBuffer process; // 过程校验记录
// 已存在用例名称
/**
* 过程校验记录
*/
private StringBuffer process;
/**
* 已存在用例名称
*/
private Set<String> testCaseNames;
// 转换后的案例信息
/**
* 转换后的案例信息
*/
private List<TestCaseWithBLOBs> testCases;
// 案例详情重写了hashCode方法去重用
/**
* 案例详情重写了hashCode方法去重用
*/
private List<TestCaseExcelData> compartDatas;
// 记录没有用例的目录
/**
* 记录没有用例的目录
*/
private List<String> nodePaths;
public XmindCaseParser(TestCaseService testCaseService, String userId, String projectId, Set<String> testCaseNames) {
......@@ -89,7 +100,9 @@ public class XmindCaseParser {
});
}
// 递归处理案例数据
/**
* 递归处理案例数据
*/
private void recursion(Attached parent, int level, List<Attached> attacheds) {
for (Attached item : attacheds) {
if (isAvailable(item.getTitle(), TC_REGEX)) { // 用例
......@@ -115,23 +128,27 @@ public class XmindCaseParser {
}
private boolean isAvailable(String str, String regex) {
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex))
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex)) {
return false;
}
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher result = pattern.matcher(str);
return result.find();
}
private String replace(String str, String regex) {
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex))
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex)) {
return str;
}
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher result = pattern.matcher(str);
str = result.replaceAll("");
return str;
}
// 获取步骤数据
/**
* 获取步骤数据
*/
private String getSteps(List<Attached> attacheds) {
JSONArray jsonArray = new JSONArray();
for (int i = 0; i < attacheds.size(); i++) {
......@@ -147,7 +164,9 @@ public class XmindCaseParser {
return jsonArray.toJSONString();
}
// 初始化一个用例
/**
* 初始化一个用例
*/
private void newTestCase(String title, String nodePath, List<Attached> attacheds) {
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
testCase.setProjectId(projectId);
......@@ -157,7 +176,7 @@ public class XmindCaseParser {
testCase.setType("functional");
String tc = title.replace(":", ":");
String tcArr[] = tc.split(":");
String[] tcArr = tc.split(":");
if (tcArr.length != 2) {
process.append(Translator.get("test_case_name") + "【 " + title + " 】" + Translator.get("incorrect_format"));
return;
......@@ -175,7 +194,7 @@ public class XmindCaseParser {
// 用例等级和用例性质处理
if (tcArr[0].indexOf("-") != -1) {
String otArr[] = tcArr[0].split("-");
String[] otArr = tcArr[0].split("-");
for (String item : otArr) {
if (item.toUpperCase().startsWith("P")) {
testCase.setPriority(item.toUpperCase());
......@@ -222,7 +241,9 @@ public class XmindCaseParser {
compartDatas.add(compartData);
}
// 验证合法性
/**
* 验证合法性
*/
private boolean validate(TestCaseWithBLOBs data) {
String nodePath = data.getNodePath();
StringBuilder stringBuilder = new StringBuilder();
......@@ -265,7 +286,9 @@ public class XmindCaseParser {
return true;
}
// 导入思维导图处理
/**
* 导入思维导图处理
*/
public String parse(MultipartFile multipartFile) {
try {
// 获取思维导图内容
......@@ -274,7 +297,8 @@ public class XmindCaseParser {
if (root != null && root.getRootTopic() != null && root.getRootTopic().getChildren() != null) {
// 判断是模块还是用例
for (Attached item : root.getRootTopic().getChildren().getAttached()) {
if (isAvailable(item.getTitle(), TC_REGEX)) { // 用例
// 用例
if (isAvailable(item.getTitle(), TC_REGEX)) {
return replace(item.getTitle(), TC_REGEX) + ":" + Translator.get("test_case_create_module_fail");
} else {
String nodePath = item.getTitle();
......@@ -288,7 +312,8 @@ public class XmindCaseParser {
if (nodePath.endsWith("/")) {
nodePath = nodePath.substring(0, nodePath.length() - 1);
}
nodePaths.add(nodePath); // 没有用例的路径
// 没有用例的路径
nodePaths.add(nodePath);
}
}
}
......
......@@ -7,7 +7,6 @@ import io.metersphere.xmind.parser.pojo.JsonRootBean;
import io.metersphere.xmind.utils.FileUtil;
import org.apache.commons.compress.archivers.ArchiveException;
import org.dom4j.DocumentException;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
......@@ -21,9 +20,9 @@ import java.util.Objects;
* @Description 解析主体
*/
public class XmindParser {
public static final String xmindZenJson = "content.json";
public static final String xmindLegacyContent = "content.xml";
public static final String xmindLegacyComments = "comments.xml";
public static final String CONTENT_JSON = "content.json";
public static final String CONTENT_XML = "content.xml";
public static final String COMMENTS_XML = "comments.xml";
/**
* 解析脑图文件,返回content整合后的内容
......@@ -38,8 +37,9 @@ public class XmindParser {
File file = FileUtil.multipartFileToFile(multipartFile);
List<String> contents = null;
String res = null;
if (file == null || !file.exists())
if (file == null || !file.exists()) {
MSException.throwException(Translator.get("incorrect_format"));
}
try {
res = ZipUtils.extract(file);
if (isXmindZen(res, file)) {
......@@ -56,8 +56,9 @@ public class XmindParser {
FileUtil.deleteDir(dir);
}
// 删除零时文件
if (file != null)
if (file != null) {
file.delete();
}
}
return contents;
}
......@@ -86,9 +87,9 @@ public class XmindParser {
public static List<String> getXmindZenContent(File file, String extractFileDir)
throws IOException, ArchiveException {
List<String> keys = new ArrayList<>();
keys.add(xmindZenJson);
keys.add(CONTENT_JSON);
Map<String, String> map = ZipUtils.getContents(keys, file, extractFileDir);
String content = map.get(xmindZenJson);
String content = map.get(CONTENT_JSON);
return XmindZen.getContent(content);
}
......@@ -98,12 +99,12 @@ public class XmindParser {
public static List<String> getXmindLegacyContent(File file, String extractFileDir)
throws IOException, ArchiveException, DocumentException {
List<String> keys = new ArrayList<>();
keys.add(xmindLegacyContent);
keys.add(xmindLegacyComments);
keys.add(CONTENT_XML);
keys.add(COMMENTS_XML);
Map<String, String> map = ZipUtils.getContents(keys, file, extractFileDir);
String contentXml = map.get(xmindLegacyContent);
String commentsXml = map.get(xmindLegacyComments);
String contentXml = map.get(CONTENT_XML);
String commentsXml = map.get(COMMENTS_XML);
List<String> xmlContent = XmindLegacy.getContent(contentXml, commentsXml);
return xmlContent;
......@@ -115,7 +116,7 @@ public class XmindParser {
if (parent.isDirectory()) {
String[] files = parent.list(new ZipUtils.FileFilter());
for (int i = 0; i < Objects.requireNonNull(files).length; i++) {
if (files[i].equals(xmindZenJson)) {
if (files[i].equals(CONTENT_JSON)) {
return true;
}
}
......
......@@ -14,7 +14,7 @@ import java.util.Objects;
*/
public class ZipUtils {
private static final String currentPath = System.getProperty("user.dir");
private static final String CURRENT_PATH = System.getProperty("user.dir");
/**
* 找到压缩文件中匹配的子文件,返回的为 getContents("comments.xml, unzip
......@@ -25,7 +25,7 @@ public class ZipUtils {
public static Map<String, String> getContents(List<String> subFileNames, File file, String extractFileDir)
throws IOException, ArchiveException {
String destFilePath = extractFileDir;
Map<String, String> map = new HashMap<>();
Map<String, String> map = new HashMap<>(16);
File destFile = new File(destFilePath);
if (destFile.isDirectory()) {
String[] res = destFile.list(new FileFilter());
......@@ -49,12 +49,14 @@ public class ZipUtils {
*/
public static String extract(File file) throws IOException, ArchiveException {
Expander expander = new Expander();
String destFileName = currentPath + File.separator + "XMind" + System.currentTimeMillis(); // 目标文件夹名字
String destFileName = CURRENT_PATH + File.separator + "XMind" + System.currentTimeMillis();
expander.expand(file, new File(destFileName));
return destFileName;
}
// 这是一个内部类过滤器,策略模式
/**
* 这是一个内部类过滤器,策略模式
*/
static class FileFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
......
......@@ -8,9 +8,14 @@ import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 工具类
*/
public class FileUtil {
//获取流文件
/**
* 获取流文件
*/
private static void inputStreamToFile(InputStream ins, File file) {
try (OutputStream os = new FileOutputStream(file);) {
int bytesRead = 0;
......@@ -57,5 +62,4 @@ public class FileUtil {
return dir.delete();
}
}
Subproject commit cf6b06526324326a563d933e07118fac014a63b4
Subproject commit ee74568be0beba46da19616f5832e83f9164c688
......@@ -33,7 +33,8 @@
"json-bigint": "^1.0.0",
"html2canvas": "^1.0.0-rc.7",
"jspdf": "^2.1.1",
"yan-progress": "^1.0.3"
"yan-progress": "^1.0.3",
"nprogress": "^0.2.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
......
......@@ -4,7 +4,7 @@
<el-col>
<span>{{ report.projectName }} / </span>
<router-link :to="path">{{ report.testName }}</router-link>
<span class="time">{{ report.createTime | timestampFormatDate }}</span>
<span class="time"> {{ report.createTime | timestampFormatDate }}</span>
<el-button :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)"
style="margin-left: 1200px">
{{$t('test_track.plan_view.export_report')}}
......
......@@ -49,6 +49,9 @@
<div class="dubbo-config-title">Consumer & Service</div>
<ms-dubbo-consumer-service :consumer="scenario.dubboConfig.consumerAndService" :is-read-only="isReadOnly"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
<ms-tcp-config :config="scenario.tcpConfig" :is-read-only="isReadOnly"/>
</el-tab-pane>
</el-tabs>
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
......@@ -68,10 +71,12 @@ import MsDubboConfigCenter from "@/business/components/api/test/components/reque
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
import MsDatabaseConfig from "./request/database/DatabaseConfig";
import {parseEnvironment} from "../model/EnvironmentModel";
import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig";
export default {
name: "MsApiScenarioForm",
components: {
MsTcpConfig,
MsDatabaseConfig,
MsDubboConsumerService,
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue
......
......@@ -20,6 +20,9 @@
<el-tab-pane :label="$t('api_test.environment.database_config')" name="sql">
<ms-database-config :configs="environment.config.databaseConfigs"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
<ms-tcp-config :config="environment.config.tcpConfig"/>
</el-tab-pane>
</el-tabs>
<div class="environment-footer">
......@@ -35,16 +38,18 @@
import MsApiScenarioVariables from "../ApiScenarioVariables";
import MsApiKeyValue from "../ApiKeyValue";
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {REQUEST_HEADERS} from "../../../../../../common/js/constants";
import {REQUEST_HEADERS} from "@/common/js/constants";
import {Environment} from "../../model/EnvironmentModel";
import MsApiHostTable from "../ApiHostTable";
import MsDatabaseConfig from "../request/database/DatabaseConfig";
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig";
export default {
name: "EnvironmentEdit",
components: {
MsTcpConfig,
MsEnvironmentCommonConfig,
MsEnvironmentHttpConfig,
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables},
......
......@@ -50,6 +50,7 @@
<el-radio :label="types.HTTP">HTTP</el-radio>
<el-radio :label="types.DUBBO">DUBBO</el-radio>
<el-radio :label="types.SQL">SQL</el-radio>
<el-radio :label="types.TCP">TCP</el-radio>
</el-radio-group>
<el-button slot="reference" :disabled="isReadOnly"
class="request-create" type="primary" size="mini" icon="el-icon-plus" plain/>
......
......@@ -2,22 +2,32 @@
<div class="request-form">
<component @runDebug="runDebug" :is="component" :is-read-only="isReadOnly" :request="request" :scenario="scenario"/>
<el-divider v-if="isCompleted"></el-divider>
<ms-request-result-tail v-loading="debugReportLoading" v-if="isCompleted" :request="request.debugRequestResult ? request.debugRequestResult : {responseResult: {}, subRequestResults: []}"
:scenario-name="request.debugScenario ? request.debugScenario.name : ''" ref="msDebugResult"/>
<ms-request-result-tail v-loading="debugReportLoading" v-if="isCompleted"
:request="request.debugRequestResult ? request.debugRequestResult : {responseResult: {}, subRequestResults: []}"
:scenario-name="request.debugScenario ? request.debugScenario.name : ''"
ref="msDebugResult"/>
</div>
</template>
<script>
import {JSR223Processor, Request, RequestFactory, Scenario} from "../../model/ScenarioModel";
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
import MsScenarioResults from "../../../report/components/ScenarioResults";
import MsRequestResultTail from "../../../report/components/RequestResultTail";
import MsApiSqlRequestForm from "./ApiSqlRequestForm";
import {JSR223Processor, Request, RequestFactory, Scenario} from "../../model/ScenarioModel";
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
import MsApiTcpRequestForm from "./ApiTcpRequestForm";
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
import MsScenarioResults from "../../../report/components/ScenarioResults";
import MsRequestResultTail from "../../../report/components/RequestResultTail";
import MsApiSqlRequestForm from "./ApiSqlRequestForm";
export default {
name: "MsApiRequestForm",
components: {MsApiSqlRequestForm, MsRequestResultTail, MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
components: {
MsApiSqlRequestForm,
MsRequestResultTail,
MsScenarioResults,
MsApiDubboRequestForm,
MsApiHttpRequestForm,
MsApiTcpRequestForm
},
props: {
scenario: Scenario,
request: Request,
......@@ -27,104 +37,107 @@ export default {
},
debugReportId: String
},
data() {
return {
reportId: "",
content: {scenarios:[]},
debugReportLoading: false,
showDebugReport: false
}
},
computed: {
component({request: {type}}) {
let name;
switch (type) {
case RequestFactory.TYPES.DUBBO:
name = "MsApiDubboRequestForm";
break;
case RequestFactory.TYPES.SQL:
name = "MsApiSqlRequestForm";
break;
default:
name = "MsApiHttpRequestForm";
}
return name;
},
isCompleted() {
return !!this.request.debugReport;
}
},
watch: {
debugReportId() {
this.getReport();
}
},
mounted() {
//兼容旧版本 beanshell
if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) {
this.request.jsr223PreProcessor = new JSR223Processor(this.request.beanShellPreProcessor);
}
if (!this.request.jsr223PostProcessor.script && this.request.beanShellPostProcessor) {
this.request.jsr223PostProcessor = new JSR223Processor(this.request.beanShellPostProcessor);
data() {
return {
reportId: "",
content: {scenarios: []},
debugReportLoading: false,
showDebugReport: false
}
},
computed: {
component({request: {type}}) {
let name;
switch (type) {
case RequestFactory.TYPES.DUBBO:
name = "MsApiDubboRequestForm";
break;
case RequestFactory.TYPES.SQL:
name = "MsApiSqlRequestForm";
break;
case RequestFactory.TYPES.TCP:
name = "MsApiTcpRequestForm";
break;
default:
name = "MsApiHttpRequestForm";
}
return name;
},
methods: {
getReport() {
if (this.debugReportId) {
this.debugReportLoading = true;
this.showDebugReport = true;
this.request.debugReport = {};
let url = "/api/report/get/" + this.debugReportId;
this.$get(url, response => {
let report = response.data || {};
let res = {};
if (response.data) {
try {
res = JSON.parse(report.content);
} catch (e) {
throw e;
}
if (res) {
this.debugReportLoading = false;
this.request.debugReport = res;
if (res.scenarios && res.scenarios.length > 0) {
this.request.debugScenario = res.scenarios[0];
this.request.debugRequestResult = this.request.debugScenario.requestResults[0];
this.deleteReport(this.debugReportId);
} else {
this.request.debugScenario = new Scenario();
this.request.debugRequestResult = {responseResult: {}, subRequestResults: []};
}
this.$refs.msDebugResult.reload();
isCompleted() {
return !!this.request.debugReport;
}
},
watch: {
debugReportId() {
this.getReport();
}
},
mounted() {
//兼容旧版本 beanshell
if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) {
this.request.jsr223PreProcessor = new JSR223Processor(this.request.beanShellPreProcessor);
}
if (!this.request.jsr223PostProcessor.script && this.request.beanShellPostProcessor) {
this.request.jsr223PostProcessor = new JSR223Processor(this.request.beanShellPostProcessor);
}
},
methods: {
getReport() {
if (this.debugReportId) {
this.debugReportLoading = true;
this.showDebugReport = true;
this.request.debugReport = {};
let url = "/api/report/get/" + this.debugReportId;
this.$get(url, response => {
let report = response.data || {};
let res = {};
if (response.data) {
try {
res = JSON.parse(report.content);
} catch (e) {
throw e;
}
if (res) {
this.debugReportLoading = false;
this.request.debugReport = res;
if (res.scenarios && res.scenarios.length > 0) {
this.request.debugScenario = res.scenarios[0];
this.request.debugRequestResult = this.request.debugScenario.requestResults[0];
this.deleteReport(this.debugReportId);
} else {
setTimeout(this.getReport, 2000)
this.request.debugScenario = new Scenario();
this.request.debugRequestResult = {responseResult: {}, subRequestResults: []};
}
this.$refs.msDebugResult.reload();
} else {
this.debugReportLoading = false;
setTimeout(this.getReport, 2000)
}
});
}
},
deleteReport(reportId) {
this.$post('/api/report/delete', {id: reportId});
},
runDebug() {
this.$emit('runDebug', this.request);
} else {
this.debugReportLoading = false;
}
});
}
},
deleteReport(reportId) {
this.$post('/api/report/delete', {id: reportId});
},
runDebug() {
this.$emit('runDebug', this.request);
}
}
}
</script>
<style scoped>
.scenario-results {
margin-top: 20px;
}
.scenario-results {
margin-top: 20px;
}
.request-form >>> .debug-button {
margin-left: auto;
display: block;
margin-right: 10px;
}
.request-form >>> .debug-button {
margin-left: auto;
display: block;
margin-right: 10px;
}
</style>
<template>
<el-form class="tcp" :model="request" :rules="rules" ref="request" label-width="auto" :disabled="isReadOnly">
<el-form-item :label="$t('api_test.request.name')" prop="name">
<el-input v-model="request.name" maxlength="300" show-word-limit/>
</el-form-item>
<el-form-item label="TCPClient" prop="classname">
<el-select v-model="request.classname" style="width: 100%">
<el-option v-for="c in classes" :key="c" :label="c" :value="c"/>
</el-select>
</el-form-item>
<el-row :gutter="10">
<el-col :span="16">
<el-form-item :label="$t('api_test.request.tcp.server')" prop="server">
<el-input v-model="request.server" maxlength="300" show-word-limit/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="$t('api_test.request.tcp.port')" prop="port" label-width="60px">
<el-input-number v-model="request.port" controls-position="right" :min="0" :max="65535"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.connect')" prop="ctimeout">
<el-input-number v-model="request.ctimeout" controls-position="right" :min="0"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.response')" prop="timeout">
<el-input-number v-model="request.timeout" controls-position="right" :min="0"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.so_linger')" prop="soLinger">
<el-input v-model="request.soLinger"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.eol_byte')" prop="eolByte">
<el-input v-model="request.eolByte"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="6">
<el-form-item>
<el-switch
v-model="request.useEnvironment"
:active-text="$t('api_test.request.refer_to_environment')"
@change="useEnvironmentChange">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-switch
v-model="request.reUseConnection"
:active-text="$t('api_test.request.tcp.re_use_connection')">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-switch
v-model="request.closeConnection"
:active-text="$t('api_test.request.tcp.close_connection')">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-switch
v-model="request.nodelay"
:active-text="$t('api_test.request.tcp.no_delay')">
</el-switch>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('api_test.request.tcp.request')" prop="request">
<el-input type="textarea" v-model="request.request" :autosize="{minRows: 4, maxRows: 6}">
</el-input>
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.username')" prop="username">
<el-input v-model="request.username" maxlength="100" show-word-limit/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.password')" prop="password">
<el-input v-model="request.password" maxlength="30" show-word-limit show-password
autocomplete="new-password"/>
</el-form-item>
</el-col>
</el-row>
<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.request.assertions.label')" name="assertions">
<ms-api-assertions :is-read-only="isReadOnly" :assertions="request.assertions"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract.label')" name="extract">
<ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.pre_exec_script')" name="beanShellPreProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PreProcessor"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.post_exec_script')" name="beanShellPostProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PostProcessor"/>
</el-tab-pane>
</el-tabs>
</el-form>
</template>
<script>
import {Scenario, TCPRequest} from "@/business/components/api/test/model/ScenarioModel";
import MsApiAssertions from "@/business/components/api/test/components/assertion/ApiAssertions";
import MsApiExtract from "@/business/components/api/test/components/extract/ApiExtract";
import MsJsr233Processor from "@/business/components/api/test/components/processor/Jsr233Processor";
export default {
name: "MsApiTcpRequestForm",
components: {MsJsr233Processor, MsApiExtract, MsApiAssertions},
props: {
request: TCPRequest,
scenario: Scenario,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
activeName: "assertions",
classes: TCPRequest.CLASSES,
rules: {
server: [
{
required: true,
message: this.$t('commons.required', [this.$t('api_test.request.tcp.server')]),
trigger: 'blur'
}
],
}
}
},
methods: {
useEnvironmentChange(value) {
if (value && !this.request.environment) {
this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000);
this.request.useEnvironment = false;
}
this.$refs["request"].clearValidate();
},
runDebug() {
this.$emit('runDebug');
}
},
}
</script>
<style scoped>
.tcp >>> .el-input-number {
width: 100%;
}
</style>
<template>
<el-form class="tcp" :model="config" :rules="rules" ref="config" label-width="120px" :disabled="isReadOnly"
size="small">
<el-form-item label="TCPClient" prop="classname">
<el-select v-model="config.classname" style="width: 100%">
<el-option v-for="c in classes" :key="c" :label="c" :value="c"/>
</el-select>
</el-form-item>
<el-row :gutter="10">
<el-col :span="16">
<el-form-item :label="$t('api_test.request.tcp.server')" prop="server">
<el-input v-model="config.server" maxlength="300" show-word-limit/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="$t('api_test.request.tcp.port')" prop="port" label-width="60px">
<el-input-number v-model="config.port" controls-position="right" :min="0" :max="65535" :controls="false"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.connect')" prop="ctimeout">
<el-input-number v-model="config.ctimeout" controls-position="right" :min="0" :step="1000" :controls="false"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.response')" prop="timeout">
<el-input-number v-model="config.timeout" controls-position="right" :min="0" :step="1000" :controls="false"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.so_linger')" prop="soLinger">
<el-input v-model="config.soLinger"/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('api_test.request.tcp.eol_byte')" prop="eolByte">
<el-input v-model="config.eolByte"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="8">
<el-form-item>
<el-switch
v-model="config.reUseConnection"
:active-text="$t('api_test.request.tcp.re_use_connection')">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item>
<el-switch
v-model="config.closeConnection"
:active-text="$t('api_test.request.tcp.close_connection')">
</el-switch>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item>
<el-switch
v-model="config.nodelay"
:active-text="$t('api_test.request.tcp.no_delay')">
</el-switch>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.username')" prop="username">
<el-input v-model="config.username" maxlength="100" show-word-limit/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('api_test.request.tcp.password')" prop="password">
<el-input v-model="config.password" maxlength="30" show-word-limit show-password
autocomplete="new-password"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
import {TCPConfig} from "@/business/components/api/test/model/ScenarioModel";
export default {
name: "MsTcpConfig",
props: {
config: TCPConfig,
isReadOnly: {
type: Boolean,
default: false
},
},
data() {
return {
classes: TCPConfig.CLASSES,
rules: {}
}
},
}
</script>
<style scoped>
.tcp >>> .el-input-number {
width: 100%;
}
</style>
import {BaseConfig, DatabaseConfig, KeyValue} from "./ScenarioModel";
import {TCPConfig} from "@/business/components/api/test/model/ScenarioModel";
export class Environment extends BaseConfig {
constructor(options = {}) {
......@@ -26,14 +27,17 @@ export class Config extends BaseConfig {
this.commonConfig = undefined;
this.httpConfig = undefined;
this.databaseConfigs = [];
this.tcpConfig = undefined;
this.set(options);
this.sets({databaseConfigs: DatabaseConfig}, options);
}
initOptions(options = {}) {
this.commonConfig = new CommonConfig(options.commonConfig);
this.httpConfig = new HttpConfig(options.httpConfig);
options.databaseConfigs = options.databaseConfigs || [];
options.tcpConfig = new TCPConfig(options.tcpConfig);
return options;
}
}
......@@ -91,7 +95,6 @@ export class Host extends BaseConfig {
}
/* ---------- Functions ------- */
export function compatibleWithEnvironment(environment) {
......
......@@ -291,6 +291,26 @@ export class JDBCSampler extends DefaultTestElement {
}
}
export class TCPSampler extends DefaultTestElement {
constructor(testName, request = {}) {
super('TCPSampler', 'TCPSamplerGui', 'TCPSampler', testName);
this.stringProp("TCPSampler.classname", request.classname);
this.stringProp("TCPSampler.server", request.server);
this.stringProp("TCPSampler.port", request.port);
this.stringProp("TCPSampler.ctimeout", request.ctimeout);
this.stringProp("TCPSampler.timeout", request.timeout);
this.boolProp("TCPSampler.reUseConnection", request.reUseConnection);
this.boolProp("TCPSampler.nodelay", request.nodelay);
this.boolProp("TCPSampler.closeConnection", request.closeConnection);
this.stringProp("TCPSampler.soLinger", request.soLinger);
this.stringProp("TCPSampler.EolByte", request.eolByte);
this.stringProp("TCPSampler.request", request.request);
this.stringProp("ConfigTestElement.username", request.username);
this.stringProp("ConfigTestElement.password", request.password);
}
}
export class HTTPSamplerProxy extends DefaultTestElement {
constructor(testName, options = {}) {
super('HTTPSamplerProxy', 'HttpTestSampleGui', 'HTTPSamplerProxy', testName);
......@@ -335,7 +355,7 @@ export class HTTPSamplerArguments extends Element {
let collectionProp = this.collectionProp('Arguments.arguments');
this.args.forEach(arg => {
if (arg.enable === true || arg.enable === undefined) { // 非禁用的条件加入执行
if (arg.enable === true || arg.enable === undefined) { // 非禁用的条件加入执行
let elementProp = collectionProp.elementProp(arg.name, 'HTTPArgument');
elementProp.boolProp('HTTPArgument.always_encode', arg.encode, true);
elementProp.boolProp('HTTPArgument.use_equals', arg.equals, true);
......
......@@ -25,7 +25,7 @@ import {
ThreadGroup,
XPath2Extractor,
IfController as JMXIfController,
ConstantTimer as JMXConstantTimer,
ConstantTimer as JMXConstantTimer, TCPSampler,
} from "./JMX";
import Mock from "mockjs";
import {funcFilters} from "@/common/js/func-filter";
......@@ -111,12 +111,16 @@ export const EXTRACT_TYPE = {
export class BaseConfig {
set(options) {
set(options, notUndefined) {
options = this.initOptions(options)
for (let name in options) {
if (options.hasOwnProperty(name)) {
if (!(this[name] instanceof Array)) {
this[name] = options[name];
if (notUndefined === true) {
this[name] = options[name] === undefined ? this[name] : options[name];
} else {
this[name] = options[name];
}
}
}
}
......@@ -219,6 +223,7 @@ export class Scenario extends BaseConfig {
this.enableCookieShare = false;
this.enable = true;
this.databaseConfigs = [];
this.tcpConfig = undefined;
this.set(options);
this.sets({
......@@ -234,6 +239,7 @@ export class Scenario extends BaseConfig {
options.requests = options.requests || [new RequestFactory()];
options.databaseConfigs = options.databaseConfigs || [];
options.dubboConfig = new DubboConfig(options.dubboConfig);
options.tcpConfig = new TCPConfig(options.tcpConfig);
return options;
}
......@@ -286,6 +292,7 @@ export class RequestFactory {
HTTP: "HTTP",
DUBBO: "DUBBO",
SQL: "SQL",
TCP: "TCP",
}
constructor(options = {}) {
......@@ -295,6 +302,8 @@ export class RequestFactory {
return new DubboRequest(options);
case RequestFactory.TYPES.SQL:
return new SqlRequest(options);
case RequestFactory.TYPES.TCP:
return new TCPRequest(options);
default:
return new HttpRequest(options);
}
......@@ -305,9 +314,15 @@ export class Request extends BaseConfig {
constructor(type, options = {}) {
super();
this.type = type;
options.id = options.id || uuid();
this.timer = options.timer = new ConstantTimer(options.timer);
this.controller = options.controller = new IfController(options.controller);
this.id = options.id || uuid();
this.name = options.name;
this.enable = options.enable === undefined ? true : options.enable;
this.assertions = new Assertions(options.assertions);
this.extract = new Extract(options.extract);
this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
this.timer = new ConstantTimer(options.timer);
this.controller = new IfController(options.controller);
}
showType() {
......@@ -322,41 +337,22 @@ export class Request extends BaseConfig {
export class HttpRequest extends Request {
constructor(options) {
super(RequestFactory.TYPES.HTTP, options);
this.name = undefined;
this.url = undefined;
this.path = undefined;
this.method = undefined;
this.url = options.url;
this.path = options.path;
this.method = options.method || "GET";
this.parameters = [];
this.headers = [];
this.body = undefined;
this.assertions = undefined;
this.extract = undefined;
this.environment = undefined;
this.useEnvironment = undefined;
this.body = new Body(options.body);
this.environment = options.environment;
this.useEnvironment = options.useEnvironment;
this.debugReport = undefined;
this.beanShellPreProcessor = undefined;
this.beanShellPostProcessor = undefined;
this.jsr223PreProcessor = undefined;
this.jsr223PostProcessor = undefined;
this.enable = true;
this.connectTimeout = 60 * 1000;
this.responseTimeout = undefined;
this.followRedirects = true;
this.connectTimeout = options.connectTimeout || 60 * 1000;
this.responseTimeout = options.responseTimeout;
this.followRedirects = options.followRedirects === undefined ? true : options.followRedirects;
this.set(options);
this.sets({parameters: KeyValue, headers: KeyValue}, options);
}
initOptions(options = {}) {
options.method = options.method || "GET";
options.body = new Body(options.body);
options.assertions = new Assertions(options.assertions);
options.extract = new Extract(options.extract);
options.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
options.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
return options;
}
isValid(environmentId, environment) {
if (this.enable) {
if (this.useEnvironment) {
......@@ -412,7 +408,6 @@ export class DubboRequest extends Request {
constructor(options = {}) {
super(RequestFactory.TYPES.DUBBO, options);
this.name = options.name;
this.protocol = options.protocol || DubboRequest.PROTOCOLS.DUBBO;
this.interface = options.interface;
this.method = options.method;
......@@ -421,16 +416,9 @@ export class DubboRequest extends Request {
this.consumerAndService = new ConsumerAndService(options.consumerAndService);
this.args = [];
this.attachmentArgs = [];
this.assertions = new Assertions(options.assertions);
this.extract = new Extract(options.extract);
// Scenario.dubboConfig
this.dubboConfig = undefined;
this.debugReport = undefined;
this.beanShellPreProcessor = new BeanShellProcessor(options.beanShellPreProcessor);
this.beanShellPostProcessor = new BeanShellProcessor(options.beanShellPostProcessor);
this.enable = options.enable === undefined ? true : options.enable;
this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
this.sets({args: KeyValue, attachmentArgs: KeyValue}, options);
}
......@@ -485,8 +473,6 @@ export class SqlRequest extends Request {
constructor(options = {}) {
super(RequestFactory.TYPES.SQL, options);
this.id = options.id || uuid();
this.name = options.name;
this.useEnvironment = options.useEnvironment;
this.resultVariable = options.resultVariable;
this.variableNames = options.variableNames;
......@@ -495,11 +481,6 @@ export class SqlRequest extends Request {
this.query = options.query;
// this.queryType = options.queryType;
this.queryTimeout = options.queryTimeout || 60000;
this.enable = options.enable === undefined ? true : options.enable;
this.assertions = new Assertions(options.assertions);
this.extract = new Extract(options.extract);
this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
this.sets({args: KeyValue, attachmentArgs: KeyValue}, options);
}
......@@ -537,6 +518,67 @@ export class SqlRequest extends Request {
}
}
export class TCPConfig extends BaseConfig {
static CLASSES = ["TCPClientImpl", "BinaryTCPClientImpl", "LengthPrefixedBinaryTCPClientImpl"]
constructor(options = {}) {
super();
this.classname = options.classname || TCPConfig.CLASSES[0];
this.server = options.server;
this.port = options.port;
this.ctimeout = options.ctimeout; // Connect
this.timeout = options.timeout; // Response
this.reUseConnection = options.reUseConnection === undefined ? true : options.reUseConnection;
this.nodelay = options.nodelay === undefined ? false : options.nodelay;
this.closeConnection = options.closeConnection === undefined ? false : options.closeConnection;
this.soLinger = options.soLinger;
this.eolByte = options.eolByte;
this.username = options.username;
this.password = options.password;
}
}
export class TCPRequest extends Request {
constructor(options = {}) {
super(RequestFactory.TYPES.TCP, options);
this.useEnvironment = options.useEnvironment;
this.debugReport = undefined;
//设置TCPConfig的属性
this.set(new TCPConfig(options));
this.request = options.request;
}
isValid() {
if (this.enable) {
if (!this.server) {
return {
isValid: false,
info: 'api_test.request.tcp.server_cannot_be_empty'
}
}
}
return {
isValid: true
}
}
showType() {
return "TCP";
}
showMethod() {
return "TCP";
}
clone() {
return new TCPRequest(this);
}
}
export class ConfigCenter extends BaseConfig {
static PROTOCOLS = ["zookeeper", "nacos", "apollo"];
......@@ -658,7 +700,7 @@ export class Body extends BaseConfig {
export class KeyValue extends BaseConfig {
constructor(options) {
options = options || {};
options.enable = options.enable != false ? true : false;
options.enable = options.enable === undefined ? true : options.enable;
super();
this.name = undefined;
......@@ -1003,6 +1045,19 @@ class JMXDubboRequest {
}
}
class JMXTCPRequest {
constructor(request, scenario) {
let obj = request.clone();
if (request.useEnvironment) {
obj.set(scenario.environment.config.tcpConfig, true);
return obj;
}
obj.set(scenario.tcpConfig, true);
return obj;
}
}
class JMeterTestPlan extends Element {
constructor() {
super('jmeterTestPlan', {
......@@ -1060,6 +1115,8 @@ class JMXGenerator {
} else if (request instanceof SqlRequest) {
request.dataSource = scenario.databaseConfigMap.get(request.dataSource);
sampler = new JDBCSampler(request.name || "", request);
} else if (request instanceof TCPRequest) {
sampler = new TCPSampler(request.name || "", new JMXTCPRequest(request, scenario));
}
this.addDNSCacheManager(sampler, scenario.environment, request.useEnvironment);
......@@ -1135,7 +1192,7 @@ class JMXGenerator {
let domain = environment.config.httpConfig.domain;
let validHosts = [];
hosts.forEach(item => {
if (item.domain != undefined && domain != undefined) {
if (item.domain !== undefined && domain !== undefined) {
let d = item.domain.trim().replace("http://", "").replace("https://", "");
if (d === domain.trim()) {
item.domain = d; // 域名去掉协议
......@@ -1295,7 +1352,7 @@ class JMXGenerator {
body.push({name: '', value: request.body.raw, encode: false, enable: true});
}
if (request.method != 'GET') {
if (request.method !== 'GET') {
httpSamplerProxy.add(new HTTPSamplerArguments(body));
}
}
......@@ -1304,7 +1361,7 @@ class JMXGenerator {
let files = [];
let kvs = this.filterKVFile(request.body.kvs);
kvs.forEach(kv => {
if ((kv.enable != false) && kv.files) {
if ((kv.enable !== false) && kv.files) {
kv.files.forEach(file => {
let arg = {};
arg.name = kv.name;
......
<template>
<el-row type="flex" align="middle" class="current-user">
<el-avatar shape="square" size="small" :src="squareUrl"/>
<span class="username">{{currentUser.name}}</span>
<div class="icon-title">
{{ currentUser.name.substring(0, 1) }}
</div>
<span class="username">{{ currentUser.name }}</span>
</el-row>
</template>
<script>
import {getCurrentUser} from "../../../common/js/utils";
export default {
name: "MsCurrentUser",
data() {
return {
editVisible: false,
id: "123456",
squareUrl: "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png",
form: {}
}
},
methods: {
edit() {
this.editVisible = true;
this.form = Object.assign({}, this.currentUser);
},
submit() {
this.editVisible = false;
}
import {getCurrentUser} from "@/common/js/utils";
export default {
name: "MsCurrentUser",
data() {
return {
editVisible: false,
id: "123456",
form: {}
}
},
methods: {
edit() {
this.editVisible = true;
this.form = Object.assign({}, this.currentUser);
},
computed: {
currentUser: () => {
return getCurrentUser();
}
submit() {
this.editVisible = false;
}
},
computed: {
currentUser: () => {
return getCurrentUser();
}
}
}
</script>
<style scoped>
.current-user .username {
display: inline-block;
font-size: 16px;
font-weight: 500;
margin: 0 5px;
overflow-x: hidden;
padding-bottom: 0;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
width: 180px;
}
.current-user .username {
display: inline-block;
font-size: 16px;
font-weight: 500;
margin: 0 5px;
overflow-x: hidden;
padding-bottom: 0;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
width: 180px;
}
.current-user .edit {
opacity: 0;
}
.current-user .edit {
opacity: 0;
}
.current-user:hover .edit {
opacity: 1;
}
.icon-title {
color: #fff;
width: 30px;
background-color: #72dc91;
height: 30px;
line-height: 30px;
text-align: center;
border-radius: 30px;
font-size: 14px;
}
.current-user:hover .edit {
opacity: 1;
}
</style>
......@@ -3,7 +3,7 @@
<div>
<el-dialog :close-on-click-modal="false"
:title="operationType == 'edit' ? $t('test_track.plan.edit_plan') : $t('test_track.plan.create_plan')"
:title="operationType === 'edit' ? $t('test_track.plan.edit_plan') : $t('test_track.plan.create_plan')"
:visible.sync="dialogFormVisible"
@close="close"
width="65%">
......@@ -75,7 +75,8 @@
:label="$t('test_track.plan.planned_start_time')"
:label-width="formLabelWidth"
prop="plannedStartTime">
<el-date-picker :placeholder="$t('test_track.plan.planned_start_time')" v-model="form.plannedStartTime" type="datetime" value-format="timestamp"></el-date-picker>
<el-date-picker :placeholder="$t('test_track.plan.planned_start_time')" v-model="form.plannedStartTime"
type="datetime" value-format="timestamp"></el-date-picker>
</el-form-item>
</el-col>
......@@ -84,7 +85,8 @@
:label="$t('test_track.plan.planned_end_time')"
:label-width="formLabelWidth"
prop="plannedEndTime">
<el-date-picker :placeholder="$t('test_track.plan.planned_end_time')" v-model="form.plannedEndTime" type="datetime" value-format="timestamp" ></el-date-picker>
<el-date-picker :placeholder="$t('test_track.plan.planned_end_time')" v-model="form.plannedEndTime"
type="datetime" value-format="timestamp"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
......@@ -102,7 +104,7 @@
</el-col>
</el-row>
<el-row v-if="operationType == 'edit'" type="flex" justify="left" style="margin-top: 10px;">
<el-row v-if="operationType === 'edit'" type="flex" justify="left" style="margin-top: 10px;">
<el-col :span="19" :offset="1">
<el-form-item :label="$t('test_track.plan.plan_status')" :label-width="formLabelWidth" prop="status">
<test-plan-status-button :status="form.status" @statusChange="statusChange"/>
......@@ -134,9 +136,9 @@
<script>
import {WORKSPACE_ID} from '../../../../../common/js/constants';
import {WORKSPACE_ID} from '@/common/js/constants';
import TestPlanStatusButton from "../common/TestPlanStatusButton";
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
export default {
......@@ -194,7 +196,7 @@ export default {
let param = {};
Object.assign(param, this.form);
param.name = param.name.trim();
if (param.name == '') {
if (param.name === '') {
this.$warning(this.$t('test_track.plan.input_plan_name'));
return;
}
......@@ -206,7 +208,7 @@ export default {
this.dbProjectIds.forEach(dbId => {
if (nowIds.indexOf(dbId) === -1 && sign) {
sign = false;
this.$confirm('取消项目关联会同时取消该项目下已关联的测试用例', '提示', {
this.$confirm(this.$t('test_track.case.cancel_relevance_project'), this.$t('commons.prompt'), {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
......@@ -263,7 +265,7 @@ export default {
resetForm() {
//防止点击修改后,点击新建触发校验
if (this.$refs['planFrom']) {
this.$refs['planFrom'].validate((valid) => {
this.$refs['planFrom'].validate(() => {
this.$refs['planFrom'].resetFields();
this.form.name = '';
this.form.projectIds = [];
......
......@@ -20,7 +20,7 @@
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="principal"
prop="userName"
:label="$t('test_track.plan.plan_principal')"
show-overflow-tooltip>
</el-table-column>
......@@ -141,7 +141,7 @@ import MsTableOperatorButton from "../../../common/components/MsTableOperatorBut
import MsTableOperator from "../../../common/components/MsTableOperator";
import PlanStatusTableItem from "../../common/tableItems/plan/PlanStatusTableItem";
import PlanStageTableItem from "../../common/tableItems/plan/PlanStageTableItem";
import {_filter, _sort, checkoutTestManagerOrTestUser} from "../../../../../common/js/utils";
import {_filter, _sort, checkoutTestManagerOrTestUser} from "@/common/js/utils";
import TestReportTemplateList from "../view/comonents/TestReportTemplateList";
import TestCaseReportView from "../view/comonents/report/TestCaseReportView";
import MsDeleteConfirm from "../../../common/components/MsDeleteConfirm";
......
......@@ -26,11 +26,15 @@
</el-col>
<el-col :span="12" class="head-right">
<span class="head-right-tip" v-if="index + 1 == testCases.length">
{{ $t('test_track.plan_view.pre_case') }} : {{ testCases[index - 1] ? testCases[index - 1].name : '' }}
<span class="head-right-tip" v-if="index + 1 === testCases.length">
{{ $t('test_track.plan_view.pre_case') }} : {{
testCases[index - 1] ? testCases[index - 1].name : ''
}}
</span>
<span class="head-right-tip" v-if="index + 1 != testCases.length">
{{ $t('test_track.plan_view.next_case') }} : {{ testCases[index + 1] ? testCases[index + 1].name : '' }}
<span class="head-right-tip" v-if="index + 1 !== testCases.length">
{{ $t('test_track.plan_view.next_case') }} : {{
testCases[index + 1] ? testCases[index + 1].name : ''
}}
</span>
<el-button plain size="mini" icon="el-icon-arrow-up"
......@@ -49,7 +53,7 @@
</el-row>
<el-row style="margin-top: 0px;">
<el-row style="margin-top: 0;">
<el-col>
<el-divider content-position="left">{{ testCase.name }}</el-divider>
</el-col>
......@@ -65,9 +69,9 @@
</el-col>
<el-col :span="5">
<span class="cast_label">{{ $t('test_track.case.case_type') }}</span>
<span class="cast_item" v-if="testCase.type == 'functional'">{{ $t('commons.functional') }}</span>
<span class="cast_item" v-if="testCase.type == 'performance'">{{ $t('commons.performance') }}</span>
<span class="cast_item" v-if="testCase.type == 'api'">{{ $t('commons.api') }}</span>
<span class="cast_item" v-if="testCase.type === 'functional'">{{ $t('commons.functional') }}</span>
<span class="cast_item" v-if="testCase.type === 'performance'">{{ $t('commons.performance') }}</span>
<span class="cast_item" v-if="testCase.type === 'api'">{{ $t('commons.api') }}</span>
</el-col>
<el-col :span="13">
<test-plan-test-case-status-button class="status-button"
......@@ -81,8 +85,8 @@
<el-row>
<el-col :span="4" :offset="1">
<span class="cast_label">{{ $t('test_track.case.method') }}</span>
<span v-if="testCase.method == 'manual'">{{ $t('test_track.case.manual') }}</span>
<span v-if="testCase.method == 'auto'">{{ $t('test_track.case.auto') }}</span>
<span v-if="testCase.method === 'manual'">{{ $t('test_track.case.manual') }}</span>
<span v-if="testCase.method === 'auto'">{{ $t('test_track.case.auto') }}</span>
</el-col>
<el-col :span="5">
<span class="cast_label">{{ $t('test_track.case.module') }}</span>
......@@ -101,25 +105,26 @@
</el-col>
</el-row>
<el-row v-if="testCase.method == 'auto' && testCase.testId">
<el-row v-if="testCase.method === 'auto' && testCase.testId">
<el-col class="test-detail" :span="20" :offset="1">
<el-tabs v-model="activeTab" type="border-card" @tab-click="testTabChange">
<el-tab-pane name="detail" :label="$t('test_track.plan_view.test_detail')">
<api-test-detail :is-read-only="isReadOnly" v-if="testCase.type == 'api'" @runTest="testRun"
<api-test-detail :is-read-only="isReadOnly" v-if="testCase.type === 'api'" @runTest="testRun"
:id="testCase.testId" ref="apiTestDetail"/>
<performance-test-detail :is-read-only="isReadOnly" v-if="testCase.type == 'performance'"
<performance-test-detail :is-read-only="isReadOnly" v-if="testCase.type === 'performance'"
@runTest="testRun" :id="testCase.testId" ref="performanceTestDetail"/>
</el-tab-pane>
<el-tab-pane name="result" :label="$t('test_track.plan_view.test_result')">
<api-test-result :report-id="testCase.reportId" v-if=" testCase.type == 'api'" ref="apiTestResult"/>
<api-test-result :report-id="testCase.reportId" v-if=" testCase.type === 'api'"
ref="apiTestResult"/>
<performance-test-result :is-read-only="isReadOnly" :report-id="testCase.reportId"
v-if="testCase.type == 'performance'" ref="performanceTestResult"/>
v-if="testCase.type === 'performance'" ref="performanceTestResult"/>
</el-tab-pane>
</el-tabs>
</el-col>
</el-row>
<el-row v-if="testCase.method && testCase.method != 'auto'">
<el-row v-if="testCase.method && testCase.method !== 'auto'">
<el-col :span="20" :offset="1">
<div>
<span class="cast_label">{{ $t('test_track.case.steps') }}</span>
......@@ -219,8 +224,11 @@
<ckeditor :editor="editor" :disabled="isReadOnly" :config="editorConfig"
v-model="testCase.issues.content"/>
<el-row v-if="hasTapdId">
Tapd平台处理人:
<el-select v-model="testCase.tapdUsers" placeholder="请选择处理人" style="width: 20%" multiple
{{ $t('test_track.issue.please_choose_current_owner') }}
<el-select v-model="testCase.tapdUsers"
multiple
style="width: 20%"
:placeholder="$t('test_track.issue.please_choose_current_owner')"
collapse-tags>
<el-option v-for="(userInfo, index) in users" :key="index" :label="userInfo.user"
:value="userInfo.user"/>
......@@ -277,7 +285,7 @@
<el-col :span="15" :offset="1">
<div>
<span class="cast_label">{{ $t('commons.remark') }}:</span>
<span v-if="testCase.remark == null || testCase.remark == ''"
<span v-if="testCase.remark == null || testCase.remark === ''"
style="color: darkgrey">{{ $t('commons.not_filled') }}</span>
</div>
<div>
......@@ -309,8 +317,7 @@ import ApiTestDetail from "./test/ApiTestDetail";
import ApiTestResult from "./test/ApiTestResult";
import PerformanceTestDetail from "./test/PerformanceTestDetail";
import PerformanceTestResult from "./test/PerformanceTestResult";
import {listenGoBack, removeGoBackListener} from "../../../../../../common/js/utils";
import {CURRENT_PROJECT} from "../../../../../../common/js/constants";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
export default {
name: "TestPlanTestCaseEdit",
......@@ -332,7 +339,6 @@ export default {
issues: [],
editor: ClassicEditor,
editorConfig: {
// 'increaseIndent','decreaseIndent'
toolbar: ['heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'insertTable', '|', 'undo', 'redo'],
},
readConfig: {toolbar: []},
......@@ -368,6 +374,7 @@ export default {
},
statusChange(status) {
this.testCase.status = status;
this.saveCase();
},
saveCase() {
let param = {};
......@@ -388,11 +395,14 @@ export default {
}
param.results = JSON.stringify(param.results);
param.issues = JSON.stringify(this.testCase.issues);
this.$post('/test/plan/case/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.updateTestCases(param);
this.setPlanStatus(this.testCase.planId);
// 结果为Pass时 自动跳转到下一用例
if (this.testCase.status === 'Pass' && this.index < this.testCases.length - 1) {
this.handleNext();
}
});
},
updateTestCases(param) {
......@@ -448,11 +458,11 @@ export default {
},
initTest() {
this.$nextTick(() => {
if (this.testCase.method == 'auto') {
if (this.$refs.apiTestDetail && this.testCase.type == 'api') {
if (this.testCase.method === 'auto') {
if (this.$refs.apiTestDetail && this.testCase.type === 'api') {
this.$refs.apiTestDetail.init();
} else if (this.testCase.type == 'performance') {
} else if (this.testCase.type === 'performance') {
this.$refs.performanceTestDetail.init();
}
}
......@@ -464,7 +474,7 @@ export default {
this.activeTab = 'result';
},
testTabChange(data) {
if (this.testCase.type == 'performance' && data.paneName == 'result') {
if (this.testCase.type === 'performance' && data.paneName === 'result') {
this.$refs.performanceTestResult.checkReportStatus();
this.$refs.performanceTestResult.init();
}
......@@ -485,7 +495,7 @@ export default {
});
},
getRelatedTest() {
if (this.testCase.method == 'auto' && this.testCase.testId && this.testCase.testId != 'other') {
if (this.testCase.method === 'auto' && this.testCase.testId && this.testCase.testId !== 'other') {
this.$get('/' + this.testCase.type + '/get/' + this.testCase.testId, response => {
let data = response.data;
if (data) {
......@@ -495,7 +505,7 @@ export default {
this.$warning(this.$t("test_track.case.relate_test_not_find"));
}
});
} else if (this.testCase.testId === 'other' && this.testCase.method == 'auto') {
} else if (this.testCase.testId === 'other' && this.testCase.method === 'auto') {
this.$warning(this.$t("test_track.case.other_relate_test_not_find"));
}
},
......@@ -506,9 +516,9 @@ export default {
let actualResult = this.addPLabel('[' + this.$t('test_track.plan_view.actual_result') + ']');
this.testCase.steps.forEach(step => {
let stepPrefix = this.$t('test_track.plan_view.step') + step.num + ':';
desc += this.addPLabel(stepPrefix + (step.desc == undefined ? '' : step.desc));
result += this.addPLabel(stepPrefix + (step.result == undefined ? '' : step.result));
actualResult += this.addPLabel(stepPrefix + (step.actualResult == undefined ? '' : step.actualResult));
desc += this.addPLabel(stepPrefix + (step.desc === undefined ? '' : step.desc));
result += this.addPLabel(stepPrefix + (step.result === undefined ? '' : step.result));
actualResult += this.addPLabel(stepPrefix + (step.actualResult === undefined ? '' : step.actualResult));
});
this.testCase.issues.content = desc + this.addPLabel('') + result + this.addPLabel('') + actualResult + this.addPLabel('');
......@@ -517,8 +527,7 @@ export default {
if (project.tapdId) {
this.hasTapdId = true;
this.result = this.$get("/issues/tapd/user/" + this.testCase.caseId, response => {
let data = response.data;
this.users = data;
this.users = response.data;
})
}
})
......@@ -531,27 +540,29 @@ export default {
this.$post('/test/plan/edit/status/' + planId);
},
stepResultChange() {
if (this.testCase.method == 'manual') {
if (this.testCase.method === 'manual') {
this.isFailure = this.testCase.steptResults.filter(s => {
return s.executeResult === 'Failure' || s.executeResult === 'Blocking';
}).length > 0;
}
},
saveIssues() {
if (!this.testCase.issues.title || !this.testCase.issues.content) {
this.$warning(this.$t('test_track.issue.title_description_required'));
return;
}
let param = {};
param.title = this.testCase.issues.title;
param.content = this.testCase.issues.content;
param.testCaseId = this.testCase.caseId;
param.tapdUsers = this.testCase.tapdUsers;
this.result = this.$post("/issues/add", param, () => {
this.$success(this.$t('commons.save_success'));
this.getIssues(param.testCaseId);
});
this.issuesSwitch = false;
this.testCase.issues.title = "";
this.testCase.issues.content = "";
......@@ -559,8 +570,7 @@ export default {
},
getIssues(caseId) {
this.result = this.$get("/issues/get/" + caseId, response => {
let data = response.data;
this.issues = data;
this.issues = response.data;
})
},
closeIssue(row) {
......
......@@ -80,7 +80,7 @@
</el-col>
</el-row>
<el-row v-if="operationType == 'edit'" type="flex" justify="left" style="margin-top: 10px;">
<el-row v-if="operationType === 'edit'" type="flex" justify="left" style="margin-top: 10px;">
<el-col :span="19" :offset="1">
<el-form-item :label="$t('test_track.review.review_status')" :label-width="formLabelWidth" prop="status">
<test-plan-status-button :status="form.status" @statusChange="statusChange"/>
......@@ -110,8 +110,8 @@
<script>
import TestPlanStatusButton from "../../plan/common/TestPlanStatusButton";
import {WORKSPACE_ID} from "../../../../../common/js/constants";
import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils";
import {WORKSPACE_ID} from "@/common/js/constants";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
export default {
name: "TestCaseReviewEdit",
......@@ -169,7 +169,7 @@ export default {
let param = {};
Object.assign(param, this.form);
param.name = param.name.trim();
if (param.name == '') {
if (param.name === '') {
this.$warning(this.$t('test_track.plan.input_plan_name'));
return;
}
......@@ -183,7 +183,7 @@ export default {
this.dbProjectIds.forEach(dbId => {
if (nowIds.indexOf(dbId) === -1 && sign) {
sign = false;
this.$confirm('取消项目关联会同时取消该项目下已关联的测试用例', '提示', {
this.$confirm(this.$t('test_track.case.cancel_relevance_project'), this.$t('commons.prompt'), {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
......@@ -240,7 +240,7 @@ export default {
resetForm() {
//防止点击修改后,点击新建触发校验
if (this.$refs['reviewForm']) {
this.$refs['reviewForm'].validate((valid) => {
this.$refs['reviewForm'].validate(() => {
this.$refs['reviewForm'].resetFields();
this.form.name = '';
this.form.stage = '';
......
Subproject commit 06d935cd1d22ab36f09763745c2aff8ad3fb08c1
Subproject commit cc38137a69a0f20fadece9c0f9f50a9468c4ace9
import router from './components/common/router/router'
import {TokenKey} from '@/common/js/constants';
import {hasRolePermissions, hasRoles} from "@/common/js/utils";
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
const whiteList = ['/login']; // no redirect whitelist
NProgress.configure({showSpinner: false}) // NProgress Configuration
export const permission = {
inserted(el, binding) {
checkRolePermission(el, binding, 'permission');
......@@ -33,6 +36,8 @@ function checkRolePermission(el, binding, type) {
}
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start();
// determine whether the user has logged in
const user = JSON.parse(localStorage.getItem(TokenKey));
......@@ -40,6 +45,7 @@ router.beforeEach(async (to, from, next) => {
if (user) {
if (to.path === '/login') {
next({path: '/'});
NProgress.done(); // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
} else {
// const roles = user.roles.filter(r => r.id);
// TODO 设置路由的权限
......@@ -50,14 +56,16 @@ router.beforeEach(async (to, from, next) => {
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
next();
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login`)
next(`/login`);
NProgress.done();
}
}
});
router.afterEach(() => {
// finish progress bar
NProgress.done();
});
......@@ -113,6 +113,7 @@ export default {
formatErr: 'Format Error',
id: 'ID',
cannot_be_null: 'not null ',
required: "{0} is required",
millisecond: 'ms',
please_upload: 'Please upload file',
reference_documentation: "Reference documentation",
......@@ -305,7 +306,7 @@ export default {
api_test_report: 'Api Test Report',
load_test_report: 'Load Test Report',
test_plan_report: 'Test Plan Report',
recent: 'Recent Report',
recent: 'My recent Report',
search_by_name: 'Search by Name',
test_name: 'Test',
test_overview: 'Test Overview',
......@@ -343,7 +344,7 @@ export default {
run: 'One click operation',
operating: 'Operating',
pressure_prediction_chart: 'Pressure Prediction Chart',
recent: 'Recent Tests',
recent: 'My recent Tests',
search_by_name: 'Search by name',
project_name: 'Project',
delete_confirm: 'Are you sure want to delete test: ',
......@@ -435,6 +436,7 @@ export default {
common_config: "Common Config",
http_config: "HTTP Config",
database_config: "Database Config",
tcp_config: "TCP Config",
},
scenario: {
scenario: "Scenario",
......@@ -555,22 +557,38 @@ export default {
input_registry_center: "Please enter the registry center",
input_consumer_service: "Please enter the consumer & service",
check_registry_center: "Can't get interface list, please check the registry center",
}
},
sql: {
dataSource: "Data Source",
sql_script: "Sql Script",
timeout: "Timeout(ms)",
database_driver: "Driver",
database_url: "Database URL",
username: "Username",
password: "Password",
pool_max: "Max Number of Configuration",
query_timeout: "Max Wait(ms)",
name_cannot_be_empty: "SQL request name cannot be empty",
dataSource_cannot_be_empty: "SQL request datasource cannot be empty",
result_variable: "Result variable",
variable_names: "Variable names",
},
sql: {
dataSource: "Data Source",
sql_script: "Sql Script",
timeout: "Timeout(ms)",
database_driver: "Driver",
database_url: "Database URL",
username: "Username",
password: "Password",
pool_max: "Max Number of Configuration",
query_timeout: "Max Wait(ms)",
name_cannot_be_empty: "SQL request name cannot be empty",
dataSource_cannot_be_empty: "SQL request datasource cannot be empty",
result_variable: "Result variable",
variable_names: "Variable names",
},
tcp: {
server: "Server Name or IP",
port: "Port Number",
connect: "Connect(ms)",
response: "Response(ms)",
re_use_connection: "Re-use connection",
no_delay: "Set NoDelay",
close_connection: "Close connection",
so_linger: "SO LINGER",
eol_byte: "End of line byte value",
request: "Text to Send",
username: "Username",
password: "Password",
login: "Login Configuration",
server_cannot_be_empty: "Server name or IP cannot be empty",
},
},
api_import: {
label: "Import",
......@@ -633,8 +651,8 @@ export default {
save: "Save",
return: "Return",
length_less_than: "The length less than",
recent_plan: "Recent plan",
recent_case: "Recent case",
recent_plan: "My recent plan",
recent_case: "My recent case",
recent_review: "Recent review",
pass_rate: "Pass rate",
execution_result: ": Please select the execution result",
......@@ -699,6 +717,7 @@ export default {
status_prepare: 'Prepare',
status_pass: 'Pass',
status_un_pass: 'UnPass',
cancel_relevance_project: "Disassociating the project will also cancel the associated test cases under the project",
import: {
import: "Import test case",
case_import: "Import test case",
......@@ -878,7 +897,9 @@ export default {
delete: "Delete",
title_description_required: "Title and description are required",
close_success: "Closed successfully",
preview: "Preview"
preview: "Preview",
please_choose_current_owner: "Please choose current owner",
tapd_current_owner: "Tapd Current Owner:",
}
},
test_resource_pool: {
......
......@@ -116,6 +116,7 @@ export default {
id: 'ID',
millisecond: '毫秒',
cannot_be_null: '不能为空',
required: "{0}是必填的",
already_exists: '名称不能重复',
date: {
select_date: '选择日期',
......@@ -305,7 +306,7 @@ export default {
api_test_report: '接口测试报告',
load_test_report: '性能测试报告',
test_plan_report: '测试计划报告',
recent: '最近的报告',
recent: '最近的报告',
search_by_name: '根据名称搜索',
test_name: '所属测试',
test_overview: '测试概览',
......@@ -342,7 +343,7 @@ export default {
same_project_test: '只能运行同一项目内的测试',
already_exists: '测试名称不能重复',
operating: '操作',
recent: '最近的测试',
recent: '最近的测试',
search_by_name: '根据名称搜索',
project_name: '所属项目',
delete_confirm: '确认删除测试: ',
......@@ -436,6 +437,7 @@ export default {
common_config: "通用配置",
http_config: "HTTP配置",
database_config: "数据库配置",
tcp_config: "TCP配置",
},
scenario: {
scenario: "场景",
......@@ -572,6 +574,22 @@ export default {
dataSource_cannot_be_empty: "SQL请求数据源不能为空",
result_variable: "存储结果",
variable_names: "按列存储",
},
tcp: {
server: "服务器名或IP",
port: "端口",
connect: "连接(ms)",
response: "响应(ms)",
re_use_connection: "Re-use connection",
no_delay: "设置无延迟",
close_connection: "关闭连接",
so_linger: "SO LINGER",
eol_byte: "行尾(EOL)字节值",
request: "要发送的文本",
username: "用户名",
password: "密码",
login: "登录设置",
server_cannot_be_empty: "服务器名或IP不能为空",
}
},
api_import: {
......@@ -635,8 +653,8 @@ export default {
save: "保 存",
return: "返 回",
length_less_than: "长度必须小于",
recent_plan: "最近的计划",
recent_case: "最近的用例",
recent_plan: "最近的计划",
recent_case: "最近的用例",
recent_review: "最近的评审",
pass_rate: "通过率",
execution_result: ": 请选择执行结果",
......@@ -701,6 +719,7 @@ export default {
status_prepare: '未开始',
status_pass: '通过',
status_un_pass: '未通过',
cancel_relevance_project: "取消项目关联会同时取消该项目下已关联的测试用例",
import: {
import: "导入用例",
case_import: "导入测试用例",
......@@ -880,7 +899,9 @@ export default {
delete: "删除缺陷",
title_description_required: "标题和描述必填",
close_success: "关闭成功",
preview: "预览"
preview: "预览",
please_choose_current_owner: "请选择处理人",
tapd_current_owner: "Tapd平台处理人:",
}
},
test_resource_pool: {
......
......@@ -116,6 +116,7 @@ export default {
id: 'ID',
millisecond: '毫秒',
cannot_be_null: '不能為空',
required: "{0}是必填的",
already_exists: '名稱不能重復',
date: {
select_date: '選擇日期',
......@@ -305,7 +306,7 @@ export default {
api_test_report: '接口測試報告',
load_test_report: '性能測試報告',
test_plan_report: '測試計劃報告',
recent: '最近的報告',
recent: '最近的報告',
search_by_name: '根據名稱搜索',
test_name: '所屬測試',
test_overview: '測試概覽',
......@@ -342,7 +343,7 @@ export default {
same_project_test: '只能運行同壹項目內的測試',
already_exists: '測試名稱不能重復',
operating: '操作',
recent: '最近的測試',
recent: '最近的測試',
search_by_name: '根據名稱搜索',
project_name: '所屬項目',
delete_confirm: '確認刪除測試: ',
......@@ -436,6 +437,7 @@ export default {
common_config: "通用配置",
http_config: "HTTP配置",
database_config: "數據庫配置",
tcp_config: "TCP配置",
},
scenario: {
scenario: "場景",
......@@ -574,6 +576,22 @@ export default {
variable_names: "按列存儲",
}
},
tcp: {
server: "服務器名或IP",
port: "端口",
connect: "連接(ms)",
response: "響應(ms)",
re_use_connection: "Re-use connection",
no_delay: "設置無延遲",
close_connection: "關閉連接",
so_linger: "SO LINGER",
eol_byte: "行尾(EOL)字節值",
request: "要發送的文本",
username: "用戶名",
password: "密碼",
login: "登錄設置",
server_cannot_be_empty: "服務器名或IP不能為空",
},
api_import: {
label: "導入",
title: "接口測試導入",
......@@ -635,8 +653,8 @@ export default {
save: "保 存",
return: "返 回",
length_less_than: "長度必須小於",
recent_plan: "最近的計劃",
recent_case: "最近的用例",
recent_plan: "最近的計劃",
recent_case: "最近的用例",
recent_review: "最近的評審",
pass_rate: "通過率",
execution_result: ": 請選擇執行結果",
......@@ -701,6 +719,7 @@ export default {
status_prepare: '未開始',
status_pass: '通過',
status_un_pass: '未通過',
cancel_relevance_project: "取消項目關聯會同時取消該項目下已關聯的測試用例",
import: {
import: "導入用例",
case_import: "導入測試用例",
......@@ -880,7 +899,9 @@ export default {
delete: "刪除缺陷",
title_description_required: "標題和描述必填",
close_success: "關閉成功",
preview: "預覽"
preview: "預覽",
please_choose_current_owner: "請選擇處理人",
tapd_current_owner: "Tapd平台處理人:",
}
},
test_resource_pool: {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册