Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
1aab595ab0
|
@ -0,0 +1,27 @@
|
|||
package io.metersphere.api.controller;
|
||||
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.service.ApiScenarioReportService;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/scenario/report")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
|
||||
public class APIScenarioReportController {
|
||||
|
||||
@Resource
|
||||
private ApiScenarioReportService apiReportService;
|
||||
|
||||
@GetMapping("/get/{reportId}")
|
||||
public APIReportResult get(@PathVariable String reportId) {
|
||||
return apiReportService.getCacheResult(reportId);
|
||||
}
|
||||
}
|
|
@ -70,10 +70,9 @@ public class ApiAutomationController {
|
|||
return apiAutomationService.getApiScenarios(ids);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/run/debug")
|
||||
@PostMapping(value = "/run")
|
||||
public void runDebug(@RequestPart("request") RunDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
apiAutomationService.run(request, bodyFiles);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package io.metersphere.api.controller;
|
||||
|
||||
import io.metersphere.api.dto.ApiMonitorSearch;
|
||||
import io.metersphere.api.dto.ApiResponseCodeMonitor;
|
||||
import io.metersphere.api.dto.ApiResponseTimeMonitor;
|
||||
import io.metersphere.api.service.APIMonitorService;
|
||||
import io.metersphere.commons.constants.RoleConstants;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/monitor")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
|
||||
public class ApiMonitorController {
|
||||
|
||||
@Resource
|
||||
private APIMonitorService apiMonitorService;
|
||||
|
||||
/**
|
||||
* 查询所有接口
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public List<ApiMonitorSearch> apiList() {
|
||||
return apiMonitorService.list();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询响应时间
|
||||
*/
|
||||
@GetMapping("/getResponseTime")
|
||||
public List<ApiResponseTimeMonitor> responseTimeData(@RequestHeader("apiUrl") String url, String startTime, String endTime) {
|
||||
return apiMonitorService.getApiResponseTimeData(url, startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询状态码
|
||||
*/
|
||||
@GetMapping("/getResponseCode")
|
||||
public List<ApiResponseCodeMonitor> responseCodeData(@RequestHeader("apiUrl") String url, String startTime, String endTime) {
|
||||
return apiMonitorService.getApiResponseCodeData(url, startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询reportId
|
||||
*/
|
||||
@GetMapping("/getReportId")
|
||||
public String searchReportId(@RequestHeader("apiUrl") String url, @RequestParam("startTime") String startTime) {
|
||||
return apiMonitorService.getReportId(url, startTime);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiMonitorSearch {
|
||||
|
||||
private String url;
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiResponseCodeMonitor {
|
||||
|
||||
private String id;
|
||||
|
||||
private String reportId;
|
||||
|
||||
private String url;
|
||||
|
||||
private String apiName;
|
||||
|
||||
private String startTime;
|
||||
|
||||
private String responseCode;
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiResponseTimeMonitor {
|
||||
|
||||
private String id;
|
||||
|
||||
private String reportId;
|
||||
|
||||
private String url;
|
||||
|
||||
private String apiName;
|
||||
|
||||
private String startTime;
|
||||
|
||||
private String responseTime;
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
package io.metersphere.api.dto.automation;
|
||||
|
||||
public enum ScenarioStatus {
|
||||
Saved, Success, Fail, Trash
|
||||
Saved, Success, Fail, Trash,Underway
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ public class RunDefinitionRequest {
|
|||
|
||||
private String reportId;
|
||||
|
||||
private String environmentId;
|
||||
|
||||
private MsTestElement testElement;
|
||||
|
||||
private Response response;
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package io.metersphere.api.dto.definition.request;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import com.google.gson.Gson;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import io.metersphere.api.service.ApiAutomationService;
|
||||
import io.metersphere.api.service.ApiTestEnvironmentService;
|
||||
import io.metersphere.base.domain.ApiScenario;
|
||||
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
@ -21,23 +25,36 @@ public class MsScenario extends MsTestElement {
|
|||
private String type = "scenario";
|
||||
@JSONField(ordinal = 10)
|
||||
private String name;
|
||||
|
||||
@JSONField(ordinal = 11)
|
||||
private String referenced;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
@JSONField(ordinal = 12)
|
||||
private String environmentId;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
if (environmentId != null) {
|
||||
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
|
||||
ApiTestEnvironmentWithBLOBs environment = environmentService.get(environmentId);
|
||||
config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
|
||||
}
|
||||
if (this.getReferenced() != null && this.getReferenced().equals("Deleted")) {
|
||||
return;
|
||||
} else if (this.getReferenced() != null && this.getReferenced().equals("REF")) {
|
||||
ApiAutomationService apiAutomationService = CommonBeanFactory.getBean(ApiAutomationService.class);
|
||||
ApiScenario scenario = apiAutomationService.getApiScenario(this.getId());
|
||||
Gson gs = new Gson();
|
||||
MsTestElement element = gs.fromJson(scenario.getScenarioDefinition(), MsTestElement.class);
|
||||
hashTree.add(element);
|
||||
JSONObject element = JSON.parseObject(scenario.getScenarioDefinition());
|
||||
List<MsTestElement> dataArr = JSON.parseArray(element.getString("hashTree"), MsTestElement.class);
|
||||
if (hashTree == null) {
|
||||
hashTree = dataArr;
|
||||
} else {
|
||||
hashTree.addAll(dataArr);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(tree, el.getHashTree());
|
||||
});
|
||||
for (MsTestElement el : hashTree) {
|
||||
el.toHashTree(tree, el.getHashTree(), config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
|||
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
|
||||
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
|
||||
import io.metersphere.api.dto.definition.request.timer.MsConstantTimer;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import lombok.Data;
|
||||
import org.apache.jmeter.protocol.http.control.AuthManager;
|
||||
|
@ -61,9 +62,10 @@ public abstract class MsTestElement {
|
|||
@JSONField(ordinal = 4)
|
||||
private LinkedList<MsTestElement> hashTree;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
// 公共环境逐层传递,如果自身有环境 以自身引用环境为准否则以公共环境作为请求环境
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
for (MsTestElement el : hashTree) {
|
||||
el.toHashTree(tree, el.hashTree);
|
||||
el.toHashTree(tree, el.hashTree, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,9 +87,15 @@ public abstract class MsTestElement {
|
|||
return null;
|
||||
}
|
||||
|
||||
public HashTree generateHashTree(EnvironmentConfig config) {
|
||||
HashTree jmeterTestPlanHashTree = new ListedHashTree();
|
||||
this.toHashTree(jmeterTestPlanHashTree, this.hashTree, config);
|
||||
return jmeterTestPlanHashTree;
|
||||
}
|
||||
|
||||
public HashTree generateHashTree() {
|
||||
HashTree jmeterTestPlanHashTree = new ListedHashTree();
|
||||
this.toHashTree(jmeterTestPlanHashTree, this.hashTree);
|
||||
this.toHashTree(jmeterTestPlanHashTree, this.hashTree, null);
|
||||
return jmeterTestPlanHashTree;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.metersphere.api.dto.definition.request;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -18,11 +19,11 @@ import java.util.List;
|
|||
public class MsTestPlan extends MsTestElement {
|
||||
private String type = "TestPlan";
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
final HashTree testPlanTree = tree.add(getPlan());
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(testPlanTree, el.getHashTree());
|
||||
el.toHashTree(testPlanTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.metersphere.api.dto.definition.request;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -18,11 +19,11 @@ import java.util.List;
|
|||
public class MsThreadGroup extends MsTestElement {
|
||||
private String type = "ThreadGroup";
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
final HashTree groupTree = tree.add(getThreadGroup());
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(groupTree, el.getHashTree());
|
||||
el.toHashTree(groupTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +38,7 @@ public class MsThreadGroup extends MsTestElement {
|
|||
|
||||
ThreadGroup threadGroup = new ThreadGroup();
|
||||
threadGroup.setEnabled(true);
|
||||
threadGroup.setName(this.getName() + "ThreadGroup");
|
||||
threadGroup.setName(this.getName());
|
||||
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
|
||||
threadGroup.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ThreadGroupGui"));
|
||||
threadGroup.setNumThreads(1);
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.api.dto.definition.request.assertions;
|
|||
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -23,10 +24,10 @@ public class MsAssertions extends MsTestElement {
|
|||
private MsAssertionDuration duration;
|
||||
private String type = "Assertions";
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
addAssertions(tree);
|
||||
|
||||
}
|
||||
|
||||
private void addAssertions(HashTree hashTree) {
|
||||
if (CollectionUtils.isNotEmpty(this.getRegex())) {
|
||||
this.getRegex().stream().filter(MsAssertionRegex::isValid).forEach(assertion ->
|
||||
|
|
|
@ -50,7 +50,7 @@ public class MsAuthManager extends MsTestElement {
|
|||
@JSONField(ordinal = 18)
|
||||
private String environment;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
AuthManager authManager = new AuthManager();
|
||||
authManager.setEnabled(true);
|
||||
authManager.setName(this.getUsername() + "AuthManager");
|
||||
|
@ -63,7 +63,7 @@ public class MsAuthManager extends MsTestElement {
|
|||
if (environment != null) {
|
||||
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
|
||||
ApiTestEnvironmentWithBLOBs environmentWithBLOBs = environmentService.get(environment);
|
||||
EnvironmentConfig config = JSONObject.parseObject(environmentWithBLOBs.getConfig(), EnvironmentConfig.class);
|
||||
config = JSONObject.parseObject(environmentWithBLOBs.getConfig(), EnvironmentConfig.class);
|
||||
this.url = config.getHttpConfig().getProtocol() + "://" + config.getHttpConfig().getSocket();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.alibaba.fastjson.annotation.JSONField;
|
|||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -24,7 +25,7 @@ public class MsHeaderManager extends MsTestElement {
|
|||
@JSONField(ordinal = 10)
|
||||
private List<KeyValue> headers;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
HeaderManager headerManager = new HeaderManager();
|
||||
headerManager.setEnabled(true);
|
||||
headerManager.setName(this.getName() + "Headers");
|
||||
|
@ -36,7 +37,7 @@ public class MsHeaderManager extends MsTestElement {
|
|||
final HashTree headersTree = tree.add(headerManager);
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(headersTree, el.getHashTree());
|
||||
el.toHashTree(headersTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.api.dto.definition.request.controller;
|
|||
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -24,11 +25,11 @@ public class MsIfController extends MsTestElement {
|
|||
private String operator;
|
||||
private String value;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
final HashTree groupTree = tree.add(ifController());
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(groupTree, el.getHashTree());
|
||||
el.toHashTree(groupTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ import java.util.List;
|
|||
@JSONType(typeName = "DNSCacheManager")
|
||||
public class MsDNSCacheManager extends MsTestElement {
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
for (MsTestElement el : hashTree) {
|
||||
el.toHashTree(tree, el.getHashTree());
|
||||
el.toHashTree(tree, el.getHashTree(), config);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.metersphere.api.dto.definition.request.extract;
|
|||
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -23,7 +24,7 @@ public class MsExtract extends MsTestElement {
|
|||
private List<MsExtractXPath> xpath;
|
||||
private String type = "Extract";
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
addRequestExtractors(tree);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,14 @@ package io.metersphere.api.dto.definition.request.processors;
|
|||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.jmeter.protocol.java.sampler.JSR223Sampler;
|
||||
import org.apache.jmeter.save.SaveService;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.apache.jmeter.protocol.java.sampler.JSR223Sampler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -25,7 +26,7 @@ public class MsJSR223Processor extends MsTestElement {
|
|||
@JSONField(ordinal = 11)
|
||||
private String scriptLanguage;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
JSR223Sampler processor = new JSR223Sampler();
|
||||
processor.setEnabled(true);
|
||||
processor.setName(this.getName() + "JSR223Processor");
|
||||
|
@ -38,7 +39,7 @@ public class MsJSR223Processor extends MsTestElement {
|
|||
final HashTree jsr223PreTree = tree.add(processor);
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(jsr223PreTree, el.getHashTree());
|
||||
el.toHashTree(jsr223PreTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.processors.post;
|
|||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -26,7 +27,7 @@ public class MsJSR223PostProcessor extends MsTestElement {
|
|||
private String scriptLanguage;
|
||||
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
JSR223PostProcessor processor = new JSR223PostProcessor();
|
||||
processor.setEnabled(true);
|
||||
processor.setName(this.getName() + "JSR223PostProcessor");
|
||||
|
@ -39,7 +40,7 @@ public class MsJSR223PostProcessor extends MsTestElement {
|
|||
final HashTree jsr223PostTree = tree.add(processor);
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(jsr223PostTree, el.getHashTree());
|
||||
el.toHashTree(jsr223PostTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.processors.pre;
|
|||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -25,7 +26,7 @@ public class MsJSR223PreProcessor extends MsTestElement {
|
|||
@JSONField(ordinal = 11)
|
||||
private String scriptLanguage;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
JSR223PreProcessor processor = new JSR223PreProcessor();
|
||||
processor.setEnabled(true);
|
||||
processor.setName(this.getName() + "JSR223PreProcessor");
|
||||
|
@ -38,7 +39,7 @@ public class MsJSR223PreProcessor extends MsTestElement {
|
|||
final HashTree jsr223PreTree = tree.add(processor);
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(jsr223PreTree, el.getHashTree());
|
||||
el.toHashTree(jsr223PreTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import io.metersphere.api.dto.definition.request.sampler.dubbo.MsConfigCenter;
|
|||
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsConsumerAndService;
|
||||
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsRegistryCenter;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -50,13 +51,13 @@ public class MsDubboSampler extends MsTestElement {
|
|||
@JSONField(ordinal = 59)
|
||||
private List<KeyValue> attachmentArgs;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
final HashTree testPlanTree = new ListedHashTree();
|
||||
testPlanTree.add(dubboConfig());
|
||||
tree.set(dubboSample(), testPlanTree);
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(testPlanTree, el.getHashTree());
|
||||
el.toHashTree(testPlanTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,12 +52,12 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
|
||||
@JSONField(ordinal = 15)
|
||||
private String connectTimeout;
|
||||
|
||||
@JSONField(ordinal = 16)
|
||||
|
||||
private String responseTimeout;
|
||||
@JSONField(ordinal = 17)
|
||||
|
||||
private List<KeyValue> arguments;
|
||||
@JSONField(ordinal = 17)
|
||||
private List<KeyValue> headers;
|
||||
|
||||
@JSONField(ordinal = 18)
|
||||
private Body body;
|
||||
|
@ -78,9 +78,9 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
private String useEnvironment;
|
||||
|
||||
@JSONField(ordinal = 24)
|
||||
private List<KeyValue> headers;
|
||||
private List<KeyValue> arguments;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
HTTPSamplerProxy sampler = new HTTPSamplerProxy();
|
||||
sampler.setEnabled(true);
|
||||
sampler.setName(this.getName());
|
||||
|
@ -93,7 +93,6 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
sampler.setFollowRedirects(this.isFollowRedirects());
|
||||
sampler.setUseKeepAlive(true);
|
||||
sampler.setDoMultipart(this.isDoMultipartPost());
|
||||
EnvironmentConfig config = null;
|
||||
if (useEnvironment != null) {
|
||||
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
|
||||
ApiTestEnvironmentWithBLOBs environment = environmentService.get(useEnvironment);
|
||||
|
@ -145,16 +144,18 @@ public class MsHTTPSamplerProxy extends MsTestElement {
|
|||
}
|
||||
|
||||
final HashTree httpSamplerTree = tree.add(sampler);
|
||||
setHeader(httpSamplerTree);
|
||||
if (CollectionUtils.isNotEmpty(this.headers)) {
|
||||
setHeader(httpSamplerTree);
|
||||
}
|
||||
//判断是否要开启DNS
|
||||
if (config != null && config.getCommonConfig() != null && config.getCommonConfig().isEnableHost()) {
|
||||
MsDNSCacheManager.addEnvironmentVariables(httpSamplerTree, this.getName(), config);
|
||||
MsDNSCacheManager.addEnvironmentDNS(httpSamplerTree, this.getName(), config);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(httpSamplerTree, el.getHashTree());
|
||||
});
|
||||
for (MsTestElement el : hashTree) {
|
||||
el.toHashTree(httpSamplerTree, el.getHashTree(), config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.alibaba.fastjson.annotation.JSONType;
|
|||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.DatabaseConfig;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -38,13 +39,13 @@ public class MsJDBCSampler extends MsTestElement {
|
|||
@JSONField(ordinal = 16)
|
||||
private String environmentId;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
final HashTree samplerHashTree = tree.add(jdbcSampler());
|
||||
tree.add(jdbcDataSource());
|
||||
tree.add(arguments(this.getName() + " Variables", this.getVariables()));
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(samplerHashTree, el.getHashTree());
|
||||
el.toHashTree(samplerHashTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.sampler;
|
|||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -48,13 +49,13 @@ public class MsTCPSampler extends MsTestElement {
|
|||
@JSONField(ordinal = 23)
|
||||
private String request;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
final HashTree samplerHashTree = new ListedHashTree();
|
||||
samplerHashTree.add(tcpConfig());
|
||||
tree.set(tcpSampler(), samplerHashTree);
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(samplerHashTree, el.getHashTree());
|
||||
el.toHashTree(samplerHashTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.api.dto.definition.request.timer;
|
|||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.definition.request.MsTestElement;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -25,11 +26,11 @@ public class MsConstantTimer extends MsTestElement {
|
|||
@JSONField(ordinal = 12)
|
||||
private String delay;
|
||||
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
|
||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, EnvironmentConfig config) {
|
||||
final HashTree groupTree = tree.add(constantTimer());
|
||||
if (CollectionUtils.isNotEmpty(hashTree)) {
|
||||
hashTree.forEach(el -> {
|
||||
el.toHashTree(groupTree, el.getHashTree());
|
||||
el.toHashTree(groupTree, el.getHashTree(), config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,13 +168,9 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
apiDefinitionExecResultService.saveApiResult(testResult);
|
||||
}
|
||||
} else if (StringUtils.equals(this.runMode, ApiRunMode.SCENARIO.name())) {
|
||||
// 调试操作,不需要存储结果
|
||||
if (StringUtils.isBlank(debugReportId)) {
|
||||
apiScenarioReportService.addResult(testResult);
|
||||
} else {
|
||||
apiScenarioReportService.addResult(testResult);
|
||||
//apiScenarioReportService.saveApiResult(testResult);
|
||||
}
|
||||
// 执行报告不需要存储,由用户确认后在存储
|
||||
testResult.setTestId(debugReportId);
|
||||
apiScenarioReportService.complete(testResult);
|
||||
} else {
|
||||
apiTestService.changeStatus(testId, APITestStatus.Completed);
|
||||
report = apiReportService.getRunningReport(testResult.getTestId());
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import io.metersphere.api.dto.ApiMonitorSearch;
|
||||
import io.metersphere.api.dto.ApiResponseCodeMonitor;
|
||||
import io.metersphere.api.dto.ApiResponseTimeMonitor;
|
||||
import io.metersphere.base.mapper.ApiDataViewMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class APIMonitorService {
|
||||
|
||||
@Resource
|
||||
private ApiDataViewMapper apiDataViewMapper;
|
||||
|
||||
public List<ApiMonitorSearch> list() {
|
||||
return apiDataViewMapper.selectAll();
|
||||
}
|
||||
|
||||
public List<ApiResponseTimeMonitor> getApiResponseTimeData(String apiUrl, String startTime, String endTime) {
|
||||
return apiDataViewMapper.selectResponseTimeByUrl(apiUrl, startTime, endTime);
|
||||
}
|
||||
|
||||
public List<ApiResponseCodeMonitor> getApiResponseCodeData(String apiUrl, String startTime, String endTime) {
|
||||
return apiDataViewMapper.selectResponseCodeByUrl(apiUrl, startTime, endTime);
|
||||
}
|
||||
|
||||
public String getReportId(String apiUrl, String startTime) {
|
||||
return apiDataViewMapper.selectReportIdByUrlAndStartTime(apiUrl, startTime);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,21 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.dto.DeleteAPIReportRequest;
|
||||
import io.metersphere.api.dto.QueryAPIReportRequest;
|
||||
import io.metersphere.api.jmeter.TestResult;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ApiDataViewMapper;
|
||||
import io.metersphere.base.mapper.ApiTestReportDetailMapper;
|
||||
import io.metersphere.base.mapper.ApiTestReportMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiTestReportMapper;
|
||||
import io.metersphere.commons.constants.APITestStatus;
|
||||
import io.metersphere.commons.constants.ReportTriggerMode;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.ServiceUtils;
|
||||
import io.metersphere.dto.DashboardTestDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
|
@ -21,8 +25,11 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -36,6 +43,8 @@ public class APIReportService {
|
|||
private ApiTestReportDetailMapper apiTestReportDetailMapper;
|
||||
@Resource
|
||||
private ExtApiTestReportMapper extApiTestReportMapper;
|
||||
@Resource
|
||||
private ApiDataViewMapper apiDataViewMapper;
|
||||
|
||||
public List<APIReportResult> list(QueryAPIReportRequest request) {
|
||||
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
||||
|
@ -63,6 +72,8 @@ public class APIReportService {
|
|||
public void delete(DeleteAPIReportRequest request) {
|
||||
apiTestReportDetailMapper.deleteByPrimaryKey(request.getId());
|
||||
apiTestReportMapper.deleteByPrimaryKey(request.getId());
|
||||
apiDataViewMapper.deleteByReportId(request.getId());
|
||||
|
||||
}
|
||||
|
||||
public void deleteByTestId(String testId) {
|
||||
|
@ -89,6 +100,8 @@ public class APIReportService {
|
|||
// report
|
||||
report.setUpdateTime(System.currentTimeMillis());
|
||||
if (!StringUtils.equals(report.getStatus(), APITestStatus.Debug.name())) {
|
||||
//新增每一条接口记录到api_data_view表中
|
||||
creatApiDataView(new String(detail.getContent(), StandardCharsets.UTF_8), report.getId());
|
||||
if (result.getError() > 0) {
|
||||
report.setStatus(APITestStatus.Error.name());
|
||||
} else {
|
||||
|
@ -99,6 +112,44 @@ public class APIReportService {
|
|||
apiTestReportMapper.updateByPrimaryKeySelective(report);
|
||||
}
|
||||
|
||||
private void creatApiDataView(String jsonString, String reportId) {
|
||||
List<ApiDataView> listApiDataView = new ArrayList<>();
|
||||
JSONObject jsonObject = JSON.parseObject(jsonString, JSONObject.class);
|
||||
JSONArray jsonArray = jsonObject.getJSONArray("scenarios");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
try {
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
JSONObject jsonInArray = jsonArray.getJSONObject(i);
|
||||
JSONArray jsonRequestResults = jsonInArray.getJSONArray("requestResults");
|
||||
for (int j = 0; j < jsonRequestResults.size(); j++) {
|
||||
JSONObject jsonInResponseResult = jsonRequestResults.getJSONObject(j).getJSONObject("responseResult");
|
||||
String responseTime = jsonInResponseResult.getString("responseTime");
|
||||
String responseCode = jsonInResponseResult.getString("responseCode");
|
||||
String startTime = jsonRequestResults.getJSONObject(j).getString("startTime");
|
||||
String name = jsonRequestResults.getJSONObject(j).getString("name");
|
||||
String url = jsonRequestResults.getJSONObject(j).getString("url");
|
||||
if (StringUtils.isBlank(url)){
|
||||
//如果非http请求不入库
|
||||
continue;
|
||||
}
|
||||
ApiDataView apiDataView = new ApiDataView();
|
||||
apiDataView.setId(UUID.randomUUID().toString());
|
||||
apiDataView.setReportId(reportId);
|
||||
apiDataView.setApiName(name);
|
||||
apiDataView.setUrl(StringUtils.substringBefore(url,"?"));
|
||||
apiDataView.setResponseTime(responseTime);
|
||||
apiDataView.setStartTime(sdf.format(new Date(Long.parseLong(startTime))));
|
||||
apiDataView.setResponseCode(responseCode);
|
||||
listApiDataView.add(apiDataView);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
apiDataViewMapper.insertListApiData(listApiDataView);
|
||||
|
||||
}
|
||||
|
||||
public String create(ApiTest test, String triggerMode) {
|
||||
ApiTestReport running = getRunningReport(test.getId());
|
||||
if (running != null) {
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.gson.Gson;
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.dto.automation.ApiScenarioDTO;
|
||||
import io.metersphere.api.dto.automation.ApiScenarioRequest;
|
||||
import io.metersphere.api.dto.automation.SaveApiScenarioRequest;
|
||||
import io.metersphere.api.dto.automation.ScenarioStatus;
|
||||
import io.metersphere.api.dto.definition.RunDefinitionRequest;
|
||||
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
|
||||
import io.metersphere.api.jmeter.JMeterService;
|
||||
import io.metersphere.base.domain.ApiScenario;
|
||||
import io.metersphere.base.domain.ApiScenarioExample;
|
||||
import io.metersphere.base.domain.ApiTag;
|
||||
import io.metersphere.base.domain.ApiTagExample;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ApiScenarioMapper;
|
||||
import io.metersphere.base.mapper.ApiTagMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
|
||||
import io.metersphere.commons.constants.APITestStatus;
|
||||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.aspectj.util.FileUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -32,6 +34,7 @@ import java.io.*;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
|
@ -45,6 +48,10 @@ public class ApiAutomationService {
|
|||
private ApiTagMapper apiTagMapper;
|
||||
@Resource
|
||||
private JMeterService jMeterService;
|
||||
@Resource
|
||||
private ApiTestEnvironmentService environmentService;
|
||||
@Resource
|
||||
private ApiScenarioReportService apiReportService;
|
||||
|
||||
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||
|
||||
|
@ -92,7 +99,11 @@ public class ApiAutomationService {
|
|||
scenario.setScenarioDefinition(request.getScenarioDefinition());
|
||||
scenario.setCreateTime(System.currentTimeMillis());
|
||||
scenario.setUpdateTime(System.currentTimeMillis());
|
||||
scenario.setStatus(ScenarioStatus.Saved.name());
|
||||
if (StringUtils.isNotEmpty(request.getStatus())) {
|
||||
scenario.setStatus(request.getStatus());
|
||||
} else {
|
||||
scenario.setStatus(ScenarioStatus.Underway.name());
|
||||
}
|
||||
if (request.getUserId() == null) {
|
||||
scenario.setUserId(SessionUtils.getUserId());
|
||||
} else {
|
||||
|
@ -117,7 +128,11 @@ public class ApiAutomationService {
|
|||
scenario.setStepTotal(request.getStepTotal());
|
||||
scenario.setScenarioDefinition(request.getScenarioDefinition());
|
||||
scenario.setUpdateTime(System.currentTimeMillis());
|
||||
scenario.setStatus(ScenarioStatus.Saved.name());
|
||||
if (StringUtils.isNotEmpty(request.getStatus())) {
|
||||
scenario.setStatus(request.getStatus());
|
||||
} else {
|
||||
scenario.setStatus(ScenarioStatus.Underway.name());
|
||||
}
|
||||
scenario.setUserId(request.getUserId());
|
||||
scenario.setDescription(request.getDescription());
|
||||
apiScenarioMapper.updateByPrimaryKeySelective(scenario);
|
||||
|
@ -143,8 +158,7 @@ public class ApiAutomationService {
|
|||
|
||||
private void checkNameExist(SaveApiScenarioRequest request) {
|
||||
ApiScenarioExample example = new ApiScenarioExample();
|
||||
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId())
|
||||
.andApiScenarioModuleIdEqualTo(request.getApiScenarioModuleId()).andIdNotEqualTo(request.getId());
|
||||
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId());
|
||||
if (apiScenarioMapper.countByExample(example) > 0) {
|
||||
MSException.throwException(Translator.get("automation_name_already_exists"));
|
||||
}
|
||||
|
@ -204,10 +218,26 @@ public class ApiAutomationService {
|
|||
public String run(RunDefinitionRequest request, List<MultipartFile> bodyFiles) {
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
|
||||
createBodyFiles(bodyUploadIds, bodyFiles);
|
||||
HashTree hashTree = request.getTestElement().generateHashTree();
|
||||
EnvironmentConfig config = null;
|
||||
if (request.getEnvironmentId() != null) {
|
||||
ApiTestEnvironmentWithBLOBs environment = environmentService.get(request.getEnvironmentId());
|
||||
config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
|
||||
}
|
||||
HashTree hashTree = request.getTestElement().generateHashTree(config);
|
||||
request.getTestElement().getJmx(hashTree);
|
||||
|
||||
// 调用执行方法
|
||||
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.SCENARIO.name());
|
||||
APIReportResult report = new APIReportResult();
|
||||
report.setId(UUID.randomUUID().toString());
|
||||
report.setTestId(request.getReportId());
|
||||
report.setName("RUN");
|
||||
report.setTriggerMode(null);
|
||||
report.setCreateTime(System.currentTimeMillis());
|
||||
report.setUpdateTime(System.currentTimeMillis());
|
||||
report.setStatus(APITestStatus.Running.name());
|
||||
report.setUserId(SessionUtils.getUserId());
|
||||
apiReportService.addResult(report);
|
||||
return request.getId();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,67 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.jmeter.TestResult;
|
||||
import io.metersphere.base.domain.ApiTestReportDetail;
|
||||
import io.metersphere.commons.constants.APITestStatus;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import sun.security.util.Cache;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class ApiScenarioReportService {
|
||||
|
||||
private static Cache cache = Cache.newHardMemoryCache(0, 3600 * 24);
|
||||
|
||||
public void addResult(TestResult res) {
|
||||
if (!res.getScenarios().isEmpty()) {
|
||||
cache.put(res.getTestId(), res);
|
||||
} else {
|
||||
MSException.throwException(Translator.get("test_not_found"));
|
||||
public void complete(TestResult result) {
|
||||
Object obj = cache.get(result.getTestId());
|
||||
if (obj == null) {
|
||||
MSException.throwException(Translator.get("api_report_is_null"));
|
||||
}
|
||||
APIReportResult report = (APIReportResult) obj;
|
||||
// report detail
|
||||
ApiTestReportDetail detail = new ApiTestReportDetail();
|
||||
detail.setReportId(result.getTestId());
|
||||
detail.setTestId(report.getTestId());
|
||||
detail.setContent(JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8));
|
||||
// report
|
||||
report.setUpdateTime(System.currentTimeMillis());
|
||||
if (!StringUtils.equals(report.getStatus(), APITestStatus.Debug.name())) {
|
||||
if (result.getError() > 0) {
|
||||
report.setStatus(APITestStatus.Error.name());
|
||||
} else {
|
||||
report.setStatus(APITestStatus.Success.name());
|
||||
}
|
||||
}
|
||||
report.setContent(new String(detail.getContent(), StandardCharsets.UTF_8));
|
||||
cache.put(report.getTestId(), report);
|
||||
}
|
||||
|
||||
public void addResult(APIReportResult res) {
|
||||
cache.put(res.getTestId(), res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取零时执行报告
|
||||
*
|
||||
* @param testId
|
||||
*/
|
||||
public APIReportResult getCacheResult(String testId) {
|
||||
Object res = cache.get(testId);
|
||||
if (res != null) {
|
||||
APIReportResult reportResult = (APIReportResult) res;
|
||||
if (!reportResult.getStatus().equals(APITestStatus.Running.name())) {
|
||||
cache.remove(testId);
|
||||
}
|
||||
return reportResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package io.metersphere.base.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class ApiDataView implements Serializable {
|
||||
private String id;
|
||||
|
||||
private String reportId;
|
||||
|
||||
private String url;
|
||||
|
||||
private String apiName;
|
||||
|
||||
private String startTime;
|
||||
|
||||
private String responseCode;
|
||||
|
||||
private String responseTime;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.metersphere.base.mapper;
|
||||
|
||||
import io.metersphere.api.dto.ApiMonitorSearch;
|
||||
import io.metersphere.api.dto.ApiResponseCodeMonitor;
|
||||
import io.metersphere.api.dto.ApiResponseTimeMonitor;
|
||||
import io.metersphere.base.domain.ApiDataView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiDataViewMapper {
|
||||
|
||||
List<ApiMonitorSearch> selectAll();
|
||||
|
||||
List<ApiResponseTimeMonitor> selectResponseTimeByUrl(String url,String startTime,String endTime);
|
||||
|
||||
List<ApiResponseCodeMonitor> selectResponseCodeByUrl(String url,String startTime,String endTime);
|
||||
|
||||
Integer insertListApiData(List<ApiDataView> list);
|
||||
|
||||
Integer deleteByReportId(String reportId);
|
||||
|
||||
String selectReportIdByUrlAndStartTime(String apiUrl,String startTime);
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.metersphere.base.mapper.ApiDataViewMapper">
|
||||
<resultMap id="apiDataView" type="io.metersphere.base.domain.ApiDataView">
|
||||
<id column="id" property="id"/>
|
||||
<result column="report_id" property="reportId"/>
|
||||
<result column="api_name" property="apiName"/>
|
||||
<result column="url" property="url"/>
|
||||
<result column="response_code" property="startTime"/>
|
||||
<result column="start_time" property="responseCode"/>
|
||||
<result column="response_time" property="responseTime"/>
|
||||
</resultMap>
|
||||
<delete id="deleteByReportId" parameterType="java.lang.String">
|
||||
delete from api_data_view where report_id = #{reportId,jdbcType=VARCHAR}
|
||||
</delete>
|
||||
|
||||
<select id="selectAll" resultType="io.metersphere.api.dto.ApiMonitorSearch">
|
||||
select distinct url from api_data_view;
|
||||
</select>
|
||||
|
||||
<select id="selectResponseTimeByUrl" parameterType="java.lang.String"
|
||||
resultType="io.metersphere.api.dto.ApiResponseTimeMonitor">
|
||||
select id,report_id,api_name,start_time,response_time,url
|
||||
from api_data_view
|
||||
<where>
|
||||
<if test="url != null and url != ''">
|
||||
url = #{url}
|
||||
</if>
|
||||
<if test="startTime != null and startTime != '' and endTime != null and endTime != ''">
|
||||
AND date_format(start_time,'%Y-%m-%d %H:%i:%s')
|
||||
between #{startTime} and #{endTime}
|
||||
</if>
|
||||
<if test="startTime == null or startTime == '' or endTime == null or endTime == ''">
|
||||
AND TO_DAYS(start_time) =TO_DAYS(NOW())
|
||||
</if>
|
||||
order by start_time;
|
||||
</where>
|
||||
|
||||
</select>
|
||||
<select id="selectResponseCodeByUrl" resultType="io.metersphere.api.dto.ApiResponseCodeMonitor">
|
||||
select id,report_id,api_name,start_time,response_code,url
|
||||
from api_data_view
|
||||
<where>
|
||||
<if test="url != null and url != ''">
|
||||
url = #{url}
|
||||
</if>
|
||||
<if test="startTime != null and startTime != '' and endTime != null and endTime != ''">
|
||||
AND date_format(start_time,'%Y-%m-%d %H:%i:%s')
|
||||
between #{startTime} and #{endTime} and length(response_code)=3
|
||||
</if>
|
||||
<if test="startTime == null or startTime == '' or endTime == null or endTime == ''">
|
||||
AND TO_DAYS(start_time) =TO_DAYS(NOW()) and length(response_code)=3
|
||||
</if>
|
||||
order by start_time;
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectReportIdByUrlAndStartTime" resultType="java.lang.String">
|
||||
select report_id from api_data_view where response_code != 200 and url=#{apiUrl} and start_time=#{startTime};
|
||||
</select>
|
||||
|
||||
<insert id="insertListApiData" parameterType="java.util.List">
|
||||
insert into api_data_view(id, report_id, api_name,url, response_code, start_time,response_time)
|
||||
values
|
||||
<foreach collection="list" item="item" index="index" separator=",">
|
||||
(
|
||||
#{item.id,jdbcType=VARCHAR},
|
||||
#{item.reportId,jdbcType=VARCHAR},
|
||||
#{item.apiName,jdbcType=VARCHAR},
|
||||
#{item.url,jdbcType=VARCHAR},
|
||||
#{item.responseCode,jdbcType=VARCHAR},
|
||||
#{item.startTime,jdbcType=VARCHAR},
|
||||
#{item.responseTime,jdbcType=VARCHAR}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE IF NOT EXISTS `api_data_view`
|
||||
(
|
||||
id varchar(50) NOT NULL primary key,
|
||||
report_id varchar(255) NOT NULL,
|
||||
api_name varchar(200) NULL,
|
||||
url varchar(255) NULL,
|
||||
response_code varchar(100) NULL,
|
||||
start_time varchar(20) NULL,
|
||||
response_time varchar(20) default '0' NULL,
|
||||
create_time timestamp default CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp default CURRENT_TIMESTAMP NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@ -21,7 +21,7 @@
|
|||
:name="item.name"
|
||||
closable>
|
||||
<div class="ms-api-scenario-div">
|
||||
<ms-edit-api-scenario :current-project="currentProject" :currentScenario="currentScenario" :moduleOptions="moduleOptions"/>
|
||||
<ms-edit-api-scenario :current-project="currentProject" :currentScenario="item.currentScenario" :moduleOptions="moduleOptions"/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
|
@ -37,77 +37,82 @@
|
|||
|
||||
<script>
|
||||
|
||||
import MsContainer from "@/business/components/common/components/MsContainer";
|
||||
import MsAsideContainer from "@/business/components/common/components/MsAsideContainer";
|
||||
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
|
||||
import MsApiScenarioList from "@/business/components/api/automation/scenario/ApiScenarioList";
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
import MsApiScenarioModule from "@/business/components/api/automation/scenario/ApiScenarioModule";
|
||||
import MsEditApiScenario from "./scenario/EditApiScenario";
|
||||
import MsContainer from "@/business/components/common/components/MsContainer";
|
||||
import MsAsideContainer from "@/business/components/common/components/MsAsideContainer";
|
||||
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
|
||||
import MsApiScenarioList from "@/business/components/api/automation/scenario/ApiScenarioList";
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
import MsApiScenarioModule from "@/business/components/api/automation/scenario/ApiScenarioModule";
|
||||
import MsEditApiScenario from "./scenario/EditApiScenario";
|
||||
|
||||
export default {
|
||||
name: "ApiAutomation",
|
||||
components: {MsApiScenarioModule, MsApiScenarioList, MsMainContainer, MsAsideContainer, MsContainer,MsEditApiScenario},
|
||||
comments: {},
|
||||
data() {
|
||||
return {
|
||||
isHide: true,
|
||||
activeName: 'default',
|
||||
currentProject: null,
|
||||
currentModule: null,
|
||||
currentScenario: {},
|
||||
moduleOptions: {},
|
||||
tabs: [],
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
addTab(tab) {
|
||||
if (tab.name === 'add') {
|
||||
let label = this.$t('api_test.automation.add_scenario');
|
||||
let name = getUUID().substring(0, 8);
|
||||
this.tabs.push({label: label, name: name});
|
||||
this.activeName = name;
|
||||
export default {
|
||||
name: "ApiAutomation",
|
||||
components: {MsApiScenarioModule, MsApiScenarioList, MsMainContainer, MsAsideContainer, MsContainer, MsEditApiScenario},
|
||||
comments: {},
|
||||
data() {
|
||||
return {
|
||||
isHide: true,
|
||||
activeName: 'default',
|
||||
currentProject: null,
|
||||
currentModule: null,
|
||||
moduleOptions: {},
|
||||
tabs: [],
|
||||
}
|
||||
},
|
||||
removeTab(targetName) {
|
||||
this.tabs = this.tabs.filter(tab => tab.name !== targetName);
|
||||
if (this.tabs.length > 0) {
|
||||
this.activeName = this.tabs[this.tabs.length - 1].name;
|
||||
} else {
|
||||
this.activeName = "default"
|
||||
}
|
||||
},
|
||||
setTabLabel(data) {
|
||||
for (const tab of this.tabs) {
|
||||
if (tab.name === this.activeName) {
|
||||
tab.label = data.name;
|
||||
break;
|
||||
watch: {},
|
||||
methods: {
|
||||
addTab(tab) {
|
||||
if (tab.name === 'add') {
|
||||
let label = this.$t('api_test.automation.add_scenario');
|
||||
let name = getUUID().substring(0, 8);
|
||||
this.activeName = name;
|
||||
this.tabs.push({label: label, name: name, currentScenario: {}});
|
||||
}
|
||||
}
|
||||
},
|
||||
selectModule(data) {
|
||||
this.currentModule = data;
|
||||
},
|
||||
saveScenario(data) {
|
||||
this.setTabLabel(data);
|
||||
this.$refs.apiScenarioList.search(data);
|
||||
},
|
||||
initTree(data) {
|
||||
this.moduleOptions = data;
|
||||
},
|
||||
changeProject(data) {
|
||||
this.currentProject = data;
|
||||
},
|
||||
refresh(data) {
|
||||
this.$refs.apiScenarioList.search(data);
|
||||
},
|
||||
editScenario(row) {
|
||||
this.currentScenario = row;
|
||||
this.addTab({name: 'add'});
|
||||
},
|
||||
if (tab.name === 'edit') {
|
||||
let label = this.$t('api_test.automation.add_scenario');
|
||||
let name = getUUID().substring(0, 8);
|
||||
this.activeName = name;
|
||||
label = tab.currentScenario.name;
|
||||
this.tabs.push({label: label, name: name, currentScenario: tab.currentScenario});
|
||||
}
|
||||
},
|
||||
removeTab(targetName) {
|
||||
this.tabs = this.tabs.filter(tab => tab.name !== targetName);
|
||||
if (this.tabs.length > 0) {
|
||||
this.activeName = this.tabs[this.tabs.length - 1].name;
|
||||
} else {
|
||||
this.activeName = "default"
|
||||
}
|
||||
},
|
||||
setTabLabel(data) {
|
||||
for (const tab of this.tabs) {
|
||||
if (tab.name === this.activeName) {
|
||||
tab.label = data.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
selectModule(data) {
|
||||
this.currentModule = data;
|
||||
},
|
||||
saveScenario(data) {
|
||||
this.setTabLabel(data);
|
||||
this.$refs.apiScenarioList.search(data);
|
||||
},
|
||||
initTree(data) {
|
||||
this.moduleOptions = data;
|
||||
},
|
||||
changeProject(data) {
|
||||
this.currentProject = data;
|
||||
},
|
||||
refresh(data) {
|
||||
this.$refs.apiScenarioList.search(data);
|
||||
},
|
||||
editScenario(row) {
|
||||
this.addTab({name: 'edit', currentScenario: row});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<template>
|
||||
<div> ApiReport</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "ApiReport",
|
||||
components: {},
|
||||
comments: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,220 @@
|
|||
<template>
|
||||
<ms-container v-loading="loading">
|
||||
<ms-main-container>
|
||||
<el-card>
|
||||
<section class="report-container" v-if="this.report.testId">
|
||||
|
||||
<ms-api-report-view-header :report="report" @reportExport="handleExport"/>
|
||||
|
||||
<main v-if="this.isNotRunning">
|
||||
<ms-metric-chart :content="content" :totalTime="totalTime"/>
|
||||
<div @click="active">
|
||||
<ms-scenario-results :scenarios="content.scenarios" v-on:requestResult="requestResult"/>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="isActive" style="width: 99%">
|
||||
<ms-request-result-tail v-if="isRequestResult" :request-type="requestType" :request="request"
|
||||
:scenario-name="scenarioName"/>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
<ms-api-report-export v-if="reportExportVisible" id="apiTestReport" :title="report.testName"
|
||||
:content="content" :total-time="totalTime"/>
|
||||
</main>
|
||||
</section>
|
||||
</el-card>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import MsRequestResult from "./components/RequestResult";
|
||||
import MsRequestResultTail from "./components/RequestResultTail";
|
||||
import MsScenarioResult from "./components/ScenarioResult";
|
||||
import MsMetricChart from "./components/MetricChart";
|
||||
import MsScenarioResults from "./components/ScenarioResults";
|
||||
import MsContainer from "@/business/components/common/components/MsContainer";
|
||||
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
|
||||
import MsApiReportExport from "./ApiReportExport";
|
||||
import MsApiReportViewHeader from "./ApiReportViewHeader";
|
||||
import {RequestFactory} from "../../definition/model/ApiTestModel";
|
||||
import {windowPrint} from "@/common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "MsApiReport",
|
||||
components: {
|
||||
MsApiReportViewHeader,
|
||||
MsApiReportExport,
|
||||
MsMainContainer,
|
||||
MsContainer, MsScenarioResults, MsRequestResultTail, MsMetricChart, MsScenarioResult, MsRequestResult
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: "total",
|
||||
content: {},
|
||||
report: {},
|
||||
loading: true,
|
||||
fails: [],
|
||||
totalTime: 0,
|
||||
isRequestResult: false,
|
||||
request: {},
|
||||
isActive: false,
|
||||
scenarioName: null,
|
||||
reportExportVisible: false,
|
||||
requestType: undefined,
|
||||
}
|
||||
},
|
||||
props: ['reportId'],
|
||||
activated() {
|
||||
this.isRequestResult = false;
|
||||
},
|
||||
watch: {
|
||||
reportId() {
|
||||
this.getReport();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.loading = true;
|
||||
this.report = {};
|
||||
this.content = {};
|
||||
this.fails = [];
|
||||
this.report = {};
|
||||
this.isRequestResult = false;
|
||||
},
|
||||
handleClick(tab, event) {
|
||||
this.isRequestResult = false
|
||||
},
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
getReport() {
|
||||
this.init();
|
||||
if (this.reportId) {
|
||||
let url = "/api/scenario/report/get/" + this.reportId;
|
||||
this.$get(url, response => {
|
||||
this.report = response.data || {};
|
||||
if (response.data) {
|
||||
if (this.isNotRunning) {
|
||||
try {
|
||||
this.content = JSON.parse(this.report.content);
|
||||
} catch (e) {
|
||||
// console.log(this.report.content)
|
||||
throw e;
|
||||
}
|
||||
this.getFails();
|
||||
this.loading = false;
|
||||
} else {
|
||||
setTimeout(this.getReport, 2000)
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.$error(this.$t('api_report.not_exist'));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
getFails() {
|
||||
if (this.isNotRunning) {
|
||||
this.fails = [];
|
||||
this.totalTime = 0
|
||||
this.content.scenarios.forEach((scenario) => {
|
||||
this.totalTime = this.totalTime + Number(scenario.responseTime)
|
||||
let failScenario = Object.assign({}, scenario);
|
||||
if (scenario.error > 0) {
|
||||
this.fails.push(failScenario);
|
||||
failScenario.requestResults = [];
|
||||
scenario.requestResults.forEach((request) => {
|
||||
if (!request.success) {
|
||||
let failRequest = Object.assign({}, request);
|
||||
failScenario.requestResults.push(failRequest);
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
requestResult(requestResult) {
|
||||
this.isRequestResult = false;
|
||||
this.requestType = undefined;
|
||||
if (requestResult.request.body.indexOf('[Callable Statement]') > -1) {
|
||||
this.requestType = RequestFactory.TYPES.SQL;
|
||||
}
|
||||
this.$nextTick(function () {
|
||||
this.isRequestResult = true;
|
||||
this.request = requestResult.request;
|
||||
this.scenarioName = requestResult.scenarioName;
|
||||
});
|
||||
},
|
||||
handleExport() {
|
||||
this.reportExportVisible = true;
|
||||
let reset = this.exportReportReset;
|
||||
this.$nextTick(() => {
|
||||
windowPrint('apiTestReport', 0.57);
|
||||
reset();
|
||||
});
|
||||
},
|
||||
exportReportReset() {
|
||||
this.$router.go(0);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getReport();
|
||||
},
|
||||
|
||||
computed: {
|
||||
path() {
|
||||
return "/api/test/edit?id=" + this.report.testId;
|
||||
},
|
||||
isNotRunning() {
|
||||
return "Running" !== this.report.status;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.report-container .el-tabs__header {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.report-container {
|
||||
height: calc(100vh - 155px);
|
||||
min-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.report-header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.report-header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.report-header .time {
|
||||
color: #909399;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.report-container .fail {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.report-container .is-active .fail {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.export-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.scenario-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,131 @@
|
|||
<template>
|
||||
<ms-report-export-template :title="title" :type="$t('report.api_test_report')">
|
||||
<ms-metric-chart :content="content" :totalTime="totalTime"/>
|
||||
<div class="scenario-result" v-for="(scenario, index) in content.scenarios" :key="index" :scenario="scenario">
|
||||
<div>
|
||||
<el-card>
|
||||
<template v-slot:header>
|
||||
{{$t('api_report.scenario_name')}}:{{scenario.name}}
|
||||
</template>
|
||||
<div class="ms-border clearfix" v-for="(request, index) in scenario.requestResults" :key="index" :request="request">
|
||||
|
||||
<div class="request-top">
|
||||
<div>
|
||||
{{request.name}}
|
||||
</div>
|
||||
<div class="url">
|
||||
{{request.url}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider/>
|
||||
<div class="request-bottom">
|
||||
<api-report-reqest-header-item :title="$t('api_test.request.method')">
|
||||
<span class="method"> {{request.method}}</span>
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.response_time')">
|
||||
{{request.responseResult.responseTime}} ms
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.latency')">
|
||||
{{request.responseResult.latency}} ms
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.request_size')">
|
||||
{{request.requestSize}} bytes
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.response_size')">
|
||||
{{request.responseResult.responseSize}} bytes
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.error')">
|
||||
{{request.error}}
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.assertions')">
|
||||
{{request.passAssertions + " / " + request.totalAssertions}}
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.response_code')">
|
||||
{{request.responseResult.responseCode}}
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
<api-report-reqest-header-item :title="$t('api_report.result')">
|
||||
<el-tag size="mini" type="success" v-if="request.success">
|
||||
{{$t('api_report.success')}}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else>
|
||||
{{$t('api_report.fail')}}
|
||||
</el-tag>
|
||||
</api-report-reqest-header-item>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</ms-report-export-template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsScenarioResult from "./components/ScenarioResult";
|
||||
import MsRequestResultTail from "./components/RequestResultTail";
|
||||
import ApiReportReqestHeaderItem from "./ApiReportReqestHeaderItem";
|
||||
import MsMetricChart from "./components/MetricChart";
|
||||
import MsReportTitle from "../../../common/components/report/MsReportTitle";
|
||||
import MsReportExportTemplate from "../../../common/components/report/MsReportExportTemplate";
|
||||
|
||||
export default {
|
||||
name: "MsApiReportExport",
|
||||
components: {
|
||||
MsReportExportTemplate,
|
||||
MsReportTitle, MsMetricChart, ApiReportReqestHeaderItem, MsRequestResultTail, MsScenarioResult
|
||||
},
|
||||
props: {
|
||||
content: Object,
|
||||
totalTime: Number,
|
||||
title: String
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.scenario-result {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.request-top, .request-bottom {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.url {
|
||||
color: #409EFF;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border-style: none;
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
.request-top div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="item">
|
||||
<div class="item-title">
|
||||
{{title}}
|
||||
</div>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ApiReportReqestHeaderItem",
|
||||
props: {title: String}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.item {
|
||||
width: 120px;
|
||||
height: 50px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-tag size="mini" type="info" v-if="row.status === 'Starting'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="primary" effect="plain" v-else-if="row.status === 'Running'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="success" v-else-if="row.status === 'Success'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="warning" v-else-if="row.status === 'Reporting'">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
|
||||
<template v-slot:content>
|
||||
<div>{{row.description}}</div>
|
||||
</template>
|
||||
<el-tag size="mini" type="danger">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else size="mini" type="info">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsApiReportStatus",
|
||||
|
||||
props: {
|
||||
row: Object
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<header class="report-header">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<span>{{ report.projectName === null || report.projectName ==='' ? "场景执行报告": report.projectName}} / </span>
|
||||
<router-link :to="path">{{ report.testName }}</router-link>
|
||||
<span class="time"> {{ report.createTime | timestampFormatDate }}</span>
|
||||
<el-button :disabled="isReadOnly" class="export-button" plain type="primary" size="mini" @click="handleExport(report.name)"
|
||||
style="margin-left: 1200px">
|
||||
{{$t('test_track.plan_view.export_report')}}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "MsApiReportViewHeader",
|
||||
props: ['report'],
|
||||
computed: {
|
||||
path() {
|
||||
return "/api/test/edit?id=" + this.report.testId;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isReadOnly: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.isReadOnly = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleExport(name) {
|
||||
this.$emit('reportExport', name);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.export-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<el-table :data="assertions" :row-style="getRowStyle" :header-cell-style="getRowStyle">
|
||||
<el-table-column prop="name" :label="$t('api_report.assertions_name')" width="300"/>
|
||||
<el-table-column prop="message" :label="$t('api_report.assertions_error_message')"/>
|
||||
<el-table-column prop="pass" :label="$t('api_report.assertions_is_success')" width="180">
|
||||
<template v-slot:default="{row}">
|
||||
<el-tag size="mini" type="success" v-if="row.pass">
|
||||
{{$t('api_report.success')}}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else>
|
||||
{{$t('api_report.fail')}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsAssertionResults",
|
||||
|
||||
props: {
|
||||
assertions: Array
|
||||
},
|
||||
|
||||
methods: {
|
||||
getRowStyle() {
|
||||
return {backgroundColor: "#F5F5F5"};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,255 @@
|
|||
<template>
|
||||
<div class="metric-container">
|
||||
<el-row type="flex" align="middle">
|
||||
<div style="width: 50%">
|
||||
<el-row type="flex" justify="center" align="middle">
|
||||
<el-row>
|
||||
<div class="metric-time">
|
||||
<div class="value" style="margin-right: 50px">{{ time }}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<ms-chart id="chart" ref="chart" :options="options" :autoresize="true"></ms-chart>
|
||||
<el-row type="flex" justify="center" align="middle">
|
||||
<i class="circle success"/>
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.success }}</div>
|
||||
<div class="name">{{ $t('api_report.success') }}</div>
|
||||
</div>
|
||||
<div style="width: 40px"></div>
|
||||
<i class="circle fail"/>
|
||||
<div class="metric-box">
|
||||
<div class="value">{{ content.error }}</div>
|
||||
<div class="name">{{ $t('api_report.fail') }}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="split"></div>
|
||||
<div style="width: 50%">
|
||||
<el-row type="flex" justify="space-around" align="middle">
|
||||
<div class="metric-icon-box">
|
||||
<i class="el-icon-warning-outline fail"></i>
|
||||
<div class="value">{{ fail }}</div>
|
||||
<div class="name">{{ $t('api_report.fail') }}</div>
|
||||
</div>
|
||||
<div class="metric-icon-box">
|
||||
<i class="el-icon-document-checked assertions"></i>
|
||||
<div class="value">{{ assertions }}</div>
|
||||
<div class="name">{{ $t('api_report.assertions_pass') }}</div>
|
||||
</div>
|
||||
<div class="metric-icon-box">
|
||||
<i class="el-icon-document-copy total"></i>
|
||||
<div class="value">{{ this.content.total }}</div>
|
||||
<div class="name">{{ $t('api_report.request') }}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsChart from "@/business/components/common/chart/MsChart";
|
||||
|
||||
export default {
|
||||
name: "MsMetricChart",
|
||||
components: {MsChart},
|
||||
props: {
|
||||
content: Object,
|
||||
totalTime: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hour: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
time: 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initTime()
|
||||
},
|
||||
methods: {
|
||||
initTime() {
|
||||
this.time = this.totalTime
|
||||
this.seconds = Math.floor(this.time / 1000)
|
||||
if (this.seconds >= 1) {
|
||||
if (this.seconds > 60) {
|
||||
this.minutes = Math.round(this.time / 60)
|
||||
this.seconds = Math.round(this.time % 60)
|
||||
this.time = this.minutes + "min" + this.seconds + "s"
|
||||
}
|
||||
if (this.seconds > 60) {
|
||||
this.minutes = Math.round(this.time / 60)
|
||||
this.seconds = Math.round(this.time % 60)
|
||||
this.time = this.minutes + "min" + this.seconds + "s"
|
||||
}
|
||||
if (this.minutes > 60) {
|
||||
this.hour = Math.round(this.minutes / 60)
|
||||
this.minutes = Math.round(this.minutes % 60)
|
||||
this.time = this.hour + "hour" + this.minutes + "min" + this.seconds + "s"
|
||||
}
|
||||
|
||||
this.time = (this.seconds) + "s"
|
||||
} else {
|
||||
this.time = this.totalTime + "ms"
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
return {
|
||||
color: ['#67C23A', '#F56C6C'],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
title: [{
|
||||
text: this.content.total,
|
||||
subtext: this.$t('api_report.request'),
|
||||
top: 'center',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
rich: {
|
||||
align: 'center',
|
||||
value: {
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
padding: [10, 0]
|
||||
},
|
||||
name: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
color: '#7F7F7F',
|
||||
}
|
||||
}
|
||||
}
|
||||
}],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['80%', '90%'],
|
||||
avoidLabelOverlap: false,
|
||||
hoverAnimation: false,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
borderColor: "#FFF",
|
||||
shadowColor: '#E1E1E1',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
{value: this.content.success},
|
||||
{value: this.content.error},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
fail() {
|
||||
return (this.content.error / this.content.total * 100).toFixed(0) + "%";
|
||||
},
|
||||
assertions() {
|
||||
return this.content.passAssertions + " / " + this.content.totalAssertions;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.metric-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.metric-container #chart {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.metric-container .split {
|
||||
margin: 20px;
|
||||
height: 100px;
|
||||
border-left: 1px solid #D8DBE1;
|
||||
}
|
||||
|
||||
.metric-container .circle {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 20px 1px rgba(200, 216, 226, .42);
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.metric-container .circle.success {
|
||||
background-color: #67C23A;
|
||||
}
|
||||
|
||||
.metric-container .circle.fail {
|
||||
background-color: #F56C6C;
|
||||
}
|
||||
|
||||
.metric-box {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-box .value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -.5px;
|
||||
}
|
||||
|
||||
.metric-time .value {
|
||||
font-size: 25px;
|
||||
font-weight: 400;
|
||||
letter-spacing: -.5px;
|
||||
}
|
||||
|
||||
.metric-box .name {
|
||||
font-size: 16px;
|
||||
letter-spacing: -.2px;
|
||||
color: #404040;
|
||||
}
|
||||
|
||||
.metric-icon-box {
|
||||
text-align: center;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.metric-icon-box .value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -.4px;
|
||||
line-height: 28px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.metric-icon-box .name {
|
||||
font-size: 13px;
|
||||
letter-spacing: 1px;
|
||||
color: #BFBFBF;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.metric-icon-box .fail {
|
||||
color: #F56C6C;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.metric-icon-box .assertions {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.metric-icon-box .total {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="metric-container">
|
||||
<el-row type="flex">
|
||||
<div class="metric">
|
||||
<div class="value">{{request.responseResult.responseTime}} ms</div>
|
||||
<div class="name">{{$t('api_report.response_time')}}</div>
|
||||
<br>
|
||||
<div class="value">{{request.responseResult.latency}} ms</div>
|
||||
<div class="name">{{$t('api_report.latency')}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="value">{{request.requestSize}} bytes</div>
|
||||
<div class="name">{{$t('api_report.request_size')}}</div>
|
||||
<br>
|
||||
<div class="value">{{request.responseResult.responseSize}} bytes</div>
|
||||
<div class="name">{{$t('api_report.response_size')}}</div>
|
||||
</div>
|
||||
|
||||
<div class="metric horizontal">
|
||||
<el-row type="flex">
|
||||
<div class="code">
|
||||
<div class="value" :class="{'error': error}">{{request.responseResult.responseCode}}</div>
|
||||
<div class="name">{{$t('api_report.response_code')}}</div>
|
||||
</div>
|
||||
<div class="split"></div>
|
||||
<div class="message">
|
||||
<div class="value">{{request.responseResult.responseMessage}}</div>
|
||||
<div class="name">{{$t('api_report.response_message')}}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsRequestMetric",
|
||||
|
||||
props: {
|
||||
request: Object
|
||||
},
|
||||
|
||||
computed: {
|
||||
error() {
|
||||
return this.request.responseResult.responseCode >= 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.metric-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
padding: 20px;
|
||||
border: 1px solid #EBEEF5;
|
||||
min-width: 120px;
|
||||
height: 114px;
|
||||
}
|
||||
|
||||
.metric + .metric {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.metric .value {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.metric .name {
|
||||
color: #404040;
|
||||
opacity: 0.5;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.metric.horizontal {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.metric .code {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.metric .code .value {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.metric .code .value.error {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.metric .split {
|
||||
height: 114px;
|
||||
border-left: 1px solid #EBEEF5;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.metric .message {
|
||||
max-height: 114px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,144 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<p class="el-divider--horizontal"></p>
|
||||
<div @click="active">
|
||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||
<el-col :span="10" v-if="indexNumber!=undefined">
|
||||
<div class="method">
|
||||
<div class="el-step__icon is-text ms-api-col" v-if="indexNumber%2 ==0">
|
||||
<div class="el-step__icon-inner"> {{ indexNumber+1 }}</div>
|
||||
</div>
|
||||
<div class="el-step__icon is-text ms-api-col-create" v-else>
|
||||
<div class="el-step__icon-inner"> {{ indexNumber+1 }}</div>
|
||||
</div>
|
||||
{{ request.name }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="method">
|
||||
{{ request.method }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-tooltip effect="dark" :content="request.responseResult.responseCode" placement="bottom" :open-delay="800">
|
||||
<div class="url">{{ request.responseResult.responseCode }}</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
{{request.responseResult.responseTime}} ms
|
||||
</el-col>
|
||||
|
||||
<el-col :span="2">
|
||||
<div class="success">
|
||||
<el-tag size="mini" type="success" v-if="request.success">
|
||||
{{ $t('api_report.success') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else>
|
||||
{{ $t('api_report.fail') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsRequestMetric from "./RequestMetric";
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsRequestText from "./RequestText";
|
||||
import MsResponseText from "./ResponseText";
|
||||
|
||||
export default {
|
||||
name: "MsRequestResult",
|
||||
components: {MsResponseText, MsRequestText, MsAssertionResults, MsRequestMetric},
|
||||
props: {
|
||||
request: Object,
|
||||
scenarioName: String,
|
||||
indexNumber: Number,
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
active() {
|
||||
this.$emit("requestResult", {request: this.request, scenarioName: this.scenarioName});
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.ms-api-col {
|
||||
background-color: #FCF1F1;
|
||||
border-color: #67C23A;
|
||||
margin-right: 10px;
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.ms-api-col-create {
|
||||
background-color: #EBF2F2;
|
||||
border-color: #008080;
|
||||
margin-right: 10px;
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 2px 0;
|
||||
background: 0 0;
|
||||
border-top: 1px solid #e8eaec;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,195 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<div @click="active">
|
||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||
<el-col :span="12">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||
{{scenarioName}}
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
{{$t('api_report.start_time')}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{$t('api_report.response_time')}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{$t('api_report.error')}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{$t('api_report.assertions')}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{$t('api_report.result')}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||
<el-col :span="2">
|
||||
<div class="method">
|
||||
{{request.method}}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<div class="name">{{request.name}}</div>
|
||||
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
|
||||
<div class="url">{{request.url}}</div>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
{{request.startTime | timestampFormatDate(true) }}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<div class="time">
|
||||
{{request.responseResult.responseTime}}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{request.error}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
{{assertion}}
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-tag size="mini" type="success" v-if="request.success">
|
||||
{{$t('api_report.success')}}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else>
|
||||
{{$t('api_report.fail')}}
|
||||
</el-tag>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="isActive">
|
||||
<el-tabs v-model="activeName" v-show="isActive" v-if="hasSub">
|
||||
<el-tab-pane :label="$t('api_report.sub_result')" name="sub">
|
||||
<ms-request-result class="sub-result" v-for="(sub, index) in request.subRequestResults"
|
||||
:key="index" :request="sub"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_report.request_result')" name="result">
|
||||
<ms-request-metric :request="request"/>
|
||||
<ms-request-text :request="request"/>
|
||||
<br>
|
||||
<ms-response-text :request-type="requestType" :response="request.responseResult"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-else>
|
||||
<ms-request-metric :request="request"/>
|
||||
<ms-request-text v-if="isCodeEditAlive" :request="request"/>
|
||||
<br>
|
||||
<ms-response-text :request-type="requestType" v-if="isCodeEditAlive" :response="request.responseResult"/>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsRequestMetric from "./RequestMetric";
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsRequestText from "./RequestText";
|
||||
import MsResponseText from "./ResponseText";
|
||||
import MsRequestResult from "./RequestResult";
|
||||
|
||||
export default {
|
||||
name: "MsRequestResultTail",
|
||||
components: {MsResponseText, MsRequestText, MsAssertionResults, MsRequestMetric, MsRequestResult},
|
||||
props: {
|
||||
request: Object,
|
||||
scenarioName: String,
|
||||
requestType: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "sub",
|
||||
isCodeEditAlive: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
reload() {
|
||||
this.isCodeEditAlive = false;
|
||||
this.$nextTick(() => (this.isCodeEditAlive = true));
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'request.responseResult'() {
|
||||
this.reload();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
assertion() {
|
||||
return this.request.passAssertions + " / " + this.request.totalAssertions;
|
||||
},
|
||||
hasSub() {
|
||||
return this.request.subRequestResults.length > 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
background-color: #F9F9F9;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.request-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<template>
|
||||
<div class="text-container">
|
||||
<div @click="active" class="collapse">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||
{{$t('api_report.request')}}
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane label="Body" name="body" class="pane">
|
||||
<pre>{{request.body}}</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Headers" name="headers" class="pane">
|
||||
<pre>{{request.headers}}</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Cookies" name="cookies" class="pane">
|
||||
<pre>{{request.cookies}}</pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsRequestText",
|
||||
|
||||
props: {
|
||||
request: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-container .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.text-container .collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-container .collapse:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text-container .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: #F9F9F9;
|
||||
padding: 10px;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<div class="text-container">
|
||||
<div @click="active" class="collapse">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||
{{ $t('api_report.response') }}
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane :class="'body-pane'" label="Body" name="body" class="pane">
|
||||
<ms-sql-result-table v-if="isSqlType" :body="response.body"/>
|
||||
<ms-code-edit v-if="!isSqlType" :mode="mode" :read-only="true" :data="response.body" :modes="modes" ref="codeEdit"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Headers" name="headers" class="pane">
|
||||
<pre>{{ response.headers }}</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
|
||||
<ms-assertion-results :assertions="response.assertions"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
|
||||
<pre>{{response.vars}}</pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane assertions">
|
||||
<template v-slot:label>
|
||||
<ms-dropdown v-if="!isSqlType" :commands="modes" :default-command="mode" @command="modeChange"/>
|
||||
<ms-dropdown v-if="isSqlType" :commands="sqlModes" :default-command="mode" @command="sqlModeChange"/>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
|
||||
import MsDropdown from "../../../../common/components/MsDropdown";
|
||||
import {BODY_FORMAT, RequestFactory, Request, SqlRequest} from "../../../definition/model/ApiTestModel";
|
||||
import MsSqlResultTable from "./SqlResultTable";
|
||||
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
|
||||
components: {
|
||||
MsSqlResultTable,
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
},
|
||||
|
||||
props: {
|
||||
requestType: String,
|
||||
response: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
sqlModes: ['text', 'table'],
|
||||
mode: BODY_FORMAT.TEXT
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
},
|
||||
sqlModeChange(mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (!this.response.headers) {
|
||||
return;
|
||||
}
|
||||
if (this.response.headers.indexOf("Content-Type: application/json") > 0) {
|
||||
this.mode = BODY_FORMAT.JSON;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isSqlType() {
|
||||
return (this.requestType === RequestFactory.TYPES.SQL && this.response.responseCode === '200');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.body-pane {
|
||||
padding: 10px !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
.text-container .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.text-container .collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-container .collapse:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text-container .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: #F5F5F5;
|
||||
padding: 0 10px;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container .pane.assertions {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div class="scenario-result">
|
||||
<div v-for="(request, index) in scenario.requestResults" :key="index">
|
||||
<ms-request-result :key="index" :request="request" :indexNumber="index"
|
||||
v-on:requestResult="requestResult"
|
||||
:scenarioName="scenario.name"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsRequestResult from "./RequestResult";
|
||||
|
||||
export default {
|
||||
name: "MsScenarioResult",
|
||||
|
||||
components: {MsRequestResult},
|
||||
|
||||
props: {
|
||||
scenario: Object,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
requestResult(requestResult) {
|
||||
this.$emit("requestResult", requestResult);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
assertion() {
|
||||
return this.scenario.passAssertions + " / " + this.scenario.totalAssertions;
|
||||
},
|
||||
success() {
|
||||
return this.scenario.error === 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scenario-result {
|
||||
width: 100%;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.scenario-result + .scenario-result {
|
||||
border-top: 1px solid #DCDFE6;
|
||||
}
|
||||
|
||||
.scenario-result .info {
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scenario-result .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.scenario-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<el-card class="scenario-results">
|
||||
<ms-scenario-result v-for="(scenario, index) in scenarios" :key="index" :scenario="scenario" :indexNumber="index"
|
||||
v-on:requestResult="requestResult"/>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsScenarioResult from "./ScenarioResult";
|
||||
|
||||
export default {
|
||||
name: "MsScenarioResults",
|
||||
|
||||
components: {MsScenarioResult},
|
||||
|
||||
props: {
|
||||
scenarios: Array
|
||||
},
|
||||
methods: {
|
||||
requestResult(requestResult) {
|
||||
this.$emit("requestResult", requestResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scenario-header {
|
||||
border: 1px solid #EBEEF5;
|
||||
background-color: #F9FCFF;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
v-for="(table, index) in tables"
|
||||
:key="index"
|
||||
:data="table.tableData"
|
||||
border
|
||||
size="mini"
|
||||
highlight-current-row>
|
||||
<el-table-column v-for="(title, index) in table.titles" :key="index" :label="title" min-width="150px">
|
||||
<template v-slot:default="scope">
|
||||
<el-popover
|
||||
placement="top"
|
||||
trigger="click">
|
||||
<el-container>
|
||||
<div>{{ scope.row[title] }}</div>
|
||||
</el-container>
|
||||
<span class="table-content" slot="reference">{{ scope.row[title] }}</span>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsSqlResultTable",
|
||||
data() {
|
||||
return {
|
||||
tables: [],
|
||||
titles: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
body: String
|
||||
},
|
||||
created() {
|
||||
if (!this.body) {
|
||||
return;
|
||||
}
|
||||
let rowArry = this.body.split("\n");
|
||||
this.getTableData(rowArry);
|
||||
if (this.tables.length > 1) {
|
||||
for (let i = 0; i < this.tables.length; i++) {
|
||||
if (this.tables[i].titles.length === 1 && i < this.tables.length - 1) {
|
||||
this.tables[i].tableData.splice(this.tables[i].tableData.length - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
let lastTable = this.tables[this.tables.length - 1];
|
||||
if (lastTable.titles.length === 1) {
|
||||
if (lastTable.tableData.length > 4) {
|
||||
lastTable.tableData.splice(lastTable.tableData.length - 4, 4);
|
||||
} else {
|
||||
this.tables.splice(this.tables.length - 1, 1);
|
||||
}
|
||||
} else {
|
||||
this.tables.splice(this.tables.length - 1, 1);
|
||||
}
|
||||
} else {
|
||||
let table = this.tables[0];
|
||||
table.tableData.splice(table.tableData.length - 4, 4);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTableData(rowArry) {
|
||||
let titles;
|
||||
let result = [];
|
||||
for (let i = 0; i < rowArry.length; i++) {
|
||||
let colArray = rowArry[i].split("\t");
|
||||
if (i === 0) {
|
||||
titles = colArray;
|
||||
} else {
|
||||
if (colArray.length != titles.length) {
|
||||
// 创建新的表
|
||||
if (colArray.length === 1 && colArray[0] === '') {
|
||||
this.getTableData(rowArry.slice(i + 1));
|
||||
} else {
|
||||
this.getTableData(rowArry.slice(i));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
let item = {};
|
||||
for (let j = 0; j < colArray.length; j++) {
|
||||
item[titles[j]] = (colArray[j] ? colArray[j] : "");
|
||||
}
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.tables.splice(0, 0, {
|
||||
titles: titles,
|
||||
tableData: result
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-table {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-table >>> .cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-container {
|
||||
overflow:auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -64,6 +64,7 @@
|
|||
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
|
||||
import ShowMoreBtn from "@/business/components/track/case/components/ShowMoreBtn";
|
||||
import MsTag from "../../../common/components/MsTag";
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioList",
|
||||
|
@ -101,7 +102,7 @@
|
|||
},
|
||||
methods: {
|
||||
search() {
|
||||
this.condition.filters = ["Saved", "Success", "Fail"];
|
||||
this.condition.filters = ["Prepare", "Underway", "Completed"];
|
||||
if (this.currentModule != null) {
|
||||
if (this.currentModule.id === "root") {
|
||||
this.condition.moduleIds = [];
|
||||
|
@ -158,10 +159,11 @@
|
|||
|
||||
},
|
||||
copy(row) {
|
||||
|
||||
row.id = getUUID();
|
||||
this.$emit('edit', row);
|
||||
},
|
||||
remove(row) {
|
||||
if (this.currentModule !== undefined && this.currentModule.id === "gc") {
|
||||
if (this.currentModule !== undefined && this.currentModule != null && this.currentModule.id === "gc") {
|
||||
this.$get('/api/automation/delete/' + row.id, () => {
|
||||
this.$success(this.$t('commons.delete_success'));
|
||||
this.search();
|
||||
|
|
|
@ -132,9 +132,9 @@
|
|||
</el-col>
|
||||
<el-col :span="8">
|
||||
{{$t('api_test.definition.request.run_env')}}:
|
||||
<el-select v-model="currentScenario.environmentId" size="small" class="ms-htt-width"
|
||||
<el-select v-model="currentEnvironmentId" size="small" class="ms-htt-width"
|
||||
:placeholder="$t('api_test.definition.request.run_env')"
|
||||
@change="environmentChange" clearable>
|
||||
clearable>
|
||||
<el-option v-for="(environment, index) in environments" :key="index"
|
||||
:label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
|
||||
:value="environment.id"/>
|
||||
|
@ -237,8 +237,8 @@
|
|||
<!--接口列表-->
|
||||
<el-drawer :visible.sync="apiListVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('api_test.automation.api_list_import')" :modal="false" size="90%">
|
||||
<ms-api-definition :visible="true" :currentRow="currentRow"/>
|
||||
<el-button style="float: right;margin: 20px" type="primary" @click="copyApi('REF')">{{$t('api_test.scenario.reference')}}</el-button>
|
||||
<el-button style="float: right;margin: 20px 0px 0px " @click="copyApi('Copy')">{{ $t('commons.copy') }}</el-button>
|
||||
<!--<el-button style="float: right;margin: 20px" type="primary" @click="copyApi('REF')">{{$t('api_test.scenario.reference')}}</el-button>-->
|
||||
<el-button style="float: right;margin: 20px 0px 0px " type="primary" @click="copyApi('Copy')">{{ $t('commons.copy') }}</el-button>
|
||||
</el-drawer>
|
||||
|
||||
<!--自定义接口-->
|
||||
|
@ -257,8 +257,12 @@
|
|||
<!--TAG-->
|
||||
<ms-add-tag @refreshTags="refreshTags" ref="tag"/>
|
||||
<!--执行组件-->
|
||||
<ms-run :debug="true" :environment="currentEnvironment" :reportId="reportId" :run-data="scenarioDefinition"
|
||||
<ms-run :debug="true" :environment="currentEnvironmentId" :reportId="reportId" :run-data="debugData"
|
||||
@runRefresh="runRefresh" ref="runTest"/>
|
||||
<!-- 调试结果 -->
|
||||
<el-drawer :visible.sync="debugVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('test_track.plan_view.test_result')" :modal="false" size="90%">
|
||||
<ms-api-report-detail :report-id="reportId"/>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
@ -283,6 +287,8 @@
|
|||
import MsRun from "./Run";
|
||||
import MsImportApiScenario from "./ImportApiScenario";
|
||||
import MsApiScenarioComponent from "./ApiScenarioComponent";
|
||||
import MsApiReportDetail from "../report/ApiReportDetail";
|
||||
|
||||
|
||||
export default {
|
||||
name: "EditApiScenario",
|
||||
|
@ -291,7 +297,7 @@
|
|||
currentProject: {},
|
||||
currentScenario: {},
|
||||
},
|
||||
components: {ApiEnvironmentConfig, MsAddTag, MsRun, MsApiScenarioComponent, MsImportApiScenario, MsJsr233Processor, MsConstantTimer, MsIfController, MsApiAssertions, MsApiExtract, MsApiDefinition, MsApiComponent, MsApiCustomize},
|
||||
components: {ApiEnvironmentConfig, MsApiReportDetail, MsAddTag, MsRun, MsApiScenarioComponent, MsImportApiScenario, MsJsr233Processor, MsConstantTimer, MsIfController, MsApiAssertions, MsApiExtract, MsApiDefinition, MsApiComponent, MsApiCustomize},
|
||||
data() {
|
||||
return {
|
||||
props: {
|
||||
|
@ -309,7 +315,7 @@
|
|||
},
|
||||
environments: [],
|
||||
tags: [],
|
||||
currentEnvironment: {},
|
||||
currentEnvironmentId: "",
|
||||
maintainerOptions: [],
|
||||
value: API_STATUS[0].id,
|
||||
options: API_STATUS,
|
||||
|
@ -319,6 +325,7 @@
|
|||
apiListVisible: false,
|
||||
customizeVisible: false,
|
||||
scenarioVisible: false,
|
||||
debugVisible: false,
|
||||
customizeRequest: {protocol: "HTTP", type: "API", hashTree: [], referenced: 'Created', active: false},
|
||||
operatingElements: [],
|
||||
currentRow: {cases: [], apis: []},
|
||||
|
@ -326,6 +333,7 @@
|
|||
expandedNode: [],
|
||||
scenarioDefinition: [],
|
||||
path: "/api/automation/create",
|
||||
debugData: [],
|
||||
reportId: "",
|
||||
}
|
||||
},
|
||||
|
@ -527,8 +535,14 @@
|
|||
},
|
||||
runDebug() {
|
||||
/*触发执行操作*/
|
||||
if (!this.currentEnvironmentId) {
|
||||
this.$error(this.$t('api_test.environment.select_environment'));
|
||||
return;
|
||||
}
|
||||
let scenario = {id: this.currentScenario.id, name: this.currentScenario.name, type: "scenario", referenced: 'Created', environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition};
|
||||
this.debugData = [];
|
||||
this.debugData.push(scenario);
|
||||
this.reportId = getUUID().substring(0, 8);
|
||||
//this.isReloadData = true;
|
||||
},
|
||||
getEnvironments() {
|
||||
if (this.currentProject) {
|
||||
|
@ -547,14 +561,6 @@
|
|||
}
|
||||
this.$refs.environmentConfig.open(this.currentProject.id);
|
||||
},
|
||||
environmentChange(value) {
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === value) {
|
||||
this.currentEnvironment = this.environments[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
environmentConfigClose() {
|
||||
this.getEnvironments();
|
||||
},
|
||||
|
@ -599,14 +605,18 @@
|
|||
})
|
||||
},
|
||||
getApiScenario() {
|
||||
if (this.currentScenario.tagId != undefined) {
|
||||
if (this.currentScenario.tagId != undefined && !(this.currentScenario.tagId instanceof Array)) {
|
||||
this.currentScenario.tagId = JSON.parse(this.currentScenario.tagId);
|
||||
}
|
||||
if (this.currentScenario.id) {
|
||||
this.path = "/api/automation/update";
|
||||
this.result = this.$get("/api/automation/getApiScenario/" + this.currentScenario.id, response => {
|
||||
if (response.data) {
|
||||
this.scenarioDefinition = JSON.parse(response.data.scenarioDefinition);
|
||||
this.path = "/api/automation/update";
|
||||
if (response.data.scenarioDefinition != null) {
|
||||
let obj = JSON.parse(response.data.scenarioDefinition);
|
||||
this.currentEnvironmentId = obj.environmentId;
|
||||
this.scenarioDefinition = obj.hashTree;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -614,11 +624,13 @@
|
|||
setParameter() {
|
||||
this.currentScenario.projectId = this.currentProject.id;
|
||||
if (!this.currentScenario.id) {
|
||||
this.currentScenario.id = getUUID().substring(0, 8);
|
||||
this.currentScenario.id = getUUID();
|
||||
}
|
||||
this.currentScenario.stepTotal = this.scenarioDefinition.length;
|
||||
this.currentScenario.modulePath = this.getPath(this.currentScenario.apiScenarioModuleId);
|
||||
this.currentScenario.scenarioDefinition = JSON.stringify(this.scenarioDefinition);
|
||||
// 构建一个场景对象 方便引用处理
|
||||
let scenario = {id: this.currentScenario.id, name: this.currentScenario.name, type: "scenario", referenced: 'Created', environmentId: this.currentEnvironmentId, hashTree: this.scenarioDefinition};
|
||||
this.currentScenario.scenarioDefinition = JSON.stringify(scenario);
|
||||
this.currentScenario.tagId = JSON.stringify(this.currentScenario.tagId);
|
||||
if (this.currentModule != null) {
|
||||
this.currentScenario.modulePath = this.currentModule.method !== undefined ? this.currentModule.method : null;
|
||||
|
@ -626,6 +638,7 @@
|
|||
}
|
||||
},
|
||||
runRefresh() {
|
||||
this.debugVisible = true;
|
||||
this.isReloadData = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
importApiScenario() {
|
||||
let scenarios = [];
|
||||
if (this.currentScenario) {
|
||||
console.log(this.currentScenario)
|
||||
this.currentScenario.forEach(item => {
|
||||
let obj = {id: item.id, name: item.name, type: "scenario", referenced: 'REF', resourceId: getUUID()};
|
||||
scenarios.push(obj);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
name: 'MsRun',
|
||||
components: {},
|
||||
props: {
|
||||
environment: Object,
|
||||
environment: String,
|
||||
debug: Boolean,
|
||||
reportId: String,
|
||||
runData: Array,
|
||||
|
@ -96,26 +96,21 @@
|
|||
},
|
||||
run() {
|
||||
let testPlan = new TestPlan();
|
||||
let threadGroup = new ThreadGroup();
|
||||
threadGroup.hashTree = [];
|
||||
testPlan.hashTree = [threadGroup];
|
||||
this.runData.forEach(item => {
|
||||
let threadGroup = new ThreadGroup();
|
||||
threadGroup.hashTree = [];
|
||||
threadGroup.name = item.name;
|
||||
threadGroup.hashTree.push(item);
|
||||
testPlan.hashTree.push(threadGroup);
|
||||
})
|
||||
let reqObj = {id: this.reportId, testElement: testPlan};
|
||||
console.log("====",testPlan)
|
||||
let reqObj = {id: this.reportId, reportId: this.reportId, environmentId: this.environment, testElement: testPlan};
|
||||
let bodyFiles = this.getBodyUploadFiles(reqObj);
|
||||
let url = "";
|
||||
if (this.debug) {
|
||||
url = "/api/automation/run/debug";
|
||||
} else {
|
||||
reqObj.reportId = "run";
|
||||
url = "/api/definition/run";
|
||||
}
|
||||
let url = "/api/automation/run";
|
||||
this.$fileUpload(url, null, bodyFiles, reqObj, response => {
|
||||
this.runId = response.data;
|
||||
this.getResult();
|
||||
}, erro => {
|
||||
this.$emit('runRefresh', {});
|
||||
}, erro => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export default class TestPlan extends HashTreeElement {
|
|||
this.serializeThreadGroups = this.initBoolProp('TestPlan.serialize_threadgroups', false);
|
||||
this.tearDownOnShutdown = this.initBoolProp('TestPlan.tearDown_on_shutdown', true);
|
||||
this.userDefineClasspath = this.initStringProp('TestPlan.user_define_classpath');
|
||||
|
||||
this.hashTree = [];
|
||||
this.userDefinedVariables = [];
|
||||
|
||||
let elementProp = this.initElementProp('TestPlan.user_defined_variables', 'Arguments');
|
||||
|
|
|
@ -40,6 +40,11 @@
|
|||
<el-divider class="menu-divider"/>
|
||||
<ms-show-all :index="'/api/report/list/all'"/>
|
||||
</el-submenu>
|
||||
|
||||
|
||||
<el-menu-item v-permission="['test_manager','test_user','test_viewer']" :index="'/api/monitor/view'">
|
||||
{{ $t('commons.monitor') }}
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
<template>
|
||||
<ms-container>
|
||||
|
||||
<ms-aside-container>
|
||||
<ms-api-monitor-search @getApiUrl="getApiUrl(arguments)" @getTodayData="getTodayData"
|
||||
@initPage="initPage(arguments)"></ms-api-monitor-search>
|
||||
</ms-aside-container>
|
||||
|
||||
<ms-main-container>
|
||||
|
||||
<div class="menu-wrapper">
|
||||
|
||||
<div class="menu-left">
|
||||
<el-radio-group id="date-radio" v-model=radioSelect size="small">
|
||||
<el-radio-button :label="$t('api_monitor.today')" @click.native.prevent="getTodayData()"/>
|
||||
<el-radio-button :label="$t('api_monitor.this_week')" @click.native.prevent="getWeekData()"/>
|
||||
<el-radio-button :label="$t('api_monitor.this_mouth')" @click.native.prevent="getMouthData()"/>
|
||||
</el-radio-group>
|
||||
<el-date-picker id="date-picker"
|
||||
v-model="datePicker"
|
||||
:end-placeholder="$t('api_monitor.end_time')"
|
||||
:range-separator="$t('api_monitor.to')"
|
||||
:start-placeholder="$t('api_monitor.start_time')"
|
||||
class="sales-view-date-picker"
|
||||
size="small"
|
||||
type="daterange"
|
||||
unlink-panels
|
||||
value-format="yyyy-MM-dd HH:mm:ss" @blur="getDatePicker"
|
||||
/>
|
||||
<el-tag id="apiInfo" type="info"><a id="api-url-title">{{ apiUrl }}</a></el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<ms-api-monitor-chart :api-url="apiUrl" :rspCodeData=this.rspCodeData
|
||||
:rspCodexAxis=this.rspCodexAxis :rspTimeData=this.rspTimeData
|
||||
:rspTimexAxis=this.rspTimexAxis></ms-api-monitor-chart>
|
||||
</ms-main-container>
|
||||
</ms-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import ApiMonitorCharts from '@/business/components/api/monitor/ApiMonitorChart';
|
||||
import MsApiMonitorChart from '@/business/components/api/monitor/ApiMonitorChart';
|
||||
import MsApiMonitorSearch from '@/business/components/api/monitor/ApiMonitorSearch';
|
||||
import MsMainContainer from '@/business/components/common/components/MsMainContainer';
|
||||
import MsAsideContainer from '@/business/components/common/components/MsAsideContainer';
|
||||
import MsContainer from '@/business/components/common/components/MsContainer';
|
||||
import {formatTime} from '@/common/js/format-utils';
|
||||
|
||||
export default {
|
||||
name: 'MsApiMonitor',
|
||||
data() {
|
||||
return {
|
||||
datePicker: null,
|
||||
rspTimeData: [],
|
||||
rspTimexAxis: [],
|
||||
rspCodeData: [],
|
||||
rspCodexAxis: [],
|
||||
apiUrl: '',
|
||||
radioSelect: this.$t('api_monitor.today'),
|
||||
};
|
||||
},
|
||||
activated() {
|
||||
this.initData();
|
||||
},
|
||||
methods: {
|
||||
initData() {
|
||||
this.rspTimeData = [];
|
||||
this.rspTimexAxis = [];
|
||||
this.rspCodeData = [];
|
||||
this.rspCodexAxis = [];
|
||||
},
|
||||
initPage(url) {
|
||||
this.apiUrl = url[0];
|
||||
let date1 = new Date();
|
||||
let today1 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()));
|
||||
let today2 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate() + 1));
|
||||
this.initData();
|
||||
this.getResponseTime(this.apiUrl, today1, today2);
|
||||
this.getResponseCode(this.apiUrl, today1, today2);
|
||||
},
|
||||
getResponseTime(apiUrl, startTime, endTime) {
|
||||
return this.$$get('/api/monitor/getResponseTime',
|
||||
{'startTime': startTime, 'endTime': endTime},
|
||||
{'apiUrl': apiUrl}, response => {
|
||||
Object.values(response.data).forEach(value => {
|
||||
this.rspTimexAxis.push(value.startTime);
|
||||
this.rspTimeData.push(value.responseTime);
|
||||
});
|
||||
});
|
||||
},
|
||||
getResponseCode(apiUrl, startTime, endTime) {
|
||||
return this.$$get('/api/monitor/getResponseCode',
|
||||
{'startTime': startTime, 'endTime': endTime},
|
||||
{'apiUrl': this.apiUrl}, response => {
|
||||
Object.values(response.data).forEach(value => {
|
||||
this.rspCodexAxis.push(value.startTime);
|
||||
this.rspCodeData.push(value.responseCode);
|
||||
});
|
||||
});
|
||||
},
|
||||
getDatePicker() {
|
||||
this.initData();
|
||||
this.getResponseTime(this.apiUrl, this.datePicker[0], this.datePicker[1]);
|
||||
this.getResponseCode(this.apiUrl, this.datePicker[0], this.datePicker[1]);
|
||||
},
|
||||
//获取周一日期
|
||||
getFirstDayOfWeek(date) {
|
||||
let day = date.getDay() || 7;
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1 - day);
|
||||
},
|
||||
getApiUrl(payload) {
|
||||
this.apiUrl = payload[0];
|
||||
},
|
||||
//获取今日数据
|
||||
getTodayData() {
|
||||
this.radioSelect = this.$t('api_monitor.today');
|
||||
let date1 = new Date();
|
||||
let today1 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()));
|
||||
let today2 = formatTime(new Date(date1.getFullYear(), date1.getMonth(), date1.getDate() + 1));
|
||||
this.initData();
|
||||
this.getResponseTime(this.apiUrl, today1, today2);
|
||||
this.getResponseCode(this.apiUrl, today1, today2);
|
||||
},
|
||||
//获取周数据
|
||||
getWeekData() {
|
||||
this.radioSelect = this.$t('api_monitor.this_week');
|
||||
const date1 = new Date();
|
||||
let today = formatTime(date1);
|
||||
let monday = formatTime(this.getFirstDayOfWeek(date1));
|
||||
this.initData();
|
||||
this.getResponseTime(this.apiUrl, monday, today);
|
||||
this.getResponseCode(this.apiUrl, monday, today);
|
||||
},
|
||||
//获取月数据
|
||||
getMouthData() {
|
||||
this.radioSelect = this.$t('api_monitor.this_mouth');
|
||||
const day = new Date(), y = day.getFullYear(), m = day.getMonth();
|
||||
let firstDay = formatTime(new Date(y, m, 1));
|
||||
let today = formatTime(day);
|
||||
this.initData();
|
||||
this.getResponseTime(this.apiUrl, firstDay, today);
|
||||
this.getResponseCode(this.apiUrl, firstDay, today);
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
MsApiMonitorChart,
|
||||
MsContainer,
|
||||
MsAsideContainer,
|
||||
MsMainContainer,
|
||||
MsApiMonitorSearch,
|
||||
ApiMonitorCharts,
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu-left #apiInfo {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#api-url-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="monitor-view">
|
||||
<div class="response-time-view">
|
||||
<el-card shadow="hover">
|
||||
<api-response-time-monitor-chart :data="rspTimeData" :xAxis="rspTimexAxis"></api-response-time-monitor-chart>
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="error-monitor-view">
|
||||
<el-card shadow="hover">
|
||||
<api-error-monitor-chart :api-url="apiUrl" :data="rspCodeData" :xAxis="rspCodexAxis"></api-error-monitor-chart>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import ApiResponseTimeMonitorChart from '@/business/components/api/monitor/components/ApiResponseTimeMonitorChart';
|
||||
import ApiErrorMonitorChart from '@/business/components/api/monitor/components/ApiErrorMonitorChart';
|
||||
|
||||
export default {
|
||||
name: 'MsApiMonitorChart',
|
||||
props: [
|
||||
'rspTimeData',
|
||||
'rspTimexAxis',
|
||||
'rspCodeData',
|
||||
'rspCodexAxis',
|
||||
'apiUrl'
|
||||
],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
components: {
|
||||
ApiErrorMonitorChart,
|
||||
ApiResponseTimeMonitorChart,
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-monitor-view {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-input v-model="input" :placeholder="$t('api_monitor.please_search')" prefix-icon="el-icon-search" type="text"
|
||||
@input="searchAction"></el-input>
|
||||
|
||||
<el-table
|
||||
:data="searchResult"
|
||||
:show-header="false"
|
||||
border
|
||||
class="question-tab2"
|
||||
style="width: 100%"
|
||||
@row-click="getRowInfo"
|
||||
>
|
||||
<el-table-column
|
||||
:label="$t('api_monitor.date')"
|
||||
prop="url"
|
||||
style="width: 100%">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function throttle(fn, delay) {
|
||||
let t = null,
|
||||
begin = new Date().getTime();
|
||||
|
||||
return function () {
|
||||
const _self = this,
|
||||
args = arguments,
|
||||
cur = new Date().getTime();
|
||||
clearTimeout(t);
|
||||
if (cur - begin >= delay) {
|
||||
fn.apply(_self, args);
|
||||
begin = cur;
|
||||
} else {
|
||||
t = setTimeout(function () {
|
||||
fn.apply(_self, args);
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'MsApiMonitorSearch',
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
searchResult: [],
|
||||
items: [],
|
||||
input: '',
|
||||
rowData: '',
|
||||
apiUrl: '',
|
||||
firstUrl: '',
|
||||
rspTimeData: [],
|
||||
rspTimexAxis: [],
|
||||
rspCodeData: [],
|
||||
rspCodexAxis: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
searchAction: throttle(function (e) {
|
||||
this.searchResult = this.items.filter((item) => {
|
||||
if (item.url.includes(this.input)) {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
search() {
|
||||
this.result = this.$get('/api/monitor/list', response => {
|
||||
this.items = response.data;
|
||||
});
|
||||
},
|
||||
getRowInfo(val) {
|
||||
this.rowData = val;
|
||||
this.apiUrl = this.rowData.url;
|
||||
this.$emit('getApiUrl', this.apiUrl, this.firstUrl);
|
||||
this.$emit('getTodayData');
|
||||
},
|
||||
|
||||
},
|
||||
activated() {
|
||||
this.searchResult=[];
|
||||
this.result = this.$get('/api/monitor/list', response => {
|
||||
if (response.data.length !== 0) {
|
||||
this.searchResult = response.data;
|
||||
this.firstUrl = response.data[0].url;
|
||||
this.$emit('initPage', this.firstUrl);
|
||||
this.search();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<common-monitor-chart>
|
||||
<template>
|
||||
<div id="response-time-chart" :style="{ width:'100%',height:'100%' }">
|
||||
<chart :options="getOptions()" :style="{ width:'100%' }"></chart>
|
||||
</div>
|
||||
</template>
|
||||
</common-monitor-chart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonMonitorChart from '@/business/components/api/monitor/components/CommonMonitorChart';
|
||||
|
||||
export default {
|
||||
|
||||
name: 'ApiErrorMonitorChart',
|
||||
components: {CommonMonitorChart},
|
||||
props: [
|
||||
'data',
|
||||
'xAxis',
|
||||
'apiUrl',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
reportId: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
click(params) {
|
||||
//如果状态不是以2开头
|
||||
if (params.value.substr(0, 1) !== '2') {
|
||||
let startTime = params.name;
|
||||
this.result = this.$$get('/api/monitor/getReportId', {'startTime': startTime}, {
|
||||
'apiUrl': this.apiUrl
|
||||
}, response => {
|
||||
this.reportId = response.data;
|
||||
let reportId = this.reportId
|
||||
let url = '#/api/report/view/' + reportId;
|
||||
let target = '_blank';
|
||||
window.open(url, target);
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
getOptions() {
|
||||
return {
|
||||
title: {text: 'HTTP状态码趋势'},
|
||||
tooltip: {},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
dataZoom: {
|
||||
yAxisIndex: 'none'
|
||||
},
|
||||
dataView: {
|
||||
readOnly: false
|
||||
},
|
||||
restore: {},
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
dataZoom: [{
|
||||
start: 0
|
||||
}],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.xAxis
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 100,
|
||||
max: 500,
|
||||
splitNumber: 5
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: this.data,
|
||||
|
||||
lineStyle: {
|
||||
color: '#32CD32'
|
||||
},
|
||||
itemStyle: {},
|
||||
}],
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<common-monitor-chart>
|
||||
<template>
|
||||
<div id="response-time-chart" :style="{ width:'100%',height:'100%' }">
|
||||
<chart :options="getOptions()" :style="{ width:'100%' }"></chart>
|
||||
</div>
|
||||
</template>
|
||||
</common-monitor-chart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonMonitorChart from '@/business/components/api/monitor/components/CommonMonitorChart';
|
||||
|
||||
export default {
|
||||
|
||||
name: 'ApiResponseTimeMonitorChart',
|
||||
components: {CommonMonitorChart},
|
||||
props: [
|
||||
'data',
|
||||
'xAxis'
|
||||
],
|
||||
methods: {
|
||||
getOptions() {
|
||||
return {
|
||||
title: {text: '响应时间趋势'},
|
||||
tooltip: {},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
dataZoom: {
|
||||
yAxisIndex: 'none'
|
||||
},
|
||||
dataView: {
|
||||
readOnly: false
|
||||
},
|
||||
restore: {},
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.xAxis
|
||||
},
|
||||
yAxis: {
|
||||
|
||||
},
|
||||
dataZoom: [{
|
||||
start: 0
|
||||
}],
|
||||
series: [{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: this.data
|
||||
}],
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="common-card">
|
||||
<div class="chart">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="line"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: String,
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.line {
|
||||
margin: 10px 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -81,7 +81,7 @@ export default {
|
|||
if (!this.response.headers) {
|
||||
return;
|
||||
}
|
||||
if (this.response.headers.indexOf("Content-Type: application/json") > 0) {
|
||||
if (this.response.headers.indexOf("application/json") > 0) {
|
||||
this.mode = BODY_FORMAT.JSON;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -118,4 +118,12 @@
|
|||
max-height: 500px;
|
||||
}
|
||||
|
||||
.el-table >>> th {
|
||||
-webkit-user-select: text;
|
||||
-khtml-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -226,8 +226,9 @@ export default {
|
|||
if (handler.rampUpTime < handler.step) {
|
||||
handler.step = handler.rampUpTime;
|
||||
}
|
||||
let color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
|
||||
handler.options = {
|
||||
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
|
||||
color: color,
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
|
@ -261,10 +262,10 @@ export default {
|
|||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: hexToRgba(handler.options.color[i], 0.3),
|
||||
color: hexToRgba(color[i % color.length], 0.3),
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: hexToRgba(handler.options.color[i], 0),
|
||||
color: hexToRgba(color[i % color.length], 0),
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
|
@ -272,7 +273,7 @@ export default {
|
|||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: hexToRgb(handler.options.color[i]),
|
||||
color: hexToRgb(color[i % color.length]),
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
}
|
||||
|
|
|
@ -268,8 +268,9 @@ export default {
|
|||
if (handler.rampUpTime < handler.step) {
|
||||
handler.step = handler.rampUpTime;
|
||||
}
|
||||
let color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
|
||||
handler.options = {
|
||||
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
|
||||
color: color,
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
|
@ -302,10 +303,10 @@ export default {
|
|||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: hexToRgba(handler.options.color[i], 0.3),
|
||||
color: hexToRgba(color[i % color.length], 0.3),
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: hexToRgba(handler.options.color[i], 0),
|
||||
color: hexToRgba(color[i % color.length], 0),
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
|
@ -313,7 +314,7 @@ export default {
|
|||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: hexToRgb(handler.options.color[i]),
|
||||
color: hexToRgb(color[i % color.length]),
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
}
|
||||
|
|
|
@ -74,12 +74,12 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$$get = function (url, data, success) {
|
||||
Vue.prototype.$$get = function (url, data, header, success) {
|
||||
let result = {loading: true};
|
||||
if (!success) {
|
||||
return axios.get(url, {params: data});
|
||||
return axios.get(url, {params: data, headers: header});
|
||||
} else {
|
||||
axios.get(url, {params: data}).then(response => {
|
||||
axios.get(url, {params: data, headers: header}).then(response => {
|
||||
then(success, response, result);
|
||||
}).catch(error => {
|
||||
exception(error, result, url);
|
||||
|
|
|
@ -4,6 +4,8 @@ import 'echarts/lib/chart/bar'
|
|||
import 'echarts/lib/chart/pie'
|
||||
import 'echarts/lib/component/tooltip'
|
||||
import 'echarts/lib/component/title'
|
||||
import 'echarts/lib/component/toolbox';
|
||||
import 'echarts/lib/component/dataZoom';
|
||||
import 'zrender/lib/svg/svg'
|
||||
|
||||
export default {
|
||||
|
|
|
@ -120,6 +120,42 @@ export function formatXml(text) {
|
|||
return outputText.replace(/\s+$/g, '').replace(/\r/g, '\r\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param time 时间
|
||||
* @param cFormat 格式
|
||||
* @returns {string|null} 字符串
|
||||
* @example formatTime('2018-1-29', '{y}/{m}/{d} {h}:{i}:{s}') // -> 2018/01/29 00:00:00
|
||||
*/
|
||||
export function formatTime(time, cFormat) {
|
||||
if (arguments.length === 0) return null;
|
||||
if ((time + '').length === 10) {
|
||||
time = +time * 1000;
|
||||
}
|
||||
let format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}', date;
|
||||
if (typeof time === 'object') {
|
||||
date = time;
|
||||
} else {
|
||||
date = new Date(time);
|
||||
}
|
||||
let formatObj = {
|
||||
y: date.getFullYear(),
|
||||
m: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
i: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
a: date.getDay()
|
||||
};
|
||||
return format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||
let value = formatObj[key];
|
||||
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
|
||||
if (result.length > 0 && value < 10) {
|
||||
value = '0' + value;
|
||||
}
|
||||
return value || 0;
|
||||
});
|
||||
}
|
||||
|
||||
function getPrefix(prefixIndex) {
|
||||
var span = ' ';
|
||||
var output = [];
|
||||
|
|
|
@ -162,7 +162,8 @@ export default {
|
|||
between: "Between",
|
||||
current_user: "Current user"
|
||||
}
|
||||
}
|
||||
},
|
||||
monitor:"monitor"
|
||||
},
|
||||
license: {
|
||||
title: 'Authorization management',
|
||||
|
@ -777,6 +778,16 @@ export default {
|
|||
running: "The test is reporting",
|
||||
not_exist: "Test report does not exist",
|
||||
},
|
||||
api_monitor: {
|
||||
to:"to",
|
||||
start_time:"Start Time",
|
||||
end_time:"End Time",
|
||||
today:"Today",
|
||||
this_week:"This Week",
|
||||
this_mouth:"This Mouth",
|
||||
please_search:"Please Search",
|
||||
date:"Date"
|
||||
},
|
||||
test_track: {
|
||||
test_track: "Track",
|
||||
confirm: "Confirm",
|
||||
|
|
|
@ -162,7 +162,8 @@ export default {
|
|||
between: "之间",
|
||||
current_user: "是当前用户"
|
||||
}
|
||||
}
|
||||
},
|
||||
monitor: "监控"
|
||||
},
|
||||
license: {
|
||||
title: '授权管理',
|
||||
|
@ -807,6 +808,16 @@ export default {
|
|||
running: "测试报告导出中",
|
||||
not_exist: "测试报告不存在",
|
||||
},
|
||||
api_monitor: {
|
||||
to:"至",
|
||||
start_time:"开始日期",
|
||||
end_time:"结束日期",
|
||||
today:"今日",
|
||||
this_week:"本周",
|
||||
this_mouth:"本月",
|
||||
please_search:"请搜索",
|
||||
date:"日期"
|
||||
},
|
||||
test_track: {
|
||||
test_track: "测试跟踪",
|
||||
confirm: "确 定",
|
||||
|
|
|
@ -162,7 +162,8 @@ export default {
|
|||
between: "之間",
|
||||
current_user: "是當前用戶"
|
||||
}
|
||||
}
|
||||
},
|
||||
monitor:"監控"
|
||||
},
|
||||
license: {
|
||||
title: '授權管理',
|
||||
|
@ -780,6 +781,16 @@ export default {
|
|||
running: "測試報告導出中",
|
||||
not_exist: "測試報告不存在",
|
||||
},
|
||||
api_monitor: {
|
||||
to:"到",
|
||||
start_time:"開始時間",
|
||||
end_time:"結束時間",
|
||||
today:"今天",
|
||||
this_week:"本週",
|
||||
this_mouth:"本月",
|
||||
please_search:"請搜索",
|
||||
date:"日期"
|
||||
},
|
||||
test_track: {
|
||||
test_track: "測試跟蹤",
|
||||
confirm: "確 定",
|
||||
|
|
Loading…
Reference in New Issue