Merge branch 'master' into local-api-delimit
# Conflicts: # frontend/package.json
This commit is contained in:
commit
5005cbdd85
|
@ -20,6 +20,7 @@ MeterSphere 是一站式的开源企业级持续测试平台,涵盖测试跟
|
||||||
|
|
||||||
![产品定位](https://metersphere.io/images/icon/ct-devops.png)
|
![产品定位](https://metersphere.io/images/icon/ct-devops.png)
|
||||||
|
|
||||||
|
|
||||||
> 如需进一步了解 MeterSphere 开源项目,推荐阅读 [MeterSphere 的初心和使命](https://mp.weixin.qq.com/s/DpCt3BNgBTlV3sJ5qtPmZw)
|
> 如需进一步了解 MeterSphere 开源项目,推荐阅读 [MeterSphere 的初心和使命](https://mp.weixin.qq.com/s/DpCt3BNgBTlV3sJ5qtPmZw)
|
||||||
|
|
||||||
## 在线体验
|
## 在线体验
|
||||||
|
|
|
@ -39,10 +39,12 @@ public class APIReportController {
|
||||||
return apiReportService.recentTest(request);
|
return apiReportService.recentTest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/list/{testId}")
|
@GetMapping("/list/{testId}/{goPage}/{pageSize}")
|
||||||
public List<APIReportResult> listByTestId(@PathVariable String testId) {
|
public Pager<List<APIReportResult>> listByTestId(@PathVariable String testId, @PathVariable int goPage, @PathVariable int pageSize) {
|
||||||
checkOwnerService.checkApiTestOwner(testId);
|
checkOwnerService.checkApiTestOwner(testId);
|
||||||
return apiReportService.listByTestId(testId);
|
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||||
|
return PageUtils.setPageInfo(page, apiReportService.listByTestId(testId));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/list/{goPage}/{pageSize}")
|
@PostMapping("/list/{goPage}/{pageSize}")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.metersphere.api.dto.scenario;
|
package io.metersphere.api.dto.scenario;
|
||||||
|
|
||||||
|
import io.metersphere.api.dto.scenario.assertions.Assertions;
|
||||||
import io.metersphere.api.dto.scenario.request.Request;
|
import io.metersphere.api.dto.scenario.request.Request;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ public class Scenario {
|
||||||
private List<KeyValue> variables;
|
private List<KeyValue> variables;
|
||||||
private List<KeyValue> headers;
|
private List<KeyValue> headers;
|
||||||
private List<Request> requests;
|
private List<Request> requests;
|
||||||
|
private Assertions assertions;
|
||||||
private DubboConfig dubboConfig;
|
private DubboConfig dubboConfig;
|
||||||
private TCPConfig tcpConfig;
|
private TCPConfig tcpConfig;
|
||||||
private List<DatabaseConfig> databaseConfigs;
|
private List<DatabaseConfig> databaseConfigs;
|
||||||
|
|
|
@ -104,6 +104,12 @@ public class PerformanceTestController {
|
||||||
return performanceTestService.getLoadConfiguration(testId);
|
return performanceTestService.getLoadConfiguration(testId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get-jmx-content/{testId}")
|
||||||
|
public String getJmxContent(@PathVariable String testId) {
|
||||||
|
checkOwnerService.checkPerformanceTestOwner(testId);
|
||||||
|
return performanceTestService.getJmxContent(testId);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/delete")
|
@PostMapping("/delete")
|
||||||
public void delete(@RequestBody DeleteTestPlanRequest request) {
|
public void delete(@RequestBody DeleteTestPlanRequest request) {
|
||||||
checkOwnerService.checkPerformanceTestOwner(request.getId());
|
checkOwnerService.checkPerformanceTestOwner(request.getId());
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class AbstractEngine implements Engine {
|
public abstract class AbstractEngine implements Engine {
|
||||||
|
@ -81,9 +82,22 @@ public abstract class AbstractEngine implements Engine {
|
||||||
String loadConfiguration = t.getLoadConfiguration();
|
String loadConfiguration = t.getLoadConfiguration();
|
||||||
JSONArray jsonArray = JSON.parseArray(loadConfiguration);
|
JSONArray jsonArray = JSON.parseArray(loadConfiguration);
|
||||||
for (int i = 0; i < jsonArray.size(); i++) {
|
for (int i = 0; i < jsonArray.size(); i++) {
|
||||||
JSONObject o = jsonArray.getJSONObject(i);
|
if (jsonArray.get(i) instanceof Map) {
|
||||||
if (StringUtils.equals(o.getString("key"), "TargetLevel")) {
|
JSONObject o = jsonArray.getJSONObject(i);
|
||||||
s = o.getInteger("value");
|
if (StringUtils.equals(o.getString("key"), "TargetLevel")) {
|
||||||
|
s = o.getInteger("value");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonArray.get(i) instanceof List) {
|
||||||
|
JSONArray o = jsonArray.getJSONArray(i);
|
||||||
|
for (int j = 0; j < o.size(); j++) {
|
||||||
|
JSONObject b = o.getJSONObject(j);
|
||||||
|
if (StringUtils.equals(b.getString("key"), "TargetLevel")) {
|
||||||
|
s += b.getInteger("value");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -10,7 +10,6 @@ public class EngineContext {
|
||||||
private String fileType;
|
private String fileType;
|
||||||
private String content;
|
private String content;
|
||||||
private String resourcePoolId;
|
private String resourcePoolId;
|
||||||
private Long threadNum;
|
|
||||||
private Long startTime;
|
private Long startTime;
|
||||||
private String reportId;
|
private String reportId;
|
||||||
private Integer resourceIndex;
|
private Integer resourceIndex;
|
||||||
|
@ -95,14 +94,6 @@ public class EngineContext {
|
||||||
this.resourcePoolId = resourcePoolId;
|
this.resourcePoolId = resourcePoolId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getThreadNum() {
|
|
||||||
return threadNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setThreadNum(Long threadNum) {
|
|
||||||
this.threadNum = threadNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getStartTime() {
|
public Long getStartTime() {
|
||||||
return startTime;
|
return startTime;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.metersphere.base.domain.TestResourcePool;
|
||||||
import io.metersphere.commons.constants.FileType;
|
import io.metersphere.commons.constants.FileType;
|
||||||
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
|
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
|
||||||
import io.metersphere.commons.exception.MSException;
|
import io.metersphere.commons.exception.MSException;
|
||||||
|
import io.metersphere.commons.utils.LogUtil;
|
||||||
import io.metersphere.config.KafkaProperties;
|
import io.metersphere.config.KafkaProperties;
|
||||||
import io.metersphere.i18n.Translator;
|
import io.metersphere.i18n.Translator;
|
||||||
import io.metersphere.performance.engine.docker.DockerTestEngine;
|
import io.metersphere.performance.engine.docker.DockerTestEngine;
|
||||||
|
@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -52,7 +54,7 @@ public class EngineFactory {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EngineContext createContext(LoadTestWithBLOBs loadTest, String resourceId, long threadNum, long startTime, String reportId, int resourceIndex) {
|
public static EngineContext createContext(LoadTestWithBLOBs loadTest, String resourceId, double ratio, long startTime, String reportId, int resourceIndex) {
|
||||||
final List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(loadTest.getId());
|
final List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(loadTest.getId());
|
||||||
if (org.springframework.util.CollectionUtils.isEmpty(fileMetadataList)) {
|
if (org.springframework.util.CollectionUtils.isEmpty(fileMetadataList)) {
|
||||||
MSException.throwException(Translator.get("run_load_test_file_not_found") + loadTest.getId());
|
MSException.throwException(Translator.get("run_load_test_file_not_found") + loadTest.getId());
|
||||||
|
@ -73,7 +75,6 @@ public class EngineFactory {
|
||||||
engineContext.setTestName(loadTest.getName());
|
engineContext.setTestName(loadTest.getName());
|
||||||
engineContext.setNamespace(loadTest.getProjectId());
|
engineContext.setNamespace(loadTest.getProjectId());
|
||||||
engineContext.setFileType(jmxFile.getType());
|
engineContext.setFileType(jmxFile.getType());
|
||||||
engineContext.setThreadNum(threadNum);
|
|
||||||
engineContext.setResourcePoolId(loadTest.getTestResourcePoolId());
|
engineContext.setResourcePoolId(loadTest.getTestResourcePoolId());
|
||||||
engineContext.setStartTime(startTime);
|
engineContext.setStartTime(startTime);
|
||||||
engineContext.setReportId(reportId);
|
engineContext.setReportId(reportId);
|
||||||
|
@ -90,8 +91,34 @@ public class EngineFactory {
|
||||||
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
|
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
|
||||||
|
|
||||||
for (int i = 0; i < jsonArray.size(); i++) {
|
for (int i = 0; i < jsonArray.size(); i++) {
|
||||||
final JSONObject jsonObject = jsonArray.getJSONObject(i);
|
if (jsonArray.get(i) instanceof Map) {
|
||||||
engineContext.addProperty(jsonObject.getString("key"), jsonObject.get("value"));
|
JSONObject o = jsonArray.getJSONObject(i);
|
||||||
|
String key = o.getString("key");
|
||||||
|
if ("TargetLevel".equals(key)) {
|
||||||
|
engineContext.addProperty(key, Math.round(((Integer) o.get("value")) * ratio));
|
||||||
|
} else {
|
||||||
|
engineContext.addProperty(key, o.get("value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonArray.get(i) instanceof List) {
|
||||||
|
JSONArray o = jsonArray.getJSONArray(i);
|
||||||
|
for (int j = 0; j < o.size(); j++) {
|
||||||
|
JSONObject b = o.getJSONObject(j);
|
||||||
|
String key = b.getString("key");
|
||||||
|
Object values = engineContext.getProperty(key);
|
||||||
|
if (values == null) {
|
||||||
|
values = new ArrayList<>();
|
||||||
|
}
|
||||||
|
if (values instanceof List) {
|
||||||
|
Object value = b.get("value");
|
||||||
|
if ("TargetLevel".equals(key)) {
|
||||||
|
value = Math.round(((Integer) b.get("value")) * ratio);
|
||||||
|
}
|
||||||
|
((List<Object>) values).add(value);
|
||||||
|
engineContext.addProperty(key, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -112,8 +139,10 @@ public class EngineFactory {
|
||||||
String content = engineSourceParser.parse(engineContext, source);
|
String content = engineSourceParser.parse(engineContext, source);
|
||||||
engineContext.setContent(content);
|
engineContext.setContent(content);
|
||||||
} catch (MSException e) {
|
} catch (MSException e) {
|
||||||
|
LogUtil.error(e);
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
LogUtil.error(e);
|
||||||
MSException.throwException(e);
|
MSException.throwException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.metersphere.base.domain.TestResource;
|
||||||
import io.metersphere.commons.constants.ResourceStatusEnum;
|
import io.metersphere.commons.constants.ResourceStatusEnum;
|
||||||
import io.metersphere.commons.exception.MSException;
|
import io.metersphere.commons.exception.MSException;
|
||||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||||
|
import io.metersphere.commons.utils.LogUtil;
|
||||||
import io.metersphere.controller.ResultHolder;
|
import io.metersphere.controller.ResultHolder;
|
||||||
import io.metersphere.dto.NodeDTO;
|
import io.metersphere.dto.NodeDTO;
|
||||||
import io.metersphere.i18n.Translator;
|
import io.metersphere.i18n.Translator;
|
||||||
|
@ -52,19 +53,21 @@ public class DockerTestEngine extends AbstractEngine {
|
||||||
|
|
||||||
for (int i = 0, size = resourceList.size(); i < size; i++) {
|
for (int i = 0, size = resourceList.size(); i < size; i++) {
|
||||||
int ratio = resourceRatio.get(i);
|
int ratio = resourceRatio.get(i);
|
||||||
double realThreadNum = ((double) ratio / totalThreadNum) * threadNum;
|
// double realThreadNum = ((double) ratio / totalThreadNum) * threadNum;
|
||||||
runTest(resourceList.get(i), Math.round(realThreadNum), i);
|
runTest(resourceList.get(i), ((double) ratio / totalThreadNum), i);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runTest(TestResource resource, long realThreadNum, int resourceIndex) {
|
private void runTest(TestResource resource, double ratio, int resourceIndex) {
|
||||||
EngineContext context = null;
|
EngineContext context = null;
|
||||||
try {
|
try {
|
||||||
context = EngineFactory.createContext(loadTest, resource.getId(), realThreadNum, this.getStartTime(), this.getReportId(), resourceIndex);
|
context = EngineFactory.createContext(loadTest, resource.getId(), ratio, this.getStartTime(), this.getReportId(), resourceIndex);
|
||||||
} catch (MSException e) {
|
} catch (MSException e) {
|
||||||
|
LogUtil.error(e);
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
LogUtil.error(e);
|
||||||
MSException.throwException(e);
|
MSException.throwException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +83,7 @@ public class DockerTestEngine extends AbstractEngine {
|
||||||
TestRequest testRequest = new TestRequest();
|
TestRequest testRequest = new TestRequest();
|
||||||
testRequest.setSize(1);
|
testRequest.setSize(1);
|
||||||
testRequest.setTestId(testId);
|
testRequest.setTestId(testId);
|
||||||
|
testRequest.setReportId(getReportId());
|
||||||
testRequest.setFileString(content);
|
testRequest.setFileString(content);
|
||||||
testRequest.setImage(JMETER_IMAGE);
|
testRequest.setImage(JMETER_IMAGE);
|
||||||
testRequest.setTestData(context.getTestData());
|
testRequest.setTestData(context.getTestData());
|
||||||
|
|
|
@ -7,4 +7,5 @@ import lombok.Setter;
|
||||||
@Setter
|
@Setter
|
||||||
public class BaseRequest {
|
public class BaseRequest {
|
||||||
private String testId;
|
private String testId;
|
||||||
|
private String reportId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -776,15 +776,12 @@ public class JmeterDocumentParser implements DocumentParser {
|
||||||
elementProp.setAttribute("name", "ThreadGroup.main_controller");
|
elementProp.setAttribute("name", "ThreadGroup.main_controller");
|
||||||
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");
|
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");
|
||||||
threadGroup.appendChild(elementProp);
|
threadGroup.appendChild(elementProp);
|
||||||
// 持续时长
|
|
||||||
String duration = context.getProperty("duration").toString();
|
|
||||||
String rampUp = context.getProperty("RampUp").toString();
|
|
||||||
int realHold = Integer.parseInt(duration) - Integer.parseInt(rampUp);
|
|
||||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
|
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
|
||||||
threadGroup.appendChild(createStringProp(document, "TargetLevel", "2"));
|
threadGroup.appendChild(createStringProp(document, "TargetLevel", "2"));
|
||||||
threadGroup.appendChild(createStringProp(document, "RampUp", "12"));
|
threadGroup.appendChild(createStringProp(document, "RampUp", "12"));
|
||||||
threadGroup.appendChild(createStringProp(document, "Steps", "2"));
|
threadGroup.appendChild(createStringProp(document, "Steps", "2"));
|
||||||
threadGroup.appendChild(createStringProp(document, "Hold", String.valueOf(realHold)));
|
threadGroup.appendChild(createStringProp(document, "Hold", "1"));
|
||||||
threadGroup.appendChild(createStringProp(document, "LogFilename", ""));
|
threadGroup.appendChild(createStringProp(document, "LogFilename", ""));
|
||||||
// bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空
|
// bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空
|
||||||
// threadGroup.appendChild(createStringProp(document, "Iterations", "1"));
|
// threadGroup.appendChild(createStringProp(document, "Iterations", "1"));
|
||||||
|
@ -803,9 +800,18 @@ public class JmeterDocumentParser implements DocumentParser {
|
||||||
</collectionProp>
|
</collectionProp>
|
||||||
</kg.apc.jmeter.timers.VariableThroughputTimer>
|
</kg.apc.jmeter.timers.VariableThroughputTimer>
|
||||||
*/
|
*/
|
||||||
if (context.getProperty("rpsLimitEnable") == null || StringUtils.equals(context.getProperty("rpsLimitEnable").toString(), "false")) {
|
if (context.getProperty("rpsLimitEnable") == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Object rpsLimitEnables = context.getProperty("rpsLimitEnable");
|
||||||
|
if (rpsLimitEnables instanceof List) {
|
||||||
|
Object o = ((List<?>) rpsLimitEnables).get(0);
|
||||||
|
((List<?>) rpsLimitEnables).remove(0);
|
||||||
|
if (o == null || "false".equals(o.toString())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Document document = element.getOwnerDocument();
|
Document document = element.getOwnerDocument();
|
||||||
|
|
||||||
|
|
||||||
|
@ -866,11 +872,6 @@ public class JmeterDocumentParser implements DocumentParser {
|
||||||
if (nodeNameEquals(ele, STRING_PROP)) {
|
if (nodeNameEquals(ele, STRING_PROP)) {
|
||||||
parseStringProp(ele);
|
parseStringProp(ele);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置具体的线程数
|
|
||||||
if (nodeNameEquals(ele, STRING_PROP) && "TargetLevel".equals(ele.getAttribute("name"))) {
|
|
||||||
ele.getFirstChild().setNodeValue(context.getThreadNum().toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -902,11 +903,28 @@ public class JmeterDocumentParser implements DocumentParser {
|
||||||
stringPropCount++;
|
stringPropCount++;
|
||||||
} else {
|
} else {
|
||||||
stringPropCount = 0;
|
stringPropCount = 0;
|
||||||
Integer duration = (Integer) context.getProperty("duration");// 传入的是分钟数, 需要转化成秒数
|
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;
|
||||||
|
}
|
||||||
prop.getFirstChild().setNodeValue(String.valueOf(duration * 60));
|
prop.getFirstChild().setNodeValue(String.valueOf(duration * 60));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
prop.getFirstChild().setNodeValue(context.getProperty("rpsLimit").toString());
|
Object rpsLimits = context.getProperty("rpsLimit");
|
||||||
|
String rpsLimit;
|
||||||
|
if (rpsLimits instanceof List) {
|
||||||
|
Object o = ((List<?>) rpsLimits).get(0);
|
||||||
|
((List<?>) rpsLimits).remove(0);
|
||||||
|
rpsLimit = o.toString();
|
||||||
|
} else {
|
||||||
|
rpsLimit = rpsLimits.toString();
|
||||||
|
}
|
||||||
|
prop.getFirstChild().setNodeValue(rpsLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -920,8 +938,15 @@ public class JmeterDocumentParser implements DocumentParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseStringProp(Element stringProp) {
|
private void parseStringProp(Element stringProp) {
|
||||||
if (stringProp.getChildNodes().getLength() > 0 && context.getProperty(stringProp.getAttribute("name")) != null) {
|
Object threadParams = context.getProperty(stringProp.getAttribute("name"));
|
||||||
stringProp.getFirstChild().setNodeValue(context.getProperty(stringProp.getAttribute("name")).toString());
|
if (stringProp.getChildNodes().getLength() > 0 && threadParams != null) {
|
||||||
|
if (threadParams instanceof List) {
|
||||||
|
Object o = ((List<?>) threadParams).get(0);
|
||||||
|
((List<?>) threadParams).remove(0);
|
||||||
|
stringProp.getFirstChild().setNodeValue(o.toString());
|
||||||
|
} else {
|
||||||
|
stringProp.getFirstChild().setNodeValue(threadParams.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -345,6 +345,17 @@ public class PerformanceTestService {
|
||||||
return Optional.ofNullable(loadTestWithBLOBs).orElse(new LoadTestWithBLOBs()).getLoadConfiguration();
|
return Optional.ofNullable(loadTestWithBLOBs).orElse(new LoadTestWithBLOBs()).getLoadConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getJmxContent(String testId) {
|
||||||
|
List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(testId);
|
||||||
|
for (FileMetadata metadata : fileMetadataList) {
|
||||||
|
if (FileType.JMX.name().equals(metadata.getType())) {
|
||||||
|
FileContent fileContent = fileService.getFileContent(metadata.getId());
|
||||||
|
return new String(fileContent.getFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public List<LoadTestWithBLOBs> selectByTestResourcePoolId(String resourcePoolId) {
|
public List<LoadTestWithBLOBs> selectByTestResourcePoolId(String resourcePoolId) {
|
||||||
LoadTestExample example = new LoadTestExample();
|
LoadTestExample example = new LoadTestExample();
|
||||||
example.createCriteria().andTestResourcePoolIdEqualTo(resourcePoolId);
|
example.createCriteria().andTestResourcePoolIdEqualTo(resourcePoolId);
|
||||||
|
|
|
@ -45,6 +45,10 @@ public class CheckOwnerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkApiTestOwner(String testId) {
|
public void checkApiTestOwner(String testId) {
|
||||||
|
// 关联为其他时
|
||||||
|
if (StringUtils.equals("other", testId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
String workspaceId = SessionUtils.getCurrentWorkspaceId();
|
String workspaceId = SessionUtils.getCurrentWorkspaceId();
|
||||||
QueryAPITestRequest request = new QueryAPITestRequest();
|
QueryAPITestRequest request = new QueryAPITestRequest();
|
||||||
request.setWorkspaceId(workspaceId);
|
request.setWorkspaceId(workspaceId);
|
||||||
|
|
|
@ -191,17 +191,19 @@ public class TestCaseNodeService {
|
||||||
* 获取当前计划下
|
* 获取当前计划下
|
||||||
* 有关联数据的节点
|
* 有关联数据的节点
|
||||||
*
|
*
|
||||||
* @param planId
|
* @param planId plan id
|
||||||
* @return
|
* @return List<TestCaseNodeDTO>
|
||||||
*/
|
*/
|
||||||
public List<TestCaseNodeDTO> getNodeByPlanId(String planId) {
|
public List<TestCaseNodeDTO> getNodeByPlanId(String planId) {
|
||||||
|
|
||||||
List<TestCaseNodeDTO> list = new ArrayList<>();
|
List<TestCaseNodeDTO> list = new ArrayList<>();
|
||||||
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(planId);
|
List<String> projectIds = testPlanProjectService.getProjectIdsByPlanId(planId);
|
||||||
projectIds.forEach(id -> {
|
projectIds.forEach(id -> {
|
||||||
String name = projectMapper.selectByPrimaryKey(id).getName();
|
Project project = projectMapper.selectByPrimaryKey(id);
|
||||||
|
String name = project.getName();
|
||||||
List<TestCaseNodeDTO> nodeList = getNodeDTO(id, planId);
|
List<TestCaseNodeDTO> nodeList = getNodeDTO(id, planId);
|
||||||
TestCaseNodeDTO testCaseNodeDTO = new TestCaseNodeDTO();
|
TestCaseNodeDTO testCaseNodeDTO = new TestCaseNodeDTO();
|
||||||
|
testCaseNodeDTO.setId(project.getId());
|
||||||
testCaseNodeDTO.setName(name);
|
testCaseNodeDTO.setName(name);
|
||||||
testCaseNodeDTO.setLabel(name);
|
testCaseNodeDTO.setLabel(name);
|
||||||
testCaseNodeDTO.setChildren(nodeList);
|
testCaseNodeDTO.setChildren(nodeList);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
for file in ${TESTS_DIR}/*.jmx; do
|
for file in ${TESTS_DIR}/*.jmx; do
|
||||||
jmeter -n -t ${file} -Jserver.rmi.ssl.disable=${SSL_DISABLED}
|
jmeter -n -t ${file} -Jserver.rmi.ssl.disable=${SSL_DISABLED} -l ${TESTS_DIR}/${REPORT_ID}.jtl
|
||||||
done
|
done
|
||||||
|
|
|
@ -17,28 +17,29 @@
|
||||||
"@fortawesome/vue-fontawesome": "^0.1.9",
|
"@fortawesome/vue-fontawesome": "^0.1.9",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"core-js": "^3.4.3",
|
"core-js": "^3.4.3",
|
||||||
"diffable-html": "^4.0.0",
|
|
||||||
"echarts": "^4.6.0",
|
"echarts": "^4.6.0",
|
||||||
"el-table-infinite-scroll": "^1.0.10",
|
|
||||||
"element-ui": "^2.13.0",
|
"element-ui": "^2.13.0",
|
||||||
"html2canvas": "^1.0.0-rc.7",
|
|
||||||
"js-base64": "^3.4.4",
|
|
||||||
"json-bigint": "^1.0.0",
|
|
||||||
"jsoneditor": "^9.1.2",
|
|
||||||
"jspdf": "^2.1.1",
|
|
||||||
"md5": "^2.3.0",
|
|
||||||
"mockjs": "^1.1.0",
|
|
||||||
"nprogress": "^0.2.0",
|
|
||||||
"sha.js": "^2.4.11",
|
|
||||||
"vue": "^2.6.10",
|
"vue": "^2.6.10",
|
||||||
"vue-calendar-heatmap": "^0.8.4",
|
|
||||||
"vue-echarts": "^4.1.0",
|
"vue-echarts": "^4.1.0",
|
||||||
"vue-i18n": "^8.15.3",
|
"vue-i18n": "^8.15.3",
|
||||||
"vue-pdf": "^4.2.0",
|
|
||||||
"vue-router": "^3.1.3",
|
"vue-router": "^3.1.3",
|
||||||
"vuedraggable": "^2.23.2",
|
"vuedraggable": "^2.23.2",
|
||||||
"vuex": "^3.1.2",
|
"vuex": "^3.1.2",
|
||||||
"yan-progress": "^1.0.3"
|
"vue-calendar-heatmap": "^0.8.4",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"sha.js": "^2.4.11",
|
||||||
|
"js-base64": "^3.4.4",
|
||||||
|
"json-bigint": "^1.0.0",
|
||||||
|
"html2canvas": "^1.0.0-rc.7",
|
||||||
|
"jspdf": "^2.1.1",
|
||||||
|
"yan-progress": "^1.0.3",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"el-table-infinite-scroll": "^1.0.10",
|
||||||
|
"vue-pdf": "^4.2.0",
|
||||||
|
"diffable-html": "^4.0.0",
|
||||||
|
"xml-js": "^1.6.11",
|
||||||
|
"jsoneditor": "^9.1.2",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "^4.1.0",
|
"@vue/cli-plugin-babel": "^4.1.0",
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
|
@ -126,6 +126,7 @@ export default {
|
||||||
let data = response.data;
|
let data = response.data;
|
||||||
this.total = data.itemCount;
|
this.total = data.itemCount;
|
||||||
this.tableData = data.listObject;
|
this.tableData = data.listObject;
|
||||||
|
this.selectRows.clear();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleSelectionChange(val) {
|
handleSelectionChange(val) {
|
||||||
|
@ -171,28 +172,13 @@ export default {
|
||||||
this.$set(row, "showMore", true);
|
this.$set(row, "showMore", true);
|
||||||
this.selectRows.add(row);
|
this.selectRows.add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
let arr = Array.from(this.selectRows);
|
|
||||||
|
|
||||||
// 选中1个以上的用例时显示更多操作
|
|
||||||
if (this.selectRows.size === 1) {
|
|
||||||
this.$set(arr[0], "showMore", false);
|
|
||||||
} else if (this.selectRows.size === 2) {
|
|
||||||
arr.forEach(row => {
|
|
||||||
this.$set(row, "showMore", true);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleSelectAll(selection) {
|
handleSelectAll(selection) {
|
||||||
if (selection.length > 0) {
|
if (selection.length > 0) {
|
||||||
if (selection.length === 1) {
|
this.tableData.forEach(item => {
|
||||||
this.selectRows.add(selection[0]);
|
this.$set(item, "showMore", true);
|
||||||
} else {
|
this.selectRows.add(item);
|
||||||
this.tableData.forEach(item => {
|
});
|
||||||
this.$set(item, "showMore", true);
|
|
||||||
this.selectRows.add(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.selectRows.clear();
|
this.selectRows.clear();
|
||||||
this.tableData.forEach(row => {
|
this.tableData.forEach(row => {
|
||||||
|
|
|
@ -22,46 +22,57 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||||
|
:total="total"/>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsApiReportStatus from "../report/ApiReportStatus";
|
import MsApiReportStatus from "../report/ApiReportStatus";
|
||||||
|
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiReportDialog",
|
name: "MsApiReportDialog",
|
||||||
|
|
||||||
components: {MsApiReportStatus},
|
components: {MsApiReportStatus, MsTablePagination},
|
||||||
|
|
||||||
props: ["testId"],
|
props: ["testId"],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
reportVisible: false,
|
reportVisible: false,
|
||||||
result: {},
|
result: {},
|
||||||
tableData: [],
|
tableData: [],
|
||||||
loading: false
|
loading: false,
|
||||||
}
|
currentPage: 1,
|
||||||
|
pageSize: 5,
|
||||||
|
total: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.reportVisible = true;
|
||||||
|
|
||||||
|
this.search();
|
||||||
},
|
},
|
||||||
|
link(row) {
|
||||||
|
this.reportVisible = false;
|
||||||
|
|
||||||
methods: {
|
this.$router.push({
|
||||||
open() {
|
path: '/api/report/view/' + row.id,
|
||||||
this.reportVisible = true;
|
})
|
||||||
|
|
||||||
let url = "/api/report/list/" + this.testId;
|
|
||||||
this.result = this.$get(url, response => {
|
|
||||||
this.tableData = response.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
link(row) {
|
|
||||||
this.reportVisible = false;
|
|
||||||
|
|
||||||
this.$router.push({
|
|
||||||
path: '/api/report/view/' + row.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
search() {
|
||||||
|
let url = "/api/report/list/" + this.testId + "/" + this.currentPage + "/" + this.pageSize;
|
||||||
|
this.result = this.$get(url, response => {
|
||||||
|
let data = response.data;
|
||||||
|
this.total = data.itemCount;
|
||||||
|
this.tableData = data.listObject;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -76,28 +76,28 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
|
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
|
||||||
import {Scenario, Test} from "./model/ScenarioModel"
|
import {Scenario, Test} from "./model/ScenarioModel"
|
||||||
import MsApiReportStatus from "../report/ApiReportStatus";
|
import MsApiReportStatus from "../report/ApiReportStatus";
|
||||||
import MsApiReportDialog from "./ApiReportDialog";
|
import MsApiReportDialog from "./ApiReportDialog";
|
||||||
import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
|
import {checkoutTestManagerOrTestUser, downloadFile, getUUID} from "@/common/js/utils";
|
||||||
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
||||||
import ApiImport from "./components/import/ApiImport";
|
import ApiImport from "./components/import/ApiImport";
|
||||||
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
|
import {ApiEvent, LIST_CHANGE} from "@/business/components/common/head/ListEvent";
|
||||||
import MsContainer from "@/business/components/common/components/MsContainer";
|
import MsContainer from "@/business/components/common/components/MsContainer";
|
||||||
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
|
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
|
||||||
import MsJarConfig from "./components/jar/JarConfig";
|
import MsJarConfig from "./components/jar/JarConfig";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiTestConfig",
|
name: "MsApiTestConfig",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
MsJarConfig,
|
MsJarConfig,
|
||||||
MsMainContainer,
|
MsMainContainer,
|
||||||
MsContainer, ApiImport, MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig
|
MsContainer, ApiImport, MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig
|
||||||
},
|
},
|
||||||
|
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -305,6 +305,7 @@
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.$router.push('/api/test/list/all');
|
this.$router.push('/api/test/list/all');
|
||||||
|
// console.log(this.test.toJMX().xml);
|
||||||
},
|
},
|
||||||
handleCommand(command) {
|
handleCommand(command) {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
|
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
|
||||||
<ms-tcp-config :config="scenario.tcpConfig" :is-read-only="isReadOnly"/>
|
<ms-tcp-config :config="scenario.tcpConfig" :is-read-only="isReadOnly"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
|
||||||
|
<ms-api-assertions :scenario="scenario" :is-read-only="isReadOnly" :assertions="scenario.assertions"/>
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
|
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
|
||||||
|
@ -72,6 +75,7 @@ import MsDubboConsumerService from "@/business/components/api/test/components/re
|
||||||
import MsDatabaseConfig from "./request/database/DatabaseConfig";
|
import MsDatabaseConfig from "./request/database/DatabaseConfig";
|
||||||
import {parseEnvironment} from "../model/EnvironmentModel";
|
import {parseEnvironment} from "../model/EnvironmentModel";
|
||||||
import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig";
|
import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig";
|
||||||
|
import MsApiAssertions from "@/business/components/api/test/components/assertion/ApiAssertions";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiScenarioForm",
|
name: "MsApiScenarioForm",
|
||||||
|
@ -79,7 +83,8 @@ export default {
|
||||||
MsTcpConfig,
|
MsTcpConfig,
|
||||||
MsDatabaseConfig,
|
MsDatabaseConfig,
|
||||||
MsDubboConsumerService,
|
MsDubboConsumerService,
|
||||||
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue
|
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue,
|
||||||
|
MsApiAssertions
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
scenario: Scenario,
|
scenario: Scenario,
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
<div class="assertion-add">
|
<div class="assertion-add">
|
||||||
<el-row :gutter="10">
|
<el-row :gutter="10">
|
||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')"
|
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
|
||||||
|
:placeholder="$t('api_test.request.assertions.select_type')"
|
||||||
size="small">
|
size="small">
|
||||||
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
|
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
|
||||||
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
|
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
|
||||||
|
@ -14,13 +15,18 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="20">
|
<el-col :span="20">
|
||||||
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
|
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
|
||||||
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
|
:callback="after"/>
|
||||||
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath" v-if="type === options.JSON_PATH" :callback="after"/>
|
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
|
||||||
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2" v-if="type === options.XPATH2" :callback="after"/>
|
:callback="after"/>
|
||||||
|
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
|
||||||
|
v-if="type === options.JSON_PATH" :callback="after"/>
|
||||||
|
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2" v-if="type === options.XPATH2"
|
||||||
|
:callback="after"/>
|
||||||
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
|
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
|
||||||
v-if="type === options.DURATION" :callback="after"/>
|
v-if="type === options.DURATION" :callback="after"/>
|
||||||
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223" :callback="after"/>
|
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223"
|
||||||
|
:callback="after"/>
|
||||||
<el-button v-if="!type" :disabled="true" type="primary" size="small">
|
<el-button v-if="!type" :disabled="true" type="primary" size="small">
|
||||||
{{ $t('api_test.request.assertions.add') }}
|
{{ $t('api_test.request.assertions.add') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
@ -28,125 +34,128 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div v-if="!scenario">
|
||||||
<el-row :gutter="10" class="json-path-suggest-button">
|
<el-row :gutter="10" class="json-path-suggest-button">
|
||||||
<el-button size="small" type="primary" @click="suggestJsonOpen">
|
<el-button size="small" type="primary" @click="suggestJsonOpen">
|
||||||
{{$t('api_test.request.assertions.json_path_suggest')}}
|
{{ $t('api_test.request.assertions.json_path_suggest') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" type="danger" @click="clearJson">
|
<el-button size="small" type="danger" @click="clearJson">
|
||||||
{{$t('api_test.request.assertions.json_path_clear')}}
|
{{ $t('api_test.request.assertions.json_path_clear') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request" ref="jsonpathSuggestList"/>
|
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request"
|
||||||
|
ref="jsonpathSuggestList"/>
|
||||||
|
|
||||||
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
|
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsApiAssertionText from "./ApiAssertionText";
|
import MsApiAssertionText from "./ApiAssertionText";
|
||||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||||
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath} from "../../model/ScenarioModel";
|
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath, Scenario} from "../../model/ScenarioModel";
|
||||||
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
|
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
|
||||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||||
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
||||||
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
|
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
|
||||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiAssertions",
|
name: "MsApiAssertions",
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
MsApiAssertionXPath2,
|
MsApiAssertionXPath2,
|
||||||
MsApiAssertionJsr223,
|
MsApiAssertionJsr223,
|
||||||
MsApiJsonpathSuggestList,
|
MsApiJsonpathSuggestList,
|
||||||
MsApiAssertionJsonPath,
|
MsApiAssertionJsonPath,
|
||||||
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText},
|
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
assertions: Assertions,
|
assertions: Assertions,
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
isReadOnly: {
|
scenario: Scenario,
|
||||||
type: Boolean,
|
isReadOnly: {
|
||||||
default: false
|
type: Boolean,
|
||||||
}
|
default: false
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
options: ASSERTION_TYPE,
|
|
||||||
time: "",
|
|
||||||
type: "",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
after() {
|
|
||||||
this.type = "";
|
|
||||||
},
|
|
||||||
suggestJsonOpen() {
|
|
||||||
if (!this.request.debugRequestResult) {
|
|
||||||
this.$message(this.$t('api_test.request.assertions.debug_first'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$refs.jsonpathSuggestList.open();
|
|
||||||
},
|
|
||||||
addJsonpathSuggest(jsonPathList) {
|
|
||||||
jsonPathList.forEach(jsonPath => {
|
|
||||||
let jsonItem = new JSONPath();
|
|
||||||
jsonItem.expression = jsonPath.json_path;
|
|
||||||
jsonItem.expect = jsonPath.json_value;
|
|
||||||
jsonItem.setJSONPathDescription();
|
|
||||||
this.assertions.jsonPath.push(jsonItem);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
clearJson() {
|
|
||||||
this.assertions.jsonPath = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
options: ASSERTION_TYPE,
|
||||||
|
time: "",
|
||||||
|
type: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
after() {
|
||||||
|
this.type = "";
|
||||||
|
},
|
||||||
|
suggestJsonOpen() {
|
||||||
|
if (!this.request.debugRequestResult) {
|
||||||
|
this.$message(this.$t('api_test.request.assertions.debug_first'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$refs.jsonpathSuggestList.open();
|
||||||
|
},
|
||||||
|
addJsonpathSuggest(jsonPathList) {
|
||||||
|
jsonPathList.forEach(jsonPath => {
|
||||||
|
let jsonItem = new JSONPath();
|
||||||
|
jsonItem.expression = jsonPath.json_path;
|
||||||
|
jsonItem.expect = jsonPath.json_value;
|
||||||
|
jsonItem.setJSONPathDescription();
|
||||||
|
this.assertions.jsonPath.push(jsonItem);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearJson() {
|
||||||
|
this.assertions.jsonPath = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.assertion-item {
|
.assertion-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assertion-add {
|
.assertion-add {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: #DCDFE6 solid 1px;
|
border: #DCDFE6 solid 1px;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-purple-dark {
|
.bg-purple-dark {
|
||||||
background: #99a9bf;
|
background: #99a9bf;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-purple {
|
.bg-purple {
|
||||||
background: #d3dce6;
|
background: #d3dce6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-purple-light {
|
.bg-purple-light {
|
||||||
background: #e5e9f2;
|
background: #e5e9f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-content {
|
.grid-content {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-bg {
|
.row-bg {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
background-color: #f9fafc;
|
background-color: #f9fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.json-path-suggest-button {
|
.json-path-suggest-button {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Arguments,
|
Arguments,
|
||||||
|
ConstantTimer as JMXConstantTimer,
|
||||||
CookieManager,
|
CookieManager,
|
||||||
DNSCacheManager,
|
DNSCacheManager,
|
||||||
DubboSample,
|
DubboSample,
|
||||||
|
@ -10,22 +11,24 @@ import {
|
||||||
HTTPSamplerArguments,
|
HTTPSamplerArguments,
|
||||||
HTTPsamplerFiles,
|
HTTPsamplerFiles,
|
||||||
HTTPSamplerProxy,
|
HTTPSamplerProxy,
|
||||||
|
IfController as JMXIfController,
|
||||||
JDBCDataSource,
|
JDBCDataSource,
|
||||||
JDBCSampler,
|
JDBCSampler,
|
||||||
JSONPathAssertion,
|
JSONPathAssertion,
|
||||||
JSONPostProcessor,
|
JSONPostProcessor,
|
||||||
|
JSR223Assertion,
|
||||||
JSR223PostProcessor,
|
JSR223PostProcessor,
|
||||||
JSR223PreProcessor,
|
JSR223PreProcessor,
|
||||||
RegexExtractor,
|
RegexExtractor,
|
||||||
ResponseCodeAssertion,
|
ResponseCodeAssertion,
|
||||||
ResponseDataAssertion,
|
ResponseDataAssertion,
|
||||||
ResponseHeadersAssertion,
|
ResponseHeadersAssertion,
|
||||||
|
TCPSampler,
|
||||||
TestElement,
|
TestElement,
|
||||||
TestPlan,
|
TestPlan,
|
||||||
ThreadGroup,
|
ThreadGroup,
|
||||||
|
XPath2Assertion,
|
||||||
XPath2Extractor,
|
XPath2Extractor,
|
||||||
IfController as JMXIfController,
|
|
||||||
ConstantTimer as JMXConstantTimer, TCPSampler, JSR223Assertion, XPath2Assertion,
|
|
||||||
} from "./JMX";
|
} from "./JMX";
|
||||||
import Mock from "mockjs";
|
import Mock from "mockjs";
|
||||||
import {funcFilters} from "@/common/js/func-filter";
|
import {funcFilters} from "@/common/js/func-filter";
|
||||||
|
@ -226,6 +229,7 @@ export class Scenario extends BaseConfig {
|
||||||
this.enable = true;
|
this.enable = true;
|
||||||
this.databaseConfigs = [];
|
this.databaseConfigs = [];
|
||||||
this.tcpConfig = undefined;
|
this.tcpConfig = undefined;
|
||||||
|
this.assertions = undefined;
|
||||||
|
|
||||||
this.set(options);
|
this.set(options);
|
||||||
this.sets({
|
this.sets({
|
||||||
|
@ -242,6 +246,7 @@ export class Scenario extends BaseConfig {
|
||||||
options.databaseConfigs = options.databaseConfigs || [];
|
options.databaseConfigs = options.databaseConfigs || [];
|
||||||
options.dubboConfig = new DubboConfig(options.dubboConfig);
|
options.dubboConfig = new DubboConfig(options.dubboConfig);
|
||||||
options.tcpConfig = new TCPConfig(options.tcpConfig);
|
options.tcpConfig = new TCPConfig(options.tcpConfig);
|
||||||
|
options.assertions = new Assertions(options.assertions);
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1151,6 +1156,9 @@ class JMXGenerator {
|
||||||
this.addScenarioCookieManager(threadGroup, scenario);
|
this.addScenarioCookieManager(threadGroup, scenario);
|
||||||
|
|
||||||
this.addJDBCDataSources(threadGroup, scenario);
|
this.addJDBCDataSources(threadGroup, scenario);
|
||||||
|
|
||||||
|
this.addAssertion(threadGroup, scenario);
|
||||||
|
|
||||||
scenario.requests.forEach(request => {
|
scenario.requests.forEach(request => {
|
||||||
if (request.enable) {
|
if (request.enable) {
|
||||||
if (!request.isValid()) return;
|
if (!request.isValid()) return;
|
||||||
|
@ -1175,7 +1183,7 @@ class JMXGenerator {
|
||||||
|
|
||||||
this.addRequestExtractor(sampler, request);
|
this.addRequestExtractor(sampler, request);
|
||||||
|
|
||||||
this.addRequestAssertion(sampler, request);
|
this.addAssertion(sampler, request);
|
||||||
|
|
||||||
this.addJSR223PreProcessor(sampler, request);
|
this.addJSR223PreProcessor(sampler, request);
|
||||||
|
|
||||||
|
@ -1467,7 +1475,7 @@ class JMXGenerator {
|
||||||
httpSamplerProxy.add(new HTTPsamplerFiles(files));
|
httpSamplerProxy.add(new HTTPsamplerFiles(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
addRequestAssertion(httpSamplerProxy, request) {
|
addAssertion(httpSamplerProxy, request) {
|
||||||
let assertions = request.assertions;
|
let assertions = request.assertions;
|
||||||
if (assertions.regex.length > 0) {
|
if (assertions.regex.length > 0) {
|
||||||
assertions.regex.filter(this.filter).forEach(regex => {
|
assertions.regex.filter(this.filter).forEach(regex => {
|
||||||
|
|
|
@ -222,28 +222,13 @@ export default {
|
||||||
this.$set(row, "showMore", true);
|
this.$set(row, "showMore", true);
|
||||||
this.selectRows.add(row);
|
this.selectRows.add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
let arr = Array.from(this.selectRows);
|
|
||||||
|
|
||||||
// 选中1个以上的用例时显示更多操作
|
|
||||||
if (this.selectRows.size === 1) {
|
|
||||||
this.$set(arr[0], "showMore", false);
|
|
||||||
} else if (this.selectRows.size === 2) {
|
|
||||||
arr.forEach(row => {
|
|
||||||
this.$set(row, "showMore", true);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleSelectAll(selection) {
|
handleSelectAll(selection) {
|
||||||
if (selection.length > 0) {
|
if (selection.length > 0) {
|
||||||
if (selection.length === 1) {
|
this.tableData.forEach(item => {
|
||||||
this.selectRows.add(selection[0]);
|
this.$set(item, "showMore", true);
|
||||||
} else {
|
this.selectRows.add(item);
|
||||||
this.tableData.forEach(item => {
|
});
|
||||||
this.$set(item, "showMore", true);
|
|
||||||
this.selectRows.add(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.selectRows.clear();
|
this.selectRows.clear();
|
||||||
this.tableData.forEach(row => {
|
this.tableData.forEach(row => {
|
||||||
|
|
|
@ -1,98 +1,81 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-loading="result.loading" class="pressure-config-container">
|
<div v-loading="result.loading" class="pressure-config-container">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10">
|
<el-col>
|
||||||
<el-form :inline="true">
|
<ms-chart class="chart-container" ref="chart1" :options="options" :autoresize="true"></ms-chart>
|
||||||
<el-form-item>
|
|
||||||
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="true"
|
|
||||||
:placeholder="$t('load_test.input_thread_num')"
|
|
||||||
v-model="threadNumber"
|
|
||||||
@change="calculateChart"
|
|
||||||
:min="1"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-form :inline="true">
|
|
||||||
<el-form-item>
|
|
||||||
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="true"
|
|
||||||
:placeholder="$t('load_test.duration')"
|
|
||||||
v-model="duration"
|
|
||||||
:min="1"
|
|
||||||
@change="calculateChart"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-form :inline="true">
|
|
||||||
<el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-switch v-model="rpsLimitEnable" :disabled="true"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="true"
|
|
||||||
:placeholder="$t('load_test.input_rps_limit')"
|
|
||||||
v-model="rpsLimit"
|
|
||||||
@change="calculateChart"
|
|
||||||
:min="1"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-form :inline="true" class="input-bottom-border">
|
|
||||||
<el-form-item>
|
|
||||||
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="true"
|
|
||||||
placeholder=""
|
|
||||||
:min="1"
|
|
||||||
:max="duration"
|
|
||||||
v-model="rampUpTime"
|
|
||||||
@change="calculateChart"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="true"
|
|
||||||
placeholder=""
|
|
||||||
:min="1"
|
|
||||||
:max="Math.min(threadNumber, rampUpTime)"
|
|
||||||
v-model="step"
|
|
||||||
@change="calculateChart"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="14">
|
|
||||||
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
|
|
||||||
<ms-chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></ms-chart>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-collapse v-model="activeNames">
|
||||||
|
<el-collapse-item :title="threadGroup.attributes.testname" :name="index"
|
||||||
|
v-for="(threadGroup, index) in threadGroups"
|
||||||
|
:key="index">
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-form :inline="true">
|
||||||
|
<el-form-item :label="$t('load_test.thread_num')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="true"
|
||||||
|
:placeholder="$t('load_test.input_thread_num')"
|
||||||
|
v-model="threadGroup.threadNumber"
|
||||||
|
:min="1"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<br>
|
||||||
|
<el-form-item :label="$t('load_test.duration')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="true"
|
||||||
|
:placeholder="$t('load_test.duration')"
|
||||||
|
v-model="threadGroup.duration"
|
||||||
|
:min="1"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<br>
|
||||||
|
<el-form-item :label="$t('load_test.rps_limit')">
|
||||||
|
<el-switch v-model="rpsLimitEnable"/>
|
||||||
|
|
||||||
|
<el-input-number
|
||||||
|
:disabled="true"
|
||||||
|
:placeholder="$t('load_test.input_rps_limit')"
|
||||||
|
v-model="threadGroup.rpsLimit"
|
||||||
|
:min="1"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<br>
|
||||||
|
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="true"
|
||||||
|
placeholder=""
|
||||||
|
:min="1"
|
||||||
|
:max="threadGroup.duration"
|
||||||
|
v-model="threadGroup.rampUpTime"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="true"
|
||||||
|
placeholder=""
|
||||||
|
:min="1"
|
||||||
|
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||||
|
v-model="threadGroup.step"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14">
|
||||||
|
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
|
||||||
|
<ms-chart class="chart-container" :options="threadGroup.options" :autoresize="true"></ms-chart>
|
||||||
|
</el-col>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import echarts from "echarts";
|
import echarts from "echarts";
|
||||||
import MsChart from "@/business/components/common/chart/MsChart";
|
import MsChart from "@/business/components/common/chart/MsChart";
|
||||||
|
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
|
||||||
|
|
||||||
const TARGET_LEVEL = "TargetLevel";
|
const TARGET_LEVEL = "TargetLevel";
|
||||||
const RAMP_UP = "RampUp";
|
const RAMP_UP = "RampUp";
|
||||||
|
@ -100,6 +83,14 @@ const STEPS = "Steps";
|
||||||
const DURATION = "duration";
|
const DURATION = "duration";
|
||||||
const RPS_LIMIT = "rpsLimit";
|
const RPS_LIMIT = "rpsLimit";
|
||||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||||
|
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 + ')';
|
||||||
|
}
|
||||||
|
const hexToRgb = function (hex) {
|
||||||
|
return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5))
|
||||||
|
+ ',' + parseInt('0x' + hex.slice(5, 7)) + ')';
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsPerformancePressureConfig",
|
name: "MsPerformancePressureConfig",
|
||||||
|
@ -108,62 +99,91 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
result: {},
|
result: {},
|
||||||
threadNumber: 10,
|
threadNumber: 0,
|
||||||
duration: 10,
|
duration: 0,
|
||||||
rampUpTime: 10,
|
rampUpTime: 0,
|
||||||
step: 10,
|
step: 0,
|
||||||
rpsLimit: 10,
|
rpsLimit: 0,
|
||||||
rpsLimitEnable: false,
|
rpsLimitEnable: false,
|
||||||
orgOptions: {},
|
options: {},
|
||||||
|
resourcePool: null,
|
||||||
|
resourcePools: [],
|
||||||
|
activeNames: ["0"],
|
||||||
|
threadGroups: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getLoadConfig();
|
// this.getJmxContent();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
calculateLoadConfiguration: function (data) {
|
calculateLoadConfiguration: function (data) {
|
||||||
data.forEach(d => {
|
for (let i = 0; i < data.length; i++) {
|
||||||
switch (d.key) {
|
let d = data[i];
|
||||||
case TARGET_LEVEL:
|
if (d instanceof Array) {
|
||||||
this.threadNumber = d.value;
|
d.forEach(item => {
|
||||||
break;
|
switch (item.key) {
|
||||||
case RAMP_UP:
|
case TARGET_LEVEL:
|
||||||
this.rampUpTime = d.value;
|
this.threadGroups[i].threadNumber = item.value;
|
||||||
break;
|
break;
|
||||||
case DURATION:
|
case RAMP_UP:
|
||||||
this.duration = d.value;
|
this.threadGroups[i].rampUpTime = item.value;
|
||||||
break;
|
break;
|
||||||
case STEPS:
|
case DURATION:
|
||||||
this.step = d.value;
|
this.threadGroups[i].duration = item.value;
|
||||||
break;
|
break;
|
||||||
case RPS_LIMIT:
|
case STEPS:
|
||||||
this.rpsLimit = d.value;
|
this.threadGroups[i].step = item.value;
|
||||||
break;
|
break;
|
||||||
default:
|
case RPS_LIMIT:
|
||||||
break;
|
this.threadGroups[i].rpsLimit = item.value;
|
||||||
|
break;
|
||||||
|
case RPS_LIMIT_ENABLE:
|
||||||
|
this.threadGroups[i].rpsLimitEnable = item.value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.calculateChart(this.threadGroups[i]);
|
||||||
|
} else {
|
||||||
|
switch (d.key) {
|
||||||
|
case TARGET_LEVEL:
|
||||||
|
this.threadGroups[0].threadNumber = d.value;
|
||||||
|
break;
|
||||||
|
case RAMP_UP:
|
||||||
|
this.threadGroups[0].rampUpTime = d.value;
|
||||||
|
break;
|
||||||
|
case DURATION:
|
||||||
|
this.threadGroups[0].duration = d.value;
|
||||||
|
break;
|
||||||
|
case STEPS:
|
||||||
|
this.threadGroups[0].step = d.value;
|
||||||
|
break;
|
||||||
|
case RPS_LIMIT:
|
||||||
|
this.threadGroups[0].rpsLimit = d.value;
|
||||||
|
break;
|
||||||
|
case RPS_LIMIT_ENABLE:
|
||||||
|
this.threadGroups[0].rpsLimitEnable = d.value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.calculateChart(this.threadGroups[0]);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
this.threadNumber = this.threadNumber || 10;
|
|
||||||
this.duration = this.duration || 30;
|
|
||||||
this.rampUpTime = this.rampUpTime || 12;
|
|
||||||
this.step = this.step || 3;
|
|
||||||
this.rpsLimit = this.rpsLimit || 10;
|
|
||||||
|
|
||||||
this.calculateChart();
|
|
||||||
},
|
},
|
||||||
getLoadConfig() {
|
getLoadConfig() {
|
||||||
if (!this.report.id) {
|
if (!this.report.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$get("/performance/report/" + this.report.id, res => {
|
this.result = this.$get("/performance/report/" + this.report.id, res => {
|
||||||
let data = res.data;
|
let data = res.data;
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.loadConfiguration) {
|
if (data.loadConfiguration) {
|
||||||
let d = JSON.parse(data.loadConfiguration);
|
let d = JSON.parse(data.loadConfiguration);
|
||||||
this.calculateLoadConfiguration(d);
|
this.calculateLoadConfiguration(d);
|
||||||
} else {
|
} else {
|
||||||
this.$get('/performance/get-load-config/' + this.report.testId, (response) => {
|
this.$get('/performance/get-load-config/' + this.report.id, (response) => {
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
let data = JSON.parse(response.data);
|
let data = JSON.parse(response.data);
|
||||||
this.calculateLoadConfiguration(data);
|
this.calculateLoadConfiguration(data);
|
||||||
|
@ -175,14 +195,127 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
calculateChart() {
|
getJmxContent() {
|
||||||
if (this.duration < this.rampUpTime) {
|
console.log(this.report.testId);
|
||||||
this.rampUpTime = this.duration;
|
if (!this.report.testId) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (this.rampUpTime < this.step) {
|
this.result = this.$get('/performance/get-jmx-content/' + this.report.testId, (response) => {
|
||||||
this.step = this.rampUpTime;
|
if (response.data) {
|
||||||
|
this.threadGroups = findThreadGroup(response.data);
|
||||||
|
this.threadGroups.forEach(tg => {
|
||||||
|
tg.options = {};
|
||||||
|
});
|
||||||
|
this.getLoadConfig();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
calculateTotalChart() {
|
||||||
|
let handler = this;
|
||||||
|
if (handler.duration < handler.rampUpTime) {
|
||||||
|
handler.rampUpTime = handler.duration;
|
||||||
}
|
}
|
||||||
this.orgOptions = {
|
if (handler.rampUpTime < handler.step) {
|
||||||
|
handler.step = handler.rampUpTime;
|
||||||
|
}
|
||||||
|
handler.options = {
|
||||||
|
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < handler.threadGroups.length; i++) {
|
||||||
|
let seriesData = {
|
||||||
|
name: handler.threadGroups[i].attributes.testname,
|
||||||
|
data: [],
|
||||||
|
type: 'line',
|
||||||
|
step: 'start',
|
||||||
|
smooth: false,
|
||||||
|
symbolSize: 5,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
normal: {
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
normal: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||||
|
offset: 0,
|
||||||
|
color: hexToRgba(handler.options.color[i], 0.3),
|
||||||
|
}, {
|
||||||
|
offset: 0.8,
|
||||||
|
color: hexToRgba(handler.options.color[i], 0),
|
||||||
|
}], false),
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowBlur: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
color: hexToRgb(handler.options.color[i]),
|
||||||
|
borderColor: 'rgba(137,189,2,0.27)',
|
||||||
|
borderWidth: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let tg = handler.threadGroups[i];
|
||||||
|
|
||||||
|
let timePeriod = Math.floor(tg.rampUpTime / tg.step);
|
||||||
|
let timeInc = timePeriod;
|
||||||
|
|
||||||
|
let threadPeriod = Math.floor(tg.threadNumber / tg.step);
|
||||||
|
let threadInc1 = Math.floor(tg.threadNumber / tg.step);
|
||||||
|
let threadInc2 = Math.ceil(tg.threadNumber / tg.step);
|
||||||
|
let inc2count = tg.threadNumber - tg.step * threadInc1;
|
||||||
|
for (let j = 0; j <= tg.duration; j++) {
|
||||||
|
|
||||||
|
if (j > timePeriod) {
|
||||||
|
timePeriod += timeInc;
|
||||||
|
if (inc2count > 0) {
|
||||||
|
threadPeriod = threadPeriod + threadInc2;
|
||||||
|
inc2count--;
|
||||||
|
} else {
|
||||||
|
threadPeriod = threadPeriod + threadInc1;
|
||||||
|
}
|
||||||
|
if (threadPeriod > tg.threadNumber) {
|
||||||
|
threadPeriod = tg.threadNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// x 轴
|
||||||
|
let xAxis = handler.options.xAxis.data;
|
||||||
|
if (xAxis.indexOf(j) < 0) {
|
||||||
|
xAxis.push(j);
|
||||||
|
}
|
||||||
|
seriesData.data.push(threadPeriod);
|
||||||
|
}
|
||||||
|
handler.options.series.push(seriesData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateChart(threadGroup) {
|
||||||
|
let handler = this;
|
||||||
|
if (threadGroup) {
|
||||||
|
handler = threadGroup;
|
||||||
|
}
|
||||||
|
if (handler.duration < handler.rampUpTime) {
|
||||||
|
handler.rampUpTime = handler.duration;
|
||||||
|
}
|
||||||
|
if (handler.rampUpTime < handler.step) {
|
||||||
|
handler.step = handler.rampUpTime;
|
||||||
|
}
|
||||||
|
handler.options = {
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
|
@ -235,16 +368,16 @@ export default {
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
let timePeriod = Math.floor(this.rampUpTime / this.step);
|
let timePeriod = Math.floor(handler.rampUpTime / handler.step);
|
||||||
let timeInc = timePeriod;
|
let timeInc = timePeriod;
|
||||||
|
|
||||||
let threadPeriod = Math.floor(this.threadNumber / this.step);
|
let threadPeriod = Math.floor(handler.threadNumber / handler.step);
|
||||||
let threadInc1 = Math.floor(this.threadNumber / this.step);
|
let threadInc1 = Math.floor(handler.threadNumber / handler.step);
|
||||||
let threadInc2 = Math.ceil(this.threadNumber / this.step);
|
let threadInc2 = Math.ceil(handler.threadNumber / handler.step);
|
||||||
let inc2count = this.threadNumber - this.step * threadInc1;
|
let inc2count = handler.threadNumber - handler.step * threadInc1;
|
||||||
for (let i = 0; i <= this.duration; i++) {
|
for (let i = 0; i <= handler.duration; i++) {
|
||||||
// x 轴
|
// x 轴
|
||||||
this.orgOptions.xAxis.data.push(i);
|
handler.options.xAxis.data.push(i);
|
||||||
if (i > timePeriod) {
|
if (i > timePeriod) {
|
||||||
timePeriod += timeInc;
|
timePeriod += timeInc;
|
||||||
if (inc2count > 0) {
|
if (inc2count > 0) {
|
||||||
|
@ -253,25 +386,22 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
threadPeriod = threadPeriod + threadInc1;
|
threadPeriod = threadPeriod + threadInc1;
|
||||||
}
|
}
|
||||||
if (threadPeriod > this.threadNumber) {
|
if (threadPeriod > handler.threadNumber) {
|
||||||
threadPeriod = this.threadNumber;
|
threadPeriod = handler.threadNumber;
|
||||||
}
|
}
|
||||||
this.orgOptions.series[0].data.push(threadPeriod);
|
handler.options.series[0].data.push(threadPeriod);
|
||||||
} else {
|
} else {
|
||||||
this.orgOptions.series[0].data.push(threadPeriod);
|
handler.options.series[0].data.push(threadPeriod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.calculateTotalChart();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
report: {
|
'report.testId': {
|
||||||
handler(val) {
|
handler() {
|
||||||
if (!val.testId) {
|
this.getJmxContent();
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.getLoadConfig();
|
|
||||||
},
|
},
|
||||||
deep: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,6 +425,7 @@ export default {
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-col .el-form {
|
.el-col .el-form {
|
||||||
|
|
|
@ -2,8 +2,8 @@ import MsProject from "@/business/components/project/MsProject";
|
||||||
|
|
||||||
const PerformanceTest = () => import('@/business/components/performance/PerformanceTest')
|
const PerformanceTest = () => import('@/business/components/performance/PerformanceTest')
|
||||||
const PerformanceTestHome = () => import('@/business/components/performance/home/PerformanceTestHome')
|
const PerformanceTestHome = () => import('@/business/components/performance/home/PerformanceTestHome')
|
||||||
const EditPerformanceTestPlan = () => import('@/business/components/performance/test/EditPerformanceTestPlan')
|
const EditPerformanceTest = () => import('@/business/components/performance/test/EditPerformanceTest')
|
||||||
const PerformanceTestPlan = () => import('@/business/components/performance/test/PerformanceTestPlan')
|
const PerformanceTestList = () => import('@/business/components/performance/test/PerformanceTestList')
|
||||||
const PerformanceTestReport = () => import('@/business/components/performance/report/PerformanceTestReport')
|
const PerformanceTestReport = () => import('@/business/components/performance/report/PerformanceTestReport')
|
||||||
const PerformanceChart = () => import('@/business/components/performance/report/components/PerformanceChart')
|
const PerformanceChart = () => import('@/business/components/performance/report/components/PerformanceChart')
|
||||||
const PerformanceReportView = () => import('@/business/components/performance/report/PerformanceReportView')
|
const PerformanceReportView = () => import('@/business/components/performance/report/PerformanceReportView')
|
||||||
|
@ -24,12 +24,12 @@ export default {
|
||||||
{
|
{
|
||||||
path: 'test/create',
|
path: 'test/create',
|
||||||
name: "createPerTest",
|
name: "createPerTest",
|
||||||
component: EditPerformanceTestPlan,
|
component: EditPerformanceTest,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "test/edit/:testId",
|
path: "test/edit/:testId",
|
||||||
name: "editPerTest",
|
name: "editPerTest",
|
||||||
component: EditPerformanceTestPlan,
|
component: EditPerformanceTest,
|
||||||
props: {
|
props: {
|
||||||
content: (route) => {
|
content: (route) => {
|
||||||
return {
|
return {
|
||||||
|
@ -41,7 +41,7 @@ export default {
|
||||||
{
|
{
|
||||||
path: "test/:projectId",
|
path: "test/:projectId",
|
||||||
name: "perPlan",
|
name: "perPlan",
|
||||||
component: PerformanceTestPlan
|
component: PerformanceTestList
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "project/:type",
|
path: "project/:type",
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
<el-card v-loading="result.loading">
|
<el-card v-loading="result.loading">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10">
|
<el-col :span="10">
|
||||||
<el-input :disabled="isReadOnly" :placeholder="$t('load_test.input_name')" v-model="testPlan.name"
|
<el-input :disabled="isReadOnly" :placeholder="$t('load_test.input_name')" v-model="test.name"
|
||||||
class="input-with-select"
|
class="input-with-select"
|
||||||
maxlength="30" show-word-limit
|
maxlength="30" show-word-limit
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<el-select filterable v-model="testPlan.projectId"
|
<el-select filterable v-model="test.projectId"
|
||||||
:placeholder="$t('load_test.select_project')">
|
:placeholder="$t('load_test.select_project')">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in projects"
|
v-for="item in projects"
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{ $t('commons.cancel') }}
|
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{ $t('commons.cancel') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
|
<ms-schedule-config :schedule="test.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
|
||||||
:check-open="checkScheduleEdit" :test-id="testId" :custom-validate="durationValidate"/>
|
:check-open="checkScheduleEdit" :test-id="testId" :custom-validate="durationValidate"/>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
@ -37,10 +37,11 @@
|
||||||
|
|
||||||
<el-tabs class="testplan-config" v-model="active" type="border-card" :stretch="true">
|
<el-tabs class="testplan-config" v-model="active" type="border-card" :stretch="true">
|
||||||
<el-tab-pane :label="$t('load_test.basic_config')">
|
<el-tab-pane :label="$t('load_test.basic_config')">
|
||||||
<performance-basic-config :is-read-only="isReadOnly" :test-plan="testPlan" ref="basicConfig"/>
|
<performance-basic-config :is-read-only="isReadOnly" :test="test" ref="basicConfig"
|
||||||
|
@fileChange="fileChange"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('load_test.pressure_config')">
|
<el-tab-pane :label="$t('load_test.pressure_config')">
|
||||||
<performance-pressure-config :is-read-only="isReadOnly" :test-plan="testPlan" :test-id="testId"
|
<performance-pressure-config :is-read-only="isReadOnly" :test="test" :test-id="testId"
|
||||||
ref="pressureConfig" @changeActive="changeTabActive"/>
|
ref="pressureConfig" @changeActive="changeTabActive"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
|
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
|
||||||
|
@ -63,7 +64,7 @@ import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
||||||
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
|
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "EditPerformanceTestPlan",
|
name: "EditPerformanceTest",
|
||||||
components: {
|
components: {
|
||||||
MsScheduleConfig,
|
MsScheduleConfig,
|
||||||
PerformancePressureConfig,
|
PerformancePressureConfig,
|
||||||
|
@ -75,7 +76,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
result: {},
|
result: {},
|
||||||
testPlan: {schedule: {}},
|
test: {schedule: {}},
|
||||||
listProjectPath: "/project/listAll",
|
listProjectPath: "/project/listAll",
|
||||||
savePath: "/performance/save",
|
savePath: "/performance/save",
|
||||||
editPath: "/performance/edit",
|
editPath: "/performance/edit",
|
||||||
|
@ -136,8 +137,8 @@ export default {
|
||||||
importAPITest() {
|
importAPITest() {
|
||||||
let apiTest = this.$store.state.api.test;
|
let apiTest = this.$store.state.api.test;
|
||||||
if (apiTest && apiTest.name) {
|
if (apiTest && apiTest.name) {
|
||||||
this.$set(this.testPlan, "projectId", apiTest.projectId);
|
this.$set(this.test, "projectId", apiTest.projectId);
|
||||||
this.$set(this.testPlan, "name", apiTest.name);
|
this.$set(this.test, "name", apiTest.name);
|
||||||
let blob = new Blob([apiTest.jmx.xml], {type: "application/octet-stream"});
|
let blob = new Blob([apiTest.jmx.xml], {type: "application/octet-stream"});
|
||||||
let file = new File([blob], apiTest.jmx.name);
|
let file = new File([blob], apiTest.jmx.name);
|
||||||
this.$refs.basicConfig.beforeUpload(file);
|
this.$refs.basicConfig.beforeUpload(file);
|
||||||
|
@ -151,9 +152,9 @@ export default {
|
||||||
this.testId = testId;
|
this.testId = testId;
|
||||||
this.result = this.$get('/performance/get/' + testId, response => {
|
this.result = this.$get('/performance/get/' + testId, response => {
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
this.testPlan = response.data;
|
this.test = response.data;
|
||||||
if (!this.testPlan.schedule) {
|
if (!this.test.schedule) {
|
||||||
this.testPlan.schedule = {};
|
this.test.schedule = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -165,7 +166,7 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
save() {
|
save() {
|
||||||
if (!this.validTestPlan()) {
|
if (!this.validTest()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,16 +181,16 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
saveAndRun() {
|
saveAndRun() {
|
||||||
if (!this.validTestPlan()) {
|
if (!this.validTest()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = this.getSaveOption();
|
let options = this.getSaveOption();
|
||||||
|
|
||||||
this.result = this.$request(options, (response) => {
|
this.result = this.$request(options, (response) => {
|
||||||
this.testPlan.id = response.data;
|
this.test.id = response.data;
|
||||||
this.$success(this.$t('commons.save_success'));
|
this.$success(this.$t('commons.save_success'));
|
||||||
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, (response) => {
|
this.result = this.$post(this.runPath, {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
|
||||||
let reportId = response.data;
|
let reportId = response.data;
|
||||||
this.$router.push({path: '/performance/report/view/' + reportId})
|
this.$router.push({path: '/performance/report/view/' + reportId})
|
||||||
// 发送广播,刷新 head 上的最新列表
|
// 发送广播,刷新 head 上的最新列表
|
||||||
|
@ -199,7 +200,7 @@ export default {
|
||||||
},
|
},
|
||||||
getSaveOption() {
|
getSaveOption() {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
let url = this.testPlan.id ? this.editPath : this.savePath;
|
let url = this.test.id ? this.editPath : this.savePath;
|
||||||
|
|
||||||
if (this.$refs.basicConfig.uploadList.length > 0) {
|
if (this.$refs.basicConfig.uploadList.length > 0) {
|
||||||
this.$refs.basicConfig.uploadList.forEach(f => {
|
this.$refs.basicConfig.uploadList.forEach(f => {
|
||||||
|
@ -207,15 +208,15 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 基本配置
|
// 基本配置
|
||||||
this.testPlan.updatedFileList = this.$refs.basicConfig.updatedFileList();
|
this.test.updatedFileList = this.$refs.basicConfig.updatedFileList();
|
||||||
// 压力配置
|
// 压力配置
|
||||||
this.testPlan.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
|
this.test.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
|
||||||
this.testPlan.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
|
this.test.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
|
||||||
// 高级配置
|
// 高级配置
|
||||||
this.testPlan.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
|
this.test.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
|
||||||
|
|
||||||
// file属性不需要json化
|
// file属性不需要json化
|
||||||
let requestJson = JSON.stringify(this.testPlan, function (key, value) {
|
let requestJson = JSON.stringify(this.test, function (key, value) {
|
||||||
return key === "file" ? undefined : value
|
return key === "file" ? undefined : value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -235,13 +236,13 @@ export default {
|
||||||
cancel() {
|
cancel() {
|
||||||
this.$router.push({path: '/performance/test/all'})
|
this.$router.push({path: '/performance/test/all'})
|
||||||
},
|
},
|
||||||
validTestPlan() {
|
validTest() {
|
||||||
if (!this.testPlan.name) {
|
if (!this.test.name) {
|
||||||
this.$error(this.$t('load_test.test_name_is_null'));
|
this.$error(this.$t('load_test.test_name_is_null'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.testPlan.projectId) {
|
if (!this.test.projectId) {
|
||||||
this.$error(this.$t('load_test.project_is_null'));
|
this.$error(this.$t('load_test.project_is_null'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -268,26 +269,26 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
saveCronExpression(cronExpression) {
|
saveCronExpression(cronExpression) {
|
||||||
this.testPlan.schedule.enable = true;
|
this.test.schedule.enable = true;
|
||||||
this.testPlan.schedule.value = cronExpression;
|
this.test.schedule.value = cronExpression;
|
||||||
this.saveSchedule();
|
this.saveSchedule();
|
||||||
},
|
},
|
||||||
saveSchedule() {
|
saveSchedule() {
|
||||||
this.checkScheduleEdit();
|
this.checkScheduleEdit();
|
||||||
let param = {};
|
let param = {};
|
||||||
param = this.testPlan.schedule;
|
param = this.test.schedule;
|
||||||
param.resourceId = this.testPlan.id;
|
param.resourceId = this.test.id;
|
||||||
let url = '/performance/schedule/create';
|
let url = '/performance/schedule/create';
|
||||||
if (param.id) {
|
if (param.id) {
|
||||||
url = '/performance/schedule/update';
|
url = '/performance/schedule/update';
|
||||||
}
|
}
|
||||||
this.$post(url, param, response => {
|
this.$post(url, param, response => {
|
||||||
this.$success(this.$t('commons.save_success'));
|
this.$success(this.$t('commons.save_success'));
|
||||||
this.getTest(this.testPlan.id);
|
this.getTest(this.test.id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
checkScheduleEdit() {
|
checkScheduleEdit() {
|
||||||
if (!this.testPlan.id) {
|
if (!this.test.id) {
|
||||||
this.$message(this.$t('api_test.environment.please_save_test'));
|
this.$message(this.$t('api_test.environment.please_save_test'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -304,6 +305,18 @@ export default {
|
||||||
return {
|
return {
|
||||||
pass: true
|
pass: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
fileChange(threadGroups) {
|
||||||
|
let handler = this.$refs.pressureConfig;
|
||||||
|
handler.threadGroups = threadGroups;
|
||||||
|
threadGroups.forEach(tg => {
|
||||||
|
tg.threadNumber = tg.threadNumber || 10;
|
||||||
|
tg.duration = tg.duration || 10;
|
||||||
|
tg.rampUpTime = tg.rampUpTime || 5;
|
||||||
|
tg.step = tg.step || 5;
|
||||||
|
tg.rpsLimit = tg.rpsLimit || 10;
|
||||||
|
handler.calculateChart(tg);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -80,7 +80,7 @@ import MsContainer from "../../common/components/MsContainer";
|
||||||
import MsMainContainer from "../../common/components/MsMainContainer";
|
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||||
import MsPerformanceTestStatus from "./PerformanceTestStatus";
|
import MsPerformanceTestStatus from "./PerformanceTestStatus";
|
||||||
import MsTableOperators from "../../common/components/MsTableOperators";
|
import MsTableOperators from "../../common/components/MsTableOperators";
|
||||||
import {_filter, _sort} from "../../../../common/js/utils";
|
import {_filter, _sort} from "@/common/js/utils";
|
||||||
import MsTableHeader from "../../common/components/MsTableHeader";
|
import MsTableHeader from "../../common/components/MsTableHeader";
|
||||||
import {TEST_CONFIGS} from "../../common/components/search/search-components";
|
import {TEST_CONFIGS} from "../../common/components/search/search-components";
|
||||||
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
|
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
|
||||||
|
@ -164,30 +164,30 @@ export default {
|
||||||
handleSelectionChange(val) {
|
handleSelectionChange(val) {
|
||||||
this.multipleSelection = val;
|
this.multipleSelection = val;
|
||||||
},
|
},
|
||||||
handleEdit(testPlan) {
|
handleEdit(test) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/performance/test/edit/' + testPlan.id,
|
path: '/performance/test/edit/' + test.id,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleCopy(testPlan) {
|
handleCopy(test) {
|
||||||
this.result = this.$post("/performance/copy", {id: testPlan.id}, () => {
|
this.result = this.$post("/performance/copy", {id: test.id}, () => {
|
||||||
this.$success(this.$t('commons.copy_success'));
|
this.$success(this.$t('commons.copy_success'));
|
||||||
this.search();
|
this.search();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleDelete(testPlan) {
|
handleDelete(test) {
|
||||||
this.$alert(this.$t('load_test.delete_confirm') + testPlan.name + "?", '', {
|
this.$alert(this.$t('load_test.delete_confirm') + test.name + "?", '', {
|
||||||
confirmButtonText: this.$t('commons.confirm'),
|
confirmButtonText: this.$t('commons.confirm'),
|
||||||
callback: (action) => {
|
callback: (action) => {
|
||||||
if (action === 'confirm') {
|
if (action === 'confirm') {
|
||||||
this._handleDelete(testPlan);
|
this._handleDelete(test);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_handleDelete(testPlan) {
|
_handleDelete(test) {
|
||||||
let data = {
|
let data = {
|
||||||
id: testPlan.id
|
id: test.id
|
||||||
};
|
};
|
||||||
|
|
||||||
this.result = this.$post(this.deletePath, data, () => {
|
this.result = this.$post(this.deletePath, data, () => {
|
|
@ -56,11 +56,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {Message} from "element-ui";
|
import {Message} from "element-ui";
|
||||||
|
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PerformanceBasicConfig",
|
name: "PerformanceBasicConfig",
|
||||||
props: {
|
props: {
|
||||||
testPlan: {
|
test: {
|
||||||
type: Object
|
type: Object
|
||||||
},
|
},
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
|
@ -81,23 +82,36 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.testPlan.id) {
|
if (this.test.id) {
|
||||||
this.getFileMetadata(this.testPlan)
|
this.getFileMetadata(this.test)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
testPlan() {
|
test() {
|
||||||
if (this.testPlan.id) {
|
if (this.test.id) {
|
||||||
this.getFileMetadata(this.testPlan)
|
this.getFileMetadata(this.test)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uploadList() {
|
||||||
|
let self = this;
|
||||||
|
let fileList = self.uploadList.filter(f => f.name.endsWith(".jmx"));
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
let file = fileList[0];
|
||||||
|
let jmxReader = new FileReader();
|
||||||
|
jmxReader.onload = function (event) {
|
||||||
|
let threadGroups = findThreadGroup(event.target.result);
|
||||||
|
self.$emit('fileChange', threadGroups);
|
||||||
|
};
|
||||||
|
jmxReader.readAsText(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getFileMetadata(testPlan) {
|
getFileMetadata(test) {
|
||||||
this.fileList = [];
|
this.fileList = [];
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.uploadList = [];
|
this.uploadList = [];
|
||||||
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
|
this.result = this.$get(this.getFileMetadataPath + "/" + test.id, response => {
|
||||||
let files = response.data;
|
let files = response.data;
|
||||||
|
|
||||||
if (!files) {
|
if (!files) {
|
||||||
|
|
|
@ -1,91 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-loading="result.loading" class="pressure-config-container">
|
<div v-loading="result.loading" class="pressure-config-container">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10">
|
<el-col>
|
||||||
<el-form :inline="true">
|
<el-form :inline="true">
|
||||||
<el-form-item>
|
<el-form-item :label="$t('load_test.select_resource_pool')">
|
||||||
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
:placeholder="$t('load_test.input_thread_num')"
|
|
||||||
v-model="threadNumber"
|
|
||||||
@change="calculateChart"
|
|
||||||
:min="1"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-form :inline="true">
|
|
||||||
<el-form-item>
|
|
||||||
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
:placeholder="$t('load_test.duration')"
|
|
||||||
v-model="duration"
|
|
||||||
:min="1"
|
|
||||||
@change="calculateChart"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<el-form :inline="true">
|
|
||||||
<el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-switch v-model="rpsLimitEnable"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="isReadOnly || !rpsLimitEnable"
|
|
||||||
:placeholder="$t('load_test.input_rps_limit')"
|
|
||||||
v-model="rpsLimit"
|
|
||||||
@change="calculateChart"
|
|
||||||
:min="1"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<el-form :inline="true" class="input-bottom-border">
|
|
||||||
<el-form-item>
|
|
||||||
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
placeholder=""
|
|
||||||
:min="1"
|
|
||||||
:max="duration"
|
|
||||||
v-model="rampUpTime"
|
|
||||||
@change="calculateChart"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-input-number
|
|
||||||
:disabled="isReadOnly"
|
|
||||||
placeholder=""
|
|
||||||
:min="1"
|
|
||||||
:max="Math.min(threadNumber, rampUpTime)"
|
|
||||||
v-model="step"
|
|
||||||
@change="calculateChart"
|
|
||||||
size="mini"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
|
|
||||||
</el-form-item>
|
|
||||||
</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" :disabled="isReadOnly" size="mini">
|
<el-select v-model="resourcePool" :disabled="isReadOnly" size="mini">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in resourcePools"
|
v-for="item in resourcePools"
|
||||||
|
@ -96,11 +14,77 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<ms-chart class="chart-container" ref="chart1" :options="options" :autoresize="true"></ms-chart>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="14">
|
</el-row>
|
||||||
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
|
<el-row>
|
||||||
<ms-chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></ms-chart>
|
<el-collapse v-model="activeNames">
|
||||||
</el-col>
|
<el-collapse-item :title="threadGroup.attributes.testname" :name="index"
|
||||||
|
v-for="(threadGroup, index) in threadGroups"
|
||||||
|
:key="index">
|
||||||
|
<el-col :span="10">
|
||||||
|
<el-form :inline="true">
|
||||||
|
<el-form-item :label="$t('load_test.thread_num')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
:placeholder="$t('load_test.input_thread_num')"
|
||||||
|
v-model="threadGroup.threadNumber"
|
||||||
|
@change="calculateChart(threadGroup)"
|
||||||
|
:min="1"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<br>
|
||||||
|
<el-form-item :label="$t('load_test.duration')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
:placeholder="$t('load_test.duration')"
|
||||||
|
v-model="threadGroup.duration"
|
||||||
|
:min="1"
|
||||||
|
@change="calculateChart(threadGroup)"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<br>
|
||||||
|
<el-form-item :label="$t('load_test.rps_limit')">
|
||||||
|
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
|
||||||
|
|
||||||
|
<el-input-number
|
||||||
|
:disabled="isReadOnly || !threadGroup.rpsLimitEnable"
|
||||||
|
:placeholder="$t('load_test.input_rps_limit')"
|
||||||
|
v-model="threadGroup.rpsLimit"
|
||||||
|
@change="calculateChart(threadGroup)"
|
||||||
|
:min="1"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<br>
|
||||||
|
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
placeholder=""
|
||||||
|
:min="1"
|
||||||
|
:max="threadGroup.duration"
|
||||||
|
v-model="threadGroup.rampUpTime"
|
||||||
|
@change="calculateChart(threadGroup)"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
placeholder=""
|
||||||
|
:min="1"
|
||||||
|
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||||
|
v-model="threadGroup.step"
|
||||||
|
@change="calculateChart(threadGroup)"
|
||||||
|
size="mini"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||||
|
</el-form>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14">
|
||||||
|
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
|
||||||
|
<ms-chart class="chart-container" :options="threadGroup.options" :autoresize="true"></ms-chart>
|
||||||
|
</el-col>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -108,6 +92,7 @@
|
||||||
<script>
|
<script>
|
||||||
import echarts from "echarts";
|
import echarts from "echarts";
|
||||||
import MsChart from "@/business/components/common/chart/MsChart";
|
import MsChart from "@/business/components/common/chart/MsChart";
|
||||||
|
import {findTestPlan, findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
|
||||||
|
|
||||||
const TARGET_LEVEL = "TargetLevel";
|
const TARGET_LEVEL = "TargetLevel";
|
||||||
const RAMP_UP = "RampUp";
|
const RAMP_UP = "RampUp";
|
||||||
|
@ -115,12 +100,22 @@ const STEPS = "Steps";
|
||||||
const DURATION = "duration";
|
const DURATION = "duration";
|
||||||
const RPS_LIMIT = "rpsLimit";
|
const RPS_LIMIT = "rpsLimit";
|
||||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||||
|
const HOLD = "Hold";
|
||||||
|
|
||||||
|
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 + ')';
|
||||||
|
}
|
||||||
|
const hexToRgb = function (hex) {
|
||||||
|
return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5))
|
||||||
|
+ ',' + parseInt('0x' + hex.slice(5, 7)) + ')';
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PerformancePressureConfig",
|
name: "PerformancePressureConfig",
|
||||||
components: {MsChart},
|
components: {MsChart},
|
||||||
props: {
|
props: {
|
||||||
testPlan: {
|
test: {
|
||||||
type: Object
|
type: Object
|
||||||
},
|
},
|
||||||
testId: {
|
testId: {
|
||||||
|
@ -134,35 +129,38 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
result: {},
|
result: {},
|
||||||
threadNumber: 10,
|
threadNumber: 0,
|
||||||
duration: 10,
|
duration: 0,
|
||||||
rampUpTime: 10,
|
rampUpTime: 0,
|
||||||
step: 10,
|
step: 0,
|
||||||
rpsLimit: 10,
|
rpsLimit: 0,
|
||||||
rpsLimitEnable: false,
|
rpsLimitEnable: false,
|
||||||
orgOptions: {},
|
options: {},
|
||||||
resourcePool: null,
|
resourcePool: null,
|
||||||
resourcePools: [],
|
resourcePools: [],
|
||||||
|
activeNames: ["0"],
|
||||||
|
threadGroups: [],
|
||||||
|
serializeThreadgroups: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.testId) {
|
if (this.testId) {
|
||||||
this.getLoadConfig();
|
this.getJmxContent();
|
||||||
} else {
|
} else {
|
||||||
this.calculateChart();
|
this.calculateTotalChart();
|
||||||
}
|
}
|
||||||
this.resourcePool = this.testPlan.testResourcePoolId;
|
this.resourcePool = this.test.testResourcePoolId;
|
||||||
this.getResourcePools();
|
this.getResourcePools();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
testPlan(n) {
|
test(n) {
|
||||||
this.resourcePool = n.testResourcePoolId;
|
this.resourcePool = n.testResourcePoolId;
|
||||||
},
|
},
|
||||||
testId() {
|
testId() {
|
||||||
if (this.testId) {
|
if (this.testId) {
|
||||||
this.getLoadConfig();
|
this.getJmxContent();
|
||||||
} else {
|
} else {
|
||||||
this.calculateChart();
|
this.calculateTotalChart();
|
||||||
}
|
}
|
||||||
this.getResourcePools();
|
this.getResourcePools();
|
||||||
},
|
},
|
||||||
|
@ -178,56 +176,191 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getLoadConfig() {
|
getLoadConfig() {
|
||||||
if (this.testId) {
|
this.$get('/performance/get-load-config/' + this.testId, (response) => {
|
||||||
|
if (response.data) {
|
||||||
this.$get('/performance/get-load-config/' + this.testId, (response) => {
|
let data = JSON.parse(response.data);
|
||||||
if (response.data) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
let data = JSON.parse(response.data);
|
let d = data[i];
|
||||||
|
if (d instanceof Array) {
|
||||||
data.forEach(d => {
|
d.forEach(item => {
|
||||||
|
switch (item.key) {
|
||||||
|
case TARGET_LEVEL:
|
||||||
|
this.threadGroups[i].threadNumber = item.value;
|
||||||
|
break;
|
||||||
|
case RAMP_UP:
|
||||||
|
this.threadGroups[i].rampUpTime = item.value;
|
||||||
|
break;
|
||||||
|
case DURATION:
|
||||||
|
this.threadGroups[i].duration = item.value;
|
||||||
|
break;
|
||||||
|
case STEPS:
|
||||||
|
this.threadGroups[i].step = item.value;
|
||||||
|
break;
|
||||||
|
case RPS_LIMIT:
|
||||||
|
this.threadGroups[i].rpsLimit = item.value;
|
||||||
|
break;
|
||||||
|
case RPS_LIMIT_ENABLE:
|
||||||
|
this.threadGroups[i].rpsLimitEnable = item.value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.calculateChart(this.threadGroups[i]);
|
||||||
|
} else {
|
||||||
switch (d.key) {
|
switch (d.key) {
|
||||||
case TARGET_LEVEL:
|
case TARGET_LEVEL:
|
||||||
this.threadNumber = d.value;
|
this.threadGroups[0].threadNumber = d.value;
|
||||||
break;
|
break;
|
||||||
case RAMP_UP:
|
case RAMP_UP:
|
||||||
this.rampUpTime = d.value;
|
this.threadGroups[0].rampUpTime = d.value;
|
||||||
break;
|
break;
|
||||||
case DURATION:
|
case DURATION:
|
||||||
this.duration = d.value;
|
this.threadGroups[0].duration = d.value;
|
||||||
break;
|
break;
|
||||||
case STEPS:
|
case STEPS:
|
||||||
this.step = d.value;
|
this.threadGroups[0].step = d.value;
|
||||||
break;
|
break;
|
||||||
case RPS_LIMIT:
|
case RPS_LIMIT:
|
||||||
this.rpsLimit = d.value;
|
this.threadGroups[0].rpsLimit = d.value;
|
||||||
break;
|
break;
|
||||||
case RPS_LIMIT_ENABLE:
|
case RPS_LIMIT_ENABLE:
|
||||||
this.rpsLimitEnable = d.value;
|
this.threadGroups[0].rpsLimitEnable = d.value;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
this.calculateChart(this.threadGroups[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.calculateTotalChart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getJmxContent() {
|
||||||
|
if (this.testId) {
|
||||||
|
this.$get('/performance/get-jmx-content/' + this.testId, (response) => {
|
||||||
|
if (response.data) {
|
||||||
|
let testPlan = findTestPlan(response.data);
|
||||||
|
testPlan.elements.forEach(e => {
|
||||||
|
if (e.attributes.name === 'TestPlan.serialize_threadgroups') {
|
||||||
|
this.serializeThreadgroups = Boolean(e.elements[0].text);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
this.threadGroups = findThreadGroup(response.data);
|
||||||
this.threadNumber = this.threadNumber || 10;
|
this.threadGroups.forEach(tg => {
|
||||||
this.duration = this.duration || 30;
|
tg.options = {};
|
||||||
this.rampUpTime = this.rampUpTime || 12;
|
});
|
||||||
this.step = this.step || 3;
|
this.getLoadConfig();
|
||||||
this.rpsLimit = this.rpsLimit || 10;
|
|
||||||
|
|
||||||
this.calculateChart();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
calculateChart() {
|
calculateTotalChart() {
|
||||||
if (this.duration < this.rampUpTime) {
|
let handler = this;
|
||||||
this.rampUpTime = this.duration;
|
if (handler.duration < handler.rampUpTime) {
|
||||||
|
handler.rampUpTime = handler.duration;
|
||||||
}
|
}
|
||||||
if (this.rampUpTime < this.step) {
|
if (handler.rampUpTime < handler.step) {
|
||||||
this.step = this.rampUpTime;
|
handler.step = handler.rampUpTime;
|
||||||
}
|
}
|
||||||
this.orgOptions = {
|
handler.options = {
|
||||||
|
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
},
|
||||||
|
series: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < handler.threadGroups.length; i++) {
|
||||||
|
let seriesData = {
|
||||||
|
name: handler.threadGroups[i].attributes.testname,
|
||||||
|
data: [],
|
||||||
|
type: 'line',
|
||||||
|
step: 'start',
|
||||||
|
smooth: false,
|
||||||
|
symbolSize: 5,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
normal: {
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
normal: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||||
|
offset: 0,
|
||||||
|
color: hexToRgba(handler.options.color[i], 0.3),
|
||||||
|
}, {
|
||||||
|
offset: 0.8,
|
||||||
|
color: hexToRgba(handler.options.color[i], 0),
|
||||||
|
}], false),
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowBlur: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
normal: {
|
||||||
|
color: hexToRgb(handler.options.color[i]),
|
||||||
|
borderColor: 'rgba(137,189,2,0.27)',
|
||||||
|
borderWidth: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let tg = handler.threadGroups[i];
|
||||||
|
|
||||||
|
let timePeriod = Math.floor(tg.rampUpTime / tg.step);
|
||||||
|
let timeInc = timePeriod;
|
||||||
|
|
||||||
|
let threadPeriod = Math.floor(tg.threadNumber / tg.step);
|
||||||
|
let threadInc1 = Math.floor(tg.threadNumber / tg.step);
|
||||||
|
let threadInc2 = Math.ceil(tg.threadNumber / tg.step);
|
||||||
|
let inc2count = tg.threadNumber - tg.step * threadInc1;
|
||||||
|
for (let j = 0; j <= tg.duration; j++) {
|
||||||
|
|
||||||
|
if (j > timePeriod) {
|
||||||
|
timePeriod += timeInc;
|
||||||
|
if (inc2count > 0) {
|
||||||
|
threadPeriod = threadPeriod + threadInc2;
|
||||||
|
inc2count--;
|
||||||
|
} else {
|
||||||
|
threadPeriod = threadPeriod + threadInc1;
|
||||||
|
}
|
||||||
|
if (threadPeriod > tg.threadNumber) {
|
||||||
|
threadPeriod = tg.threadNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// x 轴
|
||||||
|
let xAxis = handler.options.xAxis.data;
|
||||||
|
if (xAxis.indexOf(j) < 0) {
|
||||||
|
xAxis.push(j);
|
||||||
|
}
|
||||||
|
seriesData.data.push(threadPeriod);
|
||||||
|
}
|
||||||
|
handler.options.series.push(seriesData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateChart(threadGroup) {
|
||||||
|
let handler = this;
|
||||||
|
if (threadGroup) {
|
||||||
|
handler = threadGroup;
|
||||||
|
}
|
||||||
|
if (handler.duration < handler.rampUpTime) {
|
||||||
|
handler.rampUpTime = handler.duration;
|
||||||
|
}
|
||||||
|
if (handler.rampUpTime < handler.step) {
|
||||||
|
handler.step = handler.rampUpTime;
|
||||||
|
}
|
||||||
|
handler.options = {
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
|
@ -280,16 +413,16 @@ export default {
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
let timePeriod = Math.floor(this.rampUpTime / this.step);
|
let timePeriod = Math.floor(handler.rampUpTime / handler.step);
|
||||||
let timeInc = timePeriod;
|
let timeInc = timePeriod;
|
||||||
|
|
||||||
let threadPeriod = Math.floor(this.threadNumber / this.step);
|
let threadPeriod = Math.floor(handler.threadNumber / handler.step);
|
||||||
let threadInc1 = Math.floor(this.threadNumber / this.step);
|
let threadInc1 = Math.floor(handler.threadNumber / handler.step);
|
||||||
let threadInc2 = Math.ceil(this.threadNumber / this.step);
|
let threadInc2 = Math.ceil(handler.threadNumber / handler.step);
|
||||||
let inc2count = this.threadNumber - this.step * threadInc1;
|
let inc2count = handler.threadNumber - handler.step * threadInc1;
|
||||||
for (let i = 0; i <= this.duration; i++) {
|
for (let i = 0; i <= handler.duration; i++) {
|
||||||
// x 轴
|
// x 轴
|
||||||
this.orgOptions.xAxis.data.push(i);
|
handler.options.xAxis.data.push(i);
|
||||||
if (i > timePeriod) {
|
if (i > timePeriod) {
|
||||||
timePeriod += timeInc;
|
timePeriod += timeInc;
|
||||||
if (inc2count > 0) {
|
if (inc2count > 0) {
|
||||||
|
@ -298,14 +431,15 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
threadPeriod = threadPeriod + threadInc1;
|
threadPeriod = threadPeriod + threadInc1;
|
||||||
}
|
}
|
||||||
if (threadPeriod > this.threadNumber) {
|
if (threadPeriod > handler.threadNumber) {
|
||||||
threadPeriod = this.threadNumber;
|
threadPeriod = handler.threadNumber;
|
||||||
}
|
}
|
||||||
this.orgOptions.series[0].data.push(threadPeriod);
|
handler.options.series[0].data.push(threadPeriod);
|
||||||
} else {
|
} else {
|
||||||
this.orgOptions.series[0].data.push(threadPeriod);
|
handler.options.series[0].data.push(threadPeriod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.calculateTotalChart();
|
||||||
},
|
},
|
||||||
validConfig() {
|
validConfig() {
|
||||||
if (!this.resourcePool) {
|
if (!this.resourcePool) {
|
||||||
|
@ -315,24 +449,32 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.threadNumber || !this.duration || !this.rampUpTime || !this.step || !this.rpsLimit) {
|
for (let i = 0; i < this.threadGroups.length; i++) {
|
||||||
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration
|
||||||
this.$emit('changeActive', '1');
|
|| !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step || !this.threadGroups[i].rpsLimit) {
|
||||||
return false;
|
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
||||||
|
this.$emit('changeActive', '1');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
convertProperty() {
|
convertProperty() {
|
||||||
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
|
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
|
||||||
return [
|
let result = [];
|
||||||
{key: TARGET_LEVEL, value: this.threadNumber},
|
for (let i = 0; i < this.threadGroups.length; i++) {
|
||||||
{key: RAMP_UP, value: this.rampUpTime},
|
result.push([
|
||||||
{key: STEPS, value: this.step},
|
{key: TARGET_LEVEL, value: this.threadGroups[i].threadNumber},
|
||||||
{key: DURATION, value: this.duration},
|
{key: RAMP_UP, value: this.threadGroups[i].rampUpTime},
|
||||||
{key: RPS_LIMIT, value: this.rpsLimit},
|
{key: STEPS, value: this.threadGroups[i].step},
|
||||||
{key: RPS_LIMIT_ENABLE, value: this.rpsLimitEnable},
|
{key: DURATION, value: this.threadGroups[i].duration},
|
||||||
];
|
{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},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,6 +498,7 @@ export default {
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-col .el-form {
|
.el-col .el-form {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import {xml2json} from "xml-js";
|
||||||
|
|
||||||
|
let travel = function (elements, threadGroups) {
|
||||||
|
if (!elements) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let element of elements) {
|
||||||
|
if (element.name === 'ThreadGroup') {
|
||||||
|
threadGroups.push(element);
|
||||||
|
}
|
||||||
|
travel(element.elements, threadGroups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findThreadGroup(jmxContent) {
|
||||||
|
let jmxJson = JSON.parse(xml2json(jmxContent));
|
||||||
|
let threadGroups = [];
|
||||||
|
travel(jmxJson.elements, threadGroups);
|
||||||
|
return threadGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function findTestPlan(jmxContent) {
|
||||||
|
let jmxJson = JSON.parse(xml2json(jmxContent));
|
||||||
|
for (let element of jmxJson.elements[0].elements[0].elements) {
|
||||||
|
if (element.name === 'TestPlan') {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<div class="header-title" v-loading="result.loading">
|
||||||
|
<div>
|
||||||
|
<div>{{ $t('organization.integration.select_defect_platform') }}</div>
|
||||||
|
<el-radio-group v-model="platform" style="margin-top: 10px" @change="change">
|
||||||
|
<el-radio label="Tapd">
|
||||||
|
<img class="platform" src="../../../../assets/tapd.png" alt="Tapd"/>
|
||||||
|
</el-radio>
|
||||||
|
<el-radio label="Jira">
|
||||||
|
<img class="platform" src="../../../../assets/jira.png" alt="Jira"/>
|
||||||
|
</el-radio>
|
||||||
|
<el-radio label="Zentao">
|
||||||
|
<img class="platform" src="../../../../assets/zentao.jpg" alt="Zentao"/>
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<tapd-setting v-if="tapdEnable" ref="tapdSetting"/>
|
||||||
|
<jira-setting v-if="jiraEnable" ref="jiraSetting"/>
|
||||||
|
<zentao-setting v-if="zentaoEnable" ref="zentaoSetting"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TapdSetting from "@/business/components/settings/organization/components/TapdSetting";
|
||||||
|
import JiraSetting from "@/business/components/settings/organization/components/JiraSetting";
|
||||||
|
import ZentaoSetting from "@/business/components/settings/organization/components/ZentaoSetting";
|
||||||
|
import {JIRA, TAPD, ZEN_TAO} from "@/common/js/constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "BugManagement",
|
||||||
|
components: {TapdSetting, JiraSetting, ZentaoSetting},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tapdEnable: true,
|
||||||
|
jiraEnable: false,
|
||||||
|
zentaoEnable: false,
|
||||||
|
result: {},
|
||||||
|
platform: TAPD
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
change(platform) {
|
||||||
|
if (platform === TAPD) {
|
||||||
|
this.tapdEnable = true;
|
||||||
|
this.jiraEnable = false;
|
||||||
|
this.zentaoEnable = false;
|
||||||
|
} else if (platform === JIRA) {
|
||||||
|
this.tapdEnable = false;
|
||||||
|
this.jiraEnable = true;
|
||||||
|
this.zentaoEnable = false;
|
||||||
|
} else if (platform === ZEN_TAO) {
|
||||||
|
this.tapdEnable = false;
|
||||||
|
this.jiraEnable = false;
|
||||||
|
this.zentaoEnable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header-title {
|
||||||
|
padding: 10px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform {
|
||||||
|
height: 90px;
|
||||||
|
vertical-align: middle
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
<el-card>
|
<el-card>
|
||||||
<el-tabs class="system-setting" v-model="activeName">
|
<el-tabs class="system-setting" v-model="activeName">
|
||||||
<el-tab-pane :label="$t('organization.defect_manage')" name="defect">
|
<el-tab-pane :label="$t('organization.defect_manage')" name="defect">
|
||||||
<defect-management/>
|
<bug-management/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
@ -10,12 +10,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import DefectManagement from "./IssuesManagement";
|
import BugManagement from "./BugManagement";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ServiceIntegration",
|
name: "ServiceIntegration",
|
||||||
components: {
|
components: {
|
||||||
DefectManagement
|
BugManagement
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
<template>
|
||||||
|
<div style="margin-left: 120px">
|
||||||
|
<el-button type="primary" size="mini" :disabled="!show" @click="testConnection">
|
||||||
|
{{ $t('ldap.test_connect') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="showEdit" size="mini" @click="edit">
|
||||||
|
{{ $t('commons.edit') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" v-if="showSave" size="mini" @click="save">
|
||||||
|
{{ $t('commons.save') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="showCancel" size="mini" @click="cancelEdit">
|
||||||
|
{{ $t('organization.integration.cancel_edit') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="info" size="mini" :disabled="!show" @click="cancelIntegration">
|
||||||
|
{{ $t('organization.integration.cancel_integration') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "BugManageBtn",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showEdit: true,
|
||||||
|
showSave: false,
|
||||||
|
showCancel: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
form: Object,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
testConnection() {
|
||||||
|
this.$emit("testConnection");
|
||||||
|
},
|
||||||
|
edit() {
|
||||||
|
this.$emit("update:show", false);
|
||||||
|
this.showEdit = false;
|
||||||
|
this.showSave = true;
|
||||||
|
this.showCancel = true;
|
||||||
|
},
|
||||||
|
cancelEdit() {
|
||||||
|
this.showEdit = true;
|
||||||
|
this.showCancel = false;
|
||||||
|
this.showSave = false;
|
||||||
|
this.$emit("update:show", true);
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.$emit("init");
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.$emit("save");
|
||||||
|
},
|
||||||
|
cancelIntegration() {
|
||||||
|
this.$emit("cancelIntegration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,59 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="header-title" v-loading="result.loading">
|
<div>
|
||||||
<div>
|
|
||||||
<div>{{ $t('organization.integration.select_defect_platform') }}</div>
|
|
||||||
<el-radio-group v-model="platform" style="margin-top: 10px" @change="change">
|
|
||||||
<el-radio label="Tapd">
|
|
||||||
<img class="platform" src="../../../../assets/tapd.png" alt="Tapd"/>
|
|
||||||
</el-radio>
|
|
||||||
<el-radio label="Jira">
|
|
||||||
<img class="platform" src="../../../../assets/jira.png" alt="Jira"/>
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="width: 500px">
|
<div style="width: 500px">
|
||||||
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
|
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
|
||||||
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
|
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
|
||||||
<el-form-item :label="$t('organization.integration.api_account')" prop="account">
|
<el-form-item :label="$t('organization.integration.account')" prop="account">
|
||||||
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
|
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('organization.integration.api_password')" prop="password">
|
<el-form-item :label="$t('organization.integration.password')" prop="password">
|
||||||
<el-input v-model="form.password" auto-complete="new-password"
|
<el-input v-model="form.password" auto-complete="new-password"
|
||||||
:placeholder="$t('organization.integration.input_api_password')" show-password/>
|
:placeholder="$t('organization.integration.input_api_password')" show-password/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('organization.integration.jira_url')" prop="url" v-if="platform === 'Jira'">
|
<el-form-item :label="$t('organization.integration.jira_url')" prop="url">
|
||||||
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_jira_url')"/>
|
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_jira_url')"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('organization.integration.jira_issuetype')" prop="issuetype"
|
<el-form-item :label="$t('organization.integration.jira_issuetype')" prop="issuetype">
|
||||||
v-if="platform === 'Jira'">
|
|
||||||
<el-input v-model="form.issuetype" :placeholder="$t('organization.integration.input_jira_issuetype')"/>
|
<el-input v-model="form.issuetype" :placeholder="$t('organization.integration.input_jira_issuetype')"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 120px">
|
<bug-manage-btn @save="save"
|
||||||
<el-button type="primary" size="mini" :disabled="!show" @click="testConnection">{{ $t('ldap.test_connect') }}
|
@init="init"
|
||||||
</el-button>
|
@testConnection="testConnection"
|
||||||
<el-button v-if="showEdit" size="mini" @click="edit">{{ $t('commons.edit') }}</el-button>
|
@cancelIntegration="cancelIntegration"
|
||||||
<el-button type="primary" v-if="showSave" size="mini" @click="save('form')">{{ $t('commons.save') }}</el-button>
|
:form="form"
|
||||||
<el-button v-if="showCancel" size="mini" @click="cancelEdit">{{ $t('organization.integration.cancel_edit') }}
|
:show.sync="show"
|
||||||
</el-button>
|
ref="bugBtn"/>
|
||||||
<el-button type="info" size="mini" @click="cancelIntegration('form')" :disabled="!show">
|
|
||||||
{{ $t('organization.integration.cancel_integration') }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="defect-tip">
|
<div class="defect-tip">
|
||||||
<div>{{ $t('organization.integration.use_tip') }}</div>
|
<div>{{ $t('organization.integration.use_tip') }}</div>
|
||||||
<div>
|
<div>
|
||||||
1. {{ $t('organization.integration.use_tip_tapd') }}
|
1. {{ $t('organization.integration.use_tip_jira') }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
2. {{ $t('organization.integration.use_tip_jira') }}
|
2. {{ $t('organization.integration.use_tip_two') }}
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
3. {{ $t('organization.integration.use_tip_two') }}
|
|
||||||
<router-link to="/track/project/all" style="margin-left: 5px">
|
<router-link to="/track/project/all" style="margin-left: 5px">
|
||||||
{{ $t('organization.integration.link_the_project_now') }}
|
{{ $t('organization.integration.link_the_project_now') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
@ -63,20 +43,20 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {getCurrentUser} from "../../../../common/js/utils";
|
import BugManageBtn from "@/business/components/settings/organization/components/BugManageBtn";
|
||||||
|
import {getCurrentUser} from "@/common/js/utils";
|
||||||
|
import {JIRA} from "@/common/js/constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "IssuesManagement",
|
name: "JiraSetting",
|
||||||
|
components: {BugManageBtn},
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {},
|
|
||||||
result: {},
|
|
||||||
platform: '',
|
|
||||||
orgId: '',
|
|
||||||
show: true,
|
show: true,
|
||||||
showEdit: true,
|
form: {},
|
||||||
showSave: false,
|
|
||||||
showCancel: false,
|
|
||||||
rules: {
|
rules: {
|
||||||
account: {
|
account: {
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -101,17 +81,14 @@ export default {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
this.init(this.platform);
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
init(platform) {
|
init() {
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
let param = {};
|
let param = {};
|
||||||
param.platform = platform;
|
param.platform = JIRA;
|
||||||
param.orgId = getCurrentUser().lastOrganizationId;
|
param.orgId = lastOrganizationId;
|
||||||
this.result = this.$post("service/integration/type", param, response => {
|
this.$parent.result = this.$post("service/integration/type", param, response => {
|
||||||
let data = response.data;
|
let data = response.data;
|
||||||
this.platform = data.platform;
|
|
||||||
if (data.configuration) {
|
if (data.configuration) {
|
||||||
let config = JSON.parse(data.configuration);
|
let config = JSON.parse(data.configuration);
|
||||||
this.$set(this.form, 'account', config.account);
|
this.$set(this.form, 'account', config.account);
|
||||||
|
@ -123,54 +100,13 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
edit() {
|
save() {
|
||||||
this.show = false;
|
this.$refs['form'].validate(valid => {
|
||||||
this.showEdit = false;
|
|
||||||
this.showSave = true;
|
|
||||||
this.showCancel = true;
|
|
||||||
},
|
|
||||||
cancelEdit() {
|
|
||||||
this.showEdit = true;
|
|
||||||
this.showCancel = false;
|
|
||||||
this.showSave = false;
|
|
||||||
this.show = true;
|
|
||||||
this.init(this.platform);
|
|
||||||
},
|
|
||||||
cancelIntegration() {
|
|
||||||
if (this.form.account && this.form.password && this.platform) {
|
|
||||||
|
|
||||||
this.$alert(this.$t('organization.integration.cancel_confirm') + this.platform + "?", '', {
|
|
||||||
confirmButtonText: this.$t('commons.confirm'),
|
|
||||||
callback: (action) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
let param = {};
|
|
||||||
param.orgId = getCurrentUser().lastOrganizationId;
|
|
||||||
param.platform = this.platform;
|
|
||||||
this.result = this.$post("service/integration/delete", param, () => {
|
|
||||||
this.$success(this.$t('organization.integration.successful_operation'));
|
|
||||||
this.init('');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
save(form) {
|
|
||||||
if (!this.platform) {
|
|
||||||
this.$warning(this.$t('organization.integration.choose_platform'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$refs[form].validate(valid => {
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
|
||||||
let formatUrl = this.form.url.trim();
|
let formatUrl = this.form.url.trim();
|
||||||
if (!formatUrl.endsWith('/')) {
|
if (!formatUrl.endsWith('/')) {
|
||||||
formatUrl = formatUrl + '/';
|
formatUrl = formatUrl + '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
let param = {};
|
let param = {};
|
||||||
let auth = {
|
let auth = {
|
||||||
account: this.form.account,
|
account: this.form.account,
|
||||||
|
@ -178,16 +114,16 @@ export default {
|
||||||
url: formatUrl,
|
url: formatUrl,
|
||||||
issuetype: this.form.issuetype
|
issuetype: this.form.issuetype
|
||||||
};
|
};
|
||||||
param.organizationId = getCurrentUser().lastOrganizationId;
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
param.platform = this.platform;
|
param.organizationId = lastOrganizationId;
|
||||||
|
param.platform = JIRA;
|
||||||
param.configuration = JSON.stringify(auth);
|
param.configuration = JSON.stringify(auth);
|
||||||
|
this.$parent.result = this.$post("service/integration/save", param, () => {
|
||||||
this.result = this.$post("service/integration/save", param, () => {
|
|
||||||
this.show = true;
|
this.show = true;
|
||||||
this.showEdit = true;
|
this.$refs.bugBtn.showEdit = true;
|
||||||
this.showSave = false;
|
this.$refs.bugBtn.showSave = false;
|
||||||
this.showCancel = false;
|
this.$refs.bugBtn.showCancel = false;
|
||||||
this.init(this.platform);
|
this.init();
|
||||||
this.$success(this.$t('commons.save_success'));
|
this.$success(this.$t('commons.save_success'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -195,27 +131,6 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
change(platform) {
|
|
||||||
this.show = true;
|
|
||||||
this.showEdit = true;
|
|
||||||
this.showCancel = false;
|
|
||||||
this.showSave = false;
|
|
||||||
let param = {};
|
|
||||||
param.orgId = getCurrentUser().lastOrganizationId;
|
|
||||||
param.platform = platform;
|
|
||||||
this.result = this.$post("service/integration/type", param, response => {
|
|
||||||
let data = response.data;
|
|
||||||
if (data.configuration) {
|
|
||||||
let config = JSON.parse(data.configuration);
|
|
||||||
this.$set(this.form, 'account', config.account);
|
|
||||||
this.$set(this.form, 'password', config.password);
|
|
||||||
this.$set(this.form, 'url', config.url);
|
|
||||||
this.$set(this.form, 'issuetype', config.issuetype);
|
|
||||||
} else {
|
|
||||||
this.clear();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clear() {
|
clear() {
|
||||||
this.$set(this.form, 'account', '');
|
this.$set(this.form, 'account', '');
|
||||||
this.$set(this.form, 'password', '');
|
this.$set(this.form, 'password', '');
|
||||||
|
@ -226,24 +141,41 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
testConnection() {
|
testConnection() {
|
||||||
if (this.form.account && this.form.password && this.platform) {
|
if (this.form.account && this.form.password) {
|
||||||
this.result = this.$get("issues/auth/" + this.platform, () => {
|
this.$parent.result = this.$get("issues/auth/" + JIRA, () => {
|
||||||
this.$success(this.$t('organization.integration.verified'));
|
this.$success(this.$t('organization.integration.verified'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
cancelIntegration() {
|
||||||
|
if (this.form.account && this.form.password) {
|
||||||
|
this.$alert(this.$t('organization.integration.cancel_confirm') + JIRA + "?", '', {
|
||||||
|
confirmButtonText: this.$t('commons.confirm'),
|
||||||
|
callback: (action) => {
|
||||||
|
if (action === 'confirm') {
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
|
let param = {};
|
||||||
|
param.orgId = lastOrganizationId;
|
||||||
|
param.platform = JIRA;
|
||||||
|
this.$parent.result = this.$post("service/integration/delete", param, () => {
|
||||||
|
this.$success(this.$t('organization.integration.successful_operation'));
|
||||||
|
this.init('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.header-title {
|
|
||||||
padding: 10px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.defect-tip {
|
.defect-tip {
|
||||||
background: #EDEDED;
|
background: #EDEDED;
|
||||||
border: solid #E1E1E1 1px;
|
border: solid #E1E1E1 1px;
|
||||||
|
@ -251,9 +183,4 @@ export default {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.platform {
|
|
||||||
height: 90px;
|
|
||||||
vertical-align: middle
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,166 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="width: 500px">
|
||||||
|
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
|
||||||
|
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
|
||||||
|
<el-form-item :label="$t('organization.integration.api_account')" prop="account">
|
||||||
|
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('organization.integration.api_password')" prop="password">
|
||||||
|
<el-input v-model="form.password" auto-complete="new-password"
|
||||||
|
:placeholder="$t('organization.integration.input_api_password')" show-password/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bug-manage-btn @save="save"
|
||||||
|
@init="init"
|
||||||
|
@testConnection="testConnection"
|
||||||
|
@cancelIntegration="cancelIntegration"
|
||||||
|
:form="form"
|
||||||
|
:show.sync="show"
|
||||||
|
ref="bugBtn"/>
|
||||||
|
|
||||||
|
<div class="defect-tip">
|
||||||
|
<div>{{ $t('organization.integration.use_tip') }}</div>
|
||||||
|
<div>
|
||||||
|
1. {{ $t('organization.integration.use_tip_tapd') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
2. {{ $t('organization.integration.use_tip_two') }}
|
||||||
|
<router-link to="/track/project/all" style="margin-left: 5px">
|
||||||
|
{{ $t('organization.integration.link_the_project_now') }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BugManageBtn from "@/business/components/settings/organization/components/BugManageBtn";
|
||||||
|
import {getCurrentUser} from "@/common/js/utils";
|
||||||
|
import {TAPD} from "@/common/js/constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TapdSetting.vue",
|
||||||
|
components: {
|
||||||
|
BugManageBtn
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
form: {},
|
||||||
|
rules: {
|
||||||
|
account: {
|
||||||
|
required: true,
|
||||||
|
message: this.$t('organization.integration.input_api_account'),
|
||||||
|
trigger: ['change', 'blur']
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
required: true,
|
||||||
|
message: this.$t('organization.integration.input_api_password'),
|
||||||
|
trigger: ['change', 'blur']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init() {
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
|
let param = {};
|
||||||
|
param.platform = TAPD;
|
||||||
|
param.orgId = lastOrganizationId;
|
||||||
|
this.$parent.result = this.$post("service/integration/type", param, response => {
|
||||||
|
let data = response.data;
|
||||||
|
if (data.configuration) {
|
||||||
|
let config = JSON.parse(data.configuration);
|
||||||
|
this.$set(this.form, 'account', config.account);
|
||||||
|
this.$set(this.form, 'password', config.password);
|
||||||
|
} else {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.$refs['form'].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
|
||||||
|
let param = {};
|
||||||
|
let auth = {
|
||||||
|
account: this.form.account,
|
||||||
|
password: this.form.password,
|
||||||
|
};
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
|
param.organizationId = lastOrganizationId;
|
||||||
|
param.platform = TAPD;
|
||||||
|
param.configuration = JSON.stringify(auth);
|
||||||
|
|
||||||
|
this.$parent.result = this.$post("service/integration/save", param, () => {
|
||||||
|
this.show = true;
|
||||||
|
this.$refs.bugBtn.showEdit = true;
|
||||||
|
this.$refs.bugBtn.showSave = false;
|
||||||
|
this.$refs.bugBtn.showCancel = false;
|
||||||
|
this.init();
|
||||||
|
this.$success(this.$t('commons.save_success'));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.$set(this.form, 'account', '');
|
||||||
|
this.$set(this.form, 'password', '');
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.clearValidate();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
testConnection() {
|
||||||
|
if (this.form.account && this.form.password) {
|
||||||
|
this.$parent.result = this.$get("issues/auth/" + TAPD, () => {
|
||||||
|
this.$success(this.$t('organization.integration.verified'));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelIntegration() {
|
||||||
|
if (this.form.account && this.form.password) {
|
||||||
|
this.$alert(this.$t('organization.integration.cancel_confirm') + TAPD + "?", '', {
|
||||||
|
confirmButtonText: this.$t('commons.confirm'),
|
||||||
|
callback: (action) => {
|
||||||
|
if (action === 'confirm') {
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
|
let param = {};
|
||||||
|
param.orgId = lastOrganizationId;
|
||||||
|
param.platform = TAPD;
|
||||||
|
this.$parent.result = this.$post("service/integration/delete", param, () => {
|
||||||
|
this.$success(this.$t('organization.integration.successful_operation'));
|
||||||
|
this.init('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.defect-tip {
|
||||||
|
background: #EDEDED;
|
||||||
|
border: solid #E1E1E1 1px;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,164 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="width: 500px">
|
||||||
|
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
|
||||||
|
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
|
||||||
|
<el-form-item :label="$t('organization.integration.app_name')" prop="account">
|
||||||
|
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_app_name')"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('organization.integration.app_key')" prop="password">
|
||||||
|
<el-input v-model="form.password" auto-complete="new-password"
|
||||||
|
:placeholder="$t('organization.integration.input_app_key')" show-password/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bug-manage-btn @save="save"
|
||||||
|
@init="init"
|
||||||
|
@testConnection="testConnection"
|
||||||
|
@cancelIntegration="cancelIntegration"
|
||||||
|
:form="form"
|
||||||
|
:show.sync="show"
|
||||||
|
ref="bugBtn"/>
|
||||||
|
|
||||||
|
<div class="defect-tip">
|
||||||
|
<div>{{ $t('organization.integration.use_tip') }}</div>
|
||||||
|
<div>
|
||||||
|
1. {{ $t('organization.integration.use_tip_zentao') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
2. {{ $t('organization.integration.use_tip_two') }}
|
||||||
|
<router-link to="/track/project/all" style="margin-left: 5px">
|
||||||
|
{{ $t('organization.integration.link_the_project_now') }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BugManageBtn from "@/business/components/settings/organization/components/BugManageBtn";
|
||||||
|
import {getCurrentUser} from "@/common/js/utils";
|
||||||
|
import {ZEN_TAO} from "@/common/js/constants";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ZentaoSetting",
|
||||||
|
components: {
|
||||||
|
BugManageBtn
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
form: {},
|
||||||
|
rules: {
|
||||||
|
account: {
|
||||||
|
required: true,
|
||||||
|
message: this.$t('organization.integration.input_app_name'),
|
||||||
|
trigger: ['change', 'blur']
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
required: true,
|
||||||
|
message: this.$t('organization.integration.input_app_key'),
|
||||||
|
trigger: ['change', 'blur']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
save() {
|
||||||
|
this.$refs['form'].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
|
let param = {};
|
||||||
|
let auth = {
|
||||||
|
account: this.form.account,
|
||||||
|
password: this.form.password,
|
||||||
|
};
|
||||||
|
param.organizationId = lastOrganizationId;
|
||||||
|
param.platform = ZEN_TAO;
|
||||||
|
param.configuration = JSON.stringify(auth);
|
||||||
|
|
||||||
|
this.$parent.result = this.$post("service/integration/save", param, () => {
|
||||||
|
this.show = true;
|
||||||
|
this.$refs.bugBtn.showEdit = true;
|
||||||
|
this.$refs.bugBtn.showSave = false;
|
||||||
|
this.$refs.bugBtn.showCancel = false;
|
||||||
|
this.init();
|
||||||
|
this.$success(this.$t('commons.save_success'));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
|
let param = {};
|
||||||
|
param.platform = ZEN_TAO;
|
||||||
|
param.orgId = lastOrganizationId;
|
||||||
|
this.$parent.result = this.$post("service/integration/type", param, response => {
|
||||||
|
let data = response.data;
|
||||||
|
if (data.configuration) {
|
||||||
|
let config = JSON.parse(data.configuration);
|
||||||
|
this.$set(this.form, 'account', config.account);
|
||||||
|
this.$set(this.form, 'password', config.password);
|
||||||
|
} else {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.$set(this.form, 'account', '');
|
||||||
|
this.$set(this.form, 'password', '');
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.clearValidate();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
testConnection() {
|
||||||
|
if (this.form.account && this.form.password) {
|
||||||
|
this.$parent.result = this.$get("issues/auth/" + ZEN_TAO, () => {
|
||||||
|
this.$success(this.$t('organization.integration.verified'));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelIntegration() {
|
||||||
|
if (this.form.account && this.form.password) {
|
||||||
|
this.$alert(this.$t('organization.integration.cancel_confirm') + ZEN_TAO + "?", '', {
|
||||||
|
confirmButtonText: this.$t('commons.confirm'),
|
||||||
|
callback: (action) => {
|
||||||
|
if (action === 'confirm') {
|
||||||
|
const {lastOrganizationId} = getCurrentUser();
|
||||||
|
let param = {};
|
||||||
|
param.orgId = lastOrganizationId;
|
||||||
|
param.platform = ZEN_TAO;
|
||||||
|
this.$parent.result = this.$post("service/integration/delete", param, () => {
|
||||||
|
this.$success(this.$t('organization.integration.successful_operation'));
|
||||||
|
this.init('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.defect-tip {
|
||||||
|
background: #EDEDED;
|
||||||
|
border: solid #E1E1E1 1px;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -128,7 +128,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="$t('commons.operating')" min-width="100">
|
:label="$t('commons.operating')" min-width="150">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
||||||
@deleteClick="handleDelete(scope.row)">
|
@deleteClick="handleDelete(scope.row)">
|
||||||
|
@ -154,38 +154,39 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import MsCreateBox from '../../../settings/CreateBox';
|
import MsCreateBox from '../../../settings/CreateBox';
|
||||||
import TestCaseImport from '../components/TestCaseImport';
|
import TestCaseImport from '../components/TestCaseImport';
|
||||||
import TestCaseExport from '../components/TestCaseExport';
|
import TestCaseExport from '../components/TestCaseExport';
|
||||||
import MsTablePagination from '../../../../components/common/pagination/TablePagination';
|
import MsTablePagination from '../../../../components/common/pagination/TablePagination';
|
||||||
import NodeBreadcrumb from '../../common/NodeBreadcrumb';
|
import NodeBreadcrumb from '../../common/NodeBreadcrumb';
|
||||||
import MsTableHeader from '../../../../components/common/components/MsTableHeader';
|
import MsTableHeader from '../../../../components/common/components/MsTableHeader';
|
||||||
import PriorityTableItem from "../../common/tableItems/planview/PriorityTableItem";
|
import PriorityTableItem from "../../common/tableItems/planview/PriorityTableItem";
|
||||||
import TypeTableItem from "../../common/tableItems/planview/TypeTableItem";
|
import TypeTableItem from "../../common/tableItems/planview/TypeTableItem";
|
||||||
import MethodTableItem from "../../common/tableItems/planview/MethodTableItem";
|
import MethodTableItem from "../../common/tableItems/planview/MethodTableItem";
|
||||||
import MsTableOperator from "../../../common/components/MsTableOperator";
|
import MsTableOperator from "../../../common/components/MsTableOperator";
|
||||||
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
|
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
|
||||||
import MsTableButton from "../../../common/components/MsTableButton";
|
import MsTableButton from "../../../common/components/MsTableButton";
|
||||||
import {_filter, _sort} from "../../../../../common/js/utils";
|
import {_filter, _sort} from "../../../../../common/js/utils";
|
||||||
import {TEST_CASE_CONFIGS} from "../../../common/components/search/search-components";
|
import {TEST_CASE_CONFIGS} from "../../../common/components/search/search-components";
|
||||||
import ShowMoreBtn from "./ShowMoreBtn";
|
import ShowMoreBtn from "./ShowMoreBtn";
|
||||||
import BatchEdit from "./BatchEdit";
|
import BatchEdit from "./BatchEdit";
|
||||||
import {WORKSPACE_ID} from "../../../../../common/js/constants";
|
import {WORKSPACE_ID} from "../../../../../common/js/constants";
|
||||||
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
|
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
|
||||||
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
|
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
|
||||||
import TestCaseDetail from "./TestCaseDetail";
|
import TestCaseDetail from "./TestCaseDetail";
|
||||||
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
||||||
export default {
|
|
||||||
name: "TestCaseList",
|
export default {
|
||||||
components: {
|
name: "TestCaseList",
|
||||||
MsTableButton,
|
components: {
|
||||||
MsTableOperatorButton,
|
MsTableButton,
|
||||||
MsTableOperator,
|
MsTableOperatorButton,
|
||||||
MethodTableItem,
|
MsTableOperator,
|
||||||
TypeTableItem,
|
MethodTableItem,
|
||||||
PriorityTableItem,
|
TypeTableItem,
|
||||||
MsCreateBox,
|
PriorityTableItem,
|
||||||
TestCaseImport,
|
MsCreateBox,
|
||||||
|
TestCaseImport,
|
||||||
TestCaseExport,
|
TestCaseExport,
|
||||||
MsTablePagination,
|
MsTablePagination,
|
||||||
NodeBreadcrumb,
|
NodeBreadcrumb,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<related-test-plan-list ref="relatedTestPlanList"/>
|
<related-test-plan-list ref="relatedTestPlanList"/>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<review-list title="我的评审" ref="caseReviewList"/>
|
<review-list :title="$t('review.my_review')" ref="caseReviewList"/>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="9">
|
<el-col :span="9">
|
||||||
|
|
|
@ -114,6 +114,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
|
min-width="150"
|
||||||
:label="$t('commons.operating')">
|
:label="$t('commons.operating')">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
||||||
|
|
|
@ -183,7 +183,8 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:label="$t('commons.operating')">
|
min-width="100"
|
||||||
|
:label="$t('commons.operating')">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.edit')" icon="el-icon-edit"
|
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.edit')" icon="el-icon-edit"
|
||||||
@exec="handleEdit(scope.row)"/>
|
@exec="handleEdit(scope.row)"/>
|
||||||
|
|
|
@ -19,10 +19,10 @@
|
||||||
|
|
||||||
<el-tabs class="test-config" v-model="active" type="border-card" :stretch="true">
|
<el-tabs class="test-config" v-model="active" type="border-card" :stretch="true">
|
||||||
<el-tab-pane :label="$t('load_test.basic_config')">
|
<el-tab-pane :label="$t('load_test.basic_config')">
|
||||||
<performance-basic-config :is-read-only="true" :test-plan="test" ref="basicConfig"/>
|
<performance-basic-config :is-read-only="true" :test="test" ref="basicConfig"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('load_test.pressure_config')">
|
<el-tab-pane :label="$t('load_test.pressure_config')">
|
||||||
<performance-pressure-config :is-read-only="true" :test-plan="test" :test-id="id" ref="pressureConfig"/>
|
<performance-pressure-config :is-read-only="true" :test="test" :test-id="id" ref="pressureConfig"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
|
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
|
||||||
<performance-advanced-config :read-only="true" :test-id="id" ref="advancedConfig"/>
|
<performance-advanced-config :read-only="true" :test-id="id" ref="advancedConfig"/>
|
||||||
|
@ -35,22 +35,23 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import MsContainer from "../../../../../common/components/MsContainer";
|
import MsContainer from "../../../../../common/components/MsContainer";
|
||||||
import MsMainContainer from "../../../../../common/components/MsMainContainer";
|
import MsMainContainer from "../../../../../common/components/MsMainContainer";
|
||||||
import PerformanceBasicConfig from "../../../../../performance/test/components/PerformanceBasicConfig";
|
import PerformanceBasicConfig from "../../../../../performance/test/components/PerformanceBasicConfig";
|
||||||
import PerformancePressureConfig from "../../../../../performance/test/components/PerformancePressureConfig";
|
import PerformancePressureConfig from "../../../../../performance/test/components/PerformancePressureConfig";
|
||||||
import PerformanceAdvancedConfig from "../../../../../performance/test/components/PerformanceAdvancedConfig";
|
import PerformanceAdvancedConfig from "../../../../../performance/test/components/PerformanceAdvancedConfig";
|
||||||
export default {
|
|
||||||
name: "PerformanceTestDetail",
|
export default {
|
||||||
components: {
|
name: "PerformanceTestDetail",
|
||||||
PerformanceAdvancedConfig,
|
components: {
|
||||||
PerformancePressureConfig,
|
PerformanceAdvancedConfig,
|
||||||
PerformanceBasicConfig,
|
PerformancePressureConfig,
|
||||||
MsMainContainer,
|
PerformanceBasicConfig,
|
||||||
MsContainer
|
MsMainContainer,
|
||||||
},
|
MsContainer
|
||||||
data() {
|
},
|
||||||
return {
|
data() {
|
||||||
|
return {
|
||||||
result: {},
|
result: {},
|
||||||
test: {},
|
test: {},
|
||||||
savePath: "/performance/save",
|
savePath: "/performance/save",
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
|
min-width="100"
|
||||||
:label="$t('commons.operating')">
|
:label="$t('commons.operating')">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
|
min-width="100"
|
||||||
:label="$t('commons.operating')">
|
:label="$t('commons.operating')">
|
||||||
<template v-slot:default="scope">
|
<template v-slot:default="scope">
|
||||||
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.edit')" icon="el-icon-edit"
|
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.edit')" icon="el-icon-edit"
|
||||||
|
|
|
@ -65,13 +65,13 @@ body {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.adjust-table th:hover:after {
|
.adjust-table th:not([class*='el-table-column--selection']):hover:after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 25%;
|
top: 25%;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 50%;
|
height: 50%;
|
||||||
width: 3px;
|
width: 2px;
|
||||||
background-color: #EBEEF5;
|
background-color: #EBEEF5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ export const ZH_CN = 'zh_CN';
|
||||||
export const ZH_TW = 'zh_TW';
|
export const ZH_TW = 'zh_TW';
|
||||||
export const EN_US = 'en_US';
|
export const EN_US = 'en_US';
|
||||||
|
|
||||||
|
export const TAPD = 'Tapd';
|
||||||
|
export const JIRA = 'Jira';
|
||||||
|
export const ZEN_TAO = 'Zentao';
|
||||||
|
|
||||||
export const SCHEDULE_TYPE = {
|
export const SCHEDULE_TYPE = {
|
||||||
API_TEST: 'API_TEST',
|
API_TEST: 'API_TEST',
|
||||||
PERFORMANCE_TEST: 'PERFORMANCE_TEST'
|
PERFORMANCE_TEST: 'PERFORMANCE_TEST'
|
||||||
|
|
|
@ -250,15 +250,22 @@ export default {
|
||||||
basic_auth_info: 'Basic Auth account information:',
|
basic_auth_info: 'Basic Auth account information:',
|
||||||
api_account: 'API account',
|
api_account: 'API account',
|
||||||
api_password: 'API password',
|
api_password: 'API password',
|
||||||
|
app_name: 'APP name',
|
||||||
|
app_key: 'APP key',
|
||||||
|
account: 'Account',
|
||||||
|
password: 'Password',
|
||||||
jira_url: 'JIRA url',
|
jira_url: 'JIRA url',
|
||||||
jira_issuetype: 'JIRA issuetype',
|
jira_issuetype: 'JIRA issuetype',
|
||||||
input_api_account: 'please enter account',
|
input_api_account: 'please enter account',
|
||||||
input_api_password: 'Please enter password',
|
input_api_password: 'Please enter password',
|
||||||
|
input_app_name: 'Please enter the application code',
|
||||||
|
input_app_key: 'Please enter the key',
|
||||||
input_jira_url: 'Please enter Jira address, for example: https://metersphere.atlassian.net/',
|
input_jira_url: 'Please enter Jira address, for example: https://metersphere.atlassian.net/',
|
||||||
input_jira_issuetype: 'Please enter the question type',
|
input_jira_issuetype: 'Please enter the question type',
|
||||||
use_tip: 'Usage guidelines:',
|
use_tip: 'Usage guidelines:',
|
||||||
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
|
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
|
||||||
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
|
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
|
||||||
|
use_tip_zentao: 'Log in to ZenTao as a super administrator user, enter the background-secondary development-application, click [Add Application] to add an application',
|
||||||
use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the Metersphere project',
|
use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the Metersphere project',
|
||||||
link_the_project_now: 'Link the project now',
|
link_the_project_now: 'Link the project now',
|
||||||
cancel_edit: 'Cancel edit',
|
cancel_edit: 'Cancel edit',
|
||||||
|
|
|
@ -250,15 +250,22 @@ export default {
|
||||||
basic_auth_info: 'Basic Auth 账号信息:',
|
basic_auth_info: 'Basic Auth 账号信息:',
|
||||||
api_account: 'API 账号',
|
api_account: 'API 账号',
|
||||||
api_password: 'API 口令',
|
api_password: 'API 口令',
|
||||||
|
app_name: '应用代号',
|
||||||
|
app_key: '密钥',
|
||||||
|
account: '账号',
|
||||||
|
password: '密码',
|
||||||
jira_url: 'JIRA 地址',
|
jira_url: 'JIRA 地址',
|
||||||
jira_issuetype: '问题类型',
|
jira_issuetype: '问题类型',
|
||||||
input_api_account: '请输入账号',
|
input_api_account: '请输入账号',
|
||||||
input_api_password: '请输入口令',
|
input_api_password: '请输入口令',
|
||||||
|
input_app_name: '请输入应用代号',
|
||||||
|
input_app_key: '请输入密钥',
|
||||||
input_jira_url: '请输入Jira地址,例:https://metersphere.atlassian.net/',
|
input_jira_url: '请输入Jira地址,例:https://metersphere.atlassian.net/',
|
||||||
input_jira_issuetype: '请输入问题类型',
|
input_jira_issuetype: '请输入问题类型',
|
||||||
use_tip: '使用指引:',
|
use_tip: '使用指引:',
|
||||||
use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
|
use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
|
||||||
use_tip_jira: 'Jira software server 认证信息为 账号密码,Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)',
|
use_tip_jira: 'Jira software server 认证信息为 账号密码,Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)',
|
||||||
|
use_tip_zentao: '用超级管理员用户登录禅道,进入后台-二次开发-应用,点击【添加应用】新增一个应用',
|
||||||
use_tip_two: '保存 Basic Auth 账号信息后,需要在 Metersphere 项目中手动关联 ID/key',
|
use_tip_two: '保存 Basic Auth 账号信息后,需要在 Metersphere 项目中手动关联 ID/key',
|
||||||
link_the_project_now: '马上关联项目',
|
link_the_project_now: '马上关联项目',
|
||||||
cancel_edit: '取消编辑',
|
cancel_edit: '取消编辑',
|
||||||
|
|
|
@ -250,15 +250,22 @@ export default {
|
||||||
basic_auth_info: 'Basic Auth 賬號信息:',
|
basic_auth_info: 'Basic Auth 賬號信息:',
|
||||||
api_account: 'API 賬號',
|
api_account: 'API 賬號',
|
||||||
api_password: 'API 口令',
|
api_password: 'API 口令',
|
||||||
|
app_name: '應用代號',
|
||||||
|
app_key: '密鑰',
|
||||||
|
account: '賬號',
|
||||||
|
password: '密碼',
|
||||||
jira_url: 'JIRA 地址',
|
jira_url: 'JIRA 地址',
|
||||||
jira_issuetype: '問題類型',
|
jira_issuetype: '問題類型',
|
||||||
input_api_account: '請輸入賬號',
|
input_api_account: '請輸入賬號',
|
||||||
input_api_password: '請輸入口令',
|
input_api_password: '請輸入口令',
|
||||||
|
input_app_name: '請輸入應用代號',
|
||||||
|
input_app_key: '請輸入密鑰',
|
||||||
input_jira_url: '請輸入Jira地址,例:https://metersphere.atlassian.net/',
|
input_jira_url: '請輸入Jira地址,例:https://metersphere.atlassian.net/',
|
||||||
input_jira_issuetype: '請輸入問題類型',
|
input_jira_issuetype: '請輸入問題類型',
|
||||||
use_tip: '使用指引:',
|
use_tip: '使用指引:',
|
||||||
use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢',
|
use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢',
|
||||||
use_tip_jira: 'Jira software server 認證信息為 賬號密碼,Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
|
use_tip_jira: 'Jira software server 認證信息為 賬號密碼,Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
|
||||||
|
use_tip_zentao: '用超級管理員用戶登錄禪道,進入後台-二次開發-應用,點擊【添加應用】添加一個應用',
|
||||||
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key',
|
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key',
|
||||||
link_the_project_now: '馬上關聯項目',
|
link_the_project_now: '馬上關聯項目',
|
||||||
cancel_edit: '取消編輯',
|
cancel_edit: '取消編輯',
|
||||||
|
|
Loading…
Reference in New Issue