diff --git a/README.md b/README.md
index 9048e41765..23dccbda7d 100755
--- a/README.md
+++ b/README.md
@@ -80,199 +80,225 @@ v1.1.0 是 v1.0.0 之后的功能版本。
## 功能列表
-
-
-
- 测试跟踪 |
- 项目管理 |
- 多项目支持,测试用例、测试计划与项目关联 |
-
-
- 测试用例管理 |
- 在线编辑用例 |
-
-
- 以树状形式展示项目的模块及其用例 |
-
-
- 自定义用例属性 |
-
-
- 快速导入用例到系统 |
-
-
- 测试用例评审 |
- 基于已有用例发起评审 |
-
-
- 在线更新评审结果 |
-
-
- 支持多人在线添加评审评论 |
-
-
- 灵活的评审人分配形式 |
-
-
- 测试计划跟踪 |
- 基于已有用例发起测试计划 |
-
-
- 在线更新用例执行结果 |
-
-
- 灵活的用例分配方式 |
-
-
- 在线生成测试报告,支持自定义测试报告模板 |
-
-
- 与平台中的接口测试、性能测试功能结合,自动更新关联用例的结果 |
-
-
- 记录测试用例关联的缺陷 |
-
-
- 缺陷记录支持关联到 Jira/TAPD 平台 |
-
-
- 测试报告支持分享、导出 |
-
-
- 接口测试 |
- 测试脚本 |
- 在线编辑接口测试内容 |
-
-
- 支持参数化测试 |
-
-
- 灵活多样的断言支持 |
-
-
- 支持多接口的场景化测试 |
-
-
- 测试场景复用 |
-
-
- 测试场景支持引用已有环境信息 |
-
-
- 测试环境信息管理 |
-
-
- 通过浏览器插件快速录制测试脚本 |
-
-
- 支持前后置 BeanShell/Python 脚本 |
-
-
- 上传并引用自定义 Jar 包 |
-
-
- 多协议支持,支持 HTTP、Dubbo、SQL、TCP 类型请求 |
-
-
- 支持等待时间、条件判断等逻辑控制功能 |
-
-
- 测试执行 |
- 内置定时任务支持 |
-
-
- 通过 Jenkins 插件触发测试执行 |
-
-
- 多个接口测试一键合并执行 |
-
-
- 一键创建性能测试 |
-
-
- 测试报告 |
- 测试执行后自动生成动态实时测试报告 |
-
-
- 测试报告导出 |
-
-
- 通过邮件、IM 工具等通知执行结果 |
-
-
- 性能测试 |
- 测试脚本 |
- 完全兼容 JMeter 脚本 |
-
-
- 在线调整压力参数 |
-
-
- 分布式压力测试 |
-
-
- 支持参数化测试 |
-
-
- 通过浏览器插件快速录制测试脚本 |
-
-
- 多协议支持 |
-
-
- 测试执行 |
- 内置定时任务支持 |
-
-
- 通过 Jenkins 插件触发测试执行 |
-
-
- 测试报告 |
- 测试执行后自动生成测试报告 |
-
-
- 丰富的测试报告展现形式 |
-
-
- 测试报告导出 |
-
-
- 查看测试日志详情 |
-
-
- 系统管理 |
- 用户租户管理 |
- 支持多级租户体系 |
-
-
- 支持多种租户角色 |
-
-
- LDAP 认证对接 |
-
-
- 测试资源管理 |
- 性能测试资源池管理 |
-
-
- 消息通知配置 |
- IM 工具通知(如企业微信、钉钉) |
-
-
- 邮件通知配置 |
-
-
- 集成与扩展 |
- 完善的 API 列表 |
-
-
- 支持对接 Jenkins 等持续集成工具 |
-
-
- 支持对接 Jira/TAPD 等缺陷管理工具 |
-
-
-
+
+
+
+
+
+
+
+
+ 测试跟踪 |
+ 测试用例管理 |
+ 在线编辑用例 |
+
+
+ 编辑窗口支持上传附件 |
+
+
+ 查看与编辑窗口显示评审评论 |
+
+
+ 以树状形式展示项目的模块及其用例 |
+
+
+ 支持测试用例模块树拖拽排序 |
+
+
+ 自定义用例等级/用例类型/测试方式 |
+
+
+ 支持Excel/Xmind格式快速导入用例到系统 |
+
+
+ 支持Excel格式快速导出用例到本地 |
+
+
+ 测试用例评审 |
+ 基于已有用例发起评审 |
+
+
+ 支持添加多个评审人 |
+
+
+ 在线更新评审结果 |
+
+
+ 支持多人在线添加评审评论 |
+
+
+ 测试计划跟踪 |
+ 基于已有用例发起测试计划 |
+
+
+ 支持在线更新用例执行结果 |
+
+
+ 在线生成测试报告,支持自定义测试报告模板 |
+
+
+ 与平台中的接口测试、性能测试功能联动,自动更新关联用例的结果 |
+
+
+ 记录测试用例关联的缺陷 |
+
+
+ 缺陷记录支持关联到 Jira/TAPD |
+
+
+ 支持PDF格式测试报告导出 |
+
+
+ 接口测试 |
+ 接口定义 |
+ 在线编辑接口测试内容 |
+
+
+ 支持 HTTP/Dubbo/SQL/TCP 类型接口请求 |
+
+
+ 支持接口快捷调制 |
+
+
+ 支持接口列表和用例列表切换显示 |
+
+
+ 支持用例编辑窗口正则/jsonpath/Xpath等多种类型的断言规则 |
+
+
+ 支持用例编辑窗口正则/jsonpath/Xpath类型的参数提取 |
+
+
+ 支持用例编辑窗口前后置 BeanShell/Python 脚本 |
+
+
+ 测试环境信息管理 |
+
+
+ 支持单接口测试引用环境信息 |
+
+
+ 支持通过浏览器插件快速录制测试脚本 |
+
+
+ 支持Metersphere json/Postman/Swagger格式快速导入用例到系统 |
+
+
+ 支持Metersphere json格式快速导出用例到本地 |
+
+
+ 支持上传并引用自定义 Jar 包 |
+
+
+ 接口自动化 |
+ 创建多接口的场景化测试 |
+
+
+ 支持自定义场景标签 |
+
+
+ 支持多层级场景嵌套结构 |
+
+
+ 支持接口列表快速导入测试场景 |
+
+
+ 支持测试场景复用 |
+
+
+ 支持添加自定义请求/自定义脚本 |
+
+
+ 支持添加等待时间/条件判断等多类型逻辑控制器 |
+
+
+ 场景调试支持引用已有环境信息 |
+
+
+ 支持定时任务 |
+
+
+ 支持通过 Jenkins 插件触发测试执行 |
+
+
+ Jenkins 插件支持 Pipeline 方式调用 |
+
+
+ 支持一键创建性能测试 |
+
+
+ 测试报告 |
+ 测试执行后自动生成测试报告 |
+
+
+ 支持PDF格式测试报告导出 |
+
+
+ 性能测试 |
+ 性能测试脚本 |
+ 支持上传JMX/CSV/JAR格式文件创建性能测试 |
+
+
+ 支持分线程组配置压力参数 |
+
+
+ 支持下载 JTL 文件 |
+
+
+ 支持通过浏览器插件快速录制测试脚本 |
+
+
+ 测试执行 |
+ 内置定时任务支持 |
+
+
+ 支持通过 Jenkins 插件触发测试执行 |
+
+
+ 测试报告 |
+ 测试执行后自动生成动态实时测试报告 |
+
+
+ 支持PDF格式测试报告导出 |
+
+
+ 系统管理 |
+ 用户租户管理 |
+ 支持多级租户体系 |
+
+
+ 支持多种租户角色 |
+
+
+ 支持LDAP 认证对接 |
+
+
+ 测试资源管理 |
+ 性能测试资源池管理 |
+
+
+ 消息通知配置 |
+ 支持企业微信/钉钉等多种IM 工具通知配置 |
+
+
+ 支持邮件通知配置 |
+
+
+ 集成与扩展 |
+ 配置API 列表 |
+
+
+ 支持对接 Jenkins 等持续集成工具 |
+
+
+ 支持对接 Jira/TAPD 等缺陷管理工具 |
+
+
+ 项目管理 |
+ 多项目支持,测试用例、测试计划与项目关联 |
+
+
+
详细的版本规划请参考 [版本路线图](https://github.com/metersphere/metersphere/blob/master/ROADMAP.md)
diff --git a/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java b/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java
index e5f05b7805..27add6d374 100644
--- a/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java
+++ b/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java
@@ -65,10 +65,12 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
protected ApiModule getSelectModule(String moduleId) {
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
- if (StringUtils.isNotBlank(moduleId)) {
+ if (StringUtils.isNotBlank(moduleId) && !StringUtils.equals("root", moduleId)) {
ApiModule module = new ApiModule();
ApiModuleDTO moduleDTO = apiModuleService.getNode(moduleId);
- BeanUtils.copyBean(module, moduleDTO);
+ if (moduleDTO != null) {
+ BeanUtils.copyBean(module, moduleDTO);
+ }
return module;
}
return null;
diff --git a/backend/src/main/java/io/metersphere/api/parse/MsParser.java b/backend/src/main/java/io/metersphere/api/parse/MsParser.java
index 0838445d12..9f700f6501 100644
--- a/backend/src/main/java/io/metersphere/api/parse/MsParser.java
+++ b/backend/src/main/java/io/metersphere/api/parse/MsParser.java
@@ -6,7 +6,6 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
-import io.metersphere.api.dto.definition.ApiModuleDTO;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.scenario.Body;
@@ -15,10 +14,13 @@ import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.service.ApiModuleService;
import io.metersphere.base.domain.ApiModule;
import io.metersphere.commons.utils.CommonBeanFactory;
+import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.*;
public class MsParser extends ApiImportAbstractParser {
@@ -77,9 +79,9 @@ public class MsParser extends ApiImportAbstractParser {
ApiDefinitionResult apiDefinition = buildApiDefinition(request.getId(), requestName, path, method);
apiDefinition.setModuleId(module.getId());
apiDefinition.setProjectId(this.projectId);
-
parseBody(requestObject, request.getBody());
parseHeader(requestObject, request.getHeaders());
+ parsePath(request, apiDefinition);
apiDefinition.setRequest(JSONObject.toJSONString(request));
results.add(apiDefinition);
});
@@ -87,6 +89,36 @@ public class MsParser extends ApiImportAbstractParser {
return apiImport;
}
+ private void parsePath(MsHTTPSamplerProxy request, ApiDefinitionResult apiDefinition) {
+ if (StringUtils.isNotBlank(request.getPath())) {
+ String[] split = request.getPath().split("\\?");
+ String path = split[0];
+ parseQueryParameters(split, request.getArguments());
+ request.setPath(path);
+ apiDefinition.setPath(path);
+ } else {
+ request.setPath("/");
+ apiDefinition.setPath("/");
+ }
+ apiDefinition.setName(apiDefinition.getPath() + " [" + apiDefinition.getMethod() + "]");
+ }
+
+ private void parseQueryParameters(String[] split, List arguments) {
+ if (split.length > 1) {
+ try {
+ String queryParams = split[1];
+ queryParams = URLDecoder.decode(queryParams, "UTF-8");
+ String[] params = queryParams.split("&");
+ for (String param : params) {
+ String[] kv = param.split("=");
+ arguments.add(new KeyValue(kv[0], kv[1]));
+ }
+ } catch (UnsupportedEncodingException e) {
+ LogUtil.info(e.getMessage(), e);
+ return;
+ }
+ }
+ }
private void parseHeader(JSONObject requestObject, List msHeaders) {
JSONArray headers = requestObject.getJSONArray("headers");
if (CollectionUtils.isNotEmpty(headers)) {
diff --git a/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java b/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java
index 71d2379d3e..13d7f9b9f0 100644
--- a/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java
+++ b/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java
@@ -61,6 +61,16 @@ public class PostmanParser extends ApiImportAbstractParser {
MsHTTPSamplerProxy request = buildRequest(requestItem.getName(), url.getRaw(), requestDesc.getMethod());
ApiDefinitionResult apiDefinition =
buildApiDefinition(request.getId(), requestItem.getName(), url.getRaw(), requestDesc.getMethod());
+ if (StringUtils.isNotBlank(request.getPath())) {
+ String path = request.getPath().split("\\?")[0];
+ path = path.replace("{{", "${");
+ path = path.replace("}}", "}");
+ request.setPath(path);
+ apiDefinition.setPath(path);
+ } else {
+ request.setPath("/");
+ apiDefinition.setPath("/");
+ }
parseBody(request.getBody(), requestDesc);
request.setArguments(parseKeyValue(url.getQuery()));
request.setHeaders(parseKeyValue(requestDesc.getHeader()));
diff --git a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java
index 7c3ab4f710..7ef25fb2de 100644
--- a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java
+++ b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java
@@ -213,20 +213,22 @@ public class ApiModuleService extends NodeTreeService {
public int editNode(DragModuleRequest request) {
request.setUpdateTime(System.currentTimeMillis());
checkApiModuleExist(request);
- List apiModule = queryByModuleIds(request.getNodeIds());
+ List apiDefinitionResults = queryByModuleIds(request.getNodeIds());
- apiModule.forEach(apiDefinition -> {
- StringBuilder path = new StringBuilder(apiDefinition.getModulePath());
- List pathLists = Arrays.asList(path.toString().split("/"));
- pathLists.set(request.getLevel(), request.getName());
- path.delete(0, path.length());
- for (int i = 1; i < pathLists.size(); i++) {
- path = path.append("/").append(pathLists.get(i));
+ apiDefinitionResults.forEach(apiDefinition -> {
+ if (StringUtils.isNotBlank(apiDefinition.getModulePath())) {
+ StringBuilder path = new StringBuilder(apiDefinition.getModulePath());
+ List pathLists = Arrays.asList(path.toString().split("/"));
+ pathLists.set(request.getLevel(), request.getName());
+ path.delete(0, path.length());
+ for (int i = 1; i < pathLists.size(); i++) {
+ path = path.append("/").append(pathLists.get(i));
+ }
+ apiDefinition.setModulePath(path.toString());
}
- apiDefinition.setModulePath(path.toString());
});
- batchUpdateApiDefinition(apiModule);
+ batchUpdateApiDefinition(apiDefinitionResults);
return apiModuleMapper.updateByPrimaryKeySelective(request);
}
diff --git a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java
index fe1f20966f..988c61955e 100644
--- a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java
+++ b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java
@@ -96,15 +96,27 @@ public class JmeterDocumentParser implements DocumentParser {
processCheckoutArguments(ele);
processCheckoutResponseAssertion(ele);
} else if (nodeNameEquals(ele, CONCURRENCY_THREAD_GROUP)) {
- processConcurrencyThreadGroup(ele);
+ processThreadGroupName(ele);
processCheckoutTimer(ele);
processCheckoutBackendListener(ele);
} else if (nodeNameEquals(ele, VARIABLE_THROUGHPUT_TIMER)) {
processVariableThroughputTimer(ele);
} else if (nodeNameEquals(ele, THREAD_GROUP)) {
- processThreadGroup(ele);
- //
- processConcurrencyThreadGroup(ele);
+ Object threadType = context.getProperty("threadType");
+ if (threadType instanceof List) {
+ Object o = ((List>) threadType).get(0);
+ ((List>) threadType).remove(0);
+ if ("DURATION".equals(o)) {
+ processThreadGroup(ele);
+ }
+ if ("ITERATION".equals(o)) {
+ processIterationThreadGroup(ele);
+ }
+ } else {
+ processThreadGroup(ele);
+ }
+
+ processThreadGroupName(ele);
processCheckoutTimer(ele);
processCheckoutBackendListener(ele);
} else if (nodeNameEquals(ele, BACKEND_LISTENER)) {
@@ -772,22 +784,120 @@ public class JmeterDocumentParser implements DocumentParser {
*/
removeChildren(threadGroup);
// elementProp
+ Object targetLevels = context.getProperty("TargetLevel");
+ String threads = "10";
+ if (targetLevels instanceof List) {
+ Object o = ((List>) targetLevels).get(0);
+ ((List>) targetLevels).remove(0);
+ threads = o.toString();
+ }
+ Object rampUps = context.getProperty("RampUp");
+ String rampUp = "1";
+ if (rampUps instanceof List) {
+ Object o = ((List>) rampUps).get(0);
+ ((List>) rampUps).remove(0);
+ rampUp = o.toString();
+ }
+ Object steps = context.getProperty("Steps");
+ String step = "2";
+ if (steps instanceof List) {
+ Object o = ((List>) steps).get(0);
+ ((List>) steps).remove(0);
+ step = o.toString();
+ }
+ Object holds = context.getProperty("Hold");
+ String hold = "2";
+ if (holds instanceof List) {
+ Object o = ((List>) holds).get(0);
+ ((List>) holds).remove(0);
+ hold = o.toString();
+ }
Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "ThreadGroup.main_controller");
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");
threadGroup.appendChild(elementProp);
-
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
- threadGroup.appendChild(createStringProp(document, "TargetLevel", "2"));
- threadGroup.appendChild(createStringProp(document, "RampUp", "12"));
- threadGroup.appendChild(createStringProp(document, "Steps", "2"));
- threadGroup.appendChild(createStringProp(document, "Hold", "1"));
+ threadGroup.appendChild(createStringProp(document, "TargetLevel", threads));
+ threadGroup.appendChild(createStringProp(document, "RampUp", rampUp));
+ threadGroup.appendChild(createStringProp(document, "Steps", step));
+ threadGroup.appendChild(createStringProp(document, "Hold", hold));
threadGroup.appendChild(createStringProp(document, "LogFilename", ""));
// bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空
// threadGroup.appendChild(createStringProp(document, "Iterations", "1"));
threadGroup.appendChild(createStringProp(document, "Unit", "S"));
}
+ private void processIterationThreadGroup(Element threadGroup) {
+ // 检查 threadgroup 后面的hashtree是否为空
+ Node hashTree = threadGroup.getNextSibling();
+ while (!(hashTree instanceof Element)) {
+ hashTree = hashTree.getNextSibling();
+ }
+ if (!hashTree.hasChildNodes()) {
+ MSException.throwException(Translator.get("jmx_content_valid"));
+ }
+ // 重命名 tagName
+ Document document = threadGroup.getOwnerDocument();
+ removeChildren(threadGroup);
+
+ // 选择按照迭代次数处理线程组
+ /*
+ continue
+
+ false
+ 1
+
+ 100
+ 5
+ true
+ 10
+
+ true
+ */
+ // elementProp
+ Object targetLevels = context.getProperty("TargetLevel");
+ String threads = "10";
+ if (targetLevels instanceof List) {
+ Object o = ((List>) targetLevels).get(0);
+ ((List>) targetLevels).remove(0);
+ threads = o.toString();
+ }
+ Object iterateNum = context.getProperty("iterateNum");
+ String loops = "1";
+ if (iterateNum instanceof List) {
+ Object o = ((List>) iterateNum).get(0);
+ ((List>) iterateNum).remove(0);
+ loops = o.toString();
+ }
+ Object rampUps = context.getProperty("iterateRampUpTime");
+ String rampUp = "10";
+ if (rampUps instanceof List) {
+ Object o = ((List>) rampUps).get(0);
+ ((List>) rampUps).remove(0);
+ rampUp = o.toString();
+ }
+ Element elementProp = document.createElement("elementProp");
+ elementProp.setAttribute("name", "ThreadGroup.main_controller");
+ elementProp.setAttribute("elementType", "LoopController");
+ elementProp.setAttribute("guiclass", "LoopControlPanel");
+ elementProp.setAttribute("testclass", "LoopController");
+ elementProp.setAttribute("testname", "Loop Controller");
+ elementProp.setAttribute("enabled", "true");
+ elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false));
+ elementProp.appendChild(createStringProp(document, "LoopController.loops", loops));
+ threadGroup.appendChild(elementProp);
+
+ threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
+ threadGroup.appendChild(createStringProp(document, "ThreadGroup.num_threads", threads));
+ threadGroup.appendChild(createStringProp(document, "ThreadGroup.ramp_time", rampUp));
+ threadGroup.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); // 不指定执行时间
+ threadGroup.appendChild(createStringProp(document, "Hold", "1"));
+ threadGroup.appendChild(createStringProp(document, "ThreadGroup.duration", "10"));
+ threadGroup.appendChild(createStringProp(document, "ThreadGroup.delay", ""));
+ threadGroup.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true));
+ }
+
+
private void processCheckoutTimer(Element element) {
/*
@@ -856,37 +966,14 @@ public class JmeterDocumentParser implements DocumentParser {
return unit;
}
- private void processConcurrencyThreadGroup(Element concurrencyThreadGroup) {
- String testname = concurrencyThreadGroup.getAttribute("testname");
- concurrencyThreadGroup.setAttribute("testname", testname + "-" + context.getResourceIndex());
- if (concurrencyThreadGroup.getChildNodes().getLength() > 0) {
- final NodeList childNodes = concurrencyThreadGroup.getChildNodes();
- for (int i = 0; i < childNodes.getLength(); i++) {
- Node node = childNodes.item(i);
- if (node instanceof Element) {
- Element ele = (Element) node;
- if (invalid(ele)) {
- continue;
- }
-
- if (nodeNameEquals(ele, STRING_PROP)) {
- parseStringProp(ele);
- }
- }
- }
- }
+ private void processThreadGroupName(Element threadGroup) {
+ String testname = threadGroup.getAttribute("testname");
+ threadGroup.setAttribute("testname", testname + "-" + context.getResourceIndex());
}
private void processVariableThroughputTimer(Element variableThroughputTimer) {
- Object durations = context.getProperty("duration");
- Integer duration;
- if (durations instanceof List) {
- Object o = ((List>) durations).get(0);
- duration = (Integer) o;
- ((List>) durations).remove(0);
- } else {
- duration = (Integer) durations;
- }
+ // 设置rps时长
+ Integer duration = Integer.MAX_VALUE;
Object rpsLimits = context.getProperty("rpsLimit");
String rpsLimit;
if (rpsLimits instanceof List) {
diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack
index 9f4a9bbf46..068127ce59 160000
--- a/backend/src/main/java/io/metersphere/xpack
+++ b/backend/src/main/java/io/metersphere/xpack
@@ -1 +1 @@
-Subproject commit 9f4a9bbf46fc1333dbcccea21f83e27e3ec10b1f
+Subproject commit 068127ce59ea8b016434ed52a9de4a7a4b13bdb4
diff --git a/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue b/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue
index 456f61e8d3..ee34766b45 100644
--- a/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue
+++ b/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue
@@ -21,45 +21,85 @@
size="mini"/>
-
-
+
+
+ {{ $t('load_test.by_duration') }}
+ {{ $t('load_test.by_iteration') }}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -83,6 +123,10 @@ const STEPS = "Steps";
const DURATION = "duration";
const RPS_LIMIT = "rpsLimit";
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
+const THREAD_TYPE = "threadType";
+const ITERATE_NUM = "iterateNum";
+const ITERATE_RAMP_UP = "iterateRampUpTime";
+
const hexToRgba = function (hex, opacity) {
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
+ parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
@@ -128,6 +172,9 @@ export default {
case RAMP_UP:
this.threadGroups[i].rampUpTime = item.value;
break;
+ case ITERATE_RAMP_UP:
+ this.threadGroups[i].iterateRampUp = item.value;
+ break;
case DURATION:
if (item.unit) {
this.threadGroups[i].duration = item.value;
@@ -144,6 +191,12 @@ export default {
case RPS_LIMIT_ENABLE:
this.threadGroups[i].rpsLimitEnable = item.value;
break;
+ case THREAD_TYPE:
+ this.threadGroups[i].threadType = item.value;
+ break;
+ case ITERATE_NUM:
+ this.threadGroups[i].iterateNum = item.value;
+ break;
default:
break;
}
@@ -157,6 +210,9 @@ export default {
case RAMP_UP:
this.threadGroups[0].rampUpTime = d.value;
break;
+ case ITERATE_RAMP_UP:
+ this.threadGroups[0].iterateRampUp = d.value;
+ break;
case DURATION:
if (d.unit) {
this.threadGroups[0].duration = d.value;
@@ -173,6 +229,12 @@ export default {
case RPS_LIMIT_ENABLE:
this.threadGroups[0].rpsLimitEnable = d.value;
break;
+ case THREAD_TYPE:
+ this.threadGroups[0].threadType = d.value;
+ break;
+ case ITERATE_NUM:
+ this.threadGroups[0].iterateNum = d.value;
+ break;
default:
break;
}
diff --git a/frontend/src/business/components/performance/test/EditPerformanceTest.vue b/frontend/src/business/components/performance/test/EditPerformanceTest.vue
index 2e2769f1a1..8af0e56832 100644
--- a/frontend/src/business/components/performance/test/EditPerformanceTest.vue
+++ b/frontend/src/business/components/performance/test/EditPerformanceTest.vue
@@ -298,6 +298,9 @@ export default {
tg.rampUpTime = tg.rampUpTime || 5;
tg.step = tg.step || 5;
tg.rpsLimit = tg.rpsLimit || 10;
+ tg.threadType = tg.threadType || 'DURATION';
+ tg.iterateNum = tg.iterateNum || 1;
+ tg.iterateRampUp = tg.iterateRampUp || 10;
handler.calculateChart(tg);
});
}
diff --git a/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue b/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue
index c531212baa..88098c33af 100644
--- a/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue
+++ b/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue
@@ -33,45 +33,83 @@
size="mini"/>
-
-
+
+
+ {{ $t('load_test.by_duration') }}
+ {{ $t('load_test.by_iteration') }}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -91,11 +129,14 @@ import {findTestPlan, findThreadGroup} from "@/business/components/performance/t
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
+const ITERATE_RAMP_UP = "iterateRampUpTime";
const STEPS = "Steps";
const DURATION = "duration";
const RPS_LIMIT = "rpsLimit";
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
const HOLD = "Hold";
+const THREAD_TYPE = "threadType";
+const ITERATE_NUM = "iterateNum";
const hexToRgba = function (hex, opacity) {
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
@@ -186,6 +227,9 @@ export default {
case RAMP_UP:
this.threadGroups[i].rampUpTime = item.value;
break;
+ case ITERATE_RAMP_UP:
+ this.threadGroups[i].iterateRampUp = item.value;
+ break;
case DURATION:
if (item.unit) {
this.threadGroups[i].duration = item.value;
@@ -202,9 +246,19 @@ export default {
case RPS_LIMIT_ENABLE:
this.threadGroups[i].rpsLimitEnable = item.value;
break;
+ case THREAD_TYPE:
+ this.threadGroups[i].threadType = item.value;
+ break;
+ case ITERATE_NUM:
+ this.threadGroups[i].iterateNum = item.value;
+ break;
default:
break;
}
+ //
+ this.$set(this.threadGroups[i], "threadType", this.threadGroups[i].threadType || 'DURATION');
+ this.$set(this.threadGroups[i], "iterateNum", this.threadGroups[i].iterateNum || 1);
+ this.$set(this.threadGroups[i], "iterateRampUp", this.threadGroups[i].iterateRampUp || 10);
})
this.calculateChart(this.threadGroups[i]);
} else {
@@ -215,6 +269,9 @@ export default {
case RAMP_UP:
this.threadGroups[0].rampUpTime = d.value;
break;
+ case ITERATE_RAMP_UP:
+ this.threadGroups[0].iterateRampUp = d.value;
+ break;
case DURATION:
if (d.unit) {
this.threadGroups[0].duration = d.value;
@@ -231,9 +288,18 @@ export default {
case RPS_LIMIT_ENABLE:
this.threadGroups[0].rpsLimitEnable = d.value;
break;
+ case THREAD_TYPE:
+ this.threadGroups[0].threadType = d.value;
+ break;
+ case ITERATE_NUM:
+ this.threadGroups[0].iterateNum = d.value;
+ break;
default:
break;
}
+ this.$set(this.threadGroups[0], "threadType", this.threadGroups[0].threadType || 'DURATION');
+ this.$set(this.threadGroups[0], "iterateNum", this.threadGroups[0].iterateNum || 1);
+ this.$set(this.threadGroups[0], "iterateRampUp", this.threadGroups[0].iterateRampUp || 10);
this.calculateChart(this.threadGroups[0]);
}
}
@@ -461,7 +527,7 @@ export default {
for (let i = 0; i < this.threadGroups.length; i++) {
if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration
- || !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step) {
+ || !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step || !this.threadGroups[i].iterateNum) {
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
this.$emit('changeActive', '1');
return false;
@@ -488,6 +554,9 @@ export default {
{key: RPS_LIMIT, value: this.threadGroups[i].rpsLimit},
{key: RPS_LIMIT_ENABLE, value: this.threadGroups[i].rpsLimitEnable},
{key: HOLD, value: this.threadGroups[i].duration - this.threadGroups[i].rampUpTime},
+ {key: THREAD_TYPE, value: this.threadGroups[i].threadType},
+ {key: ITERATE_NUM, value: this.threadGroups[i].iterateNum},
+ {key: ITERATE_RAMP_UP, value: this.threadGroups[i].iterateRampUp},
]);
}
return result;
diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack
index 010ad7a5f0..7d43154a7c 160000
--- a/frontend/src/business/components/xpack
+++ b/frontend/src/business/components/xpack
@@ -1 +1 @@
-Subproject commit 010ad7a5f072a5e9d368c756a2473bbd20781433
+Subproject commit 7d43154a7c19732407a8e9ace8a7d1ea13c91f36
diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js
index d53f7e78ab..4629e2c072 100644
--- a/frontend/src/i18n/en-US.js
+++ b/frontend/src/i18n/en-US.js
@@ -438,6 +438,10 @@ export default {
input_rps_limit: 'Please enter a limit',
ramp_up_time_within: 'In',
ramp_up_time_minutes: 'seconds, separate',
+ ramp_up_time_seconds: 'seconds add concurrent users',
+ iterate_num: 'Iterations: ',
+ by_iteration: 'By iterations',
+ by_duration: 'By duration',
ramp_up_time_times: 'add concurrent users',
advanced_config_error: 'Advanced configuration verification failed',
domain_bind: 'Domain bind',
diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js
index 3ae529230a..b91cd2e0c4 100644
--- a/frontend/src/i18n/zh-CN.js
+++ b/frontend/src/i18n/zh-CN.js
@@ -434,6 +434,10 @@ export default {
input_rps_limit: '请输入限制',
ramp_up_time_within: '在',
ramp_up_time_minutes: '秒内,分',
+ ramp_up_time_seconds: '秒内增加并发用户',
+ iterate_num: '迭代次数 (次): ',
+ by_iteration: '按迭代次数',
+ by_duration: '按持续时间',
ramp_up_time_times: '次增加并发用户',
advanced_config_error: '高级配置校验失败',
domain_bind: '域名绑定',
diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js
index 6e5e37511b..3007e1de51 100644
--- a/frontend/src/i18n/zh-TW.js
+++ b/frontend/src/i18n/zh-TW.js
@@ -434,6 +434,10 @@ export default {
input_rps_limit: '請輸入限制',
ramp_up_time_within: '在',
ramp_up_time_minutes: '秒內,分',
+ ramp_up_time_seconds: '秒內增加並發用戶',
+ iterate_num: '迭代次數 (次): ',
+ by_iteration: '按迭代次數',
+ by_duration: '按壓測時長',
ramp_up_time_times: '次增加並發用戶',
advanced_config_error: '高級配置校驗失敗',
domain_bind: '域名綁定',