diff --git a/backend/src/main/java/io/metersphere/commons/utils/MsJMeterUtils.java b/backend/src/main/java/io/metersphere/commons/utils/MsJMeterUtils.java
new file mode 100644
index 0000000000..85a9b638f4
--- /dev/null
+++ b/backend/src/main/java/io/metersphere/commons/utils/MsJMeterUtils.java
@@ -0,0 +1,44 @@
+package io.metersphere.commons.utils;
+
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.util.JOrphanUtils;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+public class MsJMeterUtils {
+ /**
+ * Load the JMeter properties file; if not found, then
+ * default to "org/apache/jmeter/jmeter.properties" from the classpath
+ *
+ *
+ * c.f. loadProperties
+ *
+ * @param file Name of the file from which the JMeter properties should be loaded
+ */
+ public static void loadJMeterProperties(String file) {
+ InputStream is = null;
+ try {
+ JMeterUtils.loadJMeterProperties(file);
+ } catch (Exception e) {
+ try {
+ Properties p = new Properties(System.getProperties());
+ // In jar file classpath is
+ is = ClassLoader.getSystemResourceAsStream(
+ "BOOT-INF/classes/org/apache/jmeter/jmeter.properties"); // $NON-NLS-1$
+ if (is == null) {
+ throw new RuntimeException("Could not read JMeter properties file:" + file);
+ }
+ p.load(is);
+
+ FieldUtils.writeStaticField(JMeterUtils.class, "appProperties", p, true);
+ } catch (Exception ex) {
+ throw new RuntimeException("Could not read JMeter properties file:" + file);
+ }
+ } finally {
+ JOrphanUtils.closeQuietly(is);
+ }
+ }
+
+}
diff --git a/backend/src/main/java/io/metersphere/report/JtlResolver.java b/backend/src/main/java/io/metersphere/report/JtlResolver.java
index 87082b87c5..e4b79a1c79 100644
--- a/backend/src/main/java/io/metersphere/report/JtlResolver.java
+++ b/backend/src/main/java/io/metersphere/report/JtlResolver.java
@@ -3,7 +3,7 @@ package io.metersphere.report;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
-import io.metersphere.base.domain.LoadTestReportWithBLOBs;
+import io.metersphere.commons.utils.MsJMeterUtils;
import io.metersphere.report.base.*;
import io.metersphere.report.dto.ErrorsTop5DTO;
import io.metersphere.report.dto.RequestStatisticsDTO;
@@ -11,15 +11,11 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.report.core.Sample;
import org.apache.jmeter.report.core.SampleMetadata;
import org.apache.jmeter.report.dashboard.JsonizerVisitor;
-import org.apache.jmeter.report.processor.ListResultData;
-import org.apache.jmeter.report.processor.MapResultData;
-import org.apache.jmeter.report.processor.ResultData;
-import org.apache.jmeter.report.processor.SampleContext;
+import org.apache.jmeter.report.processor.*;
import org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer;
import org.apache.jmeter.report.processor.graph.impl.ActiveThreadsGraphConsumer;
import org.apache.jmeter.report.processor.graph.impl.HitsPerSecondGraphConsumer;
import org.apache.jmeter.report.processor.graph.impl.ResponseTimeOverTimeGraphConsumer;
-import org.apache.jmeter.util.JMeterUtils;
import java.io.Reader;
import java.io.StringReader;
@@ -346,61 +342,6 @@ public class JtlResolver {
return testOverview;
}
-
-// public static List getLoadChartData(String jtlString) {
-// List chartsDataList = new ArrayList<>();
-// List totalMetricList = JtlResolver.resolver(jtlString);
-//
-// if (totalMetricList != null) {
-// for (Metric metric : totalMetricList) {
-// metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp()));
-// }
-// }
-// Map> collect = Objects.requireNonNull(totalMetricList).stream().collect(Collectors.groupingBy(Metric::getTimestamp));
-// List>> entries = new ArrayList<>(collect.entrySet());
-//
-// for (Map.Entry> entry : entries) {
-// int failSize = 0;
-// List metrics = entry.getValue();
-// Map> metricsMap = metrics.stream().collect(Collectors.groupingBy(Metric::getThreadName));
-// int maxUsers = metricsMap.size();
-// for (Metric metric : metrics) {
-// String isSuccess = metric.getSuccess();
-// if (!"true".equals(isSuccess)) {
-// failSize++;
-// }
-// }
-//
-// String timeStamp = "";
-// try {
-// timeStamp = formatDate(entry.getKey());
-// } catch (ParseException e) {
-// e.printStackTrace();
-// }
-//
-// ChartsData chartsData = new ChartsData();
-// chartsData.setxAxis(timeStamp);
-// chartsData.setGroupName("hits");
-// chartsData.setyAxis(new BigDecimal(metrics.size() * 1.0 / maxUsers));
-// chartsDataList.add(chartsData);
-//
-// chartsData = new ChartsData();
-// chartsData.setxAxis(timeStamp);
-// chartsData.setGroupName("users");
-// chartsData.setyAxis(new BigDecimal(maxUsers));
-// chartsDataList.add(chartsData);
-//
-// chartsData = new ChartsData();
-// chartsData.setxAxis(timeStamp);
-// chartsData.setGroupName("errors");
-// chartsData.setyAxis(new BigDecimal(failSize));
-// chartsDataList.add(chartsData);
-//
-// }
-//
-// return chartsDataList;
-// }
-
public static List getLoadChartData(String jtlString) {
Map activeThreadMap = getResultDataMap(jtlString, new ActiveThreadsGraphConsumer());
Map hitsMap = getResultDataMap(jtlString, new HitsPerSecondGraphConsumer());
@@ -412,45 +353,6 @@ public class JtlResolver {
return activeThreadList;
}
- // public static List getResponseTimeChartData(String jtlString) {
-// List chartsDataList = new ArrayList<>();
-// List totalMetricList = JtlResolver.resolver(jtlString);
-//
-// totalMetricList.forEach(metric -> {
-// metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp()));
-// });
-//
-// Map> metricMap = totalMetricList.stream().collect(Collectors.groupingBy(Metric::getTimestamp));
-// List>> entries = new ArrayList<>(metricMap.entrySet());
-//
-// for (Map.Entry> entry : entries) {
-// List metricList = entry.getValue();
-// Map> metricsMap = metricList.stream().collect(Collectors.groupingBy(Metric::getThreadName));
-// int maxUsers = metricsMap.size();
-// int sumElapsedTime = metricList.stream().mapToInt(metric -> Integer.parseInt(metric.getElapsed())).sum();
-// String timeStamp = "";
-// try {
-// timeStamp = formatDate(entry.getKey());
-// } catch (ParseException e) {
-// e.printStackTrace();
-// }
-//
-// ChartsData chartsData = new ChartsData();
-// chartsData.setxAxis(timeStamp);
-// chartsData.setGroupName("users");
-// chartsData.setyAxis(new BigDecimal(maxUsers));
-// chartsDataList.add(chartsData);
-//
-// ChartsData chartsData2 = new ChartsData();
-// chartsData2.setxAxis(timeStamp);
-// chartsData2.setGroupName("responseTime");
-// chartsData2.setyAxis(new BigDecimal(sumElapsedTime * 1.0 / metricList.size()));
-// chartsDataList.add(chartsData2);
-//
-// }
-//
-// return chartsDataList;
-// }
public static List getResponseTimeChartData(String jtlString) {
Map activeThreadMap = getResultDataMap(jtlString, new ActiveThreadsGraphConsumer());
Map responseTimeMap = getResultDataMap(jtlString, new ResponseTimeOverTimeGraphConsumer());
@@ -464,44 +366,56 @@ public class JtlResolver {
}
public static void mapResolver(Map map, List list, String seriesName) {
+ // ThreadGroup-1
for (String key : map.keySet()) {
MapResultData mapResultData = (MapResultData) map.get(key);
ResultData maxY = mapResultData.getResult("maxY");
ListResultData series = (ListResultData) mapResultData.getResult("series");
if (series.getSize() > 0) {
- MapResultData resultData = (MapResultData) series.get(0);
- ListResultData data = (ListResultData) resultData.getResult("data");
- if (data.getSize() > 0) {
- for (int i = 0; i < data.getSize(); i++) {
- ListResultData listResultData = (ListResultData) data.get(i);
- String result = listResultData.accept(new JsonizerVisitor());
- result = result.substring(1, result.length() - 1);
- String[] split = result.split(",");
- ChartsData chartsData = new ChartsData();
- BigDecimal bigDecimal = new BigDecimal(split[0]);
- String timeStamp = bigDecimal.toPlainString();
- String time = null;
- try {
- time = formatDate(stampToDate(DATE_TIME_PATTERN, timeStamp));
- } catch (ParseException e) {
- e.printStackTrace();
+ for (int j = 0; j < series.getSize(); j++) {
+ MapResultData resultData = (MapResultData) series.get(j);
+ // data, isOverall, label, isController
+ ListResultData data = (ListResultData) resultData.getResult("data");
+ ValueResultData label = (ValueResultData) resultData.getResult("label");
+
+ if (data.getSize() > 0) {
+ for (int i = 0; i < data.getSize(); i++) {
+ ListResultData listResultData = (ListResultData) data.get(i);
+ String result = listResultData.accept(new JsonizerVisitor());
+ result = result.substring(1, result.length() - 1);
+ String[] split = result.split(",");
+ ChartsData chartsData = new ChartsData();
+ BigDecimal bigDecimal = new BigDecimal(split[0]);
+ String timeStamp = bigDecimal.toPlainString();
+ String time = null;
+ try {
+ time = formatDate(stampToDate(DATE_TIME_PATTERN, timeStamp));
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ chartsData.setxAxis(time);
+ chartsData.setyAxis(new BigDecimal(split[1].trim()));
+ if (series.getSize() == 1) {
+ chartsData.setGroupName(seriesName);
+ } else {
+ chartsData.setGroupName((String) label.getValue());
+ }
+ list.add(chartsData);
}
- chartsData.setxAxis(time);
- chartsData.setyAxis(new BigDecimal(split[1].trim()));
- chartsData.setGroupName(seriesName);
- list.add(chartsData);
}
}
+
}
}
}
public static Map getResultDataMap(String jtlString, AbstractOverTimeGraphConsumer timeGraphConsumer) {
+ int row = 0;
AbstractOverTimeGraphConsumer abstractOverTimeGraphConsumer = timeGraphConsumer;
abstractOverTimeGraphConsumer.setGranularity(60000);
- // 这个路径不存在
- JMeterUtils.loadJMeterProperties("jmeter.properties");
+ // 使用反射获取properties
+ MsJMeterUtils.loadJMeterProperties("jmeter.properties"); // 这个路径不存在
SampleMetadata sampleMetaData = createTestMetaData();
SampleContext sampleContext = new SampleContext();
abstractOverTimeGraphConsumer.setSampleContext(sampleContext);
@@ -513,7 +427,7 @@ public class JtlResolver {
while (tokenizer.hasMoreTokens()) {
String line = tokenizer.nextToken();
String[] data = line.split(",", -1);
- Sample sample = new Sample(0, sampleMetaData, data);
+ Sample sample = new Sample(row++, sampleMetaData, data);
abstractOverTimeGraphConsumer.consume(sample, 0);
}
abstractOverTimeGraphConsumer.stopConsuming();
diff --git a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java
index 850b1eafcf..ef3ce7006f 100644
--- a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java
+++ b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java
@@ -93,8 +93,6 @@ public class PerformanceTestService {
loadTest.setProjectId(request.getProjectId());
loadTest.setCreateTime(System.currentTimeMillis());
loadTest.setUpdateTime(System.currentTimeMillis());
- loadTest.setScenarioDefinition("todo");
- loadTest.setDescription("todo");
loadTest.setTestResourcePoolId(request.getTestResourcePoolId());
loadTest.setLoadConfiguration(request.getLoadConfiguration());
loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration());
@@ -133,6 +131,14 @@ public class PerformanceTestService {
}
public String edit(EditTestPlanRequest request, List files) {
+ //
+ LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
+ if (loadTest == null) {
+ MSException.throwException(Translator.get("edit_load_test_not_found") + request.getId());
+ }
+ if (StringUtils.containsAny(loadTest.getStatus(), PerformanceTestStatus.Running.name(), PerformanceTestStatus.Starting.name())) {
+ MSException.throwException(Translator.get("cannot_edit_load_test_running"));
+ }
// 新选择了一个文件,删除原来的文件
List updatedFiles = request.getUpdatedFileList();
List originFiles = fileService.getFileMetadataByTestId(request.getId());
@@ -152,22 +158,14 @@ public class PerformanceTestService {
});
}
- final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
- if (loadTest == null) {
- MSException.throwException(Translator.get("edit_load_test_not_found") + request.getId());
- } else {
- loadTest.setName(request.getName());
- loadTest.setProjectId(request.getProjectId());
- loadTest.setUpdateTime(System.currentTimeMillis());
- loadTest.setScenarioDefinition("todo");
- loadTest.setDescription("todo");
- loadTest.setLoadConfiguration(request.getLoadConfiguration());
- loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration());
- loadTest.setTestResourcePoolId(request.getTestResourcePoolId());
- // todo 修改 load_test 的时候排除状态,这里存在修改了 Running 的测试状态的风险
-// loadTest.setStatus(PerformanceTestStatus.Saved.name());
- loadTestMapper.updateByPrimaryKeySelective(loadTest);
- }
+ loadTest.setName(request.getName());
+ loadTest.setProjectId(request.getProjectId());
+ loadTest.setUpdateTime(System.currentTimeMillis());
+ loadTest.setLoadConfiguration(request.getLoadConfiguration());
+ loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration());
+ loadTest.setTestResourcePoolId(request.getTestResourcePoolId());
+ loadTest.setStatus(PerformanceTestStatus.Saved.name());
+ loadTestMapper.updateByPrimaryKeySelective(loadTest);
return request.getId();
}
diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties
index 634a2c073f..22315563d4 100644
--- a/backend/src/main/resources/i18n/messages_en_US.properties
+++ b/backend/src/main/resources/i18n/messages_en_US.properties
@@ -19,3 +19,4 @@ duplicate_node_ip=Duplicate IPs
only_one_k8s=Only one K8s can be added
organization_id_is_null=Organization ID cannot be null
max_thread_insufficient=The number of concurrent users exceeds
+cannot_edit_load_test_running=Cannot modify the running test
\ No newline at end of file
diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties
index 6a6daf57a4..bc77486217 100644
--- a/backend/src/main/resources/i18n/messages_zh_CN.properties
+++ b/backend/src/main/resources/i18n/messages_zh_CN.properties
@@ -18,4 +18,5 @@ no_nodes_message=\u6CA1\u6709\u8282\u70B9\u4FE1\u606F
duplicate_node_ip=\u8282\u70B9 IP \u91CD\u590D
only_one_k8s=\u53EA\u80FD\u6DFB\u52A0\u4E00\u4E2A K8s
organization_id_is_null=\u7EC4\u7EC7 ID \u4E0D\u80FD\u4E3A\u7A7A
-max_thread_insufficient=\u5E76\u53D1\u7528\u6237\u6570\u8D85\u989D
\ No newline at end of file
+max_thread_insufficient=\u5E76\u53D1\u7528\u6237\u6570\u8D85\u989D
+cannot_edit_load_test_running=不能修改正在运行的测试
\ No newline at end of file
diff --git a/frontend/src/business/components/api/test/ApiScenarioConfig.vue b/frontend/src/business/components/api/test/ApiScenarioConfig.vue
index 193d2f7926..fd46816c96 100644
--- a/frontend/src/business/components/api/test/ApiScenarioConfig.vue
+++ b/frontend/src/business/components/api/test/ApiScenarioConfig.vue
@@ -26,13 +26,14 @@
-
+
-
-
+
+
@@ -49,6 +50,7 @@
import MsApiRequest from "./components/ApiRequest";
import MsApiRequestForm from "./components/ApiRequestForm";
import MsApiScenarioForm from "./components/ApiScenarioForm";
+ import {Scenario, Request} from "./model/APIModel";
export default {
name: "MsApiScenarioConfig",
@@ -64,6 +66,17 @@
},
methods: {
+ createScenario: function () {
+ let scenario = new Scenario({name: "Scenario"});
+ this.scenarios.push(scenario);
+ },
+ deleteScenario: function (index) {
+ this.scenarios.splice(index, 1);
+ if (this.scenarios.length === 0) {
+ this.createScenario();
+ this.select(this.scenarios[0]);
+ }
+ },
handleChange: function (index) {
this.select(this.scenarios[index]);
},
@@ -74,35 +87,23 @@
break;
}
},
- createScenario: function () {
- return {
- type: "Scenario",
- name: "Scenario",
- address: "",
- file: "",
- variables: [],
- headers: [],
- requests: []
- }
- },
- deleteScenario: function (index) {
- this.scenarios.splice(index, 1);
- if (this.scenarios.length === 0) {
- this.create();
- }
- },
- create: function () {
- let scenario = this.createScenario();
- this.scenarios.push(scenario);
- },
select: function (obj) {
this.selected = obj;
}
},
+ computed: {
+ isScenario() {
+ return this.selected instanceof Scenario;
+ },
+ isRequest() {
+ return this.selected instanceof Request;
+ }
+ },
+
created() {
if (this.scenarios.length === 0) {
- this.create();
+ this.createScenario();
this.select(this.scenarios[0]);
}
}
@@ -137,6 +138,9 @@
}
.scenario-name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
font-size: 14px;
width: 100%;
}
diff --git a/frontend/src/business/components/api/test/components/ApiAssertionRegex.vue b/frontend/src/business/components/api/test/components/ApiAssertionRegex.vue
new file mode 100644
index 0000000000..5dd59c026b
--- /dev/null
+++ b/frontend/src/business/components/api/test/components/ApiAssertionRegex.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue b/frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue
new file mode 100644
index 0000000000..d3f3e505cf
--- /dev/null
+++ b/frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/api/test/components/ApiAssertionText.vue b/frontend/src/business/components/api/test/components/ApiAssertionText.vue
new file mode 100644
index 0000000000..5f807c5e68
--- /dev/null
+++ b/frontend/src/business/components/api/test/components/ApiAssertionText.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/api/test/components/ApiAssertions.vue b/frontend/src/business/components/api/test/components/ApiAssertions.vue
new file mode 100644
index 0000000000..10c46cc7f7
--- /dev/null
+++ b/frontend/src/business/components/api/test/components/ApiAssertions.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue b/frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue
new file mode 100644
index 0000000000..f07eefcdae
--- /dev/null
+++ b/frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue
@@ -0,0 +1,57 @@
+
+
+
+
+ {{$t("api_test.request.assertions.regex")}}
+
+
+
+
+
+
+
+
+ {{$t("api_test.request.assertions.response_time")}}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/business/components/api/test/components/ApiBody.vue b/frontend/src/business/components/api/test/components/ApiBody.vue
index a6074a6e65..4869609197 100644
--- a/frontend/src/business/components/api/test/components/ApiBody.vue
+++ b/frontend/src/business/components/api/test/components/ApiBody.vue
@@ -1,15 +1,15 @@
-
+
{{$t('api_test.request.body_kv')}}
-
+
{{$t('api_test.request.body_text')}}
-
+
@@ -18,23 +18,26 @@
diff --git a/frontend/src/business/components/api/test/model/APIModel.js b/frontend/src/business/components/api/test/model/APIModel.js
new file mode 100644
index 0000000000..c355bfd101
--- /dev/null
+++ b/frontend/src/business/components/api/test/model/APIModel.js
@@ -0,0 +1,123 @@
+import {generateId} from "element-ui/src/utils/util";
+
+const assign = function (obj, options) {
+ for (let name in options) {
+ if (options.hasOwnProperty(name)) {
+ obj[name] = options[name];
+ }
+ }
+}
+
+export const BODY_TYPE = {
+ KV: "KV",
+ TEXT: "TEXT"
+}
+
+export const ASSERTION_TYPE = {
+ TEXT: "TEXT",
+ REGEX: "REGEX",
+ RESPONSE_TIME: "RESPONSE_TIME"
+}
+
+export class Scenario {
+ constructor(options) {
+ this.name = null;
+ this.url = null;
+ this.variables = [];
+ this.headers = [];
+ this.requests = [];
+
+ assign(this, options);
+ }
+}
+
+export class Request {
+ constructor(options) {
+ this.randomId = generateId();
+ this.name = null;
+ this.url = null;
+ this.method = null;
+ this.parameters = [];
+ this.headers = [];
+ this.body = new Body();
+ this.assertions = new Assertions();
+ this.extract = [];
+
+ assign(this, options);
+ }
+}
+
+export class Body {
+ constructor(options) {
+ this.type = null;
+ this.text = null;
+ this.kvs = [];
+
+ assign(this, options);
+ }
+
+ isKV() {
+ return this.type === BODY_TYPE.KV;
+ }
+}
+
+export class KeyValue {
+ constructor(options) {
+ this.key = null;
+ this.value = null;
+
+ assign(this, options);
+ }
+}
+
+export class Assertions {
+ constructor(options) {
+ this.text = [];
+ this.regex = [];
+ this.responseTime = new ResponseTime();
+
+ assign(this, options);
+ }
+}
+
+class AssertionType {
+ constructor(type) {
+ this.type = type;
+ }
+}
+
+export class Text extends AssertionType {
+ constructor(options) {
+ super(ASSERTION_TYPE.TEXT);
+ this.subject = null;
+ this.condition = null;
+ this.value = null;
+
+ assign(this, options);
+ }
+}
+
+export class Regex extends AssertionType {
+ constructor(options) {
+ super(ASSERTION_TYPE.REGEX);
+ this.subject = null;
+ this.expression = null;
+ this.description = null;
+
+ assign(this, options);
+ }
+}
+
+export class ResponseTime extends AssertionType {
+ constructor(options) {
+ super(ASSERTION_TYPE.RESPONSE_TIME);
+ this.responseInTime = null;
+
+ assign(this, options);
+ }
+
+ isValid() {
+ return this.responseInTime !== null && this.responseInTime > 0;
+ }
+}
+
diff --git a/frontend/src/business/components/performance/report/components/TestOverview.vue b/frontend/src/business/components/performance/report/components/TestOverview.vue
index 13b87c1cfb..8a0654f6e7 100644
--- a/frontend/src/business/components/performance/report/components/TestOverview.vue
+++ b/frontend/src/business/components/performance/report/components/TestOverview.vue
@@ -157,7 +157,8 @@
this.$get("/performance/report/content/res_chart/" + this.id, res => {
let data = res.data;
let userList = data.filter(m => m.groupName === "users").map(m => m.yAxis);
- let responseTimeList = data.filter(m => m.groupName === "responseTime").map(m => m.yAxis);
+ let responseTimeList = data.filter(m => m.groupName != "users").map(m => m.yAxis);
+ let responseGroupNameList = data.filter(m => m.groupName != "users").map(m => m.groupName);
let userMax = this._getChartMax(userList);
let resMax = this._getChartMax(responseTimeList);
let resOption = {
@@ -171,7 +172,24 @@
},
tooltip: {
show: true,
- trigger: 'axis'
+ trigger: 'axis',
+ extraCssText: 'z-index: 999;',
+ formatter: function (params, ticket, callback) {
+ let result = "";
+ let name = params[0].name;
+ result += name + "
";
+ for (let i = 0; i < params.length; i++) {
+ let seriesName = params[i].seriesName;
+ if (seriesName.length > 100) {
+ seriesName = seriesName.substring(0, 100);
+ }
+ let value = params[i].value;
+ let marker = params[i].marker;
+ result += marker + seriesName + ": " + value[1] + "
";
+ }
+
+ return result;
+ }
},
legend: {},
xAxis: {},
@@ -197,14 +215,12 @@
{
name: 'users',
color: '#0CA74A',
- },
- {
- name: "responseTime",
- yAxisIndex: '1',
- color: '#99743C',
}
]
}
+ responseGroupNameList.forEach(item => {
+ setting["series"].splice(0, 0, {name: item, yAxisIndex: '1'})
+ })
this.resOption = this.generateOption(resOption, data, setting);
})
},
@@ -229,10 +245,11 @@
legend.push(name)
series[name] = []
}
- series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, Math.round(item.yAxis.toFixed(2))]);
+ series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]);
})
this.$set(option.legend, "data", legend);
- this.$set(option.legend, "bottom", 10);
+ this.$set(option.legend, "type", "scroll");
+ this.$set(option.legend, "bottom", "10px");
this.$set(option.xAxis, "data", xAxis);
for (let name in series) {
let d = series[name];
@@ -259,9 +276,6 @@
_getChartMax(arr) {
const max = Math.max(...arr);
return Math.ceil(max / 4.5) * 5;
- },
- _arraySort(a, b) {
- return a[0] > b[0];
}
},
watch: {
diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js
index 31a068f99a..5cfd619ca2 100644
--- a/frontend/src/i18n/zh-CN.js
+++ b/frontend/src/i18n/zh-CN.js
@@ -45,7 +45,8 @@ export default {
'refresh': '刷新',
'remark': '备注',
'delete': '删除',
- 'not_filled': '未填写'
+ 'not_filled': '未填写',
+ 'please_select': '请选择',
},
workspace: {
'create': '创建工作空间',
@@ -169,25 +170,43 @@ export default {
},
api_test: {
scenario: {
+ input_name: "请输入场景名称",
name: "场景名称",
base_url: "基础URL",
- base_url_describe: "基础URL作为所有请求的URL前缀",
+ base_url_description: "基础URL作为所有请求的URL前缀",
variables: "变量",
headers: "请求头"
},
request: {
+ input_name: "请输入请求名称",
name: "请求名称",
method: "请求方法",
url: "请求URL",
- url_describe: "例如: https://fit2cloud.com",
+ url_description: "例如: https://fit2cloud.com",
parameters: "请求参数",
parameters_desc: "参数追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
headers: "请求头",
body: "请求内容",
body_kv: "键值对",
body_text: "文本",
- assertions: "断言",
- extract: "提取"
+ assertions: {
+ label: "断言",
+ text: "文本",
+ regex: "正则",
+ response_time: "响应时间",
+ select_type: "请选择类型",
+ select_subject: "请选择对象",
+ select_contains: "请选择条件",
+ contains: "包含",
+ not_contains: "不包含",
+ equals: "等于",
+ start_with: "以...开始",
+ end_with: "以...结束",
+ value: "值",
+ expression: "正则表达式",
+ response_in_time: "响应时间在...毫秒以内",
+ },
+ extract: "提取",
}
},
test_track: {