This commit is contained in:
chenjianxing 2020-03-24 10:48:02 +08:00
commit 6b8c059f99
40 changed files with 1373 additions and 305 deletions

View File

@ -152,24 +152,12 @@
<artifactId>jmeter-plugins-casutg</artifactId> <artifactId>jmeter-plugins-casutg</artifactId>
<version>2.9</version> <version>2.9</version>
</dependency> </dependency>
<!-- jmeter graph -->
<dependency>
<groupId>kg.apc</groupId>
<artifactId>jmeter-plugins-cmd</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>kg.apc</groupId>
<artifactId>jmeter-plugins-synthesis</artifactId>
<version>2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/kg.apc/jmeter-plugins-standard -->
<dependency>
<groupId>kg.apc</groupId>
<artifactId>jmeter-plugins-standard</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.1</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>

View File

@ -6,6 +6,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication(exclude = {QuartzAutoConfiguration.class}) @SpringBootApplication(exclude = {QuartzAutoConfiguration.class})
@ServletComponentScan @ServletComponentScan
@ -14,4 +16,9 @@ public class Application {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
} }
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
} }

View File

@ -9,8 +9,6 @@ public class FileMetadata implements Serializable {
private String type; private String type;
private String engine;
private Long createTime; private Long createTime;
private Long updateTime; private Long updateTime;
@ -43,14 +41,6 @@ public class FileMetadata implements Serializable {
this.type = type == null ? null : type.trim(); this.type = type == null ? null : type.trim();
} }
public String getEngine() {
return engine;
}
public void setEngine(String engine) {
this.engine = engine == null ? null : engine.trim();
}
public Long getCreateTime() { public Long getCreateTime() {
return createTime; return createTime;
} }

View File

@ -314,76 +314,6 @@ public class FileMetadataExample {
return (Criteria) this; return (Criteria) this;
} }
public Criteria andEngineIsNull() {
addCriterion("engine is null");
return (Criteria) this;
}
public Criteria andEngineIsNotNull() {
addCriterion("engine is not null");
return (Criteria) this;
}
public Criteria andEngineEqualTo(String value) {
addCriterion("engine =", value, "engine");
return (Criteria) this;
}
public Criteria andEngineNotEqualTo(String value) {
addCriterion("engine <>", value, "engine");
return (Criteria) this;
}
public Criteria andEngineGreaterThan(String value) {
addCriterion("engine >", value, "engine");
return (Criteria) this;
}
public Criteria andEngineGreaterThanOrEqualTo(String value) {
addCriterion("engine >=", value, "engine");
return (Criteria) this;
}
public Criteria andEngineLessThan(String value) {
addCriterion("engine <", value, "engine");
return (Criteria) this;
}
public Criteria andEngineLessThanOrEqualTo(String value) {
addCriterion("engine <=", value, "engine");
return (Criteria) this;
}
public Criteria andEngineLike(String value) {
addCriterion("engine like", value, "engine");
return (Criteria) this;
}
public Criteria andEngineNotLike(String value) {
addCriterion("engine not like", value, "engine");
return (Criteria) this;
}
public Criteria andEngineIn(List<String> values) {
addCriterion("engine in", values, "engine");
return (Criteria) this;
}
public Criteria andEngineNotIn(List<String> values) {
addCriterion("engine not in", values, "engine");
return (Criteria) this;
}
public Criteria andEngineBetween(String value1, String value2) {
addCriterion("engine between", value1, value2, "engine");
return (Criteria) this;
}
public Criteria andEngineNotBetween(String value1, String value2) {
addCriterion("engine not between", value1, value2, "engine");
return (Criteria) this;
}
public Criteria andCreateTimeIsNull() { public Criteria andCreateTimeIsNull() {
addCriterion("create_time is null"); addCriterion("create_time is null");
return (Criteria) this; return (Criteria) this;

View File

@ -5,7 +5,6 @@
<id column="id" jdbcType="VARCHAR" property="id" /> <id column="id" jdbcType="VARCHAR" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" /> <result column="name" jdbcType="VARCHAR" property="name" />
<result column="type" jdbcType="VARCHAR" property="type" /> <result column="type" jdbcType="VARCHAR" property="type" />
<result column="engine" jdbcType="VARCHAR" property="engine" />
<result column="create_time" jdbcType="BIGINT" property="createTime" /> <result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" /> <result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="size" jdbcType="BIGINT" property="size" /> <result column="size" jdbcType="BIGINT" property="size" />
@ -69,7 +68,7 @@
</where> </where>
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, name, type, engine, create_time, update_time, size id, name, type, create_time, update_time, size
</sql> </sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.FileMetadataExample" resultMap="BaseResultMap"> <select id="selectByExample" parameterType="io.metersphere.base.domain.FileMetadataExample" resultMap="BaseResultMap">
select select
@ -103,11 +102,11 @@
</delete> </delete>
<insert id="insert" parameterType="io.metersphere.base.domain.FileMetadata"> <insert id="insert" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata (id, name, type, insert into file_metadata (id, name, type,
engine, create_time, update_time, create_time, update_time, size
size) )
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR}, values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{engine,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{size,jdbcType=BIGINT}
#{size,jdbcType=BIGINT}) )
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.FileMetadata"> <insert id="insertSelective" parameterType="io.metersphere.base.domain.FileMetadata">
insert into file_metadata insert into file_metadata
@ -121,9 +120,6 @@
<if test="type != null"> <if test="type != null">
type, type,
</if> </if>
<if test="engine != null">
engine,
</if>
<if test="createTime != null"> <if test="createTime != null">
create_time, create_time,
</if> </if>
@ -144,9 +140,6 @@
<if test="type != null"> <if test="type != null">
#{type,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
</if> </if>
<if test="engine != null">
#{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null"> <if test="createTime != null">
#{createTime,jdbcType=BIGINT}, #{createTime,jdbcType=BIGINT},
</if> </if>
@ -176,9 +169,6 @@
<if test="record.type != null"> <if test="record.type != null">
type = #{record.type,jdbcType=VARCHAR}, type = #{record.type,jdbcType=VARCHAR},
</if> </if>
<if test="record.engine != null">
engine = #{record.engine,jdbcType=VARCHAR},
</if>
<if test="record.createTime != null"> <if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT}, create_time = #{record.createTime,jdbcType=BIGINT},
</if> </if>
@ -198,7 +188,6 @@
set id = #{record.id,jdbcType=VARCHAR}, set id = #{record.id,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR}, name = #{record.name,jdbcType=VARCHAR},
type = #{record.type,jdbcType=VARCHAR}, type = #{record.type,jdbcType=VARCHAR},
engine = #{record.engine,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT}, create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT}, update_time = #{record.updateTime,jdbcType=BIGINT},
size = #{record.size,jdbcType=BIGINT} size = #{record.size,jdbcType=BIGINT}
@ -215,9 +204,6 @@
<if test="type != null"> <if test="type != null">
type = #{type,jdbcType=VARCHAR}, type = #{type,jdbcType=VARCHAR},
</if> </if>
<if test="engine != null">
engine = #{engine,jdbcType=VARCHAR},
</if>
<if test="createTime != null"> <if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
</if> </if>
@ -234,7 +220,6 @@
update file_metadata update file_metadata
set name = #{name,jdbcType=VARCHAR}, set name = #{name,jdbcType=VARCHAR},
type = #{type,jdbcType=VARCHAR}, type = #{type,jdbcType=VARCHAR},
engine = #{engine,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}, update_time = #{updateTime,jdbcType=BIGINT},
size = #{size,jdbcType=BIGINT} size = #{size,jdbcType=BIGINT}

View File

@ -1,5 +0,0 @@
package io.metersphere.commons.constants;
public enum EngineType {
DOCKER, KUBERNETES
}

View File

@ -1,6 +1,6 @@
package io.metersphere.commons.constants; package io.metersphere.commons.constants;
public enum ResourceTypeEnum { public enum ResourcePoolTypeEnum {
/** /**
* k8s 资源池 * k8s 资源池
*/ */

View File

@ -39,4 +39,13 @@ public class TestResourcePoolController {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true); Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, testResourcePoolService.listResourcePools(request)); return PageUtils.setPageInfo(page, testResourcePoolService.listResourcePools(request));
} }
@GetMapping("list/all")
public List<TestResourcePool> listResourcePools() {
PageHelper.startPage(1, 10000, true);
QueryResourcePoolRequest request = new QueryResourcePoolRequest();
return testResourcePoolService.listResourcePools(request);
}
} }

View File

@ -0,0 +1,41 @@
package io.metersphere.dto;
public class KubernetesDTO {
private String masterUrl;
private String token;
private Integer maxConcurrency;
private Boolean validate;
public String getMasterUrl() {
return masterUrl;
}
public void setMasterUrl(String masterUrl) {
this.masterUrl = masterUrl;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Integer getMaxConcurrency() {
return maxConcurrency;
}
public void setMaxConcurrency(Integer maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
public Boolean getValidate() {
return validate;
}
public void setValidate(Boolean validate) {
this.validate = validate;
}
}

View File

@ -0,0 +1,40 @@
package io.metersphere.dto;
public class NodeDTO {
private String ip;
private Integer port;
private Integer maxConcurrency;
private Boolean validate;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getMaxConcurrency() {
return maxConcurrency;
}
public void setMaxConcurrency(Integer maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
public Boolean getValidate() {
return validate;
}
public void setValidate(Boolean validate) {
this.validate = validate;
}
}

View File

@ -7,7 +7,6 @@ public class EngineContext {
private String testId; private String testId;
private String testName; private String testName;
private String namespace; private String namespace;
private String engineType;
private String fileType; private String fileType;
private String content; private String content;
private Map<String, Object> properties = new HashMap<>(); private Map<String, Object> properties = new HashMap<>();
@ -37,14 +36,6 @@ public class EngineContext {
this.namespace = namespace; this.namespace = namespace;
} }
public String getEngineType() {
return engineType;
}
public void setEngineType(String engineType) {
this.engineType = engineType;
}
public void addProperty(String key, Object value) { public void addProperty(String key, Object value) {
this.properties.put(key, value); this.properties.put(key, value);
} }

View File

@ -5,7 +5,8 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.FileContent; import io.metersphere.base.domain.FileContent;
import io.metersphere.base.domain.FileMetadata; import io.metersphere.base.domain.FileMetadata;
import io.metersphere.base.domain.LoadTestWithBLOBs; import io.metersphere.base.domain.LoadTestWithBLOBs;
import io.metersphere.commons.constants.EngineType; import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.engine.docker.DockerTestEngine; import io.metersphere.engine.docker.DockerTestEngine;
import io.metersphere.engine.kubernetes.KubernetesTestEngine; import io.metersphere.engine.kubernetes.KubernetesTestEngine;
@ -13,6 +14,7 @@ import io.metersphere.i18n.Translator;
import io.metersphere.parse.EngineSourceParser; import io.metersphere.parse.EngineSourceParser;
import io.metersphere.parse.EngineSourceParserFactory; import io.metersphere.parse.EngineSourceParserFactory;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import io.metersphere.service.TestResourcePoolService;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -26,14 +28,35 @@ import java.util.Map;
@Service @Service
public class EngineFactory { public class EngineFactory {
private static FileService fileService; private static FileService fileService;
private static TestResourcePoolService testResourcePoolService;
public static Engine createEngine(String engineType) { public static Engine createEngine(LoadTestWithBLOBs loadTest) {
final EngineType type = EngineType.valueOf(engineType); String resourcePoolId = null;
if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) {
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
for (int i = 0; i < jsonArray.size(); i++) {
final JSONObject jsonObject = jsonArray.getJSONObject(i);
if (StringUtils.equals(jsonObject.getString("key"), "resourcePoolId")) {
resourcePoolId = jsonObject.getString("value");
break;
}
}
}
if (StringUtils.isBlank(resourcePoolId)) {
MSException.throwException("Resource Pool ID is empty.");
}
TestResourcePool resourcePool = testResourcePoolService.getResourcePool(resourcePoolId);
if (resourcePool == null) {
MSException.throwException("Resource Pool is empty.");
}
final ResourcePoolTypeEnum type = ResourcePoolTypeEnum.valueOf(resourcePool.getType());
switch (type) { switch (type) {
case DOCKER: case NODE:
return new DockerTestEngine(); return new DockerTestEngine();
case KUBERNETES: case K8S:
return new KubernetesTestEngine(); return new KubernetesTestEngine();
} }
return null; return null;
@ -48,7 +71,6 @@ public class EngineFactory {
engineContext.setTestId(loadTest.getId()); engineContext.setTestId(loadTest.getId());
engineContext.setTestName(loadTest.getName()); engineContext.setTestName(loadTest.getName());
engineContext.setNamespace(loadTest.getProjectId()); engineContext.setNamespace(loadTest.getProjectId());
engineContext.setEngineType(fileMetadata.getEngine());
engineContext.setFileType(fileMetadata.getType()); engineContext.setFileType(fileMetadata.getType());
if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) { if (!StringUtils.isEmpty(loadTest.getLoadConfiguration())) {
@ -86,4 +108,9 @@ public class EngineFactory {
private void setFileService(FileService fileService) { private void setFileService(FileService fileService) {
EngineFactory.fileService = fileService; EngineFactory.fileService = fileService;
} }
@Resource
public void setTestResourcePoolService(TestResourcePoolService testResourcePoolService) {
EngineFactory.testResourcePoolService = testResourcePoolService;
}
} }

View File

@ -1,20 +1,23 @@
package io.metersphere.engine.docker; package io.metersphere.engine.docker;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.controller.request.TestRequest; import io.metersphere.controller.request.TestRequest;
import io.metersphere.engine.Engine; import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext; import io.metersphere.engine.EngineContext;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
public class DockerTestEngine implements Engine { public class DockerTestEngine implements Engine {
private EngineContext context; private EngineContext context;
RestTemplate restTemplate;
@Override @Override
public boolean init(EngineContext context) { public boolean init(EngineContext context) {
this.restTemplate = CommonBeanFactory.getBean(RestTemplate.class);
// todo 初始化操作 // todo 初始化操作
this.context = context; this.context = context;
return true; return true;
@ -22,8 +25,8 @@ public class DockerTestEngine implements Engine {
@Override @Override
public void start() { public void start() {
RestTemplate restTemplate = new RestTemplate(); // todo 运行测试
// RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId(); String testId = context.getTestId();
String content = context.getContent(); String content = context.getContent();
@ -34,6 +37,7 @@ public class DockerTestEngine implements Engine {
testRequest.setTestId(testId); testRequest.setTestId(testId);
testRequest.setFileString(content); testRequest.setFileString(content);
// todo 判断测试状态
String taskStatusUri = "http://localhost:8082/jmeter/task/status/" + testId; String taskStatusUri = "http://localhost:8082/jmeter/task/status/" + testId;
List containerList = restTemplate.getForObject(taskStatusUri, List.class); List containerList = restTemplate.getForObject(taskStatusUri, List.class);
for (int i = 0; i < containerList.size(); i++) { for (int i = 0; i < containerList.size(); i++) {
@ -48,12 +52,13 @@ public class DockerTestEngine implements Engine {
@Override @Override
public void stop() { public void stop() {
RestTemplate restTemplate = new RestTemplate(); // TODO 停止运行测试
// RestTemplate restTemplate = new RestTemplate();
String testId = context.getTestId(); String testId = context.getTestId();
String uri = "http://localhost:8082/jmeter/container/stop" + testId; String uri = "http://localhost:8082/jmeter/container/stop/" + testId;
restTemplate.getForObject(uri, String.class); restTemplate.postForObject(uri, "", String.class);
} }
} }

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.engine.Engine; import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineContext; import io.metersphere.engine.EngineContext;
@ -11,17 +12,20 @@ import io.metersphere.engine.kubernetes.crds.jmeter.Jmeter;
import io.metersphere.engine.kubernetes.crds.jmeter.JmeterSpec; import io.metersphere.engine.kubernetes.crds.jmeter.JmeterSpec;
import io.metersphere.engine.kubernetes.provider.ClientCredential; import io.metersphere.engine.kubernetes.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider; import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import io.metersphere.service.TestResourcePoolService;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import java.util.HashMap; import java.util.HashMap;
public class KubernetesTestEngine implements Engine { public class KubernetesTestEngine implements Engine {
private EngineContext context; private EngineContext context;
private TestResourcePoolService testResourcePoolService;
@Override @Override
public boolean init(EngineContext context) { public boolean init(EngineContext context) {
// todo 初始化操作 // todo 初始化操作
this.context = context; this.context = context;
this.testResourcePoolService = CommonBeanFactory.getBean(TestResourcePoolService.class);
return true; return true;
} }

View File

@ -233,9 +233,11 @@ public class JmeterDocumentParser implements DocumentParser {
collectionProp.appendChild(createKafkaProp(document, "kafka.batch.size", kafkaProperties.getBatchSize())); collectionProp.appendChild(createKafkaProp(document, "kafka.batch.size", kafkaProperties.getBatchSize()));
collectionProp.appendChild(createKafkaProp(document, "kafka.client.id", kafkaProperties.getClientId())); collectionProp.appendChild(createKafkaProp(document, "kafka.client.id", kafkaProperties.getClientId()));
collectionProp.appendChild(createKafkaProp(document, "kafka.connections.max.idle.ms", kafkaProperties.getConnectionsMaxIdleMs())); collectionProp.appendChild(createKafkaProp(document, "kafka.connections.max.idle.ms", kafkaProperties.getConnectionsMaxIdleMs()));
// 添加关联关系 test.id test.name // 添加关联关系 test.id test.name test.startTime test.size
collectionProp.appendChild(createKafkaProp(document, "test.id", context.getTestId())); collectionProp.appendChild(createKafkaProp(document, "test.id", context.getTestId()));
collectionProp.appendChild(createKafkaProp(document, "test.name", context.getTestName())); collectionProp.appendChild(createKafkaProp(document, "test.name", context.getTestName()));
collectionProp.appendChild(createKafkaProp(document, "test.startTime", "" + System.currentTimeMillis()));
collectionProp.appendChild(createKafkaProp(document, "test.size", "1"));
elementProp.appendChild(collectionProp); elementProp.appendChild(collectionProp);
// set elementProp // set elementProp

View File

@ -0,0 +1,113 @@
package io.metersphere.report;
import com.alibaba.fastjson.JSONObject;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import io.metersphere.report.base.Metric;
import io.metersphere.report.base.RequestStatistics;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import java.util.stream.Collectors;
public class JtlResolver {
private List<Metric> resolver(String jtlString) {
HeaderColumnNameMappingStrategy<Metric> ms = new HeaderColumnNameMappingStrategy<>();
ms.setType(Metric.class);
try (Reader reader = new StringReader(jtlString)) {
CsvToBean<Metric> cb = new CsvToBeanBuilder<Metric>(reader)
.withType(Metric.class)
.withSkipLines(0)
.withMappingStrategy(ms)
.withIgnoreLeadingWhiteSpace(true)
.build();
return cb.parse();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
private List<RequestStatistics> getOneRpsResult(Map<String, List<Metric>> map){
List<RequestStatistics> requestStatisticsList = new ArrayList<>();
Iterator<Map.Entry<String, List<Metric>>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<Metric>> entry = iterator.next();
String label = entry.getKey();
List<Metric> list = entry.getValue();
List<String> timestampList = list.stream().map(Metric::getTimestamp).collect(Collectors.toList());
int index=0;
//总的响应时间
int sumElapsed=0;
Integer failSize = 0;
Integer totalBytes = 0;
List<Integer> elapsedList = new ArrayList<Integer>();
for (int i = 0; i < list.size(); i++) {
try {
Metric row = list.get(i);
//响应时间
String elapsed = row.getElapsed();
sumElapsed += Integer.valueOf(elapsed);
elapsedList.add(Integer.valueOf(elapsed));
//成功与否
String success = row.getSuccess();
if (!"true".equals(success)){
failSize++;
}
//字节
String bytes = row.getBytes();
totalBytes += Integer.valueOf(bytes);
index++;
}catch (Exception e){
System.out.println("exception i:"+i);
}
}
Collections.sort(elapsedList, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
Integer tp90 = elapsedList.size()*9/10;
Integer tp95 = elapsedList.size()*95/100;
Integer tp99 = elapsedList.size()*99/100;
Long l = Long.valueOf(timestampList.get(index-1)) - Long.valueOf(timestampList.get(0));
RequestStatistics requestStatistics = new RequestStatistics();
requestStatistics.setRequestLabel(label);
requestStatistics.setSamples(index+"");
requestStatistics.setAverage(sumElapsed/index+"");
/**
* TP90的计算
* 1把一段时间内全部的请求的响应时间从小到大排序获得序列A
* 2总的请求数量乘以90%获得90%对应的请求个数C
* 3从序列A中找到第C个请求它的响应时间即为TP90的值
* 其余相似的指标还有TP95, TP99
*/
requestStatistics.setTp90(elapsedList.get(tp90)+"");
requestStatistics.setTp95(elapsedList.get(tp95)+"");
requestStatistics.setTp99(elapsedList.get(tp99)+"");
requestStatistics.setMin(elapsedList.get(0)+"");
requestStatistics.setMax(elapsedList.get(index-1)+"");
requestStatistics.setErrors(String.format("%.2f",failSize*100.0/index)+"%");
requestStatistics.setKbPerSec(String.format("%.2f",totalBytes*1.0/1024/(l*1.0/1000)));
requestStatisticsList.add(requestStatistics);
}
return requestStatisticsList;
}
public List<RequestStatistics> getRequestStatistics(String jtlString) {
List<Metric> totalLines = resolver(jtlString);
Map<String, List<Metric>> map = totalLines.stream().collect(Collectors.groupingBy(Metric::getLabel));
return getOneRpsResult(map);
}
}

View File

@ -0,0 +1,180 @@
package io.metersphere.report.base;
import com.opencsv.bean.CsvBindByName;
public class Metric {
// timestamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect
@CsvBindByName(column = "timestamp") // 访问开始时间
private String timestamp;
@CsvBindByName(column = "elapsed") // 访问开始到结束的用时 - 响应时间
private String elapsed;
@CsvBindByName(column = "label") // 请求的标签
private String label;
@CsvBindByName(column = "responseCode") // 响应码
private String responseCode;
@CsvBindByName(column = "responseMessage") // 响应信息
private String responseMessage;
@CsvBindByName(column = "threadName") // 请求所属线程
private String threadName;
@CsvBindByName(column = "dataType") // 数据类型
private String dataType;
@CsvBindByName(column = "success") // 访问是否成功
private String success;
@CsvBindByName(column = "failureMessage") // 访问失败信息
private String failureMessage;
@CsvBindByName(column = "bytes") //
private String bytes;
@CsvBindByName(column = "sentBytes") //
private String sentBytes;
@CsvBindByName(column = "grpThreads") // 线程组
private String grpThreads;
@CsvBindByName(column = "allThreads") //
private String allThreads;
@CsvBindByName(column = "URL") //
private String url;
@CsvBindByName(column = "Latency") // 延时
private String latency;
@CsvBindByName(column = "IdleTime") // 闲置时间
private String idleTime;
@CsvBindByName(column = "Connect") //
private String connect;
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getElapsed() {
return elapsed;
}
public void setElapsed(String elapsed) {
this.elapsed = elapsed;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getResponseCode() {
return responseCode;
}
public void setResponseCode(String responseCode) {
this.responseCode = responseCode;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public String getSuccess() {
return success;
}
public void setSuccess(String success) {
this.success = success;
}
public String getFailureMessage() {
return failureMessage;
}
public void setFailureMessage(String failureMessage) {
this.failureMessage = failureMessage;
}
public String getBytes() {
return bytes;
}
public void setBytes(String bytes) {
this.bytes = bytes;
}
public String getSentBytes() {
return sentBytes;
}
public void setSentBytes(String sentBytes) {
this.sentBytes = sentBytes;
}
public String getGrpThreads() {
return grpThreads;
}
public void setGrpThreads(String grpThreads) {
this.grpThreads = grpThreads;
}
public String getAllThreads() {
return allThreads;
}
public void setAllThreads(String allThreads) {
this.allThreads = allThreads;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLatency() {
return latency;
}
public void setLatency(String latency) {
this.latency = latency;
}
public String getIdleTime() {
return idleTime;
}
public void setIdleTime(String idleTime) {
this.idleTime = idleTime;
}
public String getConnect() {
return connect;
}
public void setConnect(String connect) {
this.connect = connect;
}
}

View File

@ -0,0 +1,125 @@
package io.metersphere.report.base;
public class RequestStatistics {
/**请求标签*/
private String requestLabel;
/**压测请求数*/
private String samples;
/**平均响应时间*/
private String average;
/**平均点击率*/
private Double avgHits;
/**90% Line*/
private String tp90;
/**95% Line*/
private String tp95;
/**99% Line*/
private String tp99;
/**最小请求时间 Min Response Time /ms */
private String min;
/**最大请求时间 Max Response Time /ms */
private String max;
/**吞吐量 KB/sec*/
private String kbPerSec;
/**错误率 Error Percentage */
private String errors;
public String getRequestLabel() {
return requestLabel;
}
public void setRequestLabel(String requestLabel) {
this.requestLabel = requestLabel;
}
public String getSamples() {
return samples;
}
public void setSamples(String samples) {
this.samples = samples;
}
public String getAverage() {
return average;
}
public void setAverage(String average) {
this.average = average;
}
public Double getAvgHits() {
return avgHits;
}
public void setAvgHits(Double avgHits) {
this.avgHits = avgHits;
}
public String getTp90() {
return tp90;
}
public void setTp90(String tp90) {
this.tp90 = tp90;
}
public String getTp95() {
return tp95;
}
public void setTp95(String tp95) {
this.tp95 = tp95;
}
public String getTp99() {
return tp99;
}
public void setTp99(String tp99) {
this.tp99 = tp99;
}
public String getMin() {
return min;
}
public void setMin(String min) {
this.min = min;
}
public String getMax() {
return max;
}
public void setMax(String max) {
this.max = max;
}
public String getKbPerSec() {
return kbPerSec;
}
public void setKbPerSec(String kbPerSec) {
this.kbPerSec = kbPerSec;
}
public String getErrors() {
return errors;
}
public void setErrors(String errors) {
this.errors = errors;
}
}

View File

@ -3,12 +3,9 @@ package io.metersphere.service;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtFunctionalTestMapper; import io.metersphere.base.mapper.ext.ExtFunctionalTestMapper;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.request.testplan.*; import io.metersphere.controller.request.testplan.*;
import io.metersphere.dto.FunctionalTestDTO; import io.metersphere.dto.FunctionalTestDTO;
import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineFactory;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -94,8 +91,6 @@ public class FuctionalTestService {
fileMetadata.setCreateTime(System.currentTimeMillis()); fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis()); fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType("jmx"); fileMetadata.setType("jmx");
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata); fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent(); FileContent fileContent = new FileContent();

View File

@ -6,7 +6,6 @@ import io.metersphere.base.mapper.FileMetadataMapper;
import io.metersphere.base.mapper.LoadTestFileMapper; import io.metersphere.base.mapper.LoadTestFileMapper;
import io.metersphere.base.mapper.LoadTestMapper; import io.metersphere.base.mapper.LoadTestMapper;
import io.metersphere.base.mapper.ext.ExtLoadTestMapper; import io.metersphere.base.mapper.ext.ExtLoadTestMapper;
import io.metersphere.commons.constants.EngineType;
import io.metersphere.commons.constants.FileType; import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.constants.TestStatus; import io.metersphere.commons.constants.TestStatus;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
@ -45,6 +44,8 @@ public class LoadTestService {
private LoadTestFileMapper loadTestFileMapper; private LoadTestFileMapper loadTestFileMapper;
@Resource @Resource
private FileService fileService; private FileService fileService;
@Resource
private TestResourcePoolService testResourcePoolService;
public List<LoadTestDTO> list(QueryTestPlanRequest request) { public List<LoadTestDTO> list(QueryTestPlanRequest request) {
return extLoadTestMapper.list(request); return extLoadTestMapper.list(request);
@ -102,8 +103,6 @@ public class LoadTestService {
fileMetadata.setUpdateTime(System.currentTimeMillis()); fileMetadata.setUpdateTime(System.currentTimeMillis());
FileType fileType = getFileType(fileMetadata.getName()); FileType fileType = getFileType(fileMetadata.getName());
fileMetadata.setType(fileType.name()); fileMetadata.setType(fileType.name());
// TODO engine 选择
fileMetadata.setEngine(EngineType.DOCKER.name());
fileMetadataMapper.insert(fileMetadata); fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent(); FileContent fileContent = new FileContent();
@ -179,9 +178,8 @@ public class LoadTestService {
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList()); List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
LogUtil.info("Load test started " + loadTest.getName()); LogUtil.info("Load test started " + loadTest.getName());
// engine type (DOCKER|KUBERNETES) // engine type (NODE|K8S)
// todo set type final Engine engine = EngineFactory.createEngine(loadTest);
final Engine engine = EngineFactory.createEngine(fileMetadata.getEngine());
if (engine == null) { if (engine == null) {
MSException.throwException(String.format("Test cannot be runtest ID%sfile type%s", request.getId(), fileMetadata.getType())); MSException.throwException(String.format("Test cannot be runtest ID%sfile type%s", request.getId(), fileMetadata.getType()));
} }

View File

@ -41,4 +41,8 @@ public class ReportService {
public ReportDTO getReportTestAndProInfo(String reportId) { public ReportDTO getReportTestAndProInfo(String reportId) {
return extLoadTestReportMapper.getReportTestAndProInfo(reportId); return extLoadTestReportMapper.getReportTestAndProInfo(reportId);
} }
public LoadTestReport getReport(String id) {
return loadTestReportMapper.selectByPrimaryKey(id);
}
} }

View File

@ -1,12 +1,23 @@
package io.metersphere.service; package io.metersphere.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.TestResourcePool; import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.base.domain.TestResourcePoolExample; import io.metersphere.base.domain.TestResourcePoolExample;
import io.metersphere.base.mapper.TestResourcePoolMapper; import io.metersphere.base.mapper.TestResourcePoolMapper;
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest; import io.metersphere.controller.request.resourcepool.QueryResourcePoolRequest;
import io.metersphere.dto.KubernetesDTO;
import io.metersphere.dto.NodeDTO;
import io.metersphere.engine.kubernetes.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
@ -19,6 +30,8 @@ import java.util.UUID;
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public class TestResourcePoolService { public class TestResourcePoolService {
private final static String nodeControllerUrl = "%s:%s/status";
@Resource @Resource
private TestResourcePoolMapper testResourcePoolMapper; private TestResourcePoolMapper testResourcePoolMapper;
@ -27,6 +40,7 @@ public class TestResourcePoolService {
testResourcePool.setCreateTime(System.currentTimeMillis()); testResourcePool.setCreateTime(System.currentTimeMillis());
testResourcePool.setUpdateTime(System.currentTimeMillis()); testResourcePool.setUpdateTime(System.currentTimeMillis());
testResourcePool.setStatus("1"); testResourcePool.setStatus("1");
validateTestResourcePool(testResourcePool);
testResourcePoolMapper.insertSelective(testResourcePool); testResourcePoolMapper.insertSelective(testResourcePool);
return testResourcePool; return testResourcePool;
} }
@ -37,6 +51,7 @@ public class TestResourcePoolService {
public void updateTestResourcePool(TestResourcePool testResourcePool) { public void updateTestResourcePool(TestResourcePool testResourcePool) {
testResourcePool.setUpdateTime(System.currentTimeMillis()); testResourcePool.setUpdateTime(System.currentTimeMillis());
validateTestResourcePool(testResourcePool);
testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool); testResourcePoolMapper.updateByPrimaryKeySelective(testResourcePool);
} }
@ -47,4 +62,59 @@ public class TestResourcePoolService {
} }
return testResourcePoolMapper.selectByExample(example); return testResourcePoolMapper.selectByExample(example);
} }
private void validateTestResourcePool(TestResourcePool testResourcePool) {
if (StringUtils.equalsIgnoreCase(testResourcePool.getType(), ResourcePoolTypeEnum.K8S.name())) {
validateK8s(testResourcePool);
return;
}
validateNodes(testResourcePool);
}
private void validateNodes(TestResourcePool testResourcePool) {
List<NodeDTO> nodes = JSON.parseArray(testResourcePool.getInfo(), NodeDTO.class);
if (CollectionUtils.isEmpty(nodes)) {
throw new RuntimeException("没有节点信息");
}
for (NodeDTO node : nodes) {
boolean isValidate = validateNode(node);
if (!isValidate) {
testResourcePool.setStatus("0");
}
node.setValidate(isValidate);
}
testResourcePool.setInfo(JSON.toJSONString(nodes));
}
private boolean validateNode(NodeDTO dto) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> entity = restTemplate.getForEntity(String.format(nodeControllerUrl, dto.getIp(), dto.getPort()), String.class);
return entity.getStatusCode().value() == HttpStatus.SC_OK;
}
private void validateK8s(TestResourcePool testResourcePool) {
List<KubernetesDTO> dtos = JSON.parseArray(testResourcePool.getInfo(), KubernetesDTO.class);
if (CollectionUtils.isEmpty(dtos) || dtos.size() != 1) {
throw new RuntimeException("只能添加一个 K8s");
}
ClientCredential clientCredential = new ClientCredential();
BeanUtils.copyBean(clientCredential, dtos.get(0));
try {
KubernetesProvider provider = new KubernetesProvider(JSON.toJSONString(clientCredential));
provider.validateCredential();
dtos.get(0).setValidate(true);
} catch (Exception e) {
dtos.get(0).setValidate(false);
testResourcePool.setStatus("0");
}
testResourcePool.setInfo(JSON.toJSONString(dtos));
}
public TestResourcePool getResourcePool(String resourcePoolId) {
return testResourcePoolMapper.selectByPrimaryKey(resourcePoolId);
}
} }

View File

@ -11,7 +11,6 @@ CREATE TABLE IF NOT EXISTS `file_metadata` (
`id` varchar(64) NOT NULL COMMENT 'File ID', `id` varchar(64) NOT NULL COMMENT 'File ID',
`name` varchar(64) NOT NULL COMMENT 'File name', `name` varchar(64) NOT NULL COMMENT 'File name',
`type` varchar(64) DEFAULT NULL COMMENT 'File type', `type` varchar(64) DEFAULT NULL COMMENT 'File type',
`engine` varchar(64) DEFAULT 'DOCKER' COMMENT 'engine type',
`size` bigint(13) NOT NULL COMMENT 'File size', `size` bigint(13) NOT NULL COMMENT 'File size',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp', `create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp', `update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',

View File

@ -45,10 +45,20 @@
</javaClientGenerator> </javaClientGenerator>
<!--要生成的数据库表 --> <!--要生成的数据库表 -->
<table tableName="test_plan"/>
<table tableName="test_case_node"/>
<table tableName="test_case"/>
<table tableName="test_plan_test_case"/>
<!-- <table tableName="user"/>-->
<!-- <table tableName="user_role"/>-->
<!-- <table tableName="workspace"/>-->
<!-- <table tableName="test_resource_pool"/>-->
<!-- <table tableName="test_resource"/>-->
<!-- <table tableName="system_parameter"/>-->
<!-- <table tableName="role"/>-->
<!-- <table tableName="project"/>-->
<!-- <table tableName="organization"/>-->
<!-- <table tableName="load_test_report"/>-->
<!-- <table tableName="load_test"/>-->
<!-- <table tableName="file_content"/>-->
<table tableName="file_metadata"/>
<!-- <table tableName="load_test_file"/>-->
</context> </context>
</generatorConfiguration> </generatorConfiguration>

View File

@ -1,41 +1,45 @@
package io.metersphere; package io.metersphere;
import io.metersphere.commons.constants.JmeterReportType; import com.opencsv.bean.CsvToBean;
import kg.apc.jmeter.PluginsCMDWorker; import com.opencsv.bean.CsvToBeanBuilder;
import org.apache.jmeter.util.JMeterUtils; import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import com.opencsv.bean.MappingStrategy;
import org.junit.Test; import org.junit.Test;
import java.io.File;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class GenerateGraphTest { public class GenerateGraphTest {
/*
AggregateReport = JMeter's native Aggregate Report, can be saved only as CSV
SynthesisReport = mix between JMeter's native Summary Report and Aggregate Report, can be saved only as CSV
ThreadsStateOverTime = Active Threads Over Time
BytesThroughputOverTime
HitsPerSecond
LatenciesOverTime
PerfMon = PerfMon Metrics Collector
DbMon = DbMon Metrics Collector, DataBase, get performance counters via sql
JMXMon = JMXMon Metrics Collector, Java Management Extensions counters
ResponseCodesPerSecond
ResponseTimesDistribution
ResponseTimesOverTime
ResponseTimesPercentiles
ThroughputVsThreads
TimesVsThreads = Response Times VS Threads
TransactionsPerSecond
PageDataExtractorOverTime
MergeResults = MergeResults Command Line Merge Tool to simplify the comparison of two or more load tests, need properties file (like merge-results.properties)
*/
@Test @Test
public void test1() { public void test1() {
JMeterUtils.setJMeterHome("/opt/fit2cloud/apache-jmeter-5.2.1"); File csvFile = new File("/Users/liuruibin/Desktop/0316.jtl");
JMeterUtils.loadJMeterProperties("/opt/fit2cloud/apache-jmeter-5.2.1/bin/jmeter.properties"); HeaderColumnNameMappingStrategy<Metric> ms = new HeaderColumnNameMappingStrategy<>();
PluginsCMDWorker worker = new PluginsCMDWorker(); ms.setType(Metric.class);
worker.setPluginType(JmeterReportType.AggregateReport.name()); List<Metric> metrics = beanBuilderExample(csvFile.toPath(), ms);
worker.addExportMode(2); metrics.forEach(c -> {
worker.setOutputCSVFile("/tmp/test0320.csv"); System.out.println(c.getTimestamp());
worker.setInputFile("/Users/liuruibin/Desktop/0316.jtl"); });
worker.doJob(); }
public static List<Metric> beanBuilderExample(Path path, MappingStrategy<Metric> ms) {
try (Reader reader = Files.newBufferedReader(path)) {
CsvToBean<Metric> cb = new CsvToBeanBuilder<Metric>(reader)
.withType(Metric.class)
.withSkipLines(0)
.withMappingStrategy(ms)
.withIgnoreLeadingWhiteSpace(true)
.build();
return cb.parse();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
} }
} }

View File

@ -0,0 +1,271 @@
package io.metersphere;
import com.alibaba.fastjson.JSONObject;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import io.metersphere.report.base.RequestStatistics;
import org.junit.Test;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import java.util.stream.Collectors;
public class JtlTest {
public static List<Metric> beanBuilderExample(String content) {
HeaderColumnNameMappingStrategy<io.metersphere.Metric> ms = new HeaderColumnNameMappingStrategy<>();
ms.setType(io.metersphere.Metric.class);
try (Reader reader = new StringReader(content)) {
CsvToBean<io.metersphere.Metric> cb = new CsvToBeanBuilder<Metric>(reader)
.withType(Metric.class)
.withSkipLines(0)
.withMappingStrategy(ms)
.withIgnoreLeadingWhiteSpace(true)
.build();
return cb.parse();
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
@Test
public void getRequestStatistics() {
String jtlString = "timeStamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect\n" +
"1584602493891,1107,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-3,text,true,,1473653,6950,3,3,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=c81989f3-27d5-4b1a-a2db-03ddb06475d5&login=true&scope=openid,232,0,26\n" +
"1584602493891,235,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-3,,true,,214,567,3,3,https://rddev2.fit2cloud.com/,232,0,26\n" +
"1584602494128,11,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-3,,true,,615,577,3,3,https://rddev2.fit2cloud.com/dashboard/,11,0,0\n" +
"1584602494142,33,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-3,text,true,,8068,851,3,3,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=c81989f3-27d5-4b1a-a2db-03ddb06475d5&login=true&scope=openid,32,0,0\n" +
"1584602494242,756,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-3,text,true,,1464756,4955,3,3,https://rddev2.fit2cloud.com/login,46,0,0\n" +
"1584602493891,1154,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-2,text,true,,1473685,6950,3,3,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=374d02c0-6e1f-4937-b457-27d6e6ccf264&login=true&scope=openid,232,0,25\n" +
"1584602493891,235,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-2,,true,,214,567,3,3,https://rddev2.fit2cloud.com/,232,0,25\n" +
"1584602494128,11,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-2,,true,,615,577,3,3,https://rddev2.fit2cloud.com/dashboard/,11,0,0\n" +
"1584602494142,35,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-2,text,true,,8068,851,3,3,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=374d02c0-6e1f-4937-b457-27d6e6ccf264&login=true&scope=openid,35,0,0\n" +
"1584602494242,803,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-2,text,true,,1464788,4955,3,3,https://rddev2.fit2cloud.com/login,45,0,0\n" +
"1584602493891,1316,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-1,text,true,,1473686,6942,3,3,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=515530c4-0f36-454a-b536-9a11c7d2c47a&login=true&scope=openid,232,0,25\n" +
"1584602493891,235,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-1,,true,,214,567,3,3,https://rddev2.fit2cloud.com/,232,0,25\n" +
"1584602494128,12,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-1,,true,,614,577,3,3,https://rddev2.fit2cloud.com/dashboard/,12,0,0\n" +
"1584602494142,36,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-1,text,true,,8068,850,3,3,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=515530c4-0f36-454a-b536-9a11c7d2c47a&login=true&scope=openid,35,0,0\n" +
"1584602494242,965,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-1,text,true,,1464790,4948,3,3,https://rddev2.fit2cloud.com/login,48,0,0\n" +
"1584602496824,550,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-5,text,true,,1473644,6950,5,5,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=211a68fc-eb1e-482d-b5d2-636b411a133e&login=true&scope=openid,54,0,0\n" +
"1584602496824,54,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-5,,true,,214,567,5,5,https://rddev2.fit2cloud.com/,54,0,0\n" +
"1584602496878,12,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-5,,true,,615,577,5,5,https://rddev2.fit2cloud.com/dashboard/,12,0,0\n" +
"1584602496890,29,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-5,text,true,,8074,851,5,5,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=211a68fc-eb1e-482d-b5d2-636b411a133e&login=true&scope=openid,29,0,0\n" +
"1584602496922,452,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-5,text,true,,1464741,4955,5,5,https://rddev2.fit2cloud.com/login,20,0,0\n" +
"1584602496821,559,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-4,text,true,,1473633,6958,5,5,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=e6ebc175-e3dc-4c99-933b-f6610688dbfe&login=true&scope=openid,57,0,2\n" +
"1584602496821,57,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-4,,true,,214,567,5,5,https://rddev2.fit2cloud.com/,57,0,2\n" +
"1584602496878,11,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-4,,true,,616,577,5,5,https://rddev2.fit2cloud.com/dashboard/,11,0,0\n" +
"1584602496889,27,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-4,text,true,,8068,852,5,5,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=e6ebc175-e3dc-4c99-933b-f6610688dbfe&login=true&scope=openid,27,0,0\n" +
"1584602496919,461,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-4,text,true,,1464735,4962,5,5,https://rddev2.fit2cloud.com/login,20,0,0\n" +
"1584602499028,73,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-1,text,false,,4469,1745,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,73,0,6\n" +
"1584602499125,0,https://rddev2.fit2cloud.com/dashboard/?state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1&code=efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc,Non HTTP response code: java.net.URISyntaxException,\"Non HTTP response message: Illegal character in query at index 45: https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",Thread Group 1-1,text,false,,1392,0,8,8,\"https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",0,0,0\n" +
"1584602499126,21,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,200,OK,Thread Group 1-1,text,true,,12438,559,8,8,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,21,0,0\n" +
"1584602499251,18,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,200,OK,Thread Group 1-1,text,true,,1916,573,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,18,0,0\n" +
"1584602498833,509,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-7,text,true,,1473651,6942,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=1f8130d4-f1c4-44f5-8633-03cc4892f31c&login=true&scope=openid,39,0,1\n" +
"1584602498833,39,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-7,,true,,214,567,8,8,https://rddev2.fit2cloud.com/,39,0,1\n" +
"1584602498872,9,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-7,,true,,614,577,8,8,https://rddev2.fit2cloud.com/dashboard/,9,0,0\n" +
"1584602498881,18,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-7,text,true,,8074,850,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=1f8130d4-f1c4-44f5-8633-03cc4892f31c&login=true&scope=openid,18,0,0\n" +
"1584602498901,441,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-7,text,true,,1464749,4948,8,8,https://rddev2.fit2cloud.com/login,25,0,0\n" +
"1584602499325,71,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-2,text,false,,4469,1746,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,70,0,4\n" +
"1584602499445,16,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,200,OK,Thread Group 1-1,text,true,,1570,581,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,16,0,0\n" +
"1584602498832,637,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-6,text,true,,1473640,6958,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=ceda817a-6ac6-4516-9cd8-c1b25429bf94&login=true&scope=openid,50,0,1\n" +
"1584602498832,50,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-6,,true,,214,567,8,8,https://rddev2.fit2cloud.com/,50,0,1\n" +
"1584602498882,9,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-6,,true,,616,577,8,8,https://rddev2.fit2cloud.com/dashboard/,9,0,0\n" +
"1584602498891,35,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-6,text,true,,8068,852,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=ceda817a-6ac6-4516-9cd8-c1b25429bf94&login=true&scope=openid,35,0,0\n" +
"1584602498927,542,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-6,text,true,,1464742,4962,8,8,https://rddev2.fit2cloud.com/login,23,0,0\n" +
"1584602498836,635,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-8,text,true,,1473639,6950,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=1b42574e-756d-4157-9987-a8e1df496718&login=true&scope=openid,46,0,0\n" +
"1584602498836,46,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-8,,true,,214,567,8,8,https://rddev2.fit2cloud.com/,46,0,0\n" +
"1584602498883,12,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-8,,true,,615,577,8,8,https://rddev2.fit2cloud.com/dashboard/,12,0,0\n" +
"1584602498896,36,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-8,text,true,,8074,851,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=1b42574e-756d-4157-9987-a8e1df496718&login=true&scope=openid,36,0,0\n" +
"1584602498933,538,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-8,text,true,,1464736,4955,8,8,https://rddev2.fit2cloud.com/login,26,0,0\n" +
"1584602499605,0,https://rddev2.fit2cloud.com/dashboard/?state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1&code=efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc,Non HTTP response code: java.net.URISyntaxException,\"Non HTTP response message: Illegal character in query at index 45: https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",Thread Group 1-2,text,false,,1392,0,8,8,\"https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",0,0,0\n" +
"1584602499607,19,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,200,OK,Thread Group 1-2,text,true,,12424,560,8,8,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,19,0,0\n" +
"1584602499856,21,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-list.html?_t=1577351137654,200,OK,Thread Group 1-1,text,true,,2516,572,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-list.html?_t=1577351137654,21,0,0\n" +
"1584602500034,27,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,200,OK,Thread Group 1-2,text,true,,1916,574,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,27,0,0\n" +
"1584602500182,23,https://rddev2.fit2cloud.com/dashboard/anonymous/license/validate?_nocache=1578039505321,200,OK,Thread Group 1-1,text,true,,288,566,8,8,https://rddev2.fit2cloud.com/dashboard/anonymous/license/validate?_nocache=1578039505321,23,0,0\n" +
"1584602500484,18,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,200,OK,Thread Group 1-2,text,true,,1570,582,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,18,0,0\n" +
"1584602500504,16,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-list.html?_t=1577351137654,200,OK,Thread Group 1-2,text,true,,2516,573,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-list.html?_t=1577351137654,16,0,0\n" +
"1584602500206,420,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321,200,OK,Thread Group 1-1,text,true,,1473342,5748,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fmodule%2Fall?_nocache%3D1578039505321&state=573519c4-a91b-4e46-b28d-f68231a8faf8&login=true&scope=openid,10,0,0\n" +
"1584602500206,10,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-0,302,Found,Thread Group 1-1,,true,,555,550,8,8,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321,10,0,0\n" +
"1584602500216,23,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-1,200,OK,Thread Group 1-1,text,true,,8038,1439,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fmodule%2Fall?_nocache%3D1578039505321&state=573519c4-a91b-4e46-b28d-f68231a8faf8&login=true&scope=openid,23,0,0\n" +
"1584602500243,383,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-2,200,OK,Thread Group 1-1,text,true,,1464749,3759,8,8,https://rddev2.fit2cloud.com/login,24,0,0\n" +
"1584602500622,18,https://rddev2.fit2cloud.com/dashboard/anonymous/license/validate?_nocache=1578039505321,200,OK,Thread Group 1-2,text,true,,288,567,8,8,https://rddev2.fit2cloud.com/dashboard/anonymous/license/validate?_nocache=1578039505321,18,0,0\n" +
"1584602500735,15,https://rddev2.fit2cloud.com/web-public/fit2cloud/html/loading/loading.html?_t=1577351137654,200,OK,Thread Group 1-1,text,true,,503,506,8,8,https://rddev2.fit2cloud.com/web-public/fit2cloud/html/loading/loading.html?_t=1577351137654,15,0,0\n" +
"1584602501143,58,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-5,text,false,,4469,1746,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,58,0,4\n" +
"1584602501233,0,https://rddev2.fit2cloud.com/dashboard/?state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1&code=efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc,Non HTTP response code: java.net.URISyntaxException,\"Non HTTP response message: Illegal character in query at index 45: https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",Thread Group 1-5,text,false,,1392,0,8,8,\"https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",0,0,0\n" +
"1584602501234,19,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,200,OK,Thread Group 1-5,text,true,,12438,560,8,8,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,19,0,0\n" +
"1584602501253,17,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,200,OK,Thread Group 1-5,text,true,,1916,574,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,17,0,0\n" +
"1584602500841,509,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321,200,OK,Thread Group 1-2,text,true,,1473319,5757,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fmodule%2Fall?_nocache%3D1578039505321&state=c27f5334-b14f-43e8-ba3d-a31d6f385c32&login=true&scope=openid,13,0,0\n" +
"1584602500841,13,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-0,302,Found,Thread Group 1-2,,true,,555,551,8,8,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321,13,0,0\n" +
"1584602500855,29,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-1,200,OK,Thread Group 1-2,text,true,,8038,1440,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fmodule%2Fall?_nocache%3D1578039505321&state=c27f5334-b14f-43e8-ba3d-a31d6f385c32&login=true&scope=openid,29,0,0\n" +
"1584602500887,463,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-2,200,OK,Thread Group 1-2,text,true,,1464726,3766,8,8,https://rddev2.fit2cloud.com/login,26,0,0\n" +
"1584602501352,16,https://rddev2.fit2cloud.com/web-public/fit2cloud/html/loading/loading.html?_t=1577351137654,200,OK,Thread Group 1-2,text,true,,503,507,8,8,https://rddev2.fit2cloud.com/web-public/fit2cloud/html/loading/loading.html?_t=1577351137654,16,0,0\n" +
"1584602501458,13,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,200,OK,Thread Group 1-5,text,true,,1570,582,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,13,0,0\n" +
"1584602501663,17,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-list.html?_t=1577351137654,200,OK,Thread Group 1-5,text,true,,2516,573,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-list.html?_t=1577351137654,17,0,0\n" +
"1584602501435,359,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,200,OK,Thread Group 1-1,text,true,,1473387,6761,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=832af3d4-2ca4-4e23-bf2a-e14a01d27fc0&login=true&scope=openid,11,0,0\n" +
"1584602501435,11,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-0,302,Found,Thread Group 1-1,,true,,558,653,8,8,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,11,0,0\n" +
"1584602501446,22,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-1,200,OK,Thread Group 1-1,text,true,,8030,1614,8,8,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=832af3d4-2ca4-4e23-bf2a-e14a01d27fc0&login=true&scope=openid,22,0,0\n" +
"1584602501471,323,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-2,200,OK,Thread Group 1-1,text,true,,1464799,4494,8,8,https://rddev2.fit2cloud.com/login,23,0,0\n" +
"1584602501784,17,https://rddev2.fit2cloud.com/dashboard/anonymous/license/validate?_nocache=1578039505321,200,OK,Thread Group 1-5,text,true,,288,567,8,8,https://rddev2.fit2cloud.com/dashboard/anonymous/license/validate?_nocache=1578039505321,17,0,0\n" +
"1584602501907,304,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50,200,OK,Thread Group 1-1,text,true,,1473471,6749,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fnotification%2Flist%2Funread%2F1%2F50&state=1904f64b-a8f9-44d8-867e-359cfe46297f&login=true&scope=openid,10,0,0\n" +
"1584602501907,10,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-0,302,Found,Thread Group 1-1,,true,,555,652,10,10,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50,10,0,0\n" +
"1584602501917,24,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-1,200,OK,Thread Group 1-1,text,true,,8021,1603,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fnotification%2Flist%2Funread%2F1%2F50&state=1904f64b-a8f9-44d8-867e-359cfe46297f&login=true&scope=openid,24,0,0\n" +
"1584602501943,268,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-2,200,OK,Thread Group 1-1,text,true,,1464895,4494,10,10,https://rddev2.fit2cloud.com/login,23,0,0\n" +
"1584602502213,16,https://rddev2.fit2cloud.com/web-public/project/html/pagination.html?_t=1577351137654,200,OK,Thread Group 1-1,text,true,,1162,499,10,10,https://rddev2.fit2cloud.com/web-public/project/html/pagination.html?_t=1577351137654,16,0,0\n" +
"1584602501802,513,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321,200,OK,Thread Group 1-5,text,true,,1473342,5757,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fmodule%2Fall?_nocache%3D1578039505321&state=7d3aea03-3217-44da-81be-35c41c1db2d7&login=true&scope=openid,15,0,0\n" +
"1584602501802,15,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-0,302,Found,Thread Group 1-5,,true,,555,551,10,10,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321,15,0,0\n" +
"1584602501817,23,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-1,200,OK,Thread Group 1-5,text,true,,8038,1440,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fmodule%2Fall?_nocache%3D1578039505321&state=7d3aea03-3217-44da-81be-35c41c1db2d7&login=true&scope=openid,23,0,0\n" +
"1584602501842,473,https://rddev2.fit2cloud.com/dashboard/module/all?_nocache=1578039505321-2,200,OK,Thread Group 1-5,text,true,,1464749,3766,10,10,https://rddev2.fit2cloud.com/login,18,0,0\n" +
"1584602502316,28,https://rddev2.fit2cloud.com/web-public/fit2cloud/html/loading/loading.html?_t=1577351137654,200,OK,Thread Group 1-5,text,true,,503,507,10,10,https://rddev2.fit2cloud.com/web-public/fit2cloud/html/loading/loading.html?_t=1577351137654,28,0,0\n" +
"1584602502110,631,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-10,text,true,,1473668,6950,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=26792af8-d8cd-4ed6-b0e0-68ad7220004f&login=true&scope=openid,63,0,1\n" +
"1584602502110,63,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-10,,true,,214,567,10,10,https://rddev2.fit2cloud.com/,63,0,1\n" +
"1584602502173,15,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-10,,true,,615,577,10,10,https://rddev2.fit2cloud.com/dashboard/,15,0,0\n" +
"1584602502189,39,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-10,text,true,,8074,851,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=26792af8-d8cd-4ed6-b0e0-68ad7220004f&login=true&scope=openid,39,0,0\n" +
"1584602502229,512,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-10,text,true,,1464765,4955,10,10,https://rddev2.fit2cloud.com/login,18,0,0\n" +
"1584602502169,625,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,200,OK,Thread Group 1-2,text,true,,1473329,6770,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=7bfdeacf-3f22-449d-88f9-f0d792bfe1bb&login=true&scope=openid,19,0,0\n" +
"1584602502169,19,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-0,302,Found,Thread Group 1-2,,true,,558,654,10,10,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,19,0,0\n" +
"1584602502189,32,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-1,200,OK,Thread Group 1-2,text,true,,8030,1615,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=7bfdeacf-3f22-449d-88f9-f0d792bfe1bb&login=true&scope=openid,32,0,0\n" +
"1584602502222,572,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-2,200,OK,Thread Group 1-2,text,true,,1464741,4501,10,10,https://rddev2.fit2cloud.com/login,21,0,0\n" +
"1584602502110,713,https://rddev2.fit2cloud.com/,200,OK,Thread Group 1-9,text,true,,1473667,6942,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=cb0aadfc-16eb-486b-b7f1-2c2df1c3231c&login=true&scope=openid,63,0,1\n" +
"1584602502110,63,https://rddev2.fit2cloud.com/-0,302,Found,Thread Group 1-9,,true,,214,567,10,10,https://rddev2.fit2cloud.com/,63,0,1\n" +
"1584602502174,21,https://rddev2.fit2cloud.com/-1,302,Found,Thread Group 1-9,,true,,614,577,10,10,https://rddev2.fit2cloud.com/dashboard/,21,0,0\n" +
"1584602502195,34,https://rddev2.fit2cloud.com/-2,200,OK,Thread Group 1-9,text,true,,8074,850,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2F&state=cb0aadfc-16eb-486b-b7f1-2c2df1c3231c&login=true&scope=openid,34,0,0\n" +
"1584602502231,592,https://rddev2.fit2cloud.com/-3,200,OK,Thread Group 1-9,text,true,,1464765,4948,10,10,https://rddev2.fit2cloud.com/login,21,0,0\n" +
"1584602502434,434,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,200,OK,Thread Group 1-1,text,true,,1473315,6926,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=b2a99afa-66a7-411d-bba9-239c9b20de82&login=true&scope=openid,18,0,0\n" +
"1584602502434,18,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-0,302,Found,Thread Group 1-1,,true,,558,731,10,10,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,18,0,0\n" +
"1584602502452,27,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-1,200,OK,Thread Group 1-1,text,true,,8024,1603,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=b2a99afa-66a7-411d-bba9-239c9b20de82&login=true&scope=openid,27,0,0\n" +
"1584602502481,387,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-2,200,OK,Thread Group 1-1,text,true,,1464733,4592,10,10,https://rddev2.fit2cloud.com/login,25,0,0\n" +
"1584602502839,65,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-3,text,false,,4462,1746,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,65,0,1\n" +
"1584602502961,0,https://rddev2.fit2cloud.com/dashboard/?state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1&code=efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc,Non HTTP response code: java.net.URISyntaxException,\"Non HTTP response message: Illegal character in query at index 45: https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",Thread Group 1-3,text,false,,1392,0,10,10,\"https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",0,0,0\n" +
"1584602503108,27,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,200,OK,Thread Group 1-3,text,true,,12438,560,10,10,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,27,0,0\n" +
"1584602503239,23,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,200,OK,Thread Group 1-3,text,true,,1916,574,10,10,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,23,0,0\n" +
"1584602503006,262,https://rddev2.fit2cloud.com/dashboard/user/current/info?_nocache=1578039505484,200,OK,Thread Group 1-1,text,true,,1473599,5844,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fuser%2Fcurrent%2Finfo?_nocache%3D1578039505484&state=10ff89f8-c521-42a3-99bd-d2929d4260cc&login=true&scope=openid,14,0,0\n" +
"1584602503006,14,https://rddev2.fit2cloud.com/dashboard/user/current/info?_nocache=1578039505484-0,302,Found,Thread Group 1-1,,true,,564,557,10,10,https://rddev2.fit2cloud.com/dashboard/user/current/info?_nocache=1578039505484,14,0,0\n" +
"1584602503021,22,https://rddev2.fit2cloud.com/dashboard/user/current/info?_nocache=1578039505484-1,200,OK,Thread Group 1-1,text,true,,8056,1528,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fuser%2Fcurrent%2Finfo?_nocache%3D1578039505484&state=10ff89f8-c521-42a3-99bd-d2929d4260cc&login=true&scope=openid,22,0,0\n" +
"1584602503047,221,https://rddev2.fit2cloud.com/dashboard/user/current/info?_nocache=1578039505484-2,200,OK,Thread Group 1-1,text,true,,1464979,3759,10,10,https://rddev2.fit2cloud.com/login,20,0,0\n" +
"1584602502806,506,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50,200,OK,Thread Group 1-2,text,true,,1473450,6758,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fnotification%2Flist%2Funread%2F1%2F50&state=f4ac4eb9-f92a-4bcc-8214-3eac6df6d1c9&login=true&scope=openid,12,0,0\n" +
"1584602502806,12,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-0,302,Found,Thread Group 1-2,,true,,555,653,10,10,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50,12,0,0\n" +
"1584602502819,26,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-1,200,OK,Thread Group 1-2,text,true,,8027,1604,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fnotification%2Flist%2Funread%2F1%2F50&state=f4ac4eb9-f92a-4bcc-8214-3eac6df6d1c9&login=true&scope=openid,26,0,0\n" +
"1584602502846,466,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-2,200,OK,Thread Group 1-2,text,true,,1464868,4501,10,10,https://rddev2.fit2cloud.com/login,15,0,0\n" +
"1584602503314,13,https://rddev2.fit2cloud.com/web-public/project/html/pagination.html?_t=1577351137654,200,OK,Thread Group 1-2,text,true,,1162,500,10,10,https://rddev2.fit2cloud.com/web-public/project/html/pagination.html?_t=1577351137654,13,0,0\n" +
"1584602503471,15,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/header-menu.html?_t=1577351137654,200,OK,Thread Group 1-1,text,true,,3117,574,10,10,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/header-menu.html?_t=1577351137654,15,0,0\n" +
"1584602503471,54,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-4,text,false,,4462,1747,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,54,0,4\n" +
"1584602503569,0,https://rddev2.fit2cloud.com/dashboard/?state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1&code=efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc,Non HTTP response code: java.net.URISyntaxException,\"Non HTTP response message: Illegal character in query at index 45: https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",Thread Group 1-4,text,false,,1392,0,10,10,\"https://rddev2.fit2cloud.com/dashboard/?code=\n" +
" efe49afa-7afb-4c38-8e0c-38323291938c.191d0330-dd9f-4bb0-8c24-0e3df46e2ff1.fd56cca3-6d54-44aa-b879-a35e79fc1bfc\n" +
" &state=3d31fe47-47bd-47f2-950d-9d135e0600ef&session_state=191d0330-dd9f-4bb0-8c24-0e3df46e2ff1\",0,0,0\n" +
"1584602503108,494,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,200,OK,Thread Group 1-5,text,true,,1473344,6770,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=bbc579c2-fce7-4f4c-a9e0-9e27c818248c&login=true&scope=openid,21,0,0\n" +
"1584602503108,21,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-0,302,Found,Thread Group 1-5,,true,,558,654,10,10,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,21,0,0\n" +
"1584602503129,39,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-1,200,OK,Thread Group 1-5,text,true,,8030,1615,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=bbc579c2-fce7-4f4c-a9e0-9e27c818248c&login=true&scope=openid,39,0,0\n" +
"1584602503170,432,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-2,200,OK,Thread Group 1-5,text,true,,1464756,4501,10,10,https://rddev2.fit2cloud.com/login,25,0,0\n" +
"1584602503607,363,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50,200,OK,Thread Group 1-5,text,true,,1473560,6758,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fnotification%2Flist%2Funread%2F1%2F50&state=db071ff5-a114-4a0d-b693-fd1d8e477e75&login=true&scope=openid,12,0,0\n" +
"1584602503607,12,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-0,302,Found,Thread Group 1-5,,true,,555,653,10,10,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50,12,0,0\n" +
"1584602503619,23,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-1,200,OK,Thread Group 1-5,text,true,,8027,1604,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fnotification%2Flist%2Funread%2F1%2F50&state=db071ff5-a114-4a0d-b693-fd1d8e477e75&login=true&scope=openid,23,0,0\n" +
"1584602503644,326,https://rddev2.fit2cloud.com/dashboard/notification/list/unread/1/50-2,200,OK,Thread Group 1-5,text,true,,1464978,4501,10,10,https://rddev2.fit2cloud.com/login,18,0,0\n" +
"1584602503971,15,https://rddev2.fit2cloud.com/web-public/project/html/pagination.html?_t=1577351137654,200,OK,Thread Group 1-5,text,true,,1162,500,10,10,https://rddev2.fit2cloud.com/web-public/project/html/pagination.html?_t=1577351137654,15,0,0\n" +
"1584602503971,19,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,200,OK,Thread Group 1-4,text,true,,12438,561,10,10,https://rddev2.fit2cloud.com/dashboard/anonymous/i18n/en_US.json?_t=1577351137654,19,0,0\n" +
"1584602503792,32,https://rddev2.fit2cloud.com/dashboard/user/source/list?_nocache=1578039505551,200,OK,Thread Group 1-1,text,true,,8617,2109,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fuser%2Fsource%2Flist?_nocache%3D1578039505551&state=2bc90b74-8d32-4217-a573-bfbd969b6b16&login=true&scope=openid,10,0,0\n" +
"1584602503792,10,https://rddev2.fit2cloud.com/dashboard/user/source/list?_nocache=1578039505551-0,302,Found,Thread Group 1-1,,true,,563,556,10,10,https://rddev2.fit2cloud.com/dashboard/user/source/list?_nocache=1578039505551,10,0,0\n" +
"1584602503803,21,https://rddev2.fit2cloud.com/dashboard/user/source/list?_nocache=1578039505551-1,200,OK,Thread Group 1-1,text,true,,8054,1553,10,10,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fuser%2Fsource%2Flist?_nocache%3D1578039505551&state=2bc90b74-8d32-4217-a573-bfbd969b6b16&login=true&scope=openid,21,0,0\n" +
"1584602504095,21,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-9,text,false,,4469,1745,9,9,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,21,0,0\n" +
"1584602504100,20,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,200,OK,Thread Group 1-4,text,true,,1916,575,8,8,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/task-menus.html?_t=1577351137654,20,0,0\n" +
"1584602504095,27,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-10,text,false,,4462,1746,7,7,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,27,0,0\n" +
"1584602504095,39,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,200,OK,Thread Group 1-2,text,true,,8588,2336,6,6,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=24c9d65b-0421-4732-8193-0bf5f70b3fa4&login=true&scope=openid,13,0,0\n" +
"1584602504095,13,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-0,302,Found,Thread Group 1-2,,true,,558,732,6,6,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50,13,0,0\n" +
"1584602504108,26,https://rddev2.fit2cloud.com/dashboard/flow/runtime/task/pending/1/50-1,200,OK,Thread Group 1-2,text,true,,8030,1604,6,6,https://rddev2.fit2cloud.com/auth/realms/cmp/protocol/openid-connect/auth?response_type=code&client_id=cmp-client&redirect_uri=https%3A%2F%2Frddev2.fit2cloud.com%2Fdashboard%2Fflow%2Fruntime%2Ftask%2Fpending%2F1%2F50&state=24c9d65b-0421-4732-8193-0bf5f70b3fa4&login=true&scope=openid,26,0,0\n" +
"1584602504095,55,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-8,text,false,,4469,1746,5,5,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,55,0,3\n" +
"1584602504095,59,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-6,text,false,,4462,1747,4,4,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,59,0,4\n" +
"1584602504095,65,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate?session_code=YWUneSay4qlQuHRZsD4kPaZDIIR50KJLaNpW7uhsD-Q&execution=c7620733-54ab-46b2-802b-66764e42682b&client_id=cmp-client&tab_id=S8xOQPgCmhQ,400,Bad Request,Thread Group 1-7,text,false,,4469,1745,3,3,https://rddev2.fit2cloud.com/auth/realms/cmp/login-actions/authenticate,65,0,4\n" +
"1584602504200,12,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,200,OK,Thread Group 1-3,text,true,,1570,582,2,2,https://rddev2.fit2cloud.com/dashboard/web-public/project/html/notification-menus.html?_t=1577351137654,12,0,0\n";
List<Metric> metrics = beanBuilderExample(jtlString);
Map<String, List<Metric>> map = metrics.stream().collect(Collectors.groupingBy(Metric::getLabel));
getOneRpsResult(map);
}
private void getOneRpsResult(Map<String, List<Metric>> map){
Iterator<Map.Entry<String, List<Metric>>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<Metric>> entry = iterator.next();
String label = entry.getKey();
List<Metric> list = entry.getValue();
List<String> timestampList = list.stream().map(Metric::getTimestamp).collect(Collectors.toList());
int index=0;
//总的响应时间
int sumElapsed=0;
Integer failSize = 0;
Integer totalBytes = 0;
List<Integer> elapsedList = new ArrayList<Integer>();
for (int i = 0; i < list.size(); i++) {
try {
Metric row = list.get(i);
//响应时间
String elapsed = row.getElapsed();
sumElapsed += Integer.valueOf(elapsed);
elapsedList.add(Integer.valueOf(elapsed));
//成功与否
String success = row.getSuccess();
if (!"true".equals(success)){
failSize++;
}
//字节
String bytes = row.getBytes();
totalBytes += Integer.valueOf(bytes);
index++;
}catch (Exception e){
System.out.println("exception i:"+i);
}
}
Collections.sort(elapsedList, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
Integer tp90 = elapsedList.size()*9/10;
Integer tp95 = elapsedList.size()*95/100;
Integer tp99 = elapsedList.size()*99/100;
Long l = Long.valueOf(timestampList.get(index-1)) - Long.valueOf(timestampList.get(0));
RequestStatistics sceneResult = new RequestStatistics();
sceneResult.setRequestLabel(label);
sceneResult.setSamples(index+"");
sceneResult.setAverage(sumElapsed/index+"");
sceneResult.setTp90(elapsedList.get(tp90)+"");
sceneResult.setTp95(elapsedList.get(tp95)+"");
sceneResult.setTp99(elapsedList.get(tp99)+"");
sceneResult.setMin(elapsedList.get(0)+"");
sceneResult.setMax(elapsedList.get(index-1)+"");
sceneResult.setErrors(String.format("%.2f",failSize*100.0/index)+"%");
sceneResult.setKbPerSec(String.format("%.2f",totalBytes*1.0/1024/(l*1.0/1000)));
System.out.println(JSONObject.toJSONString(sceneResult));
System.out.println();
}
}
}

View File

@ -0,0 +1,180 @@
package io.metersphere;
import com.opencsv.bean.CsvBindByName;
public class Metric {
// timestamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect
@CsvBindByName(column = "timestamp")
private String timestamp;
@CsvBindByName(column = "elapsed")
private String elapsed;
@CsvBindByName(column = "label")
private String label;
@CsvBindByName(column = "responseCode")
private String responseCode;
@CsvBindByName(column = "responseMessage")
private String responseMessage;
@CsvBindByName(column = "threadName")
private String threadName;
@CsvBindByName(column = "dataType")
private String dataType;
@CsvBindByName(column = "success")
private String success;
@CsvBindByName(column = "failureMessage")
private String failureMessage;
@CsvBindByName(column = "bytes")
private String bytes;
@CsvBindByName(column = "sentBytes")
private String sentBytes;
@CsvBindByName(column = "grpThreads")
private String grpThreads;
@CsvBindByName(column = "allThreads")
private String allThreads;
@CsvBindByName(column = "URL")
private String url;
@CsvBindByName(column = "Latency")
private String latency;
@CsvBindByName(column = "IdleTime")
private String idleTime;
@CsvBindByName(column = "Connect")
private String connect;
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getElapsed() {
return elapsed;
}
public void setElapsed(String elapsed) {
this.elapsed = elapsed;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getResponseCode() {
return responseCode;
}
public void setResponseCode(String responseCode) {
this.responseCode = responseCode;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public String getSuccess() {
return success;
}
public void setSuccess(String success) {
this.success = success;
}
public String getFailureMessage() {
return failureMessage;
}
public void setFailureMessage(String failureMessage) {
this.failureMessage = failureMessage;
}
public String getBytes() {
return bytes;
}
public void setBytes(String bytes) {
this.bytes = bytes;
}
public String getSentBytes() {
return sentBytes;
}
public void setSentBytes(String sentBytes) {
this.sentBytes = sentBytes;
}
public String getGrpThreads() {
return grpThreads;
}
public void setGrpThreads(String grpThreads) {
this.grpThreads = grpThreads;
}
public String getAllThreads() {
return allThreads;
}
public void setAllThreads(String allThreads) {
this.allThreads = allThreads;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLatency() {
return latency;
}
public void setLatency(String latency) {
this.latency = latency;
}
public String getIdleTime() {
return idleTime;
}
public void setIdleTime(String idleTime) {
this.idleTime = idleTime;
}
public String getConnect() {
return connect;
}
public void setConnect(String connect) {
this.connect = connect;
}
}

View File

@ -207,6 +207,10 @@
return false; return false;
} }
if (!this.$refs.pressureConfig.validConfig()) {
return false;
}
if (!this.$refs.advancedConfig.validConfig()) { if (!this.$refs.advancedConfig.validConfig()) {
return false; return false;
} }

View File

@ -73,6 +73,21 @@
<div>{{$t('load_test.ramp_up_time_times')}}</div> <div>{{$t('load_test.ramp_up_time_times')}}</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{$t('load_test.select_resource_pool')}}</div>
</el-form-item>
<el-form-item>
<el-select v-model="resourcePool" size="mini">
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart> <chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart>
@ -89,6 +104,7 @@
const STEPS = "Steps"; const STEPS = "Steps";
const DURATION = "duration"; const DURATION = "duration";
const RPS_LIMIT = "rpsLimit"; const RPS_LIMIT = "rpsLimit";
const RESOURCE_POOL = "resourcePoolId";
export default { export default {
name: "PerformancePressureConfig", name: "PerformancePressureConfig",
@ -101,6 +117,8 @@
step: 10, step: 10,
rpsLimit: 10, rpsLimit: 10,
orgOptions: {}, orgOptions: {},
resourcePool: null,
resourcePools: [],
} }
}, },
mounted() { mounted() {
@ -110,6 +128,8 @@
} else { } else {
this.calculateChart(); this.calculateChart();
} }
this.getResourcePools();
}, },
watch: { watch: {
'$route'(to, from) { '$route'(to, from) {
@ -125,11 +145,16 @@
} }
}, },
methods: { methods: {
getResourcePools() {
this.$get('/testresourcepool/list/all', response => {
this.resourcePools = response.data;
})
},
getLoadConfig(testId) { getLoadConfig(testId) {
if (testId) { if (testId) {
this.$get('/testplan/get-load-config/' + testId, (response) => { this.$get('/testplan/get-load-config/' + testId, (response) => {
if (response.data && response.data != "") { if (response.data) {
let data = JSON.parse(response.data); let data = JSON.parse(response.data);
data.forEach(d => { data.forEach(d => {
@ -149,6 +174,9 @@
case RPS_LIMIT: case RPS_LIMIT:
this.rpsLimit = d.value; this.rpsLimit = d.value;
break; break;
case RESOURCE_POOL:
this.resourcePool = d.value;
break;
default: default:
break; break;
} }
@ -252,6 +280,17 @@
} }
} }
}, },
validConfig() {
if (!this.resourcePool) {
this.$message({
message: this.$t('load_test.resource_pool_is_null'),
type: 'warning'
});
return false;
}
return true;
},
convertProperty() { convertProperty() {
/// todo4jmeter ConcurrencyThreadGroup plugin /// todo4jmeter ConcurrencyThreadGroup plugin
return [ return [
@ -259,7 +298,8 @@
{key: RAMP_UP, value: this.rampUpTime}, {key: RAMP_UP, value: this.rampUpTime},
{key: STEPS, value: this.step}, {key: STEPS, value: this.step},
{key: DURATION, value: this.duration}, {key: DURATION, value: this.duration},
{key: RPS_LIMIT, value: this.rpsLimit} {key: RPS_LIMIT, value: this.rpsLimit},
{key: RESOURCE_POOL, value: this.resourcePool},
]; ];
} }
} }

View File

@ -46,7 +46,7 @@
</el-card> </el-card>
<el-dialog :title="title" :visible.sync="createVisible"> <el-dialog :title="title" :visible.sync="createVisible">
<el-form :model="form" :rules="rules" ref="form" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')"> <el-form-item :label="$t('commons.name')">
<el-input v-model="form.name" autocomplete="off"></el-input> <el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item> </el-form-item>

View File

@ -51,7 +51,7 @@
</el-card> </el-card>
<el-dialog :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" ref="form" :rules="rules" label-position="left" label-width="100px" size="small"> <el-form :model="form" ref="form" :rules="rules" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
@ -81,7 +81,7 @@
</el-dialog> </el-dialog>
<el-dialog :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>

View File

@ -48,7 +48,7 @@
</el-card> </el-card>
<el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%"> <el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%">
<el-form :model="form" :rules="rules" ref="form" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
@ -114,7 +114,7 @@
<!-- add workspace member dialog --> <!-- add workspace member dialog -->
<el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="left" label-width="100px" size="small"> <el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
@ -145,7 +145,7 @@
<!-- update workspace member dialog --> <!-- update workspace member dialog -->
<el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/> <el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>

View File

@ -26,7 +26,7 @@
</el-table> </el-table>
<el-dialog :title="$t('member.modify_personal_info')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify_personal_info')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>

View File

@ -101,7 +101,7 @@
<!-- add organization form --> <!-- add organization form -->
<el-dialog :title="$t('organization.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true"> <el-dialog :title="$t('organization.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="createOrganization"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="createOrganization">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
@ -116,7 +116,7 @@
<!-- update organization form --> <!-- update organization form -->
<el-dialog :title="$t('organization.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('organization.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="updateOrganizationForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="updateOrganizationForm">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
@ -131,7 +131,7 @@
<!-- add organization member form --> <!-- add organization member form -->
<el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="orgMemberRule" label-position="left" label-width="100px" size="small"> <el-form :model="memberForm" ref="form" :rules="orgMemberRule" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
@ -162,7 +162,7 @@
<!-- update organization member form --> <!-- update organization member form -->
<el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/> <el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>

View File

@ -53,7 +53,7 @@
<!-- add workspace dialog --> <!-- add workspace dialog -->
<el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%"> <el-dialog :title="$t('workspace.create')" :visible.sync="createVisible" width="30%">
<el-form :model="form" :rules="rules" ref="form" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="form" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
@ -78,7 +78,7 @@
<!-- update workspace dialog --> <!-- update workspace dialog -->
<el-dialog :title="$t('workspace.update')" :visible.sync="updateVisible" width="30%"> <el-dialog :title="$t('workspace.update')" :visible.sync="updateVisible" width="30%">
<el-form :model="form" :rules="rules" ref="updateForm" label-position="left" label-width="100px" size="small"> <el-form :model="form" :rules="rules" ref="updateForm" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
@ -154,7 +154,7 @@
<!-- add workspace member dialog --> <!-- add workspace member dialog -->
<el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.create')" :visible.sync="addMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="left" label-width="100px" size="small"> <el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px" size="small">
<el-form-item :label="$t('commons.member')" prop="userIds"> <el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
@ -185,7 +185,7 @@
<!-- update workspace member dialog --> <!-- update workspace member dialog -->
<el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('member.modify')" :visible.sync="updateMemberVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="memberForm" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="memberForm" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/> <el-input v-model="memberForm.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>

View File

@ -69,8 +69,9 @@
</div> </div>
</el-card> </el-card>
<el-dialog title="创建资源池" :visible.sync="createVisible" width="70%" @closed="closeFunc" :destroy-on-close="true"> <el-dialog v-loading="testLoading" title="创建资源池" :visible.sync="createVisible" width="70%" @closed="closeFunc"
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" :destroy-on-close="true">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="createTestResourcePoolForm"> ref="createTestResourcePoolForm">
<el-form-item label="名称" prop="name"> <el-form-item label="名称" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
@ -85,46 +86,41 @@
</el-select> </el-select>
</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="current-row" v-if="form.type === 'K8S'"> <div class="node-line" v-if="form.type === 'K8S'">
<div style="width: 35%;float: left"> <div class="k8s-master">
<label class="el-form-item__label">Master URL</label> <el-col :span="11">
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.masterUrl" autocomplete="off" class="el-input__inner form-input"/> </el-col>
</div> <el-form-item prop="masterUrl" label="Master URL">
</div> <el-input v-model="item.masterUrl" autocomplete="off"/>
<div style="width: 35%;float: left"> </el-form-item>
<label class="el-form-item__label" style="padding-left: 20px">Token</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.token" autocomplete="off" class="el-input__inner form-input"/>
</div> </div>
<div class="k8s-token">
<el-form-item prop="token" label="Token">
<el-input v-model="item.token" show-password autocomplete="off"/>
</el-form-item>
</div> </div>
<div style="width: 30%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label> <el-form-item prop="maxConcurrency" label="最大并发数">
<div class="el-form-item__content" style="margin-left: 102px"> <el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
<input v-model="item.maxConcurrency" autocomplete="off" type="number" </el-form-item>
class="el-input__inner form-input"/>
</div> </div>
</div> </div>
<div class="node-line" v-if="form.type === 'NODE'">
<div style="width: 30%;float: left">
<el-form-item prop="ip" label="IP">
<el-input v-model="item.ip" autocomplete="off"/>
</el-form-item>
</div> </div>
<div class="current-row" v-if="form.type === 'NODE'"> <div style="width: 30%;float: left">
<div style="width: 42%;float: left"> <el-form-item prop="port" label="Port">
<label class="el-form-item__label">IP</label> <el-input-number v-model="item.port" :min="1" :max="9999"></el-input-number>
<div class="el-form-item__content" style="margin-left: 100px"> </el-form-item>
<input v-model="item.ip" autocomplete="off" class="el-input__inner form-input"/>
</div>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">port</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.port" autocomplete="off" type="number" class="el-input__inner form-input"/>
</div>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label>
<div class="el-form-item__content" style="margin-left: 102px">
<input v-model="item.maxConcurrency" autocomplete="off" type="number"
class="el-input__inner form-input"/>
</div> </div>
<div style="width: 30%;float: left">
<el-form-item prop="maxConcurrency" label="最大并发数">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div> </div>
<div class="op"> <div class="op">
<span class="box"> <span class="box">
@ -148,8 +144,9 @@
</span> </span>
</el-dialog> </el-dialog>
<el-dialog title="修改资源池" :visible.sync="updateVisible" width="70%" :destroy-on-close="true" @close="closeFunc"> <el-dialog v-loading="testLoading" title="修改资源池" :visible.sync="updateVisible" width="70%" :destroy-on-close="true"
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" @close="closeFunc">
<el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule"
ref="updateTestResourcePoolForm"> ref="updateTestResourcePoolForm">
<el-form-item label="名称" prop="name"> <el-form-item label="名称" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
@ -158,52 +155,44 @@
<el-input v-model="form.description" autocomplete="off"/> <el-input v-model="form.description" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item label="资源类型" prop="type"> <el-form-item label="资源类型" prop="type">
<el-select v-model="form.type" placeholder="选择资源类型"> <el-select v-model="form.type" placeholder="选择资源类型" @change="changeResourceType()">
<el-option key="K8S" value="K8S" label="Kubernetes">Kubernetes</el-option> <el-option key="K8S" value="K8S" label="Kubernetes">Kubernetes</el-option>
<el-option key="NODE" value="NODE" label="独立节点">独立节点</el-option> <el-option key="NODE" value="NODE" label="独立节点">独立节点</el-option>
</el-select> </el-select>
</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="current-row" v-if="form.type === 'K8S'"> <div class="node-line" v-if="form.type === 'K8S'">
<div style="width: 35%;float: left"> <div class="k8s-master">
<label class="el-form-item__label">Master URL</label> <el-form-item prop="masterUrl" label="Master URL">
<div class="el-form-item__content" style="margin-left: 100px"> <el-input v-model="item.masterUrl" autocomplete="off"/>
<input v-model="item.masterUrl" autocomplete="off" class="el-input__inner form-input"/> </el-form-item>
</div>
</div>
<div style="width: 35%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">Token</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.token" autocomplete="off" class="el-input__inner form-input"/>
</div> </div>
<div class="k8s-token">
<el-form-item prop="password" label="Token" style="padding-left: 20px">
<el-input v-model="item.token" show-password autocomplete="off"/>
</el-form-item>
</div> </div>
<div style="width: 30%;float: left"> <div style="width: 30%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label> <el-form-item prop="maxConcurrency" label="最大并发数" style="padding-left: 20px">
<div class="el-form-item__content" style="margin-left: 102px"> <el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
<input v-model="item.maxConcurrency" autocomplete="off" type="number" </el-form-item>
class="el-input__inner form-input"/>
</div> </div>
</div> </div>
<div class="node-line" v-if="form.type === 'NODE'">
<div style="width: 30%;float: left">
<el-form-item prop="ip" label="IP">
<el-input v-model="item.ip" autocomplete="off"/>
</el-form-item>
</div> </div>
<div class="current-row" v-if="form.type === 'NODE'"> <div style="width: 30%;float: left">
<div style="width: 42%;float: left"> <el-form-item prop="port" label="Port" style="padding-left: 20px">
<label class="el-form-item__label">IP</label> <el-input-number v-model="item.port" :min="1" :max="9999"></el-input-number>
<div class="el-form-item__content" style="margin-left: 100px"> </el-form-item>
<input v-model="item.ip" autocomplete="off" class="el-input__inner form-input"/>
</div>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">port</label>
<div class="el-form-item__content" style="margin-left: 100px">
<input v-model="item.port" autocomplete="off" type="number" class="el-input__inner form-input"/>
</div>
</div>
<div style="width: 20%;float: left">
<label class="el-form-item__label" style="padding-left: 20px">最大并发数</label>
<div class="el-form-item__content" style="margin-left: 102px">
<input v-model="item.maxConcurrency" autocomplete="off" type="number"
class="el-input__inner form-input"/>
</div> </div>
<div style="width: 30%;float: left">
<el-form-item prop="maxConcurrency" label="最大并发数" style="padding-left: 20px">
<el-input-number v-model="item.maxConcurrency" :min="1" :max="9999"></el-input-number>
</el-form-item>
</div> </div>
<div class="op"> <div class="op">
<span class="box"> <span class="box">
@ -238,6 +227,7 @@
data() { data() {
return { return {
loading: false, loading: false,
testLoading: false,
createVisible: false, createVisible: false,
infoList: [], infoList: [],
updateVisible: false, updateVisible: false,
@ -264,6 +254,9 @@
], ],
description: [ description: [
{max: 60, message: '最大长度 60 个字符', trigger: 'blur'} {max: 60, message: '最大长度 60 个字符', trigger: 'blur'}
],
type: [
{required: true, message: '请选择资源类型', trigger: 'blur'}
] ]
} }
} }
@ -303,6 +296,28 @@
}); });
} }
}, },
validateResourceInfo() {
if (this.infoList.length <= 0) {
return {validate: false, msg: "资源池不能为空"}
}
let resultValidate = {validate: true, msg: "请完善数据"}
this.infoList.forEach(function (info) {
for (let key in info) {
if (info[key] != '0' && !info[key]) {
resultValidate.validate = false
return false;
}
}
if (!info.maxConcurrency) {
resultValidate.validate = false
return false;
}
});
return resultValidate;
},
buildPagePath(path) { buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize; return path + "/" + this.currentPage + "/" + this.pageSize;
}, },
@ -349,6 +364,9 @@
createTestResourcePool(createTestResourcePoolForm) { createTestResourcePool(createTestResourcePoolForm) {
this.$refs[createTestResourcePoolForm].validate(valide => { this.$refs[createTestResourcePoolForm].validate(valide => {
if (valide) { if (valide) {
let vri = this.validateResourceInfo();
if (vri.validate) {
this.testLoading = true;
this.form.info = JSON.stringify(this.infoList); this.form.info = JSON.stringify(this.infoList);
this.$post("/testresourcepool/add", this.form) this.$post("/testresourcepool/add", this.form)
.then(() => { .then(() => {
@ -357,8 +375,18 @@
message: '添加成功!' message: '添加成功!'
}, },
this.createVisible = false, this.createVisible = false,
this.initTableData()) this.initTableData());
this.testLoading = false;
}); });
} else {
this.$message({
type: 'warning',
message: vri.msg
});
this.testLoading = false;
return false;
}
} else { } else {
return false; return false;
} }
@ -367,6 +395,9 @@
updateTestResourcePool(updateTestResourcePoolForm) { updateTestResourcePool(updateTestResourcePoolForm) {
this.$refs[updateTestResourcePoolForm].validate(valide => { this.$refs[updateTestResourcePoolForm].validate(valide => {
if (valide) { if (valide) {
this.testLoading = true;
let vri = this.validateResourceInfo();
if (vri.validate) {
this.form.info = JSON.stringify(this.infoList); this.form.info = JSON.stringify(this.infoList);
this.$post("/testresourcepool/update", this.form) this.$post("/testresourcepool/update", this.form)
.then(() => { .then(() => {
@ -376,8 +407,17 @@
}, },
this.updateVisible = false, this.updateVisible = false,
this.initTableData(), this.initTableData(),
self.loading = false) self.loading = false);
this.testLoading = false;
}); });
} else {
this.$message({
type: 'warning',
message: vri.msg
});
this.testLoading = false;
return false;
}
} else { } else {
return false; return false;
} }
@ -414,12 +454,29 @@
} }
.op { .op {
line-height: 40px;
float: left; float: left;
width: 16%; width: 10%;
} }
.box { .box {
padding-left: 5px; padding-left: 5px;
} }
.k8s-master {
width: 34%;
float: left
}
.k8s-token {
width: 36%;
float: left
}
.k8s-token .el-form-item__label {
padding-left: 20px;
}
.node-line {
clear: both;
}
</style> </style>

View File

@ -65,7 +65,7 @@
</el-card> </el-card>
<el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true"> <el-dialog :title="$t('user.create')" :visible.sync="createVisible" width="30%" @closed="closeFunc" :destroy-on-close="true">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="createUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="createUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off"/> <el-input v-model="form.id" autocomplete="off"/>
</el-form-item> </el-form-item>
@ -85,7 +85,7 @@
</el-dialog> </el-dialog>
<el-dialog :title="$t('user.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog :title="$t('user.modify')" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" :rules="rule" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" :rules="rule" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>

View File

@ -50,7 +50,7 @@
</el-card> </el-card>
<el-dialog title="添加成员" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog title="添加成员" :visible.sync="createVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" ref="form" :rules="rules" label-position="left" label-width="100px" size="small"> <el-form :model="form" ref="form" :rules="rules" label-position="right" label-width="100px" size="small">
<el-form-item label="成员" prop="userIds"> <el-form-item label="成员" prop="userIds">
<el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width"> <el-select v-model="form.userIds" multiple :placeholder="$t('member.please_choose_member')" class="select-width">
<el-option <el-option
@ -80,7 +80,7 @@
</el-dialog> </el-dialog>
<el-dialog title="修改成员" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc"> <el-dialog title="修改成员" :visible.sync="updateVisible" width="30%" :destroy-on-close="true" @close="closeFunc">
<el-form :model="form" label-position="left" label-width="100px" size="small" ref="updateUserForm"> <el-form :model="form" label-position="right" label-width="100px" size="small" ref="updateUserForm">
<el-form-item label="ID" prop="id"> <el-form-item label="ID" prop="id">
<el-input v-model="form.id" autocomplete="off" :disabled="true"/> <el-input v-model="form.id" autocomplete="off" :disabled="true"/>
</el-form-item> </el-form-item>

View File

@ -158,6 +158,8 @@ export default {
'custom_http_code': 'Custom HTTP response success status code', 'custom_http_code': 'Custom HTTP response success status code',
'separated_by_commas': 'Separated by commas', 'separated_by_commas': 'Separated by commas',
'create': 'Create Test', 'create': 'Create Test',
'select_resource_pool': 'Please Select Resource Pool',
'resource_pool_is_null': 'Resource Pool is empty',
}, },
fuc_test: { fuc_test: {
'select_resource_pool': 'Please select resource pool' 'select_resource_pool': 'Please select resource pool'

View File

@ -158,6 +158,8 @@ export default {
'custom_http_code': '自定义 HTTP 响应成功状态码', 'custom_http_code': '自定义 HTTP 响应成功状态码',
'separated_by_commas': '按逗号分隔', 'separated_by_commas': '按逗号分隔',
'create': '创建测试', 'create': '创建测试',
'select_resource_pool': '请选择资源池',
'resource_pool_is_null': '资源池为空',
}, },
fuc_test: { fuc_test: {
'select_resource_pool': '请选择资源池' 'select_resource_pool': '请选择资源池'