Merge branch 'master' of https://github.com/metersphere/metersphere
# Conflicts: # backend/src/main/java/io/metersphere/xpack
This commit is contained in:
commit
7f8f571ed4
|
@ -4,7 +4,7 @@ pipeline {
|
||||||
label 'master'
|
label 'master'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options { quietPeriod(2400) }
|
options { quietPeriod(1200) }
|
||||||
parameters {
|
parameters {
|
||||||
string(name: 'IMAGE_NAME', defaultValue: 'metersphere', description: '构建后的 Docker 镜像名称')
|
string(name: 'IMAGE_NAME', defaultValue: 'metersphere', description: '构建后的 Docker 镜像名称')
|
||||||
string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀')
|
string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀')
|
||||||
|
@ -24,11 +24,12 @@ pipeline {
|
||||||
sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}"
|
sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Notification') {
|
}
|
||||||
steps {
|
post('Notification') {
|
||||||
withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) {
|
always {
|
||||||
qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: '${WEBHOOK}'
|
sh "echo \$WEBHOOK\n"
|
||||||
}
|
withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) {
|
||||||
|
qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: "$WEBHOOK"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,6 +379,12 @@
|
||||||
<artifactId>reflections8</artifactId>
|
<artifactId>reflections8</artifactId>
|
||||||
<version>0.11.7</version>
|
<version>0.11.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- k8s client -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.fabric8</groupId>
|
||||||
|
<artifactId>kubernetes-client</artifactId>
|
||||||
|
<version>4.13.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -121,9 +121,11 @@
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="list" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
|
<select id="list" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
|
||||||
select test_plan_test_case.id as id, test_case.id as caseId, test_case.name, test_case.priority, test_case.type,
|
select test_plan_test_case.id as id, test_case.id as caseId, test_case.name, test_case.priority,
|
||||||
|
test_case.type,test_case.test_id as testId,
|
||||||
test_case.node_path, test_case.method, test_case.num, test_plan_test_case.executor, test_plan_test_case.status,
|
test_case.node_path, test_case.method, test_case.num, test_plan_test_case.executor, test_plan_test_case.status,
|
||||||
test_plan_test_case.update_time, test_case_node.name as model, project.name as projectName, test_plan_test_case.plan_id as planId
|
test_plan_test_case.update_time, test_case_node.name as model, project.name as projectName,
|
||||||
|
test_plan_test_case.plan_id as planId
|
||||||
from test_plan_test_case
|
from test_plan_test_case
|
||||||
inner join test_case on test_plan_test_case.case_id = test_case.id
|
inner join test_case on test_plan_test_case.case_id = test_case.id
|
||||||
left join test_case_node on test_case_node.id=test_case.node_id
|
left join test_case_node on test_case_node.id=test_case.node_id
|
||||||
|
|
|
@ -16,4 +16,5 @@ public class JmeterProperties {
|
||||||
|
|
||||||
private String home;
|
private String home;
|
||||||
|
|
||||||
|
private String heap = "-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m";
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ public class ShiroConfig implements EnvironmentAware {
|
||||||
ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
|
ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
|
||||||
filterChainDefinitionMap.put("/display/info", "anon");
|
filterChainDefinitionMap.put("/display/info", "anon");
|
||||||
filterChainDefinitionMap.put("/display/file/**", "anon");
|
filterChainDefinitionMap.put("/display/file/**", "anon");
|
||||||
|
filterChainDefinitionMap.put("/jmeter/download/**", "anon");
|
||||||
filterChainDefinitionMap.put("/**", "apikey, authc");
|
filterChainDefinitionMap.put("/**", "apikey, authc");
|
||||||
return shiroFilterFactoryBean;
|
return shiroFilterFactoryBean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package io.metersphere.performance.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import io.metersphere.performance.service.JmeterFileService;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("jmeter")
|
||||||
|
public class JmeterFileController {
|
||||||
|
@Resource
|
||||||
|
private JmeterFileService jmeterFileService;
|
||||||
|
|
||||||
|
@GetMapping("download")
|
||||||
|
public ResponseEntity<byte[]> downloadJmeterFiles(@RequestParam("testId") String testId, @RequestParam("resourceId") String resourceId,
|
||||||
|
@RequestParam("ratio") double ratio, @RequestParam("startTime") long startTime,
|
||||||
|
@RequestParam("reportId") String reportId, @RequestParam("resourceIndex") int resourceIndex,
|
||||||
|
@RequestParam("threadNum") int threadNum) {
|
||||||
|
byte[] bytes = jmeterFileService.downloadZip(testId, resourceId, ratio, startTime, reportId, resourceIndex, threadNum);
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType("application/octet-stream"))
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + testId + ".zip\"")
|
||||||
|
.body(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,7 +15,6 @@ public class EngineContext {
|
||||||
private Integer resourceIndex;
|
private Integer resourceIndex;
|
||||||
private Map<String, Object> properties = new HashMap<>();
|
private Map<String, Object> properties = new HashMap<>();
|
||||||
private Map<String, String> testData = new HashMap<>();
|
private Map<String, String> testData = new HashMap<>();
|
||||||
private Map<String, String> env = new HashMap<>();
|
|
||||||
private Map<String, byte[]> testJars = new HashMap<>();
|
private Map<String, byte[]> testJars = new HashMap<>();
|
||||||
|
|
||||||
public String getTestId() {
|
public String getTestId() {
|
||||||
|
@ -50,14 +49,6 @@ public class EngineContext {
|
||||||
this.properties.putAll(props);
|
this.properties.putAll(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getEnv() {
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnv(Map<String, String> env) {
|
|
||||||
this.env = env;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getProperty(String key) {
|
public Object getProperty(String key) {
|
||||||
return this.properties.get(key);
|
return this.properties.get(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import io.metersphere.commons.constants.FileType;
|
||||||
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
|
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
|
||||||
import io.metersphere.commons.exception.MSException;
|
import io.metersphere.commons.exception.MSException;
|
||||||
import io.metersphere.commons.utils.LogUtil;
|
import io.metersphere.commons.utils.LogUtil;
|
||||||
import io.metersphere.config.KafkaProperties;
|
|
||||||
import io.metersphere.i18n.Translator;
|
import io.metersphere.i18n.Translator;
|
||||||
import io.metersphere.performance.engine.docker.DockerTestEngine;
|
import io.metersphere.performance.engine.docker.DockerTestEngine;
|
||||||
import io.metersphere.performance.parse.EngineSourceParser;
|
import io.metersphere.performance.parse.EngineSourceParser;
|
||||||
|
@ -34,7 +33,6 @@ import java.util.stream.Collectors;
|
||||||
public class EngineFactory {
|
public class EngineFactory {
|
||||||
private static FileService fileService;
|
private static FileService fileService;
|
||||||
private static TestResourcePoolService testResourcePoolService;
|
private static TestResourcePoolService testResourcePoolService;
|
||||||
private static KafkaProperties kafkaProperties;
|
|
||||||
private static Class<? extends KubernetesTestEngine> kubernetesTestEngineClass;
|
private static Class<? extends KubernetesTestEngine> kubernetesTestEngineClass;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -99,13 +97,6 @@ public class EngineFactory {
|
||||||
engineContext.setStartTime(startTime);
|
engineContext.setStartTime(startTime);
|
||||||
engineContext.setReportId(reportId);
|
engineContext.setReportId(reportId);
|
||||||
engineContext.setResourceIndex(resourceIndex);
|
engineContext.setResourceIndex(resourceIndex);
|
||||||
HashMap<String, String> env = new HashMap<String, String>() {{
|
|
||||||
put("BOOTSTRAP_SERVERS", kafkaProperties.getBootstrapServers());
|
|
||||||
put("LOG_TOPIC", kafkaProperties.getLog().getTopic());
|
|
||||||
put("REPORT_ID", reportId);
|
|
||||||
put("RESOURCE_ID", resourceId);
|
|
||||||
}};
|
|
||||||
engineContext.setEnv(env);
|
|
||||||
|
|
||||||
if (StringUtils.isNotEmpty(loadTest.getLoadConfiguration())) {
|
if (StringUtils.isNotEmpty(loadTest.getLoadConfiguration())) {
|
||||||
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
|
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
|
||||||
|
@ -197,9 +188,4 @@ public class EngineFactory {
|
||||||
public void setTestResourcePoolService(TestResourcePoolService testResourcePoolService) {
|
public void setTestResourcePoolService(TestResourcePoolService testResourcePoolService) {
|
||||||
EngineFactory.testResourcePoolService = testResourcePoolService;
|
EngineFactory.testResourcePoolService = testResourcePoolService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resource
|
|
||||||
public void setKafkaProperties(KafkaProperties kafkaProperties) {
|
|
||||||
EngineFactory.kafkaProperties = kafkaProperties;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,20 @@ import io.metersphere.base.domain.TestResource;
|
||||||
import io.metersphere.commons.constants.ResourceStatusEnum;
|
import io.metersphere.commons.constants.ResourceStatusEnum;
|
||||||
import io.metersphere.commons.exception.MSException;
|
import io.metersphere.commons.exception.MSException;
|
||||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||||
import io.metersphere.commons.utils.LogUtil;
|
import io.metersphere.config.JmeterProperties;
|
||||||
|
import io.metersphere.config.KafkaProperties;
|
||||||
import io.metersphere.controller.ResultHolder;
|
import io.metersphere.controller.ResultHolder;
|
||||||
|
import io.metersphere.dto.BaseSystemConfigDTO;
|
||||||
import io.metersphere.dto.NodeDTO;
|
import io.metersphere.dto.NodeDTO;
|
||||||
import io.metersphere.i18n.Translator;
|
import io.metersphere.i18n.Translator;
|
||||||
import io.metersphere.performance.engine.AbstractEngine;
|
import io.metersphere.performance.engine.AbstractEngine;
|
||||||
import io.metersphere.performance.engine.EngineContext;
|
import io.metersphere.performance.engine.request.StartTestRequest;
|
||||||
import io.metersphere.performance.engine.EngineFactory;
|
import io.metersphere.service.SystemParameterService;
|
||||||
import io.metersphere.performance.engine.docker.request.TestRequest;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DockerTestEngine extends AbstractEngine {
|
public class DockerTestEngine extends AbstractEngine {
|
||||||
|
@ -60,38 +63,40 @@ public class DockerTestEngine extends AbstractEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runTest(TestResource resource, double ratio, int resourceIndex) {
|
private void runTest(TestResource resource, double ratio, int resourceIndex) {
|
||||||
EngineContext context = null;
|
|
||||||
try {
|
|
||||||
context = EngineFactory.createContext(loadTest, resource.getId(), ratio, this.getStartTime(), this.getReportId(), resourceIndex);
|
|
||||||
} catch (MSException e) {
|
|
||||||
LogUtil.error(e.getMessage(), e);
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
LogUtil.error(e.getMessage(), e);
|
|
||||||
MSException.throwException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
String configuration = resource.getConfiguration();
|
String configuration = resource.getConfiguration();
|
||||||
NodeDTO node = JSON.parseObject(configuration, NodeDTO.class);
|
NodeDTO node = JSON.parseObject(configuration, NodeDTO.class);
|
||||||
String nodeIp = node.getIp();
|
String nodeIp = node.getIp();
|
||||||
Integer port = node.getPort();
|
Integer port = node.getPort();
|
||||||
String testId = context.getTestId();
|
|
||||||
String content = context.getContent();
|
BaseSystemConfigDTO baseInfo = CommonBeanFactory.getBean(SystemParameterService.class).getBaseInfo();
|
||||||
|
KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class);
|
||||||
|
JmeterProperties jmeterProperties = CommonBeanFactory.getBean(JmeterProperties.class);
|
||||||
|
String metersphereUrl = "http://localhost:8081";
|
||||||
|
if (baseInfo != null) {
|
||||||
|
metersphereUrl = baseInfo.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> env = new HashMap<>();
|
||||||
|
env.put("RATIO", "" + ratio);
|
||||||
|
env.put("RESOURCE_INDEX", "" + resourceIndex);
|
||||||
|
env.put("METERSPHERE_URL", metersphereUrl);
|
||||||
|
env.put("START_TIME", "" + this.getStartTime());
|
||||||
|
env.put("TEST_ID", this.loadTest.getId());
|
||||||
|
env.put("REPORT_ID", this.getReportId());
|
||||||
|
env.put("BOOTSTRAP_SERVERS", kafkaProperties.getBootstrapServers());
|
||||||
|
env.put("LOG_TOPIC", kafkaProperties.getLog().getTopic());
|
||||||
|
env.put("RESOURCE_ID", resource.getId());
|
||||||
|
env.put("THREAD_NUM", "" + threadNum);
|
||||||
|
env.put("HEAP", jmeterProperties.getHeap());
|
||||||
|
|
||||||
|
|
||||||
|
StartTestRequest startTestRequest = new StartTestRequest();
|
||||||
|
startTestRequest.setImage(JMETER_IMAGE);
|
||||||
|
startTestRequest.setEnv(env);
|
||||||
|
|
||||||
String uri = String.format(BASE_URL + "/jmeter/container/start", nodeIp, port);
|
String uri = String.format(BASE_URL + "/jmeter/container/start", nodeIp, port);
|
||||||
|
ResultHolder result = restTemplate.postForObject(uri, startTestRequest, ResultHolder.class);
|
||||||
TestRequest testRequest = new TestRequest();
|
|
||||||
testRequest.setSize(1);
|
|
||||||
testRequest.setTestId(testId);
|
|
||||||
testRequest.setReportId(getReportId());
|
|
||||||
testRequest.setFileString(content);
|
|
||||||
testRequest.setImage(JMETER_IMAGE);
|
|
||||||
testRequest.setTestData(context.getTestData());
|
|
||||||
testRequest.setTestJars(context.getTestJars());
|
|
||||||
testRequest.setEnv(context.getEnv());
|
|
||||||
|
|
||||||
|
|
||||||
ResultHolder result = restTemplate.postForObject(uri, testRequest, ResultHolder.class);
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
MSException.throwException(Translator.get("start_engine_fail"));
|
MSException.throwException(Translator.get("start_engine_fail"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package io.metersphere.performance.engine.docker.request;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class BaseRequest {
|
|
||||||
private String testId;
|
|
||||||
private String reportId;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package io.metersphere.performance.engine.docker.request;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class TestRequest extends BaseRequest {
|
|
||||||
private int size;
|
|
||||||
private String fileString;
|
|
||||||
private String image;
|
|
||||||
private Map<String, String> testData = new HashMap<>();
|
|
||||||
private Map<String, String> env = new HashMap<>();
|
|
||||||
private Map<String, byte[]> testJars = new HashMap<>();
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package io.metersphere.performance.engine.request;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class BaseRequest {
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package io.metersphere.performance.engine.request;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class StartTestRequest {
|
||||||
|
private String image;
|
||||||
|
private Map<String, String> env = new HashMap<>();
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package io.metersphere.performance.engine.request;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class StopTestRequest {
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package io.metersphere.performance.service;
|
||||||
|
|
||||||
|
|
||||||
|
import com.alibaba.excel.util.CollectionUtils;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import io.metersphere.base.domain.LoadTestWithBLOBs;
|
||||||
|
import io.metersphere.base.mapper.LoadTestMapper;
|
||||||
|
import io.metersphere.commons.exception.MSException;
|
||||||
|
import io.metersphere.commons.utils.LogUtil;
|
||||||
|
import io.metersphere.performance.engine.EngineContext;
|
||||||
|
import io.metersphere.performance.engine.EngineFactory;
|
||||||
|
import org.apache.commons.lang3.SerializationUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class JmeterFileService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LoadTestMapper loadTestMapper;
|
||||||
|
|
||||||
|
public byte[] downloadZip(String testId, String resourceId, double ratio, long startTime, String reportId, int resourceIndex, int threadNum) {
|
||||||
|
try {
|
||||||
|
LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(testId);
|
||||||
|
// deep copy
|
||||||
|
LoadTestWithBLOBs subTest = SerializationUtils.clone(loadTest);
|
||||||
|
setThreadNum(subTest, threadNum);
|
||||||
|
EngineContext context = EngineFactory.createContext(subTest, resourceId, ratio, startTime, reportId, resourceIndex);
|
||||||
|
return zipFilesToByteArray(context);
|
||||||
|
} catch (MSException e) {
|
||||||
|
LogUtil.error(e.getMessage(), e);
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.error(e.getMessage(), e);
|
||||||
|
MSException.throwException(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setThreadNum(LoadTestWithBLOBs t, Integer limit) {
|
||||||
|
String loadConfiguration = t.getLoadConfiguration();
|
||||||
|
JSONArray jsonArray = JSON.parseArray(loadConfiguration);
|
||||||
|
for (int i = 0; i < jsonArray.size(); i++) {
|
||||||
|
if (jsonArray.get(i) instanceof Map) {
|
||||||
|
JSONObject o = jsonArray.getJSONObject(i);
|
||||||
|
if (StringUtils.equals(o.getString("key"), "TargetLevel")) {
|
||||||
|
o.put("value", limit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonArray.get(i) instanceof List) {
|
||||||
|
JSONArray o = jsonArray.getJSONArray(i);
|
||||||
|
for (int j = 0; j < o.size(); j++) {
|
||||||
|
JSONObject b = o.getJSONObject(j);
|
||||||
|
if (StringUtils.equals(b.getString("key"), "TargetLevel")) {
|
||||||
|
b.put("value", limit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置线程数
|
||||||
|
t.setLoadConfiguration(jsonArray.toJSONString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] zipFilesToByteArray(EngineContext context) throws IOException {
|
||||||
|
String testId = context.getTestId();
|
||||||
|
String fileName = testId + ".jmx";
|
||||||
|
|
||||||
|
Map<String, byte[]> files = new HashMap<>();
|
||||||
|
|
||||||
|
// 每个测试生成一个文件夹
|
||||||
|
files.put(fileName, context.getContent().getBytes(StandardCharsets.UTF_8));
|
||||||
|
// 保存测试数据文件
|
||||||
|
Map<String, String> testData = context.getTestData();
|
||||||
|
if (!CollectionUtils.isEmpty(testData)) {
|
||||||
|
for (String k : testData.keySet()) {
|
||||||
|
String v = testData.get(k);
|
||||||
|
files.put("k", v.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存 byte[] jar
|
||||||
|
Map<String, byte[]> jarFiles = context.getTestJars();
|
||||||
|
if (!CollectionUtils.isEmpty(jarFiles)) {
|
||||||
|
for (String k : jarFiles.keySet()) {
|
||||||
|
byte[] v = jarFiles.get(k);
|
||||||
|
files.put("k", v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return listBytesToZip(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] listBytesToZip(Map<String, byte[]> mapReport) throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ZipOutputStream zos = new ZipOutputStream(baos);
|
||||||
|
for (Map.Entry<String, byte[]> report : mapReport.entrySet()) {
|
||||||
|
ZipEntry entry = new ZipEntry(report.getKey());
|
||||||
|
entry.setSize(report.getValue().length);
|
||||||
|
zos.putNextEntry(entry);
|
||||||
|
zos.write(report.getValue());
|
||||||
|
}
|
||||||
|
zos.closeEntry();
|
||||||
|
zos.close();
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,9 +47,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSelect(index) {
|
handleSelect(index) {
|
||||||
console.log(index)
|
|
||||||
this.activeIndex = index
|
this.activeIndex = index
|
||||||
|
|
||||||
},
|
},
|
||||||
active() {
|
active() {
|
||||||
if (this.activeIndex === '/api') {
|
if (this.activeIndex === '/api') {
|
||||||
|
|
|
@ -70,13 +70,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-for="(item,index) in infoList " :key="index">
|
<div v-for="(item,index) in infoList " :key="index">
|
||||||
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
|
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
|
||||||
<el-row>
|
|
||||||
<el-col>
|
|
||||||
<el-form-item prop="controllerUrl" label="Controller URL">
|
|
||||||
<el-input v-model="item.controllerUrl" autocomplete="new-password"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col>
|
<el-col>
|
||||||
<el-form-item prop="masterUrl" label="Master URL">
|
<el-form-item prop="masterUrl" label="Master URL">
|
||||||
|
@ -164,13 +157,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-for="(item,index) in infoList " :key="index">
|
<div v-for="(item,index) in infoList " :key="index">
|
||||||
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
|
<div class="node-line" v-if="form.type === 'K8S'" v-xpack>
|
||||||
<el-row>
|
|
||||||
<el-col>
|
|
||||||
<el-form-item prop="controllerUrl" label="Controller URL">
|
|
||||||
<el-input v-model="item.controllerUrl" autocomplete="off"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col>
|
<el-col>
|
||||||
<el-form-item prop="masterUrl" label="Master URL">
|
<el-form-item prop="masterUrl" label="Master URL">
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<review-comment-item v-for="(comment,index) in comments"
|
<review-comment-item v-for="(comment,index) in comments"
|
||||||
:key="index"
|
:key="index"
|
||||||
:comment="comment"
|
:comment="comment"
|
||||||
|
:read-only="readOnly"
|
||||||
@refresh="getComments()"/>
|
@refresh="getComments()"/>
|
||||||
<div v-if="comments.length === 0" style="text-align: center">
|
<div v-if="comments.length === 0" style="text-align: center">
|
||||||
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
|
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
|
||||||
|
@ -31,6 +32,10 @@ export default {
|
||||||
caseId: {
|
caseId: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
readOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -1,163 +1,174 @@
|
||||||
<template>
|
<template>
|
||||||
<el-form :model="form" ref="caseFrom" v-loading="result.loading">
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-card>
|
||||||
|
<el-form :model="form" ref="caseFrom" v-loading="result.loading">
|
||||||
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" :offset="1">
|
<el-col :span="10" :offset="1">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
:placeholder="$t('test_track.case.input_name')"
|
:placeholder="$t('test_track.case.input_name')"
|
||||||
:label="$t('test_track.case.name')"
|
:label="$t('test_track.case.name')"
|
||||||
:label-width="formLabelWidth"
|
:label-width="formLabelWidth"
|
||||||
prop="name">
|
prop="name">
|
||||||
<el-input class="case-name" :disabled="readOnly" v-model="testCase.name"></el-input>
|
<el-input class="case-name" :disabled="readOnly" v-model="testCase.name"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item :label="$t('test_track.case.module')" :label-width="formLabelWidth" prop="module">
|
<el-form-item :label="$t('test_track.case.module')" :label-width="formLabelWidth" prop="module">
|
||||||
<el-input class="case-name" :disabled="readOnly" v-model="testCase.nodePath"></el-input>
|
<el-input class="case-name" :disabled="readOnly" v-model="testCase.nodePath"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" :offset="1">
|
<el-col :span="10" :offset="1">
|
||||||
<el-form-item :label="$t('test_track.case.maintainer')" :label-width="formLabelWidth" prop="maintainer">
|
<el-form-item :label="$t('test_track.case.maintainer')" :label-width="formLabelWidth" prop="maintainer">
|
||||||
<el-select :disabled="readOnly" v-model="testCase.maintainer"
|
<el-select :disabled="readOnly" v-model="testCase.maintainer"
|
||||||
:placeholder="$t('test_track.case.input_maintainer')" filterable>
|
:placeholder="$t('test_track.case.input_maintainer')" filterable>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item :label="$t('test_track.case.priority')" :label-width="formLabelWidth" prop="priority">
|
<el-form-item :label="$t('test_track.case.priority')" :label-width="formLabelWidth" prop="priority">
|
||||||
<el-select :disabled="readOnly" v-model="testCase.priority" clearable
|
<el-select :disabled="readOnly" v-model="testCase.priority" clearable
|
||||||
:placeholder="$t('test_track.case.input_priority')">
|
:placeholder="$t('test_track.case.input_priority')">
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" :offset="1">
|
<el-col :span="10" :offset="1">
|
||||||
<el-form-item :label="$t('test_track.case.type')" :label-width="formLabelWidth" prop="type">
|
<el-form-item :label="$t('test_track.case.type')" :label-width="formLabelWidth" prop="type">
|
||||||
<el-select :disabled="readOnly" v-model="testCase.type"
|
<el-select :disabled="readOnly" v-model="testCase.type"
|
||||||
:placeholder="$t('test_track.case.input_type')">
|
:placeholder="$t('test_track.case.input_type')">
|
||||||
<el-option :label="$t('commons.functional')" value="functional"></el-option>
|
<el-option :label="$t('commons.functional')" value="functional"></el-option>
|
||||||
<el-option :label="$t('commons.performance')" value="performance"></el-option>
|
<el-option :label="$t('commons.performance')" value="performance"></el-option>
|
||||||
<el-option :label="$t('commons.api')" value="api"></el-option>
|
<el-option :label="$t('commons.api')" value="api"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item :label="$t('test_track.case.method')" :label-width="formLabelWidth" prop="method">
|
<el-form-item :label="$t('test_track.case.method')" :label-width="formLabelWidth" prop="method">
|
||||||
<el-select :disabled="readOnly" v-model="testCase.method" :placeholder="$t('test_track.case.input_method')">
|
<el-select :disabled="readOnly" v-model="testCase.method" :placeholder="$t('test_track.case.input_method')">
|
||||||
<el-option :label="$t('test_track.case.auto')" value="auto"></el-option>
|
<el-option :label="$t('test_track.case.auto')" value="auto"></el-option>
|
||||||
<el-option :label="$t('test_track.case.manual')" value="manual"></el-option>
|
<el-option :label="$t('test_track.case.manual')" value="manual"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row v-if="testCase.method && testCase.method == 'auto'">
|
<el-row v-if="testCase.method && testCase.method == 'auto'">
|
||||||
<el-col :span="10" :offset="1">
|
<el-col :span="10" :offset="1">
|
||||||
<el-form-item :label="$t('test_track.case.relate_test')" :label-width="formLabelWidth" prop="testId">
|
<el-form-item :label="$t('test_track.case.relate_test')" :label-width="formLabelWidth" prop="testId">
|
||||||
<el-select filterable :disabled="readOnly" v-model="testCase.testId"
|
<el-select filterable :disabled="readOnly" v-model="testCase.testId"
|
||||||
:placeholder="$t('test_track.case.input_type')">
|
:placeholder="$t('test_track.case.input_type')">
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12" v-if="testCase.testId=='other'">
|
<el-col :span="12" v-if="testCase.testId=='other'">
|
||||||
<el-form-item :label="$t('test_track.case.test_name')" :label-width="formLabelWidth" prop="testId">
|
<el-form-item :label="$t('test_track.case.test_name')" :label-width="formLabelWidth" prop="testId">
|
||||||
<el-input v-model="testCase.otherTestName" :placeholder="$t('test_track.case.input_test_case')"
|
<el-input v-model="testCase.otherTestName" :placeholder="$t('test_track.case.input_test_case')"
|
||||||
:disabled="readOnly"></el-input>
|
:disabled="readOnly"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row style="margin-top: 15px;">
|
<el-row style="margin-top: 15px;">
|
||||||
<el-col :offset="1">{{ $t('test_track.case.prerequisite') }}:</el-col>
|
<el-col :offset="1">{{ $t('test_track.case.prerequisite') }}:</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row type="flex" justify="center" style="margin-top: 10px;">
|
<el-row type="flex" justify="center" style="margin-top: 10px;">
|
||||||
<el-col :span="22">
|
<el-col :span="22">
|
||||||
<el-form-item prop="prerequisite">
|
<el-form-item prop="prerequisite">
|
||||||
<el-input :disabled="readOnly" v-model="testCase.prerequisite"
|
<el-input :disabled="readOnly" v-model="testCase.prerequisite"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:autosize="{ minRows: 2, maxRows: 4}"
|
:autosize="{ minRows: 2, maxRows: 4}"
|
||||||
:rows="2"
|
:rows="2"
|
||||||
:placeholder="$t('test_track.case.input_prerequisite')"></el-input>
|
:placeholder="$t('test_track.case.input_prerequisite')"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row v-if="testCase.method && testCase.method != 'auto'" style="margin-bottom: 10px">
|
<el-row v-if="testCase.method && testCase.method != 'auto'" style="margin-bottom: 10px">
|
||||||
<el-col :offset="1">{{ $t('test_track.case.steps') }}:</el-col>
|
<el-col :offset="1">{{ $t('test_track.case.steps') }}:</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row v-if="testCase.method && testCase.method != 'auto'" type="flex" justify="center">
|
<el-row v-if="testCase.method && testCase.method != 'auto'" type="flex" justify="center">
|
||||||
<el-col :span="22">
|
<el-col :span="22">
|
||||||
<el-table
|
<el-table
|
||||||
v-if="isStepTableAlive"
|
v-if="isStepTableAlive"
|
||||||
:data="JSON.parse(testCase.steps)"
|
:data="JSON.parse(testCase.steps)"
|
||||||
class="tb-edit"
|
class="tb-edit"
|
||||||
border
|
border
|
||||||
size="mini"
|
|
||||||
:default-sort="{prop: 'num', order: 'ascending'}"
|
|
||||||
highlight-current-row>
|
|
||||||
<el-table-column :label="$t('test_track.case.number')" prop="num" min-width="15%"></el-table-column>
|
|
||||||
<el-table-column :label="$t('test_track.case.step_desc')" prop="desc" min-width="35%">
|
|
||||||
<template v-slot:default="scope">
|
|
||||||
<el-input
|
|
||||||
class="table-edit-input"
|
|
||||||
size="mini"
|
size="mini"
|
||||||
:disabled="readOnly"
|
:default-sort="{prop: 'num', order: 'ascending'}"
|
||||||
type="textarea"
|
highlight-current-row>
|
||||||
:autosize="{ minRows: 1, maxRows: 6}"
|
<el-table-column :label="$t('test_track.case.number')" prop="num" min-width="15%"></el-table-column>
|
||||||
:rows="2"
|
<el-table-column :label="$t('test_track.case.step_desc')" prop="desc" min-width="35%">
|
||||||
v-model="scope.row.desc"
|
<template v-slot:default="scope">
|
||||||
:placeholder="$t('commons.input_content')"
|
<el-input
|
||||||
clearable/>
|
class="table-edit-input"
|
||||||
</template>
|
size="mini"
|
||||||
</el-table-column>
|
:disabled="readOnly"
|
||||||
<el-table-column :label="$t('test_track.case.expected_results')" prop="result" min-width="35%">
|
type="textarea"
|
||||||
<template v-slot:default="scope">
|
:autosize="{ minRows: 1, maxRows: 6}"
|
||||||
<el-input
|
:rows="2"
|
||||||
class="table-edit-input"
|
v-model="scope.row.desc"
|
||||||
size="mini"
|
:placeholder="$t('commons.input_content')"
|
||||||
:disabled="readOnly"
|
clearable/>
|
||||||
type="textarea"
|
</template>
|
||||||
:autosize="{ minRows: 1, maxRows: 6}"
|
</el-table-column>
|
||||||
:rows="2"
|
<el-table-column :label="$t('test_track.case.expected_results')" prop="result" min-width="35%">
|
||||||
v-model="scope.row.result"
|
<template v-slot:default="scope">
|
||||||
:placeholder="$t('commons.input_content')"
|
<el-input
|
||||||
clearable/>
|
class="table-edit-input"
|
||||||
</template>
|
size="mini"
|
||||||
</el-table-column>
|
:disabled="readOnly"
|
||||||
</el-table>
|
type="textarea"
|
||||||
</el-col>
|
:autosize="{ minRows: 1, maxRows: 6}"
|
||||||
</el-row>
|
:rows="2"
|
||||||
|
v-model="scope.row.result"
|
||||||
|
:placeholder="$t('commons.input_content')"
|
||||||
|
clearable/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<el-row style="margin-top: 15px;margin-bottom: 10px">
|
<el-row style="margin-top: 15px;margin-bottom: 10px">
|
||||||
<el-col :offset="1">{{ $t('commons.remark') }}:</el-col>
|
<el-col :offset="1">{{ $t('commons.remark') }}:</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row type="flex" justify="center">
|
<el-row type="flex" justify="center">
|
||||||
<el-col :span="22">
|
<el-col :span="22">
|
||||||
<el-form-item prop="remark">
|
<el-form-item prop="remark">
|
||||||
<el-input v-model="testCase.remark"
|
<el-input v-model="testCase.remark"
|
||||||
:autosize="{ minRows: 2, maxRows: 4}"
|
:autosize="{ minRows: 2, maxRows: 4}"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:disabled="readOnly"
|
:disabled="readOnly"
|
||||||
:rows="2"
|
:rows="2"
|
||||||
:placeholder="$t('commons.input_content')"></el-input>
|
:placeholder="$t('commons.input_content')"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<case-comment :case-id="testCaseId" :read-only="true"/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import CaseComment from "@/business/components/track/case/components/CaseComment";
|
||||||
export default {
|
export default {
|
||||||
name: "TestCaseDetail",
|
name: "TestCaseDetail",
|
||||||
|
components: {CaseComment},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
result: {},
|
result: {},
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
<el-popover
|
<el-popover
|
||||||
placement="right-end"
|
placement="right-end"
|
||||||
:title="$t('test_track.case.view_case')"
|
:title="$t('test_track.case.view_case')"
|
||||||
width="60%"
|
width="70%"
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
>
|
>
|
||||||
<test-case-detail v-if="currentCaseId === scope.row.id" :test-case-id="currentCaseId"/>
|
<test-case-detail v-if="currentCaseId === scope.row.id" :test-case-id="currentCaseId"/>
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
{{ comment.createTime | timestampFormatDate }}
|
{{ comment.createTime | timestampFormatDate }}
|
||||||
</span>
|
</span>
|
||||||
<span class="comment-delete">
|
<span class="comment-delete">
|
||||||
<i class="el-icon-edit" style="font-size: 9px;margin-right: 6px;" @click="openEdit"/>
|
<el-link icon="el-icon-edit" style="font-size: 9px;margin-right: 6px;" @click="openEdit" :disabled="readOnly"/>
|
||||||
<i class="el-icon-close" @click="deleteComment"/>
|
<el-link icon="el-icon-close" @click="deleteComment" :disabled="readOnly"/>
|
||||||
</span>
|
</span>
|
||||||
<br/>
|
<br/>
|
||||||
<div class="comment-desc" style="font-size: 10px;color: #303133">
|
<div class="comment-desc" style="font-size: 10px;color: #303133">
|
||||||
|
@ -50,7 +50,11 @@ export default {
|
||||||
name: "ReviewCommentItem",
|
name: "ReviewCommentItem",
|
||||||
components: {MsDialogFooter},
|
components: {MsDialogFooter},
|
||||||
props: {
|
props: {
|
||||||
comment: Object
|
comment: Object,
|
||||||
|
readOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
Loading…
Reference in New Issue