Merge branch 'v1.8' of github.com:metersphere/metersphere into v1.8

This commit is contained in:
chenjianxing 2021-03-30 10:54:04 +08:00
commit e56e2e19b5
118 changed files with 3884 additions and 2628 deletions

View File

@ -17,7 +17,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<shiro.version>1.6.0</shiro.version>
<java.version>1.8</java.version>
<jmeter.version>5.2.1</jmeter.version>
<jmeter.version>5.4.1</jmeter.version>
<nacos.version>1.1.3</nacos.version>
<dubbo.version>2.7.8</dubbo.version>
<graalvm.version>20.1.0</graalvm.version>
@ -276,18 +276,11 @@
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<!-- swagger2 解析 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-parser</artifactId>
<version>1.0.51</version>
</dependency>
<!-- swagger3 解析 最新版本会有swagger-core版本冲突 -->
<!-- swagger 解析 -->
<dependency>
<groupId>io.swagger.parser.v3</groupId>
<artifactId>swagger-parser</artifactId>
<version>2.0.18</version>
<version>2.0.22</version>
</dependency>
<!-- 执行 js 代码依赖 -->
@ -674,4 +667,4 @@
</plugins>
</build>
</project>
</project>

View File

@ -74,11 +74,21 @@ public class ApiAutomationController {
apiAutomationService.deleteBatch(ids);
}
@PostMapping("/deleteBatchByCondition")
public void deleteBatchByCondition(@RequestBody ApiScenarioBatchRequest request) {
apiAutomationService.deleteBatchByCondition(request);
}
@PostMapping("/removeToGc")
public void removeToGc(@RequestBody List<String> ids) {
apiAutomationService.removeToGc(ids);
}
@PostMapping("/removeToGcByBatch")
public void removeToGcByBatch(@RequestBody ApiScenarioBatchRequest request) {
apiAutomationService.removeToGcByBatch(request);
}
@PostMapping("/reduction")
public void reduction(@RequestBody List<String> ids) {
apiAutomationService.reduction(ids);

View File

@ -7,12 +7,12 @@ import io.metersphere.api.dto.definition.*;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.ApiTestCase;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.track.request.testcase.ApiCaseRelevanceRequest;
import io.metersphere.track.service.TestPlanApiCaseService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
@ -29,7 +29,8 @@ public class ApiTestCaseController {
@Resource
private ApiTestCaseService apiTestCaseService;
@Resource
private TestPlanApiCaseService testPlanApiCaseService;
@PostMapping("/list")
public List<ApiTestCaseResult> list(@RequestBody ApiTestCaseRequest request) {
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
@ -48,6 +49,12 @@ public class ApiTestCaseController {
return null;
}
}
@GetMapping("/getStateByTestPlan/{id}")
public String getStateByTestPlan(@PathVariable String id ) {
String status=testPlanApiCaseService.getState(id);
return status;
}
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<ApiTestCaseDTO>> listSimple(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiTestCaseRequest request) {
@ -131,7 +138,8 @@ public class ApiTestCaseController {
return apiTestCaseService.run(request);
}
@GetMapping(value = "/jenkins/exec/result/{id}")
public String getExecResult(@PathVariable String id) {
return apiTestCaseService.getExecResult(id);
public String getExecResult(@PathVariable String id) {
return apiTestCaseService.getExecResult(id);
}
}

View File

@ -29,6 +29,7 @@ public class EsbDataStruct {
private boolean required;
private String description;
private List<EsbDataStruct> children;
private String status = "";
public void init(){
this.uuid = UUID.randomUUID().toString();
@ -127,6 +128,9 @@ public class EsbDataStruct {
if (element != null) {
if (this.children == null || this.children.isEmpty()) {
if(this.value == null ){
this.value = "";
}
element.addText(this.value);
} else {
for (EsbDataStruct child : children) {

View File

@ -75,6 +75,8 @@ import org.apache.jorphan.collections.HashTree;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
@ -132,12 +134,68 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
return (HashTree) field.get(scriptWrapper);
}
public boolean isProtocolDefaultPort(HTTPSamplerProxy source) {
String portAsString = source.getPropertyAsString("HTTPSampler.port");
if (portAsString != null && !portAsString.isEmpty()) {
return false;
} else {
return true;
}
}
public String url(String protocol, String host, String port, String file) {
protocol = protocol.toLowerCase();
if (StringUtils.isNotEmpty(file) && !file.startsWith("/")) {
file += "/";
}
return protocol + "://" + host + ":" + port + file;
}
public String getUrl(HTTPSamplerProxy source) throws MalformedURLException {
String path = source.getPath();
if (!path.startsWith("http://") && !path.startsWith("https://")) {
String domain = source.getDomain();
String protocol = source.getProtocol();
String method = source.getMethod();
StringBuilder pathAndQuery = new StringBuilder(100);
if ("file".equalsIgnoreCase(protocol)) {
domain = null;
} else if (!path.startsWith("/")) {
pathAndQuery.append('/');
}
pathAndQuery.append(path);
if ("GET".equals(method) || "DELETE".equals(method) || "OPTIONS".equals(method)) {
String queryString = source.getQueryString(source.getContentEncoding());
if (queryString.length() > 0) {
if (path.contains("?")) {
pathAndQuery.append("&");
} else {
pathAndQuery.append("?");
}
pathAndQuery.append(queryString);
}
}
String portAsString = source.getPropertyAsString("HTTPSampler.port");
return this.isProtocolDefaultPort(source) ? new URL(protocol, domain, pathAndQuery.toString()).toExternalForm() : this.url(protocol, domain, portAsString, pathAndQuery.toString());
} else {
return new URL(path).toExternalForm();
}
}
private void convertHttpSampler(MsHTTPSamplerProxy samplerProxy, Object key) {
try {
HTTPSamplerProxy source = (HTTPSamplerProxy) key;
BeanUtils.copyBean(samplerProxy, source);
samplerProxy.setRest(new ArrayList<KeyValue>() {{
this.add(new KeyValue());
}});
samplerProxy.setArguments(new ArrayList<KeyValue>() {{
this.add(new KeyValue());
}});
if (source != null && source.getHTTPFiles().length > 0) {
samplerProxy.getBody().setBinary(new ArrayList<>());
samplerProxy.getBody().initBinary();
samplerProxy.getBody().setType(Body.FORM_DATA);
List<KeyValue> keyValues = new LinkedList<>();
for (HTTPFileArg arg : source.getHTTPFiles()) {
@ -156,13 +214,15 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
samplerProxy.getBody().setKvs(keyValues);
}
samplerProxy.setProtocol(RequestType.HTTP);
samplerProxy.setPort(source.getPort() + "");
samplerProxy.setPort(source.getPropertyAsString("HTTPSampler.port"));
samplerProxy.setDomain(source.getDomain());
if (source.getArguments() != null) {
if (source.getPostBodyRaw()) {
samplerProxy.getBody().setType(Body.RAW);
source.getArguments().getArgumentsAsMap().forEach((k, v) -> {
samplerProxy.getBody().setRaw(v);
});
samplerProxy.getBody().initKvs();
} else {
List<KeyValue> keyValues = new LinkedList<>();
source.getArguments().getArgumentsAsMap().forEach((k, v) -> {
@ -173,11 +233,12 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
samplerProxy.setArguments(keyValues);
}
}
samplerProxy.getBody().initBinary();
}
samplerProxy.setPath("");
// samplerProxy.setPath(source.getPath());
samplerProxy.setMethod(source.getMethod());
if (source.getUrl() != null) {
samplerProxy.setUrl(source.getUrl().toString());
if (this.getUrl(source) != null) {
samplerProxy.setUrl(this.getUrl(source));
}
samplerProxy.setId(UUID.randomUUID().toString());
samplerProxy.setType("HTTPSamplerProxy");

View File

@ -58,14 +58,16 @@ public class MsDefinitionParser extends MsAbstractParser<ApiDefinitionImport> {
private ApiDefinitionImport parseMsFormat(String testStr, ApiTestImportRequest importRequest) {
ApiDefinitionImport apiDefinitionImport = JSON.parseObject(testStr, ApiDefinitionImport.class);
Map<String, List<ApiTestCaseWithBLOBs>> caseMap = new HashMap<>();
apiDefinitionImport.getCases().forEach(item -> {
List<ApiTestCaseWithBLOBs> caseList = caseMap.get(item.getApiDefinitionId());
if (caseList == null) {
caseList = new ArrayList<>();
caseMap.put(item.getApiDefinitionId(), caseList);
}
caseList.add(item);
});
if (apiDefinitionImport.getCases() != null) {
apiDefinitionImport.getCases().forEach(item -> {
List<ApiTestCaseWithBLOBs> caseList = caseMap.get(item.getApiDefinitionId());
if (caseList == null) {
caseList = new ArrayList<>();
caseMap.put(item.getApiDefinitionId(), caseList);
}
caseList.add(item);
});
}
apiDefinitionImport.getData().forEach(apiDefinition -> {
parseApiDefinition(apiDefinition, importRequest, caseMap);
});

View File

@ -70,8 +70,9 @@ public class Swagger2Parser extends SwaggerAbstractParser {
parseParameters(operation, request);
addBodyHeader(request);
if (StringUtils.isNotBlank(basePath)) {
apiDefinition.setPath(basePath + apiDefinition.getPath());
request.setPath(basePath + request.getPath());
String pathStr = basePath + apiDefinition.getPath().replaceAll("//","/");
apiDefinition.setPath(pathStr);
request.setPath(pathStr);
}
apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation, operation.getResponses())));

View File

@ -114,23 +114,26 @@ public class MsScenario extends MsTestElement {
// 设置共享cookie
config.setEnableCookieShare(enableCookieShare);
Map<String, EnvironmentConfig> envConfig = new HashMap<>(16);
// 兼容历史数据
if (environmentMap == null || environmentMap.isEmpty()) {
environmentMap = new HashMap<>(16);
if (StringUtils.isNotBlank(environmentId)) {
environmentMap.put(SessionUtils.getCurrentProjectId(), environmentId);
}
}
if (environmentMap != null && !environmentMap.isEmpty()) {
environmentMap.keySet().forEach(projectId -> {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(environmentMap.get(projectId));
if (environment != null && environment.getConfig() != null) {
EnvironmentConfig env = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
envConfig.put(projectId, env);
if (config.getConfig() == null) {
// 兼容历史数据
if (this.environmentMap == null || this.environmentMap.isEmpty()) {
this.environmentMap = new HashMap<>(16);
if (StringUtils.isNotBlank(environmentId)) {
// 兼容1.8之前 没有environmentMap但有environmentId的数据
this.environmentMap.put("historyProjectID", environmentId);
}
});
config.setConfig(envConfig);
}
if (this.environmentMap != null && !this.environmentMap.isEmpty()) {
this.environmentMap.keySet().forEach(projectId -> {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(this.environmentMap.get(projectId));
if (environment != null && environment.getConfig() != null) {
EnvironmentConfig env = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
envConfig.put(projectId, env);
}
});
config.setConfig(envConfig);
}
}
if (CollectionUtils.isNotEmpty(this.getVariables())) {
config.setVariables(this.variables);

View File

@ -213,7 +213,7 @@ public abstract class MsTestElement {
csvDataSet.setName(StringUtils.isEmpty(item.getName()) ? "CSVDataSet" : item.getName());
csvDataSet.setProperty("fileEncoding", StringUtils.isEmpty(item.getEncoding()) ? "UTF-8" : item.getEncoding());
if (CollectionUtils.isNotEmpty(item.getFiles())) {
if (!config.isOperating() && new File(BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName()).exists()) {
if (!config.isOperating() && !new File(BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName()).exists()) {
MSException.throwException(StringUtils.isEmpty(item.getName()) ? "CSVDataSet" : item.getName() + "[ CSV文件不存在 ]");
}
csvDataSet.setProperty("filename", BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName());

View File

@ -49,7 +49,7 @@ public class MsIfController extends MsTestElement {
ifController.setName(this.getName());
ifController.setProperty(TestElement.TEST_CLASS, IfController.class.getName());
ifController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("IfControllerPanel"));
ifController.setCondition("true");
ifController.setCondition(this.getCondition());
ifController.setEvaluateAll(false);
ifController.setUseExpression(true);
return ifController;

View File

@ -91,6 +91,9 @@ public class MsHTTPSamplerProxy extends MsTestElement {
@JSONField(ordinal = 36)
private MsAuthManager authManager;
@JSONField(ordinal = 37)
private boolean urlOrPath;
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
// 非导出操作且不是启用状态则跳过执行
@ -126,6 +129,11 @@ public class MsHTTPSamplerProxy extends MsTestElement {
config.setConfig(getEnvironmentConfig(useEnvironment));
}
// 1.8 之前历史数据
if(StringUtils.isEmpty(this.getProjectId()) && config.getConfig()!= null && !config.getConfig().isEmpty()){
this.setProjectId("historyProjectID");
}
// 添加环境中的公共变量
Arguments arguments = this.addArguments(config);
if (arguments != null) {
@ -140,23 +148,26 @@ public class MsHTTPSamplerProxy extends MsTestElement {
url = this.getUrl();
isUrl = true;
}
URL urlObject = new URL(url);
if (isUrl) {
if (StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
url.replaceAll(this.getPort(), "10990");
}
URL urlObject = new URL(url);
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), "UTF-8"));
if (urlObject.getPort() > 0) {
if (urlObject.getPort() > 0 && urlObject.getPort() != 10990 && StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
sampler.setPort(urlObject.getPort());
} else {
sampler.setProperty("HTTPSampler.port", this.getPort());
}
sampler.setProtocol(urlObject.getProtocol());
sampler.setPath(urlObject.getPath());
} else {
sampler.setDomain(config.getConfig().get(this.getProjectId()).getHttpConfig().getDomain());
sampler.setPort(config.getConfig().get(this.getProjectId()).getHttpConfig().getPort());
sampler.setProtocol(config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol());
sampler.setPath(this.getPath());
}
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
if (StringUtils.isNotBlank(this.getPath()) && !isUrl) {
envPath += this.getPath();
sampler.setPath(envPath);
}
String envPath = sampler.getPath();
if (CollectionUtils.isNotEmpty(this.getRest()) && this.isRest()) {
envPath = getRestParameters(URLDecoder.decode(envPath, "UTF-8"));
sampler.setPath(envPath);
@ -177,9 +188,16 @@ public class MsHTTPSamplerProxy extends MsTestElement {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
if (StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
url.replaceAll(this.getPort(), "10990");
}
URL urlObject = new URL(url);
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), "UTF-8"));
sampler.setPort(urlObject.getPort());
if (urlObject.getPort() > 0 && urlObject.getPort() != 10990 && StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
sampler.setPort(urlObject.getPort());
} else {
sampler.setProperty("HTTPSampler.port", this.getPort());
}
sampler.setProtocol(urlObject.getProtocol());
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
sampler.setPath(envPath);
@ -327,10 +345,16 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
public boolean isURL(String str) {
//转换为小写
try {
new URL(str);
return true;
String regex = "^((https|http|ftp|rtsp|mms)?://)"
+ "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?"
+ "(([0-9]{1,3}\\.){3}[0-9]{1,3}" + "|" + "([0-9a-z_!~*'()-]+\\.)*"
+ "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\."
+ "[a-z]{2,6})"
+ "(:[0-9]{1,5})?"
+ "((/?)|"
+ "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
return str.matches(regex) || (str.matches("^(http|https|ftp)://.*$") && str.matches(".*://\\$\\{.*$"));
} catch (Exception e) {
return false;
}
@ -339,5 +363,5 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private boolean isRest() {
return this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).toArray().length > 0;
}
}

View File

@ -2,6 +2,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.automation.EsbDataStruct;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor;
@ -70,6 +71,12 @@ public class MsTCPSampler extends MsTestElement {
@JSONField(ordinal = 39)
private String projectId;
/**
* 新加两个参数场景保存/修改时需要的参数不会传递JMeter只是用于最后的保留
*/
private List<EsbDataStruct> esbDataStruct;
private List<EsbDataStruct> backEsbDataStruct;
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
// 非导出操作且不是启用状态则跳过执行

View File

@ -66,14 +66,11 @@ public class Body {
sampler.setDoMultipart(true);
}
} else {
if (!this.isJson()) {
sampler.setPostBodyRaw(true);
} else {
if (StringUtils.isNotEmpty(this.format) && "JSON-SCHEMA".equals(this.format) && this.getJsonSchema() != null) {
this.raw = JSONSchemaGenerator.getJson(com.alibaba.fastjson.JSON.toJSONString(this.getJsonSchema()));
}
if (StringUtils.isNotEmpty(this.format) && "JSON-SCHEMA".equals(this.format) && this.getJsonSchema() != null) {
this.raw = JSONSchemaGenerator.getJson(com.alibaba.fastjson.JSON.toJSONString(this.getJsonSchema()));
}
KeyValue keyValue = new KeyValue("", "JSON-SCHEMA", this.getRaw(), true, true);
sampler.setPostBodyRaw(true);
keyValue.setEnable(true);
keyValue.setEncode(false);
body.add(keyValue);

View File

@ -1,12 +1,10 @@
package io.metersphere.api.jmeter;
import io.metersphere.api.dto.definition.ApiTestCaseInfo;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.service.*;
import io.metersphere.base.domain.ApiDefinitionExecResult;
import io.metersphere.base.domain.ApiScenarioReport;
import io.metersphere.base.domain.ApiTestReport;
import io.metersphere.base.domain.TestPlanReport;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
@ -16,7 +14,6 @@ import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.SystemParameterService;
import io.metersphere.track.service.TestPlanReportService;
import io.metersphere.track.service.TestPlanService;
import io.metersphere.track.service.TestPlanTestCaseService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -201,11 +198,11 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
apiDefinitionService.addResult(testResult);
//测试计划定时任务-接口执行逻辑的话需要同步测试计划的报告数据
if (StringUtils.equals(this.runMode, ApiRunMode.SCHEDULE_API_PLAN.name())) {
if (StringUtils.equalsAny(this.runMode, ApiRunMode.SCHEDULE_API_PLAN.name(), ApiRunMode.JENKINS_API_PLAN.name())) {
apiDefinitionExecResultService.saveApiResultByScheduleTask(testResult, ApiRunMode.SCHEDULE_API_PLAN.name());
List<String> testPlanReportIdList = new ArrayList<>();
testPlanReportIdList.add(debugReportId);
for(String testPlanReportId : testPlanReportIdList) { // 更新每个测试计划的状态
for (String testPlanReportId : testPlanReportIdList) { // 更新每个测试计划的状态
testPlanReportService.checkTestPlanStatus(testPlanReportId);
}
testPlanReportService.updateReport(testPlanReportIdList, ApiRunMode.SCHEDULE_API_PLAN.name(), ReportTriggerMode.SCHEDULE.name());
@ -250,10 +247,16 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
}
}
sendTask(report, reportUrl, testResult);
if (StringUtils.equals(ReportTriggerMode.API.name(), report.getTriggerMode())||StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), report.getTriggerMode())) {
sendTask(report, reportUrl, testResult);
}
}
private static void sendTask(ApiTestReport report, String reportUrl, TestResult testResult) {
if (report == null) {
return;
}
SystemParameterService systemParameterService = CommonBeanFactory.getBean(SystemParameterService.class);
NoticeSendService noticeSendService = CommonBeanFactory.getBean(NoticeSendService.class);
assert systemParameterService != null;

View File

@ -90,6 +90,8 @@ public class ApiAutomationService {
@Resource
@Lazy
private TestPlanScenarioCaseService testPlanScenarioCaseService;
@Resource
private EsbApiParamService esbApiParamService;
public List<ApiScenarioDTO> list(ApiScenarioRequest request) {
request = this.initRequest(request, true, true);
@ -184,6 +186,9 @@ public class ApiAutomationService {
scenario.setCreateTime(System.currentTimeMillis());
scenario.setNum(getNextNum(request.getProjectId()));
//检查场景的请求步骤如果含有ESB请求步骤的话要做参数计算处理
esbApiParamService.checkScenarioRequests(request);
apiScenarioMapper.insert(scenario);
List<String> bodyUploadIds = request.getBodyUploadIds();
@ -205,6 +210,9 @@ public class ApiAutomationService {
List<String> bodyUploadIds = request.getBodyUploadIds();
FileUtils.createBodyFiles(bodyUploadIds, bodyFiles);
//检查场景的请求步骤如果含有ESB请求步骤的话要做参数计算处理
esbApiParamService.checkScenarioRequests(request);
final ApiScenarioWithBLOBs scenario = buildSaveScenario(request);
apiScenarioMapper.updateByPrimaryKeySelective(scenario);
extScheduleMapper.updateNameByResourceID(request.getId(), request.getName());// 修改场景name同步到修改首页定时任务
@ -234,6 +242,10 @@ public class ApiAutomationService {
} else {
scenario.setUserId(request.getUserId());
}
if (StringUtils.isEmpty(request.getApiScenarioModuleId()) || StringUtils.isEmpty(request.getModulePath())) {
scenario.setApiScenarioModuleId("root");
scenario.setModulePath("/默认模块");
}
return scenario;
}
@ -269,7 +281,7 @@ public class ApiAutomationService {
}
private void deleteApiScenarioReport(List<String> scenarioIds) {
if(scenarioIds == null || scenarioIds.isEmpty()){
if (scenarioIds == null || scenarioIds.isEmpty()) {
return;
}
ApiScenarioReportExample scenarioReportExample = new ApiScenarioReportExample();
@ -371,6 +383,9 @@ public class ApiAutomationService {
public APIScenarioReportResult createScenarioReport(String id, String scenarioId, String scenarioName, String triggerMode, String execType, String projectId, String userID) {
APIScenarioReportResult report = new APIScenarioReportResult();
if (triggerMode.equals(ApiRunMode.SCENARIO.name()) || triggerMode.equals(ApiRunMode.DEFINITION.name())) {
triggerMode = ReportTriggerMode.MANUAL.name();
}
report.setId(id);
report.setTestId(id);
if (StringUtils.isNotEmpty(scenarioName)) {
@ -998,4 +1013,18 @@ public class ApiAutomationService {
}
});
}
public void removeToGcByBatch(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
this.removeToGc(request.getIds());
}
public void deleteBatchByCondition(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
this.deleteBatch(request.getIds());
}
}

View File

@ -3,10 +3,7 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.datacount.ExecutedCaseInfoResult;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.ApiDefinitionExecResult;
import io.metersphere.base.domain.ApiDefinitionExecResultExample;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.base.domain.TestPlanApiCase;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ApiTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
@ -43,7 +40,7 @@ public class ApiDefinitionExecResultService {
@Resource
private ApiTestCaseMapper apiTestCaseMapper;
@Resource
private TestCaseReviewApiCaseService testCaseReviewApiCaseService;
private TestCaseReviewApiCaseService testCaseReviewApiCaseService;
@Resource
SqlSessionFactory sqlSessionFactory;
@ -73,6 +70,13 @@ public class ApiDefinitionExecResultService {
testCaseReviewApiCaseService.setExecResult(item.getName(), status);
}
// 清空上次执行结果的内容只保留当前最新一条内容
ApiDefinitionExecResult prevResult = extApiDefinitionExecResultMapper.selectMaxResultByResourceIdAndType(item.getName(), type);
if (prevResult != null) {
prevResult.setContent(null);
definitionExecResultMapper.updateByPrimaryKeyWithBLOBs(prevResult);
}
// 更新用例最后执行结果
ApiTestCaseWithBLOBs apiTestCaseWithBLOBs = new ApiTestCaseWithBLOBs();
apiTestCaseWithBLOBs.setId(saveResult.getResourceId());
@ -94,7 +98,7 @@ public class ApiDefinitionExecResultService {
*/
public void saveApiResultByScheduleTask(TestResult result, String type) {
String saveResultType = type;
if(StringUtils.equalsAny(ApiRunMode.SCHEDULE_API_PLAN.name(),saveResultType)){
if (StringUtils.equalsAny(ApiRunMode.SCHEDULE_API_PLAN.name(), saveResultType)) {
saveResultType = ApiRunMode.API_PLAN.name();
}
@ -127,6 +131,12 @@ public class ApiDefinitionExecResultService {
}
saveResult.setUserId(userID);
// 前一条数据内容清空
ApiDefinitionExecResult prevResult = extApiDefinitionExecResultMapper.selectMaxResultByResourceIdAndType(item.getName(), finalSaveResultType);
if (prevResult != null) {
prevResult.setContent(null);
apiDefinitionExecResultMapper.updateByPrimaryKeyWithBLOBs(prevResult);
}
apiDefinitionExecResultMapper.insert(saveResult);
});
}

View File

@ -262,7 +262,10 @@ public class ApiDefinitionService {
test.setEnvironmentId(request.getEnvironmentId());
test.setUserId(request.getUserId());
test.setTags(request.getTags());
if (StringUtils.isEmpty(request.getModulePath()) || StringUtils.isEmpty(request.getModuleId())) {
test.setModulePath("/默认模块");
test.setModuleId("root");
}
apiDefinitionMapper.updateByPrimaryKeySelective(test);
return test;
}
@ -279,7 +282,6 @@ public class ApiDefinitionService {
test.setProtocol(request.getProtocol());
test.setMethod(request.getMethod());
test.setPath(request.getPath());
test.setModuleId(request.getModuleId());
test.setProjectId(request.getProjectId());
request.getRequest().setId(request.getId());
test.setRequest(JSONObject.toJSONString(request.getRequest()));
@ -287,6 +289,11 @@ public class ApiDefinitionService {
test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Underway.name());
test.setModulePath(request.getModulePath());
test.setModuleId(request.getModuleId());
if (StringUtils.isEmpty(request.getModulePath()) || StringUtils.isEmpty(request.getModuleId())) {
test.setModulePath("/默认模块");
test.setModuleId("root");
}
test.setResponse(JSONObject.toJSONString(request.getResponse()));
test.setEnvironmentId(request.getEnvironmentId());
test.setNum(getNextNum(request.getProjectId()));
@ -344,13 +351,13 @@ public class ApiDefinitionService {
private void _importCreate(List<ApiDefinition> sameRequest, ApiDefinitionMapper batchMapper, ApiDefinitionWithBLOBs apiDefinition,
ApiTestCaseMapper apiTestCaseMapper, ApiTestImportRequest apiTestImportRequest, List<ApiTestCaseWithBLOBs> cases) {
if (CollectionUtils.isEmpty(sameRequest)) {
if(StringUtils.equalsIgnoreCase(apiDefinition.getProtocol(),RequestType.HTTP)){
if (StringUtils.equalsIgnoreCase(apiDefinition.getProtocol(), RequestType.HTTP)) {
String request = setImportHashTree(apiDefinition);
batchMapper.insert(apiDefinition);
apiDefinition.setRequest(request);
importApiCase(apiDefinition, apiTestCaseMapper, apiTestImportRequest, true);
}else{
if(StringUtils.equalsAnyIgnoreCase(apiDefinition.getProtocol(),RequestType.TCP)){
} else {
if (StringUtils.equalsAnyIgnoreCase(apiDefinition.getProtocol(), RequestType.TCP)) {
String request = setImportTCPHashTree(apiDefinition);
}
batchMapper.insert(apiDefinition);
@ -358,7 +365,7 @@ public class ApiDefinitionService {
} else {
String originId = apiDefinition.getId();
if(StringUtils.equalsIgnoreCase(apiDefinition.getProtocol(),RequestType.HTTP)){
if (StringUtils.equalsIgnoreCase(apiDefinition.getProtocol(), RequestType.HTTP)) {
//如果存在则修改
apiDefinition.setId(sameRequest.get(0).getId());
String request = setImportHashTree(apiDefinition);
@ -373,9 +380,9 @@ public class ApiDefinitionService {
}
});
}
}else {
} else {
apiDefinition.setId(sameRequest.get(0).getId());
if(StringUtils.equalsAnyIgnoreCase(apiDefinition.getProtocol(),RequestType.TCP)){
if (StringUtils.equalsAnyIgnoreCase(apiDefinition.getProtocol(), RequestType.TCP)) {
String request = setImportTCPHashTree(apiDefinition);
}
apiDefinitionMapper.updateByPrimaryKeyWithBLOBs(apiDefinition);
@ -392,6 +399,7 @@ public class ApiDefinitionService {
apiDefinition.setRequest(JSONObject.toJSONString(msHTTPSamplerProxy));
return request;
}
private String setImportTCPHashTree(ApiDefinitionWithBLOBs apiDefinition) {
String request = apiDefinition.getRequest();
MsTCPSampler tcpSampler = JSONObject.parseObject(request, MsTCPSampler.class);
@ -411,7 +419,7 @@ public class ApiDefinitionService {
if (CollectionUtils.isNotEmpty(cases)) {
int batchCount = 0;
cases.forEach(item -> {
if(!existCaseName.contains(item.getName())) {
if (!existCaseName.contains(item.getName())) {
item.setId(UUID.randomUUID().toString());
item.setCreateTime(System.currentTimeMillis());
item.setUpdateTime(System.currentTimeMillis());
@ -509,7 +517,7 @@ public class ApiDefinitionService {
}
public void addResult(TestResult res) {
if (!res.getScenarios().isEmpty() && !res.getScenarios().get(0).getRequestResults().isEmpty()) {
if (res != null && CollectionUtils.isNotEmpty(res.getScenarios()) && res.getScenarios().get(0) != null && CollectionUtils.isNotEmpty(res.getScenarios().get(0).getRequestResults())) {
RequestResult result = res.getScenarios().get(0).getRequestResults().get(0);
if (result.getName().indexOf("<->") != -1) {
result.setName(result.getName().substring(0, result.getName().indexOf("<->")));
@ -596,21 +604,25 @@ public class ApiDefinitionService {
}
for (int i = 0; i < data.size(); i++) {
ApiDefinitionWithBLOBs item = data.get(i);
if (StringUtils.isEmpty(item.getModuleId()) || StringUtils.isEmpty(item.getModulePath())) {
item.setModuleId("root");
item.setModulePath("/默认模块");
}
if (item.getName().length() > 255) {
item.setName(item.getName().substring(0, 255));
}
item.setNum(num++);
//如果EsbData需要存储,则需要进行接口是否更新的判断
if(apiImport.getEsbApiParamsMap()!= null){
if (apiImport.getEsbApiParamsMap() != null) {
String apiId = item.getId();
EsbApiParamsWithBLOBs model = apiImport.getEsbApiParamsMap().get(apiId);
importCreate(item, batchMapper, apiTestCaseMapper, request, apiImport.getCases());
if(model!=null){
if (model != null) {
apiImport.getEsbApiParamsMap().remove(apiId);
model.setResourceId(item.getId());
apiImport.getEsbApiParamsMap().put(item.getId(),model);
apiImport.getEsbApiParamsMap().put(item.getId(), model);
}
}else {
} else {
importCreate(item, batchMapper, apiTestCaseMapper, request, apiImport.getCases());
}
if (i % 300 == 0) {
@ -618,15 +630,15 @@ public class ApiDefinitionService {
}
}
//判断EsbData是否需要存储
if(apiImport.getEsbApiParamsMap()!= null && apiImport.getEsbApiParamsMap().size() > 0){
if (apiImport.getEsbApiParamsMap() != null && apiImport.getEsbApiParamsMap().size() > 0) {
EsbApiParamsMapper esbApiParamsMapper = sqlSession.getMapper(EsbApiParamsMapper.class);
for (EsbApiParamsWithBLOBs model : apiImport.getEsbApiParamsMap().values()) {
EsbApiParamsExample example = new EsbApiParamsExample();
example.createCriteria().andResourceIdEqualTo(model.getResourceId());
List<EsbApiParamsWithBLOBs> exiteModelList = esbApiParamsMapper.selectByExampleWithBLOBs(example);
if(exiteModelList.isEmpty()){
if (exiteModelList.isEmpty()) {
esbApiParamsMapper.insert(model);
}else{
} else {
model.setId(exiteModelList.get(0).getId());
esbApiParamsMapper.updateByPrimaryKeyWithBLOBs(model);
}

View File

@ -86,216 +86,240 @@ public class ApiDocumentService {
apiInfoDTO.setStatus(apiModel.getStatus());
if (apiModel.getRequest() != null) {
JSONObject requestJsonObj = JSONObject.parseObject(apiModel.getRequest());
//head赋值conversionModelToDTO
if (requestJsonObj.containsKey("headers")) {
JSONArray requestHeadDataArr = new JSONArray();
//head赋值
JSONArray headArr = requestJsonObj.getJSONArray("headers");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
requestHeadDataArr.add(headObj);
JSONObject requestObj = this.genJSONObject(apiModel.getRequest());
if(requestObj!=null){
if (requestObj.containsKey("headers")) {
JSONArray requestHeadDataArr = new JSONArray();
//head赋值
JSONArray headArr = requestObj.getJSONArray("headers");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
requestHeadDataArr.add(headObj);
}
}
apiInfoDTO.setRequestHead(requestHeadDataArr.toJSONString());
}
apiInfoDTO.setRequestHead(requestHeadDataArr.toJSONString());
}
//url参数赋值
JSONArray urlParamArr = new JSONArray();
if (requestJsonObj.containsKey("arguments")) {
//urlParam -- query赋值
JSONArray headArr = requestJsonObj.getJSONArray("arguments");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
urlParamArr.add(headObj);
}
}
}
if (requestJsonObj.containsKey("rest")) {
//urlParam -- rest赋值
JSONArray headArr = requestJsonObj.getJSONArray("rest");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
urlParamArr.add(headObj);
}
}
}
apiInfoDTO.setUrlParams(urlParamArr.toJSONString());
//请求体参数类型
if (requestJsonObj.containsKey("body")) {
JSONObject bodyObj = requestJsonObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
if (StringUtils.equals(type, "WWW_FORM")) {
apiInfoDTO.setRequestBodyParamType("x-www-from-urlencoded");
} else if (StringUtils.equals(type, "Form Data")) {
apiInfoDTO.setRequestBodyParamType("form-data");
} else {
apiInfoDTO.setRequestBodyParamType(type);
}
//url参数赋值
JSONArray urlParamArr = new JSONArray();
if (requestObj.containsKey("arguments")) {
try{
JSONArray headArr = requestObj.getJSONArray("arguments");
for (int index = 0; index < headArr.size(); index++) {
if (StringUtils.equals(type, "JSON")) {
//判断是否是JsonSchema
boolean isJsonSchema = false;
if (bodyObj.containsKey("format")) {
String foramtValue = String.valueOf(bodyObj.get("format"));
if (StringUtils.equals("JSON-SCHEMA", foramtValue)) {
isJsonSchema = true;
}
}
if (isJsonSchema) {
apiInfoDTO.setRequestBodyParamType("JSON-SCHEMA");
apiInfoDTO.setJsonSchemaBody(bodyObj);
} else {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setRequestBodyStrutureData(raw);
//转化jsonObje 或者 jsonArray
this.setPreviewData(previewJsonArray, raw);
}
}
} else if (StringUtils.equalsAny(type, "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setRequestBodyStrutureData(raw);
JSONObject previewObj = JSONObject.parseObject(raw);
this.setPreviewData(previewJsonArray, raw);
}
} else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) {
if (bodyObj.containsKey("kvs")) {
JSONArray bodyParamArr = new JSONArray();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
Map<String, String> previewObjMap = new LinkedHashMap<>();
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("name") && kv.containsKey("value")) {
bodyParamArr.add(kv);
previewObjMap.put(String.valueOf(kv.get("name")), String.valueOf(kv.get("value")));
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
urlParamArr.add(headObj);
}
}
this.setPreviewData(previewJsonArray, JSONObject.toJSONString(previewObjMap));
apiInfoDTO.setRequestBodyFormData(bodyParamArr.toJSONString());
}
} else if (StringUtils.equals(type, "BINARY")) {
if (bodyObj.containsKey("binary")) {
List<Map<String, String>> bodyParamList = new ArrayList<>();
JSONArray kvsArr = bodyObj.getJSONArray("binary");
}catch (Exception e){
}
}
if (requestObj.containsKey("rest")) {
try{
//urlParam -- rest赋值
JSONArray headArr = requestObj.getJSONArray("rest");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
urlParamArr.add(headObj);
}
}
}catch (Exception e){
}
}
apiInfoDTO.setUrlParams(urlParamArr.toJSONString());
//请求体参数类型
if (requestObj.containsKey("body")) {
try{
JSONObject bodyObj = requestObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
if (StringUtils.equals(type, "WWW_FORM")) {
apiInfoDTO.setRequestBodyParamType("x-www-from-urlencoded");
} else if (StringUtils.equals(type, "Form Data")) {
apiInfoDTO.setRequestBodyParamType("form-data");
} else {
apiInfoDTO.setRequestBodyParamType(type);
}
Map<String, String> previewObjMap = new LinkedHashMap<>();
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("description") && kv.containsKey("files")) {
Map<String, String> bodyMap = new HashMap<>();
String name = kv.getString("description");
JSONArray fileArr = kv.getJSONArray("files");
String value = "";
for (int j = 0; j < fileArr.size(); j++) {
JSONObject fileObj = fileArr.getJSONObject(j);
if (fileObj.containsKey("name")) {
value += fileObj.getString("name") + " ;";
if (StringUtils.equals(type, "JSON")) {
//判断是否是JsonSchema
boolean isJsonSchema = false;
if (bodyObj.containsKey("format")) {
String foramtValue = String.valueOf(bodyObj.get("format"));
if (StringUtils.equals("JSON-SCHEMA", foramtValue)) {
isJsonSchema = true;
}
}
if (isJsonSchema) {
apiInfoDTO.setRequestBodyParamType("JSON-SCHEMA");
apiInfoDTO.setJsonSchemaBody(bodyObj);
} else {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setRequestBodyStrutureData(raw);
//转化jsonObje 或者 jsonArray
this.setPreviewData(previewJsonArray, raw);
}
}
} else if (StringUtils.equalsAny(type, "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setRequestBodyStrutureData(raw);
this.setPreviewData(previewJsonArray, raw);
}
} else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) {
if (bodyObj.containsKey("kvs")) {
JSONArray bodyParamArr = new JSONArray();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
Map<String, String> previewObjMap = new LinkedHashMap<>();
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("name") && kv.containsKey("value")) {
bodyParamArr.add(kv);
previewObjMap.put(String.valueOf(kv.get("name")), String.valueOf(kv.get("value")));
}
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyMap.put("contentType", "File");
bodyParamList.add(bodyMap);
this.setPreviewData(previewJsonArray, JSONObject.toJSONString(previewObjMap));
apiInfoDTO.setRequestBodyFormData(bodyParamArr.toJSONString());
}
} else if (StringUtils.equals(type, "BINARY")) {
if (bodyObj.containsKey("binary")) {
List<Map<String, String>> bodyParamList = new ArrayList<>();
JSONArray kvsArr = bodyObj.getJSONArray("binary");
previewObjMap.put(String.valueOf(name), String.valueOf(value));
Map<String, String> previewObjMap = new LinkedHashMap<>();
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("description") && kv.containsKey("files")) {
Map<String, String> bodyMap = new HashMap<>();
String name = kv.getString("description");
JSONArray fileArr = kv.getJSONArray("files");
String value = "";
for (int j = 0; j < fileArr.size(); j++) {
JSONObject fileObj = fileArr.getJSONObject(j);
if (fileObj.containsKey("name")) {
value += fileObj.getString("name") + " ;";
}
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyMap.put("contentType", "File");
bodyParamList.add(bodyMap);
previewObjMap.put(String.valueOf(name), String.valueOf(value));
}
}
this.setPreviewData(previewJsonArray, JSONObject.toJSONString(previewObjMap));
apiInfoDTO.setRequestBodyFormData(JSONArray.toJSONString(bodyParamList));
}
}
this.setPreviewData(previewJsonArray, JSONObject.toJSONString(previewObjMap));
apiInfoDTO.setRequestBodyFormData(JSONArray.toJSONString(bodyParamList));
}
}catch (Exception e){
}
}
}
}
//赋值响应头
if (apiModel.getResponse() != null) {
JSONObject responseJsonObj = JSONObject.parseObject(apiModel.getResponse());
JSONObject responseJsonObj = this.genJSONObject(apiModel.getResponse());
if (responseJsonObj!=null && responseJsonObj.containsKey("headers")) {
JSONArray responseHeadDataArr = new JSONArray();
JSONArray headArr = responseJsonObj.getJSONArray("headers");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
responseHeadDataArr.add(headObj);
try{
JSONArray responseHeadDataArr = new JSONArray();
JSONArray headArr = responseJsonObj.getJSONArray("headers");
for (int index = 0; index < headArr.size(); index++) {
JSONObject headObj = headArr.getJSONObject(index);
if (headObj.containsKey("name") && headObj.containsKey("value")) {
responseHeadDataArr.add(headObj);
}
}
apiInfoDTO.setResponseHead(responseHeadDataArr.toJSONString());
}catch (Exception e){
}
apiInfoDTO.setResponseHead(responseHeadDataArr.toJSONString());
}
// 赋值响应体
if (responseJsonObj!=null && responseJsonObj.containsKey("body")) {
JSONObject bodyObj = responseJsonObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
if (StringUtils.equals(type, "WWW_FORM")) {
apiInfoDTO.setResponseBodyParamType("x-www-from-urlencoded");
} else if (StringUtils.equals(type, "Form Data")) {
apiInfoDTO.setResponseBodyParamType("form-data");
} else {
apiInfoDTO.setResponseBodyParamType(type);
}
if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setResponseBodyStrutureData(raw);
try {
JSONObject bodyObj = responseJsonObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
if (StringUtils.equals(type, "WWW_FORM")) {
apiInfoDTO.setResponseBodyParamType("x-www-from-urlencoded");
} else if (StringUtils.equals(type, "Form Data")) {
apiInfoDTO.setResponseBodyParamType("form-data");
} else {
apiInfoDTO.setResponseBodyParamType(type);
}
} else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) {
if (bodyObj.containsKey("kvs")) {
JSONArray bodyParamArr = new JSONArray();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("name")) {
bodyParamArr.add(kv);
}
if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setResponseBodyStrutureData(raw);
}
apiInfoDTO.setResponseBodyFormData(bodyParamArr.toJSONString());
}
} else if (StringUtils.equals(type, "BINARY")) {
if (bodyObj.containsKey("binary")) {
List<Map<String, String>> bodyParamList = new ArrayList<>();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("description") && kv.containsKey("files")) {
Map<String, String> bodyMap = new HashMap<>();
String name = kv.getString("description");
JSONArray fileArr = kv.getJSONArray("files");
String value = "";
for (int j = 0; j < fileArr.size(); j++) {
JSONObject fileObj = fileArr.getJSONObject(j);
if (fileObj.containsKey("name")) {
value += fileObj.getString("name") + " ;";
}
} else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) {
if (bodyObj.containsKey("kvs")) {
JSONArray bodyParamArr = new JSONArray();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("name")) {
bodyParamArr.add(kv);
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyParamList.add(bodyMap);
}
apiInfoDTO.setResponseBodyFormData(bodyParamArr.toJSONString());
}
} else if (StringUtils.equals(type, "BINARY")) {
if (bodyObj.containsKey("binary")) {
List<Map<String, String>> bodyParamList = new ArrayList<>();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("description") && kv.containsKey("files")) {
Map<String, String> bodyMap = new HashMap<>();
String name = kv.getString("description");
JSONArray fileArr = kv.getJSONArray("files");
String value = "";
for (int j = 0; j < fileArr.size(); j++) {
JSONObject fileObj = fileArr.getJSONObject(j);
if (fileObj.containsKey("name")) {
value += fileObj.getString("name") + " ;";
}
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyParamList.add(bodyMap);
}
}
apiInfoDTO.setResponseBodyFormData(JSONArray.toJSONString(bodyParamList));
}
apiInfoDTO.setResponseBodyFormData(JSONArray.toJSONString(bodyParamList));
}
}
}catch (Exception e){
}
}
// 赋值响应码
if (responseJsonObj!=null && responseJsonObj.containsKey("statusCode")) {
JSONArray responseStatusDataArr = new JSONArray();
JSONArray statusArr = responseJsonObj.getJSONArray("statusCode");
for (int index = 0; index < statusArr.size(); index++) {
JSONObject statusObj = statusArr.getJSONObject(index);
if (statusObj.containsKey("name") && statusObj.containsKey("value")) {
responseStatusDataArr.add(statusObj);
try {
JSONArray responseStatusDataArr = new JSONArray();
JSONArray statusArr = responseJsonObj.getJSONArray("statusCode");
for (int index = 0; index < statusArr.size(); index++) {
JSONObject statusObj = statusArr.getJSONObject(index);
if (statusObj.containsKey("name") && statusObj.containsKey("value")) {
responseStatusDataArr.add(statusObj);
}
}
apiInfoDTO.setResponseCode(responseStatusDataArr.toJSONString());
}catch (Exception e){
}
apiInfoDTO.setResponseCode(responseStatusDataArr.toJSONString());
}
}
}
@ -304,6 +328,15 @@ public class ApiDocumentService {
return apiInfoDTO;
}
private JSONObject genJSONObject(String request) {
JSONObject returnObj = null;
try{
returnObj = JSONObject.parseObject(request);
}catch (Exception e){
}
return returnObj;
}
private void setPreviewData(JSONArray previewArray, String data) {
try {
JSONObject previewObj = JSONObject.parseObject(data);

View File

@ -253,6 +253,13 @@ public class ApiScenarioReportService {
String status = "Success";
report.setStatus(status);
scenarioReportMapper.updateByPrimaryKeySelective(report);
// 把上一条调试的数据内容清空
ApiScenarioReport prevResult = extApiScenarioReportMapper.selectPreviousReportByScenarioId(report.getScenarioId(), reportId);
if (prevResult != null) {
ApiScenarioReportDetailExample example = new ApiScenarioReportDetailExample();
example.createCriteria().andReportIdEqualTo(prevResult.getId());
apiScenarioReportDetailMapper.deleteByExample(example);
}
});
sqlSession.flushStatements();
}

View File

@ -19,6 +19,7 @@ import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.*;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
@ -551,7 +552,14 @@ public class ApiTestCaseService {
}
public String run(RunCaseRequest request) {
ApiTestCaseWithBLOBs testCaseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(request.getCaseId());
ApiTestCaseWithBLOBs testCaseWithBLOBs=new ApiTestCaseWithBLOBs();
if(StringUtils.equals(request.getRunMode(), ApiRunMode.JENKINS_API_PLAN.name())){
testCaseWithBLOBs= apiTestCaseMapper.selectByPrimaryKey(request.getReportId());
request.setCaseId(request.getReportId());
}else{
testCaseWithBLOBs= apiTestCaseMapper.selectByPrimaryKey(request.getCaseId());
}
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (testCaseWithBLOBs != null && StringUtils.isNotEmpty(testCaseWithBLOBs.getRequest())) {
try {

View File

@ -3,11 +3,13 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.automation.EsbDataStruct;
import io.metersphere.api.dto.automation.SaveApiScenarioRequest;
import io.metersphere.api.dto.automation.parse.EsbDataParser;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.ApiTestCaseResult;
import io.metersphere.api.dto.definition.SaveApiDefinitionRequest;
import io.metersphere.api.dto.definition.SaveApiTestCaseRequest;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
@ -313,6 +315,35 @@ public class EsbApiParamService {
return keyValueList;
}
private List<KeyValue> genKeyValueListByDataStruct(MsTCPSampler tcpSampler, List<EsbDataStruct> dataStructRequestList) {
List<KeyValue> keyValueList = new ArrayList<>();
String sendRequest = tcpSampler.getRequest();
String paramRegexStr = "\\$\\{([^}]*)\\}";
try {
if (StringUtils.isNotEmpty(sendRequest)) {
List<String> paramList = new ArrayList<>();
Pattern regex = Pattern.compile(paramRegexStr);
Matcher matcher = regex.matcher(sendRequest);
while (matcher.find()) {
paramList.add(matcher.group(1));
}
for (String param : paramList) {
String value = this.genValueFromEsbDataStructByParam(dataStructRequestList, param);
if (StringUtils.isNotEmpty(value)) {
KeyValue kv = new KeyValue();
kv.setName(param);
kv.setValue(value);
kv.setRequired(true);
keyValueList.add(kv);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return keyValueList;
}
//通过报文模版中的变量参数解析报文数据结构生成对应的xml数据
private String genValueFromEsbDataStructByParam(List<EsbDataStruct> dataStructRequestList, String param) {
String returnValue = "";
@ -341,10 +372,32 @@ public class EsbApiParamService {
return request;
}
public void handleEsbRequest(MsTCPSampler tcpSampler) {
try {
//修改reqeust.parameters, 将树结构类型数据转化为表格类型数据供执行时参数的提取
if (tcpSampler.getEsbDataStruct() != null ) {
List<KeyValue> keyValueList = this.genKeyValueListByDataStruct(tcpSampler, tcpSampler.getEsbDataStruct());
tcpSampler.setParameters(keyValueList);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void deleteByResourceIdIn(List<String> apiIds) {
EsbApiParamsExample example = new EsbApiParamsExample();
example.createCriteria().andResourceIdIn(apiIds);
esbApiParamsMapper.deleteByExample(example);
}
public void checkScenarioRequests(SaveApiScenarioRequest request) {
if(request.getScenarioDefinition() != null ){
List<MsTestElement> hashTreeList = request.getScenarioDefinition().getHashTree();
for (MsTestElement testElement :hashTreeList) {
if(testElement instanceof MsTCPSampler){
this.handleEsbRequest((MsTCPSampler)testElement);
}
}
}
}
}

View File

@ -76,12 +76,17 @@ public class HistoricalDataUpgradeService {
return scenario;
}
private MsScenario createScenario(Scenario oldScenario) {
private MsScenario createScenario(Scenario oldScenario, String projectId) {
MsScenario scenario = new MsScenario();
scenario.setOldVariables(oldScenario.getVariables());
scenario.setName(oldScenario.getName());
scenario.setEnableCookieShare(oldScenario.isEnableCookieShare());
scenario.setEnvironmentId(oldScenario.getEnvironmentId());
if (StringUtils.isNotEmpty(oldScenario.getEnvironmentId())) {
HashMap<String, String> envMap = new HashMap<>();
envMap.put(projectId, oldScenario.getEnvironmentId());
scenario.setEnvironmentMap(envMap);
}
scenario.setReferenced("Upgrade");
scenario.setId(oldScenario.getId());
scenario.setResourceId(UUID.randomUUID().toString());
@ -397,6 +402,7 @@ public class HistoricalDataUpgradeService {
MsScenario scenarioTest = createScenarioByTest(test);
LinkedList<MsTestElement> listSteps = new LinkedList<>();
List<Scenario> scenarios = JSON.parseArray(test.getScenarioDefinition(), Scenario.class);
String envId = null;
if (CollectionUtils.isNotEmpty(scenarios)) {
// 批量处理
for (Scenario scenario : scenarios) {
@ -405,7 +411,7 @@ public class HistoricalDataUpgradeService {
}
scenario.setId(test.getId() + "=" + scenario.getId());
scenario.setName(test.getName() + "_" + scenario.getName());
MsScenario scenario1 = createScenario(scenario);
MsScenario scenario1 = createScenario(scenario, saveHistoricalDataUpgrade.getProjectId());
String scenarioDefinition = JSON.toJSONString(scenario1);
num++;
createApiScenarioWithBLOBs(saveHistoricalDataUpgrade, scenario.getId(), scenario.getName(), scenario.getRequests().size(), scenarioDefinition, mapper, num);
@ -417,10 +423,20 @@ public class HistoricalDataUpgradeService {
step.setResourceId(UUID.randomUUID().toString());
step.setReferenced("REF");
listSteps.add(step);
if (StringUtils.isNotEmpty(scenario.getEnvironmentId())) {
envId = scenario.getEnvironmentId();
}
}
}
num++;
scenarioTest.setHashTree(listSteps);
if (StringUtils.isNotEmpty(envId)) {
HashMap<String, String> envMap = new HashMap<>();
envMap.put(saveHistoricalDataUpgrade.getProjectId(), envId);
scenarioTest.setEnvironmentMap(envMap);
scenarioTest.setEnvironmentId(envId);
}
String scenarioDefinition = JSON.toJSONString(scenarioTest);
createApiScenarioWithBLOBs(saveHistoricalDataUpgrade, scenarioTest.getId(), scenarioTest.getName(), listSteps.size(), scenarioDefinition, mapper, num);
}

View File

@ -1,17 +1,18 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class TestCaseWithBLOBs extends TestCase implements Serializable {
private String remark;
private String steps;
private String steps; //与TestCaseExcelData里的属性名不一致BeanUtils.copyBean()复制不了值需要手动赋值
private static final long serialVersionUID = 1L;
}

View File

@ -3,6 +3,7 @@ package io.metersphere.base.mapper.ext;
import io.metersphere.api.dto.QueryAPIReportRequest;
import io.metersphere.api.dto.automation.APIScenarioReportResult;
import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.base.domain.ApiDefinitionExecResult;
import io.metersphere.base.domain.ApiScenarioReport;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@ -23,4 +24,7 @@ public interface ExtApiScenarioReportMapper {
List<ApiDataCountResult> countByProjectIdGroupByExecuteResult(String projectId);
List<ApiScenarioReport> selectLastReportByIds(@Param("scenarioIdList") List<String> ids);
ApiScenarioReport selectPreviousReportByScenarioId(@Param("scenarioId") String scenarioId, @Param("nowId") String nowId);
}

View File

@ -216,4 +216,9 @@
) orderData ON orderData.id = report.id;
</select>
<select id="selectPreviousReportByScenarioId" resultType="io.metersphere.base.domain.ApiScenarioReport">
select * from api_scenario_report
WHERE execute_type in ("Completed","Debug") and scenario_id=#{scenarioId} and id != #{nowId} ORDER BY create_time desc LIMIT 1
</select>
</mapper>

View File

@ -91,6 +91,9 @@
<if test="reportRequest.projectId != null">
AND project.id = #{reportRequest.projectId,jdbcType=VARCHAR}
</if>
<if test="reportRequest.testId != null">
AND ltr.test_id = #{reportRequest.testId,jdbcType=VARCHAR}
</if>
<if test="reportRequest.filters != null and reportRequest.filters.size() > 0">
<foreach collection="reportRequest.filters.entrySet()" index="key" item="values">
<if test="values != null and values.size() > 0">

View File

@ -274,7 +274,7 @@
#{value}
</foreach>
</when>
<when test="key=='status'">
<when test="key=='reviewStatus'">
and test_case.review_status in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}

View File

@ -1,6 +1,7 @@
package io.metersphere.commons.user;
import io.metersphere.commons.utils.CodingUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.dto.UserDTO;
import lombok.Getter;
import lombok.Setter;
@ -28,7 +29,7 @@ public class SessionUser extends UserDTO implements Serializable {
SessionUser sessionUser = new SessionUser();
BeanUtils.copyProperties(user, sessionUser);
List<String> infos = Arrays.asList(user.getId(), RandomStringUtils.random(6), "" + System.currentTimeMillis());
List<String> infos = Arrays.asList(user.getId(), RandomStringUtils.randomAlphabetic(6), SessionUtils.getSessionId(), "" + System.currentTimeMillis());
sessionUser.csrfToken = CodingUtil.aesEncrypt(StringUtils.join(infos, "|"), secret, iv);
return sessionUser;
}

View File

@ -10,7 +10,6 @@ import org.apache.shiro.subject.support.DefaultSubjectContext;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import static io.metersphere.commons.constants.SessionConstants.ATTR_USER;
@ -32,6 +31,10 @@ public class SessionUtils {
}
}
public static String getSessionId() {
return (String) SecurityUtils.getSubject().getSession().getId();
}
private static Session getSessionByUsername(String username) {
DefaultSessionManager sessionManager = CommonBeanFactory.getBean(DefaultSessionManager.class);
Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();

View File

@ -10,7 +10,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class KafkaProperties {
public static final String KAFKA_PREFIX = "kafka";
private String acks = "all";
private String acks = "0"; // 不要设置all
private String topic;
private String fields;
private String timestamp;

View File

@ -9,5 +9,7 @@ public class ExcelResponse<T> {
private Boolean success;
private List<ExcelErrData<T>> errList;
private Boolean isUpdated; //是否有更新过用例
}

View File

@ -8,6 +8,8 @@ import lombok.Setter;
@Setter
public class TestCaseExcelData {
@ExcelIgnore
private Integer num;
@ExcelIgnore
private String name;
@ExcelIgnore

View File

@ -13,6 +13,10 @@ import javax.validation.constraints.Pattern;
@ColumnWidth(15)
public class TestCaseExcelDataCn extends TestCaseExcelData {
@ExcelProperty("ID")
@NotRequired
private Integer num;
@NotBlank(message = "{cannot_be_null}")
@Length(max = 255)
@ExcelProperty("用例名称")

View File

@ -13,6 +13,10 @@ import javax.validation.constraints.Pattern;
@ColumnWidth(15)
public class TestCaseExcelDataTw extends TestCaseExcelData {
@ExcelProperty("ID")
@NotRequired
private Integer num;
@NotBlank(message = "{cannot_be_null}")
@Length(max = 255)
@ExcelProperty("用例名稱")

View File

@ -14,6 +14,10 @@ import javax.validation.constraints.Pattern;
@ColumnWidth(15)
public class TestCaseExcelDataUs extends TestCaseExcelData {
@ExcelProperty("ID")
@NotRequired
private Integer num;
@NotBlank(message = "{cannot_be_null}")
@Length(max = 255)
@ExcelProperty("Name")

View File

@ -1,12 +1,16 @@
package io.metersphere.excel.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.TestCaseWithBLOBs;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.excel.utils.ExcelValidateHelper;
import io.metersphere.i18n.Translator;
import io.metersphere.track.service.TestCaseService;
import org.apache.commons.lang3.StringUtils;
@ -22,10 +26,18 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
private String projectId;
protected List<TestCaseExcelData> updateList = new ArrayList<>(); //存储待更新用例的集合
protected boolean isUpdated = false; //判断是否更新过用例将会传给前端
Set<String> testCaseNames;
Set<String> userIds;
public boolean isUpdated() {
return isUpdated;
}
public TestCaseDataListener(Class clazz, String projectId, Set<String> testCaseNames, Set<String> userIds) {
this.clazz = clazz;
this.testCaseService = (TestCaseService) CommonBeanFactory.getBean("testCaseService");
@ -39,12 +51,15 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
String nodePath = data.getNodePath();
StringBuilder stringBuilder = new StringBuilder(errMsg);
//校验所属模块"
if (nodePath != null) {
String[] nodes = nodePath.split("/");
//校验模块深度
if (nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
stringBuilder.append(Translator.get("test_case_node_level_tip") +
TestCaseConstants.MAX_NODE_DEPTH + Translator.get("test_case_node_level") + "; ");
}
//模块名不能为空
for (int i = 0; i < nodes.length; i++) {
if (i != 0 && StringUtils.equals(nodes[i].trim(), "")) {
stringBuilder.append(Translator.get("module_not_null") + "; ");
@ -57,10 +72,39 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
// stringBuilder.append(Translator.get("functional_method_tip") + "; ");
// }
//校验维护人
if (!userIds.contains(data.getMaintainer())) {
stringBuilder.append(Translator.get("user_not_exists") + "" + data.getMaintainer() + "; ");
}
/*
校验Excel中是否有ID
有的话校验ID是否已在当前项目中存在存在则更新用例
不存在则继续校验看是否重复不重复则新建用例
*/
if (null != data.getNum()) { //当前读取的数据有ID
if (null != testCaseService.checkIdExist(data.getNum(), projectId)) { //该ID在当前项目中存在
//如果前面所经过的校验都没报错
if (StringUtils.isEmpty(stringBuilder)) {
updateList.add(data); //将当前数据存入更新列表
stringBuilder.append("update_testcase"); //该信息用于在invoke方法中判断是否该更新用例
}
return stringBuilder.toString();
} else {
/*
该ID在当前数据库中不存在应当继续校验用例是否重复,
在下面的校验过程中num的值会被用于判断是否重复所以应当先设置为null
*/
data.setNum(null);
}
}
/*
校验用例
*/
if (testCaseNames.contains(data.getName())) {
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
BeanUtils.copyBean(testCase, data);
@ -96,18 +140,27 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
@Override
public void saveData() {
//无错误数据才插入数据
if (!errList.isEmpty()) {
//excel中用例都有错误时就返回只要有用例可用于更新或者插入就不返回
if (!errList.isEmpty() && list.size() == 0 && updateList.size() == 0) {
return;
}
Collections.reverse(list);
if (!(list.size() == 0)){
Collections.reverse(list); //因为saveImportData里面是先分配最大的ID这个ID应该先发给list中最后的数据所以要reverse
List<TestCaseWithBLOBs> result = list.stream()
.map(item -> this.convert2TestCase(item))
.collect(Collectors.toList());
testCaseService.saveImportData(result, projectId);
}
List<TestCaseWithBLOBs> result = list.stream()
.map(item -> this.convert2TestCase(item))
.collect(Collectors.toList());
testCaseService.saveImportData(result, projectId);
if (!(updateList.size() == 0)) {
List<TestCaseWithBLOBs> result2 = updateList.stream()
.map(item -> this.convert2TestCaseForUpdate(item))
.collect(Collectors.toList());
testCaseService.updateImportDataCarryId(result2, projectId);
this.isUpdated = true;
updateList.clear();
}
}
@ -131,6 +184,32 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
testCase.setNodePath(nodePath);
String steps = getSteps(data);
testCase.setSteps(steps);
return testCase;
}
/**
* 将Excel中的数据对象转换为用于更新操作的用例数据对象
* @param data
* @return
*/
private TestCaseWithBLOBs convert2TestCaseForUpdate(TestCaseExcelData data) {
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
BeanUtils.copyBean(testCase, data);
testCase.setProjectId(this.projectId);
testCase.setUpdateTime(System.currentTimeMillis());
String nodePath = data.getNodePath();
if (!nodePath.startsWith("/")) {
nodePath = "/" + nodePath;
}
if (nodePath.endsWith("/")) {
nodePath = nodePath.substring(0, nodePath.length() - 1);
}
testCase.setNodePath(nodePath);
String steps = getSteps(data);
testCase.setSteps(steps);
@ -189,4 +268,38 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
return jsonArray.toJSONString();
}
@Override
public void invoke(TestCaseExcelData testCaseExcelData, AnalysisContext analysisContext) {
String errMsg;
Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
String updateMsg = "update_testcase";
try {
//根据excel数据实体中的javax.validation + 正则表达式来校验excel数据
errMsg = ExcelValidateHelper.validateEntity(testCaseExcelData);
//自定义校验规则
errMsg = validate(testCaseExcelData, errMsg);
} catch (NoSuchFieldException e) {
errMsg = Translator.get("parse_data_error");
LogUtil.error(e.getMessage(), e);
}
if (!StringUtils.isEmpty(errMsg)) {
//如果errMsg只有"update testcase"说明用例待更新
if (!errMsg.equals(updateMsg)){
ExcelErrData excelErrData = new ExcelErrData(testCaseExcelData, rowIndex,
Translator.get("number") + " " + rowIndex + " " + Translator.get("row") + Translator.get("error")
+ "" + errMsg);
errList.add(excelErrData);
}
} else {
list.add(testCaseExcelData);
}
if (list.size() > BATCH_COUNT) {
saveData();
list.clear();
}
}
}

View File

@ -12,6 +12,7 @@ import java.util.Map;
public class ReportRequest {
private String name;
private String workspaceId;
private String testId;
private String userId;
private List<OrderRequest> orders;
private Map<String, List<String>> filters;

View File

@ -127,12 +127,18 @@ public class DockerTestEngine extends AbstractEngine {
Integer port = node.getPort();
String uri = String.format(BASE_URL + "/jmeter/container/stop/" + testId, ip, port);
ResultHolder result = restTemplateWithTimeOut.getForObject(uri, ResultHolder.class);
if (result == null) {
MSException.throwException(Translator.get("container_delete_fail"));
}
if (!result.isSuccess()) {
MSException.throwException(result.getMessage());
try {
ResultHolder result = restTemplateWithTimeOut.getForObject(uri, ResultHolder.class);
if (result == null) {
MSException.throwException(Translator.get("container_delete_fail"));
}
if (!result.isSuccess()) {
MSException.throwException(result.getMessage());
}
} catch (MSException e) {
throw e;
} catch (Exception e) {
MSException.throwException("Please check node-controller status.");
}
});
}

View File

@ -71,11 +71,14 @@ public class CsrfFilter extends AnonymousFilter {
csrfToken = CodingUtil.aesDecrypt(csrfToken, SessionUser.secret, SessionUser.iv);
String[] signatureArray = StringUtils.split(StringUtils.trimToNull(csrfToken), "|");
if (signatureArray.length != 3) {
if (signatureArray.length != 4) {
throw new RuntimeException("invalid token");
}
if (!StringUtils.equals(SessionUtils.getUserId(), signatureArray[0])) {
throw new RuntimeException("Please check csrf token.");
}
if (!StringUtils.equals(SessionUtils.getSessionId(), signatureArray[2])) {
throw new RuntimeException("Please check csrf token.");
}
}
}

View File

@ -7,13 +7,13 @@ import io.metersphere.base.mapper.TestResourceMapper;
import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.ResultHolder;
import io.metersphere.dto.NodeDTO;
import io.metersphere.dto.TestResourcePoolDTO;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@ -71,12 +71,18 @@ public class NodeResourcePoolService {
private boolean validateNode(NodeDTO node) {
try {
ResponseEntity<String> entity = restTemplateWithTimeOut.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), String.class);
return HttpStatus.OK.equals(entity.getStatusCode());
ResponseEntity<ResultHolder> entity = restTemplateWithTimeOut.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), ResultHolder.class);
ResultHolder body = entity.getBody();
if (body == null) {
return false;
}
if (body.getData() != null && StringUtils.equalsIgnoreCase("OK", body.getData().toString())) {
return true;
}
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
return false;
}
return false;
}
private void updateTestResource(TestResource testResource) {

View File

@ -14,13 +14,11 @@ import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.service.CheckPermissionService;
import io.metersphere.service.FileService;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.request.testcase.EditTestCaseRequest;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import io.metersphere.track.request.testcase.TestCaseBatchRequest;
import io.metersphere.track.request.testcase.TestCaseMinderEditRequest;
import io.metersphere.track.request.testplan.FileOperationRequest;
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
import io.metersphere.track.service.TestCaseService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -126,8 +124,8 @@ public class TestCaseController {
@PostMapping(value = "/edit", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void editTestCase(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file") List<MultipartFile> files) {
testCaseService.edit(request, files);
public String editTestCase(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file") List<MultipartFile> files) {
return testCaseService.edit(request, files);
}
@PostMapping("/delete/{testCaseId}")

View File

@ -92,7 +92,7 @@ public class TestPlanController {
@PostMapping("/edit")
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void editTestPlan(@RequestBody TestPlanDTO testPlanDTO) {
testPlanService.editTestPlan(testPlanDTO);
testPlanService.editTestPlan(testPlanDTO, true);
}
@PostMapping("/edit/status/{planId}")

View File

@ -2,22 +2,20 @@ package io.metersphere.track.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.TestCaseReport;
import io.metersphere.base.domain.TestPlanReport;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.track.dto.TestCaseReportMetricDTO;
import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.dto.TestPlanReportDTO;
import io.metersphere.track.request.report.QueryTestPlanReportRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.report.TestPlanReportSaveRequest;
import io.metersphere.track.service.TestPlanReportService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
/**
* @author song.tianyang
@ -62,14 +60,18 @@ public class TestPlanReportController {
@GetMapping("/apiExecuteFinish/{planId}/{userId}")
public void apiExecuteFinish(@PathVariable String planId,@PathVariable String userId) {
TestPlanReport report = testPlanReportService.genTestPlanReport(planId,userId,ReportTriggerMode.API.name());
String reportId = UUID.randomUUID().toString();
TestPlanReportSaveRequest saveRequest = new TestPlanReportSaveRequest(reportId,planId,userId,ReportTriggerMode.API.name());
TestPlanReport report = testPlanReportService.genTestPlanReport(saveRequest);
testPlanReportService.countReportByTestPlanReportId(report.getId(),null, ReportTriggerMode.API.name());
}
@GetMapping("/saveTestPlanReport/{planId}/{triggerMode}")
public String saveTestPlanReport(@PathVariable String planId,@PathVariable String triggerMode) {
String userId = SessionUtils.getUser().getId();
TestPlanReport report = testPlanReportService.genTestPlanReport(planId,userId,triggerMode);
String reportId = UUID.randomUUID().toString();
TestPlanReportSaveRequest saveRequest = new TestPlanReportSaveRequest(reportId,planId,userId,triggerMode);
TestPlanReport report = testPlanReportService.genTestPlanReport(saveRequest);
testPlanReportService.countReportByTestPlanReportId(report.getId(),null, triggerMode);
return "success";
}

View File

@ -0,0 +1,53 @@
package io.metersphere.track.request.report;
import lombok.Getter;
import lombok.Setter;
/**
* @author song.tianyang
* @Date 2021/1/8 4:36 下午
* @Description
*/
@Getter
@Setter
public class TestPlanReportSaveRequest {
private String reportID;
private String planId;
private String userId;
private String triggerMode;
private boolean countResources;
private boolean apiCaseIsExecuting;
private boolean scenarioIsExecuting;
private boolean performanceIsExecuting;
private String apiCaseIdListJSON;
private String scenarioIdListJSON;
private String performanceIdListJSON;
public TestPlanReportSaveRequest(String reportID, String planId, String userId, String triggerMode) {
this.reportID = reportID;
this.planId = planId;
this.userId = userId;
this.triggerMode = triggerMode;
this.countResources = true;
}
public TestPlanReportSaveRequest(String reportID, String planId, String userId, String triggerMode, boolean apiCaseIsExecuting, boolean scenarioIsExecuting, boolean performanceIsExecuting, String apiCaseIdListJSON, String scenarioIdListJSON, String performanceIdListJSON) {
this.reportID = reportID;
this.planId = planId;
this.userId = userId;
this.triggerMode = triggerMode;
this.countResources = false;
this.apiCaseIsExecuting = apiCaseIsExecuting;
this.scenarioIsExecuting = scenarioIsExecuting;
this.performanceIsExecuting = performanceIsExecuting;
this.apiCaseIdListJSON = apiCaseIdListJSON;
this.scenarioIdListJSON = scenarioIdListJSON;
this.performanceIdListJSON = performanceIdListJSON;
}
}

View File

@ -331,20 +331,6 @@ public class TestCaseReviewService {
}
public void testReviewRelevance(ReviewRelevanceRequest request) {
String reviewId = request.getReviewId();
List<String> userIds = getTestCaseReviewerIds(reviewId);
String creator = "";
TestCaseReview review = testCaseReviewMapper.selectByPrimaryKey(reviewId);
if (review != null) {
creator = review.getCreator();
}
String currentId = SessionUtils.getUser().getId();
if (!userIds.contains(currentId) && !StringUtils.equals(creator, currentId)) {
MSException.throwException("没有权限,不能关联用例!");
}
List<String> testCaseIds = request.getTestCaseIds();
if (testCaseIds.isEmpty()) {

View File

@ -19,7 +19,6 @@ import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.excel.domain.TestCaseExcelDataFactory;
import io.metersphere.excel.listener.EasyExcelListener;
import io.metersphere.excel.listener.TestCaseDataListener;
import io.metersphere.excel.utils.EasyExcelExporter;
import io.metersphere.i18n.Translator;
@ -126,11 +125,26 @@ public class TestCaseService {
// 全部字段值相同才判断为用例存在
if (testCase != null) {
/*
例如对于/模块5用户的输入可能为模块5或者/模块5/或者模块5/
不这样处理的话下面进行判断时就会用用户输入的错误格式进行判断而模块名为/模块5
模块5/模块5/模块5/它们应该被认为是同一个模块
数据库存储的node_path都是/模块5这种格式的
*/
String nodePath = testCase.getNodePath();
if (!nodePath.startsWith("/")) {
nodePath = "/" + nodePath;
}
if (nodePath.endsWith("/")) {
nodePath = nodePath.substring(0, nodePath.length() - 1);
}
TestCaseExample example = new TestCaseExample();
TestCaseExample.Criteria criteria = example.createCriteria();
criteria.andNameEqualTo(testCase.getName())
.andProjectIdEqualTo(testCase.getProjectId())
.andNodePathEqualTo(testCase.getNodePath())
.andNodePathEqualTo(nodePath)
.andTypeEqualTo(testCase.getType())
.andMaintainerEqualTo(testCase.getMaintainer())
.andPriorityEqualTo(testCase.getPriority());
@ -165,7 +179,7 @@ public class TestCaseService {
String steps = tc.getSteps();
String remark = tc.getRemark();
if (StringUtils.equals(steps, testCase.getSteps()) && StringUtils.equals(remark, caseRemark)) {
MSException.throwException(Translator.get("test_case_already_exists"));
//MSException.throwException(Translator.get("test_case_already_exists"));
isExt = true;
}
}
@ -177,6 +191,26 @@ public class TestCaseService {
return null;
}
/**
* 根据id和pojectId查询id是否在数据库中存在
* 在数据库中单id的话是可重复的,id与projectId的组合是唯一的
*/
public Integer checkIdExist(Integer id, String projectId){
TestCaseExample example = new TestCaseExample();
TestCaseExample.Criteria criteria = example.createCriteria();
if (null != id) {
criteria.andNumEqualTo(id);
criteria.andProjectIdEqualTo(projectId);
long count = testCaseMapper.countByExample(example); //查询是否有包含此ID的数据
if(count == 0){ //如果ID不存在
return null;
}else { //有对应ID的数据
return id;
}
}
return null;
}
public int deleteTestCase(String testCaseId) {
TestPlanTestCaseExample example = new TestPlanTestCaseExample();
example.createCriteria().andCaseIdEqualTo(testCaseId);
@ -286,6 +320,7 @@ public class TestCaseService {
public ExcelResponse testCaseImport(MultipartFile multipartFile, String projectId, String userId) {
ExcelResponse excelResponse = new ExcelResponse();
boolean isUpdated = false; //判断是否更新了用例
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryTestCaseRequest queryTestCaseRequest = new QueryTestCaseRequest();
queryTestCaseRequest.setProjectId(projectId);
@ -338,10 +373,15 @@ public class TestCaseService {
Set<String> userIds = userRoleMapper.selectByExample(userRoleExample).stream().map(UserRole::getUserId).collect(Collectors.toSet());
try {
//根据本地语言环境选择用哪种数据对象进行存放读取的数据
Class clazz = new TestCaseExcelDataFactory().getExcelDataByLocal();
EasyExcelListener easyExcelListener = new TestCaseDataListener(clazz, projectId, testCaseNames, userIds);
TestCaseDataListener easyExcelListener = new TestCaseDataListener(clazz, projectId, testCaseNames, userIds);
//读取excel数据
EasyExcelFactory.read(multipartFile.getInputStream(), clazz, easyExcelListener).sheet().doRead();
errList = easyExcelListener.getErrList();
isUpdated = easyExcelListener.isUpdated();
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(e.getMessage());
@ -352,6 +392,7 @@ public class TestCaseService {
if (!errList.isEmpty()) {
excelResponse.setSuccess(false);
excelResponse.setErrList(errList);
excelResponse.setIsUpdated(isUpdated);
} else {
excelResponse.setSuccess(true);
}
@ -374,7 +415,7 @@ public class TestCaseService {
testcase.setSort(sort.getAndIncrement());
testcase.setNum(num.decrementAndGet());
testcase.setReviewStatus(TestCaseReviewStatus.Prepare.name());
mapper.insert(testcase);
mapper.insert(testcase);
});
}
sqlSession.flushStatements();
@ -400,6 +441,43 @@ public class TestCaseService {
sqlSession.flushStatements();
}
/**
* 把Excel中带ID的数据更新到数据库
* @param testCases
* @param projectId
*/
public void updateImportDataCarryId(List<TestCaseWithBLOBs> testCases, String projectId) {
Map<String, String> nodePathMap = testCaseNodeService.createNodeByTestCases(testCases, projectId);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
/*
获取用例的网页上所显示id数据库ID映射
*/
List<Integer> nums = testCases.stream()
.map(TestCase::getNum)
.collect(Collectors.toList());
TestCaseExample example = new TestCaseExample();
example.createCriteria().andNumIn(nums)
.andProjectIdEqualTo(projectId);
List<TestCase> testCasesList = testCaseMapper.selectByExample(example);
Map<Integer, String> numIdMap = testCasesList.stream()
.collect(Collectors.toMap(TestCase::getNum, TestCase::getId));
if (!testCases.isEmpty()) {
AtomicInteger sort = new AtomicInteger();
testCases.forEach(testcase -> {
testcase.setUpdateTime(System.currentTimeMillis());
testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
testcase.setSort(sort.getAndIncrement());
testcase.setId(numIdMap.get(testcase.getNum()));
mapper.updateByPrimaryKeySelective(testcase);
});
}
sqlSession.flushStatements();
}
public void testCaseTemplateExport(HttpServletResponse response) {
try {
@ -510,6 +588,7 @@ public class TestCaseService {
StringBuilder result = new StringBuilder("");
TestCaseList.forEach(t -> {
TestCaseExcelData data = new TestCaseExcelData();
data.setNum(t.getNum());
data.setName(t.getName());
data.setNodePath(t.getNodePath());
data.setPriority(t.getPriority());
@ -533,12 +612,15 @@ public class TestCaseService {
}
}
for (int j = 0; j < jsonArray.size(); j++) {
int num = j + 1;
step.append(num + "." + jsonArray.getJSONObject(j).getString("desc") + "\r\n");
result.append(num + "." + jsonArray.getJSONObject(j).getString("result") + "\r\n");
if (CollectionUtils.isNotEmpty(jsonArray)) {
for (int j = 0; j < jsonArray.size(); j++) {
int num = j + 1;
step.append(num + "." + jsonArray.getJSONObject(j).getString("desc") + "\r\n");
result.append(num + "." + jsonArray.getJSONObject(j).getString("result") + "\r\n");
}
}
data.setStepDesc(step.toString());
data.setStepResult(result.toString());
step.setLength(0);

View File

@ -1,21 +1,13 @@
package io.metersphere.track.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.dto.definition.request.MsThreadGroup;
import io.metersphere.api.service.ApiDefinitionExecResultService;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.ApiTestCaseExample;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.base.domain.TestPlanApiCase;
import io.metersphere.base.domain.TestPlanApiCaseExample;
import io.metersphere.base.mapper.TestPlanApiCaseMapper;
@ -25,15 +17,16 @@ import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.track.request.testcase.TestPlanApiCaseBatchRequest;
import org.apache.jmeter.testelement.TestElement;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
@Transactional(rollbackFor = Exception.class)
@ -150,4 +143,11 @@ public class TestPlanApiCaseService {
});
}
}
public String getState(String id) {
TestPlanApiCaseExample example = new TestPlanApiCaseExample();
example.createCriteria().andApiCaseIdEqualTo(id);
return testPlanApiCaseMapper.selectByExample(example).get(0).getStatus();
}
}

View File

@ -2,9 +2,6 @@ package io.metersphere.track.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtTestPlanApiCaseMapper;
@ -21,15 +18,14 @@ import io.metersphere.track.Factory.ReportComponentFactory;
import io.metersphere.track.domain.ReportComponent;
import io.metersphere.track.dto.*;
import io.metersphere.track.request.report.QueryTestPlanReportRequest;
import io.metersphere.track.request.report.TestPlanReportSaveRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.LoadCaseRequest;
import org.apache.commons.lang3.StringUtils;
import org.python.bouncycastle.pqc.math.linearalgebra.IntUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -80,36 +76,25 @@ public class TestPlanReportService {
}
/**
* 生成测试计划
* @param planId
* @param userId
* @param triggerMode
* @param reportId 报告ID(外部传入
* @param planId 测试计划ID
* @param userId 用户ID
* @param triggerMode 执行方式
* @param countResources 是否统计资源-false的话 下面三个不同资源是否运行则由参数决定 true的话则由统计后的结果决定
* @param apiCaseIsExecuting 接口案例是否执行中
* @param scenarioIsExecuting 场景案例是否执行中
* @param performanceIsExecuting 性能案例是否执行中
* @return
*/
public TestPlanReport genTestPlanReport(String planId, String userId,String triggerMode) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
public TestPlanReport genTestPlanReport(TestPlanReportSaveRequest saveRequest) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(saveRequest.getPlanId());
testPlan.setExecutionTimes(1);
testPlan.setExecutionTimes(testPlan.getExecutionTimes() + 1);
testPlanMapper.updateByPrimaryKey(testPlan);
TestPlanApiCaseExample apiExample = new TestPlanApiCaseExample();
apiExample.createCriteria().andTestPlanIdEqualTo(planId);
List<String> apiCaseIdList = testPlanApiCaseMapper.selectByExample(apiExample)
.stream().map(TestPlanApiCase::getApiCaseId).collect(Collectors.toList());
TestPlanApiScenarioExample example = new TestPlanApiScenarioExample();
example.createCriteria().andTestPlanIdEqualTo(planId);
List<String> scenarioIdList = testPlanScenarioCaseMapper.selectByExample(example)
.stream().map(TestPlanApiScenario::getApiScenarioId).collect(Collectors.toList());
LoadCaseRequest loadCaseRequest = new LoadCaseRequest();
loadCaseRequest.setTestPlanId(planId);
loadCaseRequest.setProjectId(testPlan.getProjectId());
List<String> performanceIdList = testPlanLoadCaseService.list(loadCaseRequest)
.stream().map(TestPlanLoadCaseDTO::getLoadCaseId).collect(Collectors.toList());
String testPlanReportID = UUID.randomUUID().toString();
String testPlanReportID = saveRequest.getReportID();
TestPlanReport testPlanReport = new TestPlanReport();
testPlanReport.setTestPlanId(planId);
testPlanReport.setTestPlanId(saveRequest.getPlanId());
testPlanReport.setId(testPlanReportID);
testPlanReport.setCreateTime(System.currentTimeMillis());
testPlanReport.setUpdateTime(System.currentTimeMillis());
@ -117,41 +102,68 @@ public class TestPlanReportService {
testPlanReport.setName(testPlan.getName() + "-" + DateUtils.getTimeString(new Date()));
} catch (Exception e) {
}
testPlanReport.setTriggerMode(triggerMode);
testPlanReport.setCreator(userId);
testPlanReport.setTriggerMode(saveRequest.getTriggerMode());
testPlanReport.setCreator(saveRequest.getUserId());
testPlanReport.setStartTime(System.currentTimeMillis());
testPlanReport.setEndTime(System.currentTimeMillis());
if (apiCaseIdList.isEmpty()) {
testPlanReport.setIsApiCaseExecuting(false);
} else {
testPlanReport.setIsApiCaseExecuting(true);
}
if (scenarioIdList.isEmpty()) {
testPlanReport.setIsScenarioExecuting(false);
} else {
testPlanReport.setIsScenarioExecuting(true);
}
if (performanceIdList.isEmpty()) {
testPlanReport.setIsPerformanceExecuting(false);
} else {
testPlanReport.setIsPerformanceExecuting(true);
}
testPlanReport.setPrincipal(testPlan.getPrincipal());
if(testPlanReport.getIsScenarioExecuting() || testPlanReport.getIsApiCaseExecuting() || testPlanReport.getIsPerformanceExecuting()){
testPlanReport.setStatus(APITestStatus.Starting.name());
}else {
testPlanReport.setStatus(APITestStatus.Completed.name());
}
testPlanReportMapper.insert(testPlanReport);
TestPlanReportDataWithBLOBs testPlanReportData = new TestPlanReportDataWithBLOBs();
testPlanReportData.setId(UUID.randomUUID().toString());
testPlanReportData.setTestPlanReportId(testPlanReportID);
testPlanReportData.setApiCaseInfo(JSONArray.toJSONString(apiCaseIdList));
testPlanReportData.setScenarioInfo(JSONArray.toJSONString(scenarioIdList));
testPlanReportData.setPerformanceInfo(JSONArray.toJSONString(performanceIdList));
if (saveRequest.isCountResources()) {
TestPlanApiCaseExample apiExample = new TestPlanApiCaseExample();
apiExample.createCriteria().andTestPlanIdEqualTo(saveRequest.getPlanId());
List<String> apiCaseIdList = testPlanApiCaseMapper.selectByExample(apiExample)
.stream().map(TestPlanApiCase::getApiCaseId).collect(Collectors.toList());
if (apiCaseIdList.isEmpty()) {
testPlanReport.setIsApiCaseExecuting(false);
} else {
testPlanReport.setIsApiCaseExecuting(true);
}
TestPlanApiScenarioExample example = new TestPlanApiScenarioExample();
example.createCriteria().andTestPlanIdEqualTo(saveRequest.getPlanId());
List<String> scenarioIdList = testPlanScenarioCaseMapper.selectByExample(example)
.stream().map(TestPlanApiScenario::getApiScenarioId).collect(Collectors.toList());
if (scenarioIdList.isEmpty()) {
testPlanReport.setIsScenarioExecuting(false);
} else {
testPlanReport.setIsScenarioExecuting(true);
}
LoadCaseRequest loadCaseRequest = new LoadCaseRequest();
loadCaseRequest.setTestPlanId(saveRequest.getPlanId());
loadCaseRequest.setProjectId(testPlan.getProjectId());
List<String> performanceIdList = testPlanLoadCaseService.list(loadCaseRequest)
.stream().map(TestPlanLoadCaseDTO::getLoadCaseId).collect(Collectors.toList());
if (performanceIdList.isEmpty()) {
testPlanReport.setIsPerformanceExecuting(false);
} else {
testPlanReport.setIsPerformanceExecuting(true);
}
testPlanReportData.setApiCaseInfo(JSONArray.toJSONString(apiCaseIdList));
testPlanReportData.setScenarioInfo(JSONArray.toJSONString(scenarioIdList));
testPlanReportData.setPerformanceInfo(JSONArray.toJSONString(performanceIdList));
} else {
testPlanReport.setIsApiCaseExecuting(saveRequest.isApiCaseIsExecuting());
testPlanReport.setIsScenarioExecuting(saveRequest.isScenarioIsExecuting());
testPlanReport.setIsPerformanceExecuting(saveRequest.isPerformanceIsExecuting());
testPlanReportData.setApiCaseInfo(saveRequest.getApiCaseIdListJSON());
testPlanReportData.setScenarioInfo(saveRequest.getScenarioIdListJSON());
testPlanReportData.setPerformanceInfo(saveRequest.getPerformanceIdListJSON());
}
testPlanReport.setPrincipal(testPlan.getPrincipal());
if (testPlanReport.getIsScenarioExecuting() || testPlanReport.getIsApiCaseExecuting() || testPlanReport.getIsPerformanceExecuting()) {
testPlanReport.setStatus(APITestStatus.Starting.name());
} else {
testPlanReport.setStatus(APITestStatus.Completed.name());
}
testPlanReportMapper.insert(testPlanReport);
testPlanReportDataMapper.insert(testPlanReportData);
//更新TestPlan状态改为进行中
@ -203,9 +215,9 @@ public class TestPlanReportService {
return returnDTO;
}
public synchronized void updateReport(List<String> testPlanReportIdList, String runMode,String triggerMode) {
public synchronized void updateReport(List<String> testPlanReportIdList, String runMode, String triggerMode) {
for (String planReportId : testPlanReportIdList) {
this.countReportByTestPlanReportId(planReportId,runMode,triggerMode);
this.countReportByTestPlanReportId(planReportId, runMode, triggerMode);
}
}
@ -219,12 +231,11 @@ public class TestPlanReportService {
}
/**
*
* @param planReportId 测试计划报告ID
* @param resourceRunMode 资源的运行模式,triggerMode非Scedule可以为null
* @param triggerMode 触发方式 ReportTriggerMode.enum
* @param planReportId 测试计划报告ID
* @param resourceRunMode 资源的运行模式,triggerMode非Scedule可以为null
* @param triggerMode 触发方式 ReportTriggerMode.enum
*/
public void countReportByTestPlanReportId(String planReportId,String resourceRunMode,String triggerMode) {
public void countReportByTestPlanReportId(String planReportId, String resourceRunMode, String triggerMode) {
TestPlanReport testPlanReport = testPlanReportMapper.selectByPrimaryKey(planReportId);
QueryTestPlanRequest queryTestPlanRequest = new QueryTestPlanRequest();
@ -233,26 +244,26 @@ public class TestPlanReportService {
String issuesInfo = null;
//因为接口案例的定时任务是单个案例开线程运行 所以要检查是否都执行完成全部执行完成时才会进行统一整理
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
&&StringUtils.equalsAny(resourceRunMode, ApiRunMode.SCHEDULE_API_PLAN.name())) {
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), triggerMode)
&& StringUtils.equalsAny(resourceRunMode, ApiRunMode.SCHEDULE_API_PLAN.name())) {
List<String> statusList = extTestPlanApiCaseMapper.getStatusByTestPlanId(testPlan.getId());
for (String status : statusList) {
if (status == null) {
return;
}
}
}else if(StringUtils.equals(ReportTriggerMode.TEST_PLAN_SCHEDULE.name(),triggerMode)){
} else if (StringUtils.equals(ReportTriggerMode.TEST_PLAN_SCHEDULE.name(), triggerMode)) {
}
testPlanReport.setEndTime(System.currentTimeMillis());
testPlanReport.setUpdateTime(System.currentTimeMillis());
//手动触发的需要保存手工执行的信息
int [] componentIndexArr = null;
if(StringUtils.equals(ReportTriggerMode.MANUAL.name(),triggerMode)){
componentIndexArr = new int[]{1,2,3,4,5};
}else {
componentIndexArr = new int[]{1,3,4};
int[] componentIndexArr = null;
if (StringUtils.equals(ReportTriggerMode.MANUAL.name(), triggerMode)) {
componentIndexArr = new int[]{1, 2, 3, 4, 5};
} else {
componentIndexArr = new int[]{1, 3, 4};
}
testPlanReport.setComponents(JSONArray.toJSONString(componentIndexArr));
@ -263,23 +274,23 @@ public class TestPlanReportService {
testPlanService.buildScenarioCaseReport(testPlanReport.getTestPlanId(), components);
testPlanService.buildLoadCaseReport(testPlanReport.getTestPlanId(), components);
if(StringUtils.equals(ReportTriggerMode.MANUAL.name(),triggerMode)){
if (StringUtils.equals(ReportTriggerMode.MANUAL.name(), triggerMode)) {
List<Issues> issues = testPlanService.buildFunctionalCaseReport(testPlanReport.getTestPlanId(), components);
issuesInfo = JSONArray.toJSONString(issues);
}
//只针对定时任务做处理
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
&&StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_API_PLAN.name())) {
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), triggerMode)
&& StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_API_PLAN.name())) {
testPlanReport.setIsApiCaseExecuting(false);
} else if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
&&StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_SCENARIO_PLAN.name())) {
} else if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), triggerMode)
&& StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_SCENARIO_PLAN.name())) {
testPlanReport.setIsScenarioExecuting(false);
} else if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
&&StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name())) {
} else if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), triggerMode)
&& StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name())) {
testPlanReport.setIsPerformanceExecuting(false);
}else {
} else {
testPlanReport.setIsPerformanceExecuting(false);
testPlanReport.setIsScenarioExecuting(false);
testPlanReport.setIsApiCaseExecuting(false);
@ -289,12 +300,12 @@ public class TestPlanReportService {
component.afterBuild(testCaseReportMetricDTO);
});
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
&&StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name())) {
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(), triggerMode)
&& StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name())) {
//如果是性能测试作为触发由于延迟原因可能会出现报告已经结束但是状态还是进行中的状态
List<TestCaseReportStatusResultDTO> loadResult = testCaseReportMetricDTO.getExecuteResult().getLoadResult();
for (TestCaseReportStatusResultDTO dto: loadResult) {
if(StringUtils.equals(dto.getStatus(),TestPlanTestCaseStatus.Underway.name())){
for (TestCaseReportStatusResultDTO dto : loadResult) {
if (StringUtils.equals(dto.getStatus(), TestPlanTestCaseStatus.Underway.name())) {
dto.setStatus(TestPlanTestCaseStatus.Pass.name());
}
}
@ -311,49 +322,50 @@ public class TestPlanReportService {
testPlanReportData.setExecuteResult(JSONObject.toJSONString(testCaseReportMetricDTO.getExecuteResult()));
testPlanReportData.setFailurTestCases(JSONObject.toJSONString(testCaseReportMetricDTO.getFailureTestCases()));
testPlanReportData.setModuleExecuteResult(JSONArray.toJSONString(testCaseReportMetricDTO.getModuleExecuteResult()));
if(issuesInfo!=null){
if (issuesInfo != null) {
testPlanReportData.setIssuesInfo(issuesInfo);
}
testPlanReportDataMapper.updateByPrimaryKeyWithBLOBs(testPlanReportData);
}
String testPlanStatus = this.getTestPlanReportStatus(testPlanReport,testPlanReportData);
String testPlanStatus = this.getTestPlanReportStatus(testPlanReport, testPlanReportData);
testPlanReport.setStatus(testPlanStatus);
this.update(testPlanReport);
}
/**
* 计算测试计划的状态
*
* @param testPlanReport
* @return
*/
private String getTestPlanReportStatus(TestPlanReport testPlanReport, TestPlanReportDataWithBLOBs testPlanReportData) {
String status = TestPlanReportStatus.COMPLETED.name();
if(testPlanReport!=null){
if(testPlanReport.getIsApiCaseExecuting() || testPlanReport.getIsPerformanceExecuting() || testPlanReport.getIsScenarioExecuting()){
if (testPlanReport != null) {
if (testPlanReport.getIsApiCaseExecuting() || testPlanReport.getIsPerformanceExecuting() || testPlanReport.getIsScenarioExecuting()) {
status = TestPlanReportStatus.RUNNING.name();
}else {
if(testPlanReportData == null){
} else {
if (testPlanReportData == null) {
String failCaseString = testPlanReportData.getFailurTestCases();
status = TestPlanReportStatus.SUCCESS.name();
try {
JSONObject failurCaseObject = JSONObject.parseObject(failCaseString);
if(failurCaseObject.containsKey("apiTestCases")&&failurCaseObject.getJSONArray("apiTestCases").size()>=0){
if (failurCaseObject.containsKey("apiTestCases") && failurCaseObject.getJSONArray("apiTestCases").size() >= 0) {
status = TestPlanReportStatus.FAILED.name();
return status;
}
if(failurCaseObject.containsKey("loadTestCases")&&failurCaseObject.getJSONArray("loadTestCases").size()>=0){
if (failurCaseObject.containsKey("loadTestCases") && failurCaseObject.getJSONArray("loadTestCases").size() >= 0) {
status = TestPlanReportStatus.FAILED.name();
return status;
}
if(failurCaseObject.containsKey("scenarioTestCases")&&failurCaseObject.getJSONArray("scenarioTestCases").size()>=0){
if (failurCaseObject.containsKey("scenarioTestCases") && failurCaseObject.getJSONArray("scenarioTestCases").size() >= 0) {
status = TestPlanReportStatus.FAILED.name();
return status;
}
}catch (Exception e){
} catch (Exception e) {
status = TestPlanReportStatus.FAILED.name();
}
}else {
} else {
status = TestPlanReportStatus.COMPLETED.name();
}
}
@ -371,7 +383,7 @@ public class TestPlanReportService {
testPlanMapper.updateByPrimaryKeySelective(testPlan);
}
if(StringUtils.equalsAny(report.getTriggerMode(),ReportTriggerMode.SCHEDULE.name())){
if (StringUtils.equalsAny(report.getTriggerMode(), ReportTriggerMode.SCHEDULE.name())) {
//发送通知
sendMessage(report);
}
@ -379,7 +391,7 @@ public class TestPlanReportService {
} catch (Exception e) {
}
}else {
} else {
}
testPlanReportMapper.updateByPrimaryKey(report);
}
@ -417,7 +429,7 @@ public class TestPlanReportService {
String successfulMailTemplate = "";
String errfoMailTemplate = "";
if(StringUtils.equals(testPlanReport.getTriggerMode(),ReportTriggerMode.SCHEDULE.name())){
if (StringUtils.equals(testPlanReport.getTriggerMode(), ReportTriggerMode.SCHEDULE.name())) {
successfulMailTemplate = "TestPlanSuccessfulNotification";
errfoMailTemplate = "TestPlanFailedNotification";
}
@ -446,7 +458,7 @@ public class TestPlanReportService {
* @param testPlanReport
* @param performaneReportIDList
*/
public void updatePerformanceInfo(TestPlanReport testPlanReport, List<String> performaneReportIDList,String triggerMode) {
public void updatePerformanceInfo(TestPlanReport testPlanReport, List<String> performaneReportIDList, String triggerMode) {
TestPlanReportDataExample example = new TestPlanReportDataExample();
example.createCriteria().andTestPlanReportIdEqualTo(testPlanReport.getId());
List<TestPlanReportDataWithBLOBs> reportDataList = testPlanReportDataMapper.selectByExampleWithBLOBs(example);
@ -462,36 +474,36 @@ public class TestPlanReportService {
List<String> updatePerformaneReportIDList = new ArrayList<>(performaneReportIDList);
executorService.submit(() -> {
//错误数据检查集合 如果错误数据出现超过20次则取消该条数据的检查
Map<String,Integer> errorDataCheckMap = new HashMap<>();
while (performaneReportIDList.size()>0) {
Map<String, Integer> errorDataCheckMap = new HashMap<>();
while (performaneReportIDList.size() > 0) {
List<String> selectList = new ArrayList<>(performaneReportIDList);
for (String loadTestReportId:selectList) {
for (String loadTestReportId : selectList) {
LoadTestReportWithBLOBs loadTestReportFromDatabase = loadTestReportMapper.selectByPrimaryKey(loadTestReportId);
if(loadTestReportFromDatabase == null){
if (loadTestReportFromDatabase == null) {
//检查错误数据
if(errorDataCheckMap.containsKey(loadTestReportId)){
if(errorDataCheckMap.get(loadTestReportId)>10){
if (errorDataCheckMap.containsKey(loadTestReportId)) {
if (errorDataCheckMap.get(loadTestReportId) > 10) {
performaneReportIDList.remove(loadTestReportId);
}else {
errorDataCheckMap.put(loadTestReportId,errorDataCheckMap.get(loadTestReportId)+1);
} else {
errorDataCheckMap.put(loadTestReportId, errorDataCheckMap.get(loadTestReportId) + 1);
}
}else {
errorDataCheckMap.put(loadTestReportId,1);
} else {
errorDataCheckMap.put(loadTestReportId, 1);
}
}else if (StringUtils.equalsAny(loadTestReportFromDatabase.getStatus(),
} else if (StringUtils.equalsAny(loadTestReportFromDatabase.getStatus(),
PerformanceTestStatus.Completed.name(), PerformanceTestStatus.Error.name())) {
performaneReportIDList.remove(loadTestReportId);
}
}
if(performaneReportIDList.isEmpty()){
for (String string: updatePerformaneReportIDList) {
if (performaneReportIDList.isEmpty()) {
for (String string : updatePerformaneReportIDList) {
TestPlanLoadCaseEventDTO eventDTO = new TestPlanLoadCaseEventDTO();
eventDTO.setReportId(string);
eventDTO.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
eventDTO.setStatus(PerformanceTestStatus.Completed.name());
this.updatePerformanceTestStatus(eventDTO);
}
}else {
} else {
try {
//查询定时任务是否关闭
Thread.sleep(1000 * 10);// 检查 loadtest 的状态
@ -505,7 +517,7 @@ public class TestPlanReportService {
public void updatePerformanceTestStatus(TestPlanLoadCaseEventDTO eventDTO) {
List<String> testPlanReportId = extTestPlanMapper.findIdByPerformanceReportId(eventDTO.getReportId());
this.updateReport(testPlanReportId, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name(),eventDTO.getTriggerMode());
this.updateReport(testPlanReportId, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name(), eventDTO.getTriggerMode());
}
public void delete(List<String> testPlanReportIdList) {

View File

@ -33,6 +33,7 @@ import io.metersphere.service.SystemParameterService;
import io.metersphere.track.Factory.ReportComponentFactory;
import io.metersphere.track.domain.ReportComponent;
import io.metersphere.track.dto.*;
import io.metersphere.track.request.report.TestPlanReportSaveRequest;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.AddTestPlanRequest;
@ -128,6 +129,8 @@ public class TestPlanService {
private ApiScenarioMapper apiScenarioMapper;
@Resource
private TestCaseTestMapper testCaseTestMapper;
@Resource
private ApiScenarioReportMapper apiScenarioReportMapper;
public synchronized String addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
@ -173,7 +176,7 @@ public class TestPlanService {
return Optional.ofNullable(testPlanMapper.selectByPrimaryKey(testPlanId)).orElse(new TestPlan());
}
public int editTestPlan(TestPlanDTO testPlan) {
public int editTestPlan(TestPlanDTO testPlan, Boolean isSendMessage) {
checkTestPlanExist(testPlan);
TestPlan res = testPlanMapper.selectByPrimaryKey(testPlan.getId()); // 先查一次库
testPlan.setUpdateTime(System.currentTimeMillis());
@ -211,7 +214,7 @@ public class TestPlanService {
extScheduleMapper.updateNameByResourceID(testPlan.getId(), testPlan.getName());// 同步更新该测试的定时任务的name
i = testPlanMapper.updateByPrimaryKeyWithBLOBs(testPlan); // 更新
}
if (!StringUtils.isBlank(testPlan.getStatus())) {
if (!StringUtils.isBlank(testPlan.getStatus()) && isSendMessage) {
BeanUtils.copyBean(testPlans, getTestPlan(testPlan.getId()));
String context = getTestPlanContext(testPlans, NoticeConstants.Event.UPDATE);
User user = userMapper.selectByPrimaryKey(testPlans.getCreator());
@ -397,7 +400,7 @@ public class TestPlanService {
testPlanDTO.setId(testPlanId);
if(statusList.size() == 0) { // 原先status不是prepare, 但删除所有关联用例的情况
testPlanDTO.setStatus(TestPlanStatus.Prepare.name());
editTestPlan(testPlanDTO);
editTestPlan(testPlanDTO, false);
return;
}
int passNum = 0, prepareNum = 0, failNum = 0;
@ -406,7 +409,7 @@ public class TestPlanService {
|| StringUtils.equals(res, "success")
|| StringUtils.equals(res, ScenarioStatus.Success.name())) {
passNum++;
} else if (res == null) {
} else if (res == null || StringUtils.equals(TestPlanStatus.Prepare.name(), res)) {
prepareNum++;
} else {
failNum++;
@ -414,13 +417,13 @@ public class TestPlanService {
}
if(passNum == statusList.size()) { // 全部通过
testPlanDTO.setStatus(TestPlanStatus.Completed.name());
this.editTestPlan(testPlanDTO);
this.editTestPlan(testPlanDTO, false);
} else if(prepareNum == 0 && passNum + failNum == statusList.size()) { // 已结束
testPlanDTO.setStatus(TestPlanStatus.Finished.name());
editTestPlan(testPlanDTO);
editTestPlan(testPlanDTO, false);
} else if(prepareNum != 0) { // 进行中
testPlanDTO.setStatus(TestPlanStatus.Underway.name());
editTestPlan(testPlanDTO);
editTestPlan(testPlanDTO, false);
}
}
@ -863,12 +866,13 @@ public class TestPlanService {
* @return
*/
public String runScenarioCase(SchedulePlanScenarioExecuteRequest request) {
String returnId = "";
MsTestPlan testPlan = new MsTestPlan();
testPlan.setHashTree(new LinkedList<>());
HashTree jmeterHashTree = new ListedHashTree();
Map<String, Map<String, String>> testPlanScenarioIdMap = request.getTestPlanScenarioIDMap();
for (Map.Entry<String, Map<String, String>> entry : testPlanScenarioIdMap.entrySet()) {
String testPlanID = entry.getKey();
Map<String, String> planScenarioIdMap = entry.getValue();
List<ApiScenarioWithBLOBs> apiScenarios = extApiScenarioMapper.selectIds(new ArrayList<>(planScenarioIdMap.keySet()));
try {
@ -918,23 +922,27 @@ public class TestPlanService {
scenarios.add(scenario);
// 创建场景报告
//不同的运行模式第二个参数入参不同
apiAutomationService.createScenarioReport(group.getName(),
APIScenarioReportResult report = apiAutomationService.createScenarioReport(group.getName(),
planScenarioID + ":" + request.getTestPlanReportId(),
item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);
apiScenarioReportMapper.insert(report);
returnId = request.getId();
}
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
String runMode = ApiRunMode.SCHEDULE_SCENARIO_PLAN.name();
// 调用执行方法
jMeterService.runDefinition(request.getId(), jmeterHashTree, request.getReportId(), runMode);
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
String runMode = ApiRunMode.SCHEDULE_SCENARIO_PLAN.name();
// 调用执行方法
jMeterService.runDefinition(request.getId(), jmeterHashTree, request.getReportId(), runMode);
return request.getId();
return returnId;
}
public void run(String testPlanID, String projectID, String userId, String triggerMode) {
@ -966,37 +974,21 @@ public class TestPlanService {
LogUtil.info("-------------- start testplan schedule ----------");
TestPlanReportService testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class);
//首先创建testPlanReport然后返回的ID重新赋值为resourceID作为后续的参数
TestPlanReport testPlanReport = testPlanReportService.genTestPlanReport(testPlanID, userId, triggerMode);
//执行接口案例任务
for (Map.Entry<String, String> entry : apiTestCaseIdMap.entrySet()) {
String apiCaseID = entry.getKey();
String planCaseID = entry.getValue();
ApiTestCaseWithBLOBs blobs = apiTestCaseService.get(apiCaseID);
//需要更新这里来保证PlanCase的状态能正常更改
apiTestCaseService.run(blobs, UUID.randomUUID().toString(), testPlanReport.getId(), testPlanID, ApiRunMode.SCHEDULE_API_PLAN.name());
}
//执行场景执行任务
if (!planScenarioIdMap.isEmpty()) {
LogUtil.info("-------------- testplan schedule ---------- api case over -----------------");
SchedulePlanScenarioExecuteRequest scenarioRequest = new SchedulePlanScenarioExecuteRequest();
String senarionReportID = UUID.randomUUID().toString();
scenarioRequest.setId(senarionReportID);
scenarioRequest.setReportId(senarionReportID);
scenarioRequest.setProjectId(projectID);
scenarioRequest.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
scenarioRequest.setExecuteType(ExecuteType.Saved.name());
Map<String, Map<String, String>> testPlanScenarioIdMap = new HashMap<>();
testPlanScenarioIdMap.put(testPlanID, planScenarioIdMap);
scenarioRequest.setTestPlanScenarioIDMap(testPlanScenarioIdMap);
scenarioRequest.setReportUserID(userId);
scenarioRequest.setTestPlanID(testPlanID);
scenarioRequest.setRunMode(ApiRunMode.SCHEDULE_SCENARIO_PLAN.name());
scenarioRequest.setTestPlanReportId(testPlanReport.getId());
this.runScenarioCase(scenarioRequest);
LogUtil.info("-------------- testplan schedule ---------- scenario case over -----------------");
}
boolean apiCaseIsExcuting = false;
boolean scenarioIsExcuting = false;
boolean performaceIsExcuting = false;
String apiCaseIdArray = "";
String scenarioCaseIdArray = "";
String performanceCaseIdArray = "";
String planReportId = UUID.randomUUID().toString();
//创建测试报告然后返回的ID重新赋值为resourceID作为后续的参数
TestPlanReportSaveRequest saveRequest = new TestPlanReportSaveRequest(planReportId,testPlanID,userId,triggerMode,
apiTestCaseIdMap.size()>0,planScenarioIdMap.size()>0,performanceIdMap.size()>0,
JSONArray.toJSONString(new ArrayList<>(apiTestCaseIdMap.keySet())),JSONArray.toJSONString(new ArrayList<>(planScenarioIdMap.keySet())),JSONArray.toJSONString(new ArrayList<>(performanceIdMap.values())));
TestPlanReport testPlanReport = testPlanReportService.genTestPlanReport(saveRequest);
//执行性能测试任务
List<String> performaneReportIDList = new ArrayList<>();
for (Map.Entry<String, String> entry : performanceIdMap.entrySet()) {
@ -1016,20 +1008,76 @@ public class TestPlanService {
testPlanLoadCase.setId(performanceRequest.getTestPlanLoadId());
testPlanLoadCase.setLoadReportId(reportId);
testPlanLoadCaseService.update(testPlanLoadCase);
//更新关联处的报告
TestPlanLoadCase loadCase = new TestPlanLoadCaseDTO();
loadCase.setId(id);
loadCase.setLoadReportId(reportId);
testPlanLoadCaseService.update(loadCase);
}
} catch (Exception e) {
e.printStackTrace();
}
//更新关联处的报告
TestPlanLoadCase loadCase = new TestPlanLoadCaseDTO();
loadCase.setId(id);
loadCase.setLoadReportId(reportId);
testPlanLoadCaseService.update(loadCase);
if(StringUtils.isEmpty(reportId)){
performaceIsExcuting = false;
}
}
if(performaceIsExcuting){
performanceCaseIdArray= JSONArray.toJSONString(new ArrayList<>(performanceIdMap.values()));
}
if (!performaneReportIDList.isEmpty()) {
//性能测试时保存性能测试报告ID在结果返回时用于捕捉并进行
testPlanReportService.updatePerformanceInfo(testPlanReport, performaneReportIDList, ReportTriggerMode.SCHEDULE.name());
}
//执行接口案例任务
for (Map.Entry<String, String> entry : apiTestCaseIdMap.entrySet()) {
String apiCaseID = entry.getKey();
String planCaseID = entry.getValue();
ApiTestCaseWithBLOBs blobs = apiTestCaseService.get(apiCaseID);
//需要更新这里来保证PlanCase的状态能正常更改
apiTestCaseService.run(blobs, UUID.randomUUID().toString(), planReportId, testPlanID, ApiRunMode.SCHEDULE_API_PLAN.name());
apiCaseIsExcuting = true;
}
if(apiCaseIsExcuting){
apiCaseIdArray = JSONArray.toJSONString(new ArrayList<>(apiTestCaseIdMap.keySet()));
}
//执行场景执行任务
if (!planScenarioIdMap.isEmpty()) {
LogUtil.info("-------------- testplan schedule ---------- api case over -----------------");
SchedulePlanScenarioExecuteRequest scenarioRequest = new SchedulePlanScenarioExecuteRequest();
String senarionReportID = UUID.randomUUID().toString();
scenarioRequest.setId(senarionReportID);
scenarioRequest.setReportId(senarionReportID);
scenarioRequest.setProjectId(projectID);
scenarioRequest.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
scenarioRequest.setExecuteType(ExecuteType.Saved.name());
Map<String, Map<String, String>> testPlanScenarioIdMap = new HashMap<>();
testPlanScenarioIdMap.put(testPlanID, planScenarioIdMap);
scenarioRequest.setTestPlanScenarioIDMap(testPlanScenarioIdMap);
scenarioRequest.setReportUserID(userId);
scenarioRequest.setTestPlanID(testPlanID);
scenarioRequest.setRunMode(ApiRunMode.SCHEDULE_SCENARIO_PLAN.name());
scenarioRequest.setTestPlanReportId(planReportId);
String scenarioReportID = this.runScenarioCase(scenarioRequest);
if(StringUtils.isNotEmpty(scenarioReportID)){
scenarioIsExcuting = true;
scenarioCaseIdArray= JSONArray.toJSONString(new ArrayList<>(planScenarioIdMap.keySet()));
}
LogUtil.info("-------------- testplan schedule ---------- scenario case over -----------------");
}
//如果report参数和预期不对某些原因执行失败则更新report
if(saveRequest.isApiCaseIsExecuting() != apiCaseIsExcuting ||saveRequest.isScenarioIsExecuting()!=scenarioIsExcuting ||saveRequest.isPerformanceIsExecuting() != performaceIsExcuting){
testPlanReport.setIsApiCaseExecuting(apiCaseIsExcuting);
testPlanReport.setIsScenarioExecuting(scenarioIsExcuting);
testPlanReport.setIsPerformanceExecuting(performaceIsExcuting);
testPlanReportService.update(testPlanReport);
}
}
}

View File

@ -67,7 +67,7 @@
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, downloadFile, checkoutTestManagerOrTestUser} from "@/common/js/utils";
import {getUUID, downloadFile, checkoutTestManagerOrTestUser,getCurrentUser} from "@/common/js/utils";
import MsApiScenarioModule from "@/business/components/api/automation/scenario/ApiScenarioModule";
import MsEditApiScenario from "./scenario/EditApiScenario";
@ -127,7 +127,6 @@
'$route'(to, from) { // ctrl s
if (to.path.indexOf('/api/automation') == -1) {
if (this.$refs && this.$refs.autoScenarioConfig) {
// console.log(this.$refs.autoScenarioConfig);
this.$refs.autoScenarioConfig.forEach(item => {
item.removeListener();
});
@ -189,7 +188,16 @@
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: {apiScenarioModuleId: "", id: getUUID()}});
let currentScenario = {
status: "Underway", principal: getCurrentUser().id,
apiScenarioModuleId: "root", id: getUUID(),
modulePath: "/" + this.$t("commons.module_title")
};
if (this.nodeTree && this.nodeTree.length > 0) {
currentScenario.apiScenarioModuleId = this.nodeTree[0].id;
currentScenario.modulePath = this.nodeTree[0].path;
}
this.tabs.push({label: label, name: name, currentScenario: currentScenario});
}
if (tab.name === 'edit') {
let label = this.$t('api_test.automation.add_scenario');

View File

@ -16,32 +16,9 @@
ref="nodeTree">
<template v-slot:header>
<el-input :placeholder="$t('test_track.module.search')" v-model="condition.filterText" size="small">
<template v-slot:append>
<el-dropdown v-if="!isReadOnly" size="small" split-button type="primary" class="ms-api-button" @click="handleCommand('add-api')"
v-tester
@command="handleCommand" trigger="click">
<el-button icon="el-icon-folder-add" @click="addScenario"></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="add-scenario">{{ $t('api_test.automation.add_scenario') }}</el-dropdown-item>
<el-dropdown-item command="import">{{ $t('api_test.api_import.label') }}</el-dropdown-item>
<el-dropdown-item command="exports">
<el-dropdown placement="right-start" @command="chooseExportType">
<span>
{{ $t('report.export') }} <i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<template>
<el-dropdown-item command="export">{{ $t('report.export_to_ms_format') }}</el-dropdown-item>
<el-dropdown-item command="exportJmx">{{ $t('report.export') }} JMETER 格式</el-dropdown-item>
</template>
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-input>
<ms-search-bar
:condition="condition"
:commands="operators"/>
<module-trash-button v-if="!isReadOnly" :condition="condition" :exe="enableTrash"/>
</template>
@ -52,7 +29,7 @@
@refresh="refresh"
ref="basisScenario"/>
<api-import ref="apiImport" :moduleOptions="moduleOptions" @refreshAll="$emit('refreshAll')"/>
<api-import ref="apiImport" :moduleOptions="extendTreeNodes" @refreshAll="$emit('refreshAll')"/>
</div>
</template>
@ -61,13 +38,15 @@
import SelectMenu from "../../../track/common/SelectMenu";
import MsAddBasisScenario from "@/business/components/api/automation/scenario/AddBasisScenario";
import MsNodeTree from "../../../track/common/NodeTree";
import {buildNodePath} from "../../definition/model/NodeTree";
import {buildNodePath, buildTree} from "../../definition/model/NodeTree";
import ModuleTrashButton from "../../definition/components/module/ModuleTrashButton";
import ApiImport from "./common/ScenarioImport";
import MsSearchBar from "@/business/components/common/components/search/MsSearchBar";
export default {
name: 'MsApiScenarioModule',
components: {
MsSearchBar,
ApiImport,
ModuleTrashButton,
MsNodeTree,
@ -103,8 +82,36 @@
trashEnable: false
},
data: [],
extendTreeNodes: [],
currentModule: undefined,
moduleOptions: [],
operators: [
{
label: this.$t('api_test.automation.add_scenario'),
callback: this.addScenario
},
{
label: this.$t('api_test.api_import.label'),
callback: this.handleImport
},
{
label: this.$t('report.export'),
children: [
{
label: this.$t('report.export_to_ms_format'),
callback: () => {
this.$emit('exportAPI');
}
},
{
label: this.$t('report.export') + 'JMETER 格式',
callback: () => {
this.$emit('exportJmx');
}
}
]
}
]
}
},
mounted() {
@ -151,14 +158,19 @@
break;
}
},
chooseExportType(e) {
switch (e) {
case "export":
this.$emit('exportAPI');
break;
case "exportJmx":
this.$emit('exportJmx');
break;
handleImport() {
if (this.projectId) {
this.result = this.$get("/api/automation/module/list/" + this.projectId, response => {
if (response.data != undefined && response.data != null) {
this.data = response.data;
let moduleOptions = [];
this.data.forEach(node => {
buildNodePath(node, {path: ''}, moduleOptions);
});
this.moduleOptions = moduleOptions
}
});
this.$refs.apiImport.open(this.currentModule);
}
},
list(projectId) {
@ -176,11 +188,17 @@
this.result = this.$get(url, response => {
if (response.data != undefined && response.data != null) {
this.data = response.data;
let moduleOptions = [];
this.data.forEach(node => {
buildNodePath(node, {path: ''}, moduleOptions);
this.extendTreeNodes = [];
this.extendTreeNodes.unshift({
"id": "root",
"name": this.$t('commons.module_title'),
"level": 0,
"children": this.data,
});
this.$emit('setModuleOptions', moduleOptions);
this.extendTreeNodes.forEach(node => {
buildTree(node, {path: ''});
});
this.$emit('setModuleOptions', this.extendTreeNodes);
this.$emit('setNodeTree', this.data);
if (this.$refs.nodeTree) {
this.$refs.nodeTree.filter(this.condition.filterText);

View File

@ -22,9 +22,7 @@
</el-col>
<el-col :span="7">
<el-form-item :label="$t('test_track.module.module')" prop="apiScenarioModuleId">
<el-select class="ms-scenario-input" size="small" v-model="currentScenario.apiScenarioModuleId">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</el-select>
<ms-select-tree size="small" :data="moduleOptions" :defaultKey="currentScenario.apiScenarioModuleId" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
</el-form-item>
</el-col>
<el-col :span="7">
@ -201,7 +199,8 @@
@closePage="close" @unFullScreen="unFullScreen" @showAllBtn="showAllBtn" @runDebug="runDebug" @setProjectEnvMap="setProjectEnvMap" @showScenarioParameters="showScenarioParameters" @setCookieShare="setCookieShare" ref="maximizeHeader"/>
</template>
<maximize-scenario :scenario-definition="scenarioDefinition" :envMap="projectEnvMap" :moduleOptions="moduleOptions" :currentScenario="currentScenario" :type="type" ref="maximizeScenario" @openScenario="openScenario"/>
<maximize-scenario :scenario-definition="scenarioDefinition" :envMap="projectEnvMap" :moduleOptions="moduleOptions"
:currentScenario="currentScenario" :type="type" ref="maximizeScenario" @openScenario="openScenario"/>
</ms-drawer>
</div>
@ -238,6 +237,7 @@
import MaximizeScenario from "./maximize/MaximizeScenario";
import ScenarioHeader from "./maximize/ScenarioHeader";
import MsDrawer from "../../../common/components/MsDrawer";
import MsSelectTree from "../../../common/select-tree/SelectTree";
let jsonPath = require('jsonpath');
export default {
@ -260,7 +260,8 @@
EnvPopover,
MaximizeScenario,
ScenarioHeader,
MsDrawer
MsDrawer,
MsSelectTree
},
data() {
return {
@ -268,6 +269,10 @@
label: "label",
children: "hashTree"
},
moduleObj: {
id: 'id',
label: 'name',
},
rules: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
@ -442,6 +447,10 @@
},
},
methods: {
setModule(id,data) {
this.currentScenario.apiScenarioModuleId = id;
this.currentScenario.modulePath = data.path;
},
setHideBtn() {
this.isBtnHide = false;
},
@ -577,13 +586,14 @@
recursiveSorting(arr, scenarioProjectId) {
for (let i in arr) {
arr[i].index = Number(i) + 1;
if (arr[i].type === ELEMENT_TYPE.LoopController && arr[i].hashTree && arr[i].hashTree.length > 1) {
if (arr[i].type === ELEMENT_TYPE.LoopController && arr[i].loopType === "LOOP_COUNT" && arr[i].hashTree && arr[i].hashTree.length > 1) {
arr[i].countController.proceed = true;
}
if (!arr[i].projectId) {
// IDIDIDID
arr[i].projectId = scenarioProjectId ? scenarioProjectId : this.projectId;
}
if (arr[i].hashTree != undefined && arr[i].hashTree.length > 0) {
this.recursiveSorting(arr[i].hashTree, arr[i].projectId);
}
@ -606,6 +616,7 @@
if (!this.scenarioDefinition[i].projectId) {
this.scenarioDefinition[i].projectId = this.projectId;
}
if (this.scenarioDefinition[i].hashTree != undefined && this.scenarioDefinition[i].hashTree.length > 0) {
this.recursiveSorting(this.scenarioDefinition[i].hashTree, this.scenarioDefinition[i].projectId);
}
@ -753,6 +764,7 @@
if (!sign) {
return;
}
this.$refs['currentScenario'].validate((valid) => {
if (valid) {
Promise.all([
@ -841,15 +853,6 @@
this.expandedNode.splice(this.expandedNode.indexOf(data.resourceId), 1);
}
},
getPath(id) {
if (id === null) {
return null;
}
let path = this.moduleOptions.filter(function (item) {
return item.id === id ? item.path : "";
});
return path[0].path;
},
setFiles(item, bodyUploadFiles, obj) {
if (item.body) {
if (item.body.kvs) {
@ -926,7 +929,7 @@
return bodyUploadFiles;
},
editScenario() {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
document.getElementById("inputDelay").focus(); // input
this.$refs['currentScenario'].validate((valid) => {
if (valid) {
@ -1008,7 +1011,6 @@
setParameter() {
this.currentScenario.stepTotal = this.scenarioDefinition.length;
this.currentScenario.projectId = this.projectId;
this.currentScenario.modulePath = this.getPath(this.currentScenario.apiScenarioModuleId);
// 便
let scenario = {
id: this.currentScenario.id,

View File

@ -3,6 +3,7 @@
v-model="visible"
placement="bottom"
width="400"
:disabled="isReadOnly"
@show="showPopover"
trigger="click">
<env-select :project-ids="projectIds" :env-map="envMap" @close="visible = false"
@ -24,6 +25,12 @@ export default {
envMap: Map,
projectIds: Set,
projectList: Array,
isReadOnly: {
type: Boolean,
default() {
return false;
}
}
},
data() {
return {

View File

@ -5,12 +5,12 @@
<el-option v-for="(environment, index) in pe.envs" :key="index"
:label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
:value="environment.id"/>
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<div class="empty-environment">
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
@ -78,12 +78,12 @@ export default {
const project = this.projectList.find(p => p.id === id);
return project ? project.name : "";
},
openEnvironmentConfig(projectId) {
openEnvironmentConfig(projectId, envId) {
if (!projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(projectId);
this.$refs.environmentConfig.open(projectId, envId);
},
handleConfirm() {
let map = new Map();

View File

@ -18,7 +18,7 @@ export const ELEMENTS = new Map([
['CustomizeReq', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['MaxSamplerProxy', ["JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['AllSamplerProxy', ["HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler"]],
['AllCanExecType', ["HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "JSR223Processor"]]
])
export const ELEMENT_TYPE = {

View File

@ -31,7 +31,7 @@
</el-form-item>
<el-form-item :label="$t('test_track.module.module')" prop="moduleId">
<ms-select-tree size="small" :data="moduleOptions" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
<ms-select-tree size="small" :data="moduleOptions" :defaultKey="httpForm.moduleId" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
</el-form-item>
<el-form-item :label="$t('commons.description')" prop="description" style="margin-bottom: 29px">
@ -64,6 +64,7 @@
import {createComponent, Request} from "../../../definition/components/jmeter/components";
import {getUUID} from "@/common/js/utils";
import MsSelectTree from "@/business/components/common/select-tree/SelectTree";
import {buildTree} from "../../../definition/model/NodeTree";
export default {
@ -83,7 +84,7 @@
callback();
};
return {
httpForm: {environmentId: ""},
httpForm: {environmentId: "", moduleId: "root"},
moduleOptions: [],
httpVisible: false,
currentModule: {},
@ -250,13 +251,23 @@
let url = "/api/module/list/" + getCurrentProjectID() + "/" + data.protocol;
this.result = this.$get(url, response => {
if (response.data != undefined && response.data != null) {
this.moduleOptions = response.data;
let data = response.data;
this.moduleOptions = [];
this.moduleOptions.unshift({
"id": "root",
"name": this.$t('commons.module_title'),
"level": 0,
"children": data,
});
this.moduleOptions.forEach(node => {
buildTree(node, {path: ''});
});
}
});
},
setModule(id) {
setModule(id, data) {
this.httpForm.moduleId = id;
//this.reload();
this.httpForm.modulePath = data.path;
},
reload() {
this.loading = true
@ -271,7 +282,7 @@
data.protocol = "DUBBO";
}
data.id = getUUID();
this.httpForm = {id: data.id, name: data.name, protocol: data.protocol, path: data.path, method: api.method, userId: getCurrentUser().id, request: data};
this.httpForm = {id: data.id, name: data.name, protocol: data.protocol, path: data.path, method: api.method, userId: getCurrentUser().id, request: data, moduleId: "root"};
this.getMaintainerOptions();
this.list(data);
this.httpVisible = true;

View File

@ -101,25 +101,25 @@ export default {
let requestObj = JSON.parse(item.request);
if(requestObj.esbDataStruct != null ){
//ESB
let param = {};
param.request = requestObj;
param.method = "ESB";
param.esbDataStruct = JSON.stringify(requestObj.esbDataStruct);
if(requestObj.backEsbDataStruct != null){
param.backEsbDataStruct = JSON.stringify(requestObj.backEsbDataStruct);
}else{
param.backEsbDataStruct = "";
}
// //ESB
// let param = {};
// param.request = requestObj;
// param.method = "ESB";
// param.esbDataStruct = JSON.stringify(requestObj.esbDataStruct);
// if(requestObj.backEsbDataStruct != null){
// param.backEsbDataStruct = JSON.stringify(requestObj.backEsbDataStruct);
// }else{
// param.backEsbDataStruct = "";
// }
this.$post("/api/definition/updateEsbRequest", param, response => {
if(response.data!=null){
if(response.data.request!=null){
item.request = JSON.stringify(response.data.request);
param.method = "TCP";
}
}
})
// this.$post("/api/definition/updateEsbRequest", param, response => {
// if(response.data!=null){
// if(response.data.request!=null){
// item.request = JSON.stringify(response.data.request);
// param.method = "TCP";
// }
// }
// })
}
});
this.$emit('save', apiCases, 'CASE', reference);

View File

@ -28,9 +28,12 @@
<div class="header-right" @click.stop>
<slot name="message"></slot>
<el-tooltip :content="$t('test_resource_pool.enable_disable')" placement="top" v-if="showBtn">
<el-switch v-model="data.enable" class="enable-switch" size="mini" :disabled="data.disabled && !data.root"/>
<el-switch v-model="data.enable" class="enable-switch" size="mini" :disabled="data.disabled && !data.root" style="width: 30px"/>
</el-tooltip>
<slot name="button"></slot>
<el-tooltip content="Copy" placement="top">
<el-button size="mini" icon="el-icon-copy-document" circle @click="copyRow" style="padding: 5px"/>
</el-tooltip>
<step-extend-btns style="display: contents" :data="data" @copy="copyRow" @remove="remove" @openScenario="openScenario" v-if="showBtn && (!data.disabled || data.root)"/>
</div>

View File

@ -22,11 +22,9 @@
<el-row>
<el-col :span="11">
<el-form-item :label="$t('commons.import_module')">
<el-select size="small" v-model="formData.moduleId" class="project-select" clearable>
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</el-select>
<ms-select-tree size="small" :data="moduleOptions" :defaultKey="formData.moduleId" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
</el-form-item>
<el-form-item v-if="!isHar" :label="$t('commons.import_mode')">
<el-form-item v-if="!isHar" :label="$t('commons.import_mode')">
<el-select size="small" v-model="formData.modeId" class="project-select" clearable>
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
@ -70,16 +68,17 @@
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import MsSelectTree from "../../../../common/select-tree/SelectTree";
export default {
name: "ScenarioImport",
components: {MsDialogFooter},
components: {MsDialogFooter, MsSelectTree},
props: {
saved: {
type: Boolean,
default: true,
},
moduleOptions: {}
moduleOptions: Array,
},
data() {
return {
@ -140,7 +139,11 @@
},
rules: {},
currentModule: {},
fileList: []
fileList: [],
moduleObj: {
id: 'id',
label: 'name',
},
}
},
activated() {
@ -257,7 +260,11 @@
this.fileList = [];
removeGoBackListener(this.close);
this.visible = false;
}
},
setModule(id, data) {
this.formData.moduleId = id;
this.formData.modulePath = data.path;
},
}
}
</script>

View File

@ -23,7 +23,7 @@
<template v-slot:button>
<el-tooltip :content="$t('api_test.run')" placement="top">
<el-button @click="run" icon="el-icon-video-play" class="ms-btn" size="mini" circle/>
<el-button @click="run" icon="el-icon-video-play" style="padding: 5px" class="ms-btn" size="mini" circle/>
</el-tooltip>
</template>
@ -289,6 +289,9 @@
},
active(item) {
this.request.active = !this.request.active;
if (this.node) {
this.node.expanded = this.request.active;
}
this.reload();
},
run() {

View File

@ -105,11 +105,11 @@
remove() {
this.$emit('remove', this.scenario, this.node);
},
active(item) {
if (item && item.active) {
item.active = !item.active;
active() {
if (this.node) {
this.node.expanded = !this.node.expanded;
}
this.reload();
}
},
copyRow() {
this.$emit('copyRow', this.scenario, this.node);

View File

@ -61,6 +61,9 @@
},
active() {
this.request.active = !this.request.active;
if (this.node) {
this.node.expanded = this.request.active;
}
},
}
}

View File

@ -27,7 +27,7 @@
</template>
<template v-slot:button>
<el-button @click="runDebug" :tip="$t('api_test.run')" icon="el-icon-video-play" style="background-color: #409EFF;color: white;" size="mini" circle/>
<el-button @click="runDebug" :tip="$t('api_test.run')" icon="el-icon-video-play" style="background-color: #409EFF;color: white;padding: 5px" size="mini" circle/>
</template>
<div v-if="controller.loopType==='LOOP_COUNT'" draggable>
<el-row>
@ -246,6 +246,9 @@
},
active(item) {
item.active = !item.active;
if (this.node) {
this.node.expanded = item.active;
}
this.reload();
},
changeRadio() {

View File

@ -5,7 +5,7 @@
<el-icon class="el-icon-more"></el-icon>
</el-link>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="copy">复制步骤</el-dropdown-item>
<!--<el-dropdown-item command="copy">复制步骤</el-dropdown-item>-->
<el-dropdown-item command="remove" v-tester>删除步骤</el-dropdown-item>
<el-dropdown-item command="scenarioVar" v-tester v-if="data.type==='scenario'">查看场景变量</el-dropdown-item>
<el-dropdown-item command="openScenario" v-tester v-if="data.type==='scenario' && data.referenced==='REF'">打开场景</el-dropdown-item>
@ -45,7 +45,7 @@
this.$emit('remove');
break;
case "scenarioVar":
this.$refs.scenarioParameters.open(this.data.variables, this.data.headers, true);
this.$refs.scenarioParameters.open(this.data.variables, this.data.headers, this.data.referenced === 'REF');
break;
case "openScenario":
this.getScenario();

View File

@ -50,39 +50,41 @@
</div>
</ms-aside-container>
<ms-main-container v-if="!loading">
<!-- 第一层当前节点内容-->
<ms-component-config
:isMax="false"
:showBtn="false"
:type="selectedTreeNode.type"
:scenario="selectedTreeNode"
:response="response"
:currentScenario="currentScenario"
:currentEnvironmentId="currentEnvironmentId"
:node="selectedNode"
:project-list="projectList"
:env-map="projectEnvMap"
:draggable="false"
@remove="remove" @copyRow="copyRow" @suggestClick="suggestClick" @refReload="refReload" @openScenario="openScenario"
v-if="selectedTreeNode && selectedNode"/>
<!-- 请求下还有的子步骤-->
<div v-if="selectedTreeNode && selectedTreeNode.hashTree && showNode(selectedTreeNode)">
<div v-for="item in selectedTreeNode.hashTree" :key="item.id" class="ms-col-one">
<ms-component-config
:showBtn="false"
:isMax="false"
:type="item.type"
:scenario="item"
:response="response"
:currentScenario="currentScenario"
:currentEnvironmentId="currentEnvironmentId"
:project-list="projectList"
:env-map="projectEnvMap"
:draggable="false"
@remove="remove" @copyRow="copyRow" @suggestClick="suggestClick"
@refReload="refReload" @openScenario="openScenario"
v-if="selectedTreeNode && selectedNode"/>
<ms-main-container v-loading="loading">
<div v-if="!loading">
<!-- 第一层当前节点内容-->
<ms-component-config
:isMax="false"
:showBtn="false"
:type="selectedTreeNode.type"
:scenario="selectedTreeNode"
:response="response"
:currentScenario="currentScenario"
:currentEnvironmentId="currentEnvironmentId"
:node="selectedNode"
:project-list="projectList"
:env-map="projectEnvMap"
:draggable="false"
@remove="remove" @copyRow="copyRow" @suggestClick="suggestClick" @refReload="refReload" @openScenario="openScenario"
v-if="selectedTreeNode && selectedNode"/>
<!-- 请求下还有的子步骤-->
<div v-if="selectedTreeNode && selectedTreeNode.hashTree && showNode(selectedTreeNode)">
<div v-for="item in selectedTreeNode.hashTree" :key="item.id" class="ms-col-one">
<ms-component-config
:showBtn="false"
:isMax="false"
:type="item.type"
:scenario="item"
:response="response"
:currentScenario="currentScenario"
:currentEnvironmentId="currentEnvironmentId"
:project-list="projectList"
:env-map="projectEnvMap"
:draggable="false"
@remove="remove" @copyRow="copyRow" @suggestClick="suggestClick"
@refReload="refReload" @openScenario="openScenario"
v-if="selectedTreeNode && selectedNode"/>
</div>
</div>
</div>
</ms-main-container>
@ -474,6 +476,7 @@
}
this.selectedTreeNode = data;
this.selectedNode = node;
this.reload();
},
suggestClick(node) {
this.response = {};
@ -493,7 +496,7 @@
recursiveSorting(arr, scenarioProjectId) {
for (let i in arr) {
arr[i].index = Number(i) + 1;
if (arr[i].type === ELEMENT_TYPE.LoopController && arr[i].hashTree && arr[i].hashTree.length > 1) {
if (arr[i].type === ELEMENT_TYPE.LoopController && arr[i].loopType === "LOOP_COUNT" && arr[i].hashTree && arr[i].hashTree.length > 1) {
arr[i].countController.proceed = true;
}
if (!arr[i].projectId) {
@ -521,6 +524,7 @@
if (!this.scenarioDefinition[i].projectId) {
this.scenarioDefinition[i].projectId = this.projectId;
}
if (this.scenarioDefinition[i].hashTree != undefined && this.scenarioDefinition[i].hashTree.length > 0) {
this.recursiveSorting(this.scenarioDefinition[i].hashTree, this.scenarioDefinition[i].projectId);
}

View File

@ -296,8 +296,12 @@
}
let api = {
status: "Underway", method: "GET", userId: getCurrentUser().id,
url: "", protocol: this.currentProtocol, environmentId: ""
url: "", protocol: this.currentProtocol, environmentId: "", moduleId: 'root', modulePath: "/" + this.$t("commons.module_title")
};
if (this.nodeTree && this.nodeTree.length > 0) {
api.moduleId = this.nodeTree[0].id;
api.modulePath = this.nodeTree[0].path;
}
this.handleTabsEdit(this.$t('api_test.definition.request.title'), e, api);
},
handleTabClose() {

View File

@ -185,9 +185,6 @@ export default {
}
this.response.body = body;
}
if (this.currentApi.moduleId && this.currentApi.moduleId === "root") {
this.currentApi.moduleId = "";
}
},
saveApi(data) {
this.setParameters(data);

View File

@ -68,8 +68,8 @@
let projectId = "";
// envMap
if (!this.envMap) {
projectId = getCurrentProjectID();
if (!this.envMap || this.envMap.size === 0) {
projectId = this.$store.state.projectId;
} else {
//
projectId = this.runData.projectId;

View File

@ -275,19 +275,13 @@
}
},
addModule(row) {
let url = '/api/module/getModuleByName/' + getCurrentProjectID() + "/" + this.api.protocol;
this.$get(url, response => {
if (response.data) {
this.$emit('refreshModule');
this.saveApi(row, response.data);
}
});
this.saveApi(row, "root");
},
saveApi(row, module) {
let data = this.api;
data.name = this.apiCase.name;
data.moduleId = module.id;
data.modulePath = '/bug';
data.moduleId = module;
data.modulePath ="/"+ this.$t('commons.module_title');
this.setParameters(data);
let bodyFiles = this.getBodyUploadFiles(data);
this.$fileUpload("/api/definition/create", null, bodyFiles, data, () => {

View File

@ -77,7 +77,7 @@
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(this.projectId);
this.$refs.environmentConfig.open(this.projectId, this.environmentId);
},
environmentChange(value) {
for (let i in this.environments) {

View File

@ -10,20 +10,7 @@
</el-col>
<el-col :span="12">
<el-form-item :label="$t('test_track.module.module')" prop="moduleId">
<el-select class="ms-http-input" size="small" v-model="basicForm.moduleId" style="width: 100%" @change="reload">
<div v-if="moduleOptions.length>0">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</div>
<div v-else>
<el-option :key="0" :value="''">
<div style="margin-left: 40px">
<span style="font-size: 14px;color: #606266;font-weight: 48.93">{{ $t('api_test.definition.select_comp.no_data') }},
</span>
<el-link type="primary" @click="createModules">{{ $t('api_test.definition.select_comp.add_data') }}</el-link>
</div>
</el-option>
</div>
</el-select>
<ms-select-tree size="small" :data="moduleOptions" :defaultKey="basicForm.moduleId" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
</el-form-item>
</el-col>
</el-row>
@ -73,70 +60,79 @@
</template>
<script>
import {API_STATUS} from "../../model/JsonData";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import {API_STATUS} from "../../model/JsonData";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import MsSelectTree from "../../../../common/select-tree/SelectTree";
export default {
name: "MsBasisApi",
components: {MsInputTag},
props: {
currentProtocol: {
type: String,
default: "HTTP"
},
moduleOptions: Array,
basisData: {},
},
created() {
this.getMaintainerOptions();
this.basicForm = this.basisData;
},
data() {
return {
basicForm: {},
httpVisible: false,
currentModule: {},
maintainerOptions: [],
loading: false,
rule: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
],
userId: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
moduleId: [{required: true, message: this.$t('test_track.case.input_module'), trigger: 'change'}],
status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
export default {
name: "MsBasisApi",
components: {MsInputTag, MsSelectTree},
props: {
currentProtocol: {
type: String,
default: "HTTP"
},
moduleOptions: Array,
basisData: {},
},
created() {
this.getMaintainerOptions();
this.basicForm = this.basisData;
},
data() {
return {
basicForm: {},
httpVisible: false,
currentModule: {},
maintainerOptions: [],
moduleObj: {
id: 'id',
label: 'name',
},
loading: false,
rule: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
],
userId: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
moduleId: [{required: true, message: this.$t('test_track.case.input_module'), trigger: 'change'}],
status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
},
value: API_STATUS[0].id,
options: API_STATUS,
}
},
methods: {
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data;
});
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
setModule(id,data) {
this.basicForm.moduleId = id;
this.basisData.modulePath = data.path;
},
validate() {
this.$refs['basicForm'].validate((valid) => {
if (valid) {
this.$emit('callback');
}
})
},
createModules() {
this.$emit("createRootModelInTree");
},
value: API_STATUS[0].id,
options: API_STATUS,
}
},
methods: {
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data;
});
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
validate() {
this.$refs['basicForm'].validate((valid) => {
if (valid) {
this.$emit('callback');
}
})
},
createModules() {
this.$emit("createRootModelInTree");
},
}
}
</script>
<style scoped>

View File

@ -49,20 +49,7 @@
</el-col>
<el-col :span="7">
<el-form-item :label="$t('test_track.module.module')" prop="moduleId">
<el-select class="ms-http-select" size="small" v-model="httpForm.moduleId">
<div v-if="moduleOptions.length>0">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</div>
<div v-else>
<el-option :key="0" :value="''">
<div style="margin-left: 40px">
<span style="font-size: 14px;color: #606266;font-weight: 48.93">{{ $t('api_test.definition.select_comp.no_data') }},
</span>
<el-link type="primary" @click="createModules">{{ $t('api_test.definition.select_comp.add_data') }}</el-link>
</div>
</el-option>
</div>
</el-select>
<ms-select-tree size="small" :data="moduleOptions" :defaultKey="httpForm.moduleId" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
</el-form-item>
</el-col>
@ -109,25 +96,26 @@
<script>
import MsApiRequestForm from "../request/http/ApiHttpRequestForm";
import MsResponseText from "../response/ResponseText";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import {API_STATUS, REQ_METHOD} from "../../model/JsonData";
import {KeyValue} from "../../model/ApiTestModel";
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import MsApiRequestForm from "../request/http/ApiHttpRequestForm";
import MsResponseText from "../response/ResponseText";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import {API_STATUS, REQ_METHOD} from "../../model/JsonData";
import {KeyValue} from "../../model/ApiTestModel";
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import MsSelectTree from "../../../../common/select-tree/SelectTree";
export default {
name: "MsAddCompleteHttpApi",
components: {MsJsr233Processor, MsResponseText, MsApiRequestForm, MsInputTag},
data() {
let validateURL = (rule, value, callback) => {
if (!this.httpForm.path.startsWith("/") || this.httpForm.path.match(/\s/) != null) {
callback(this.$t('api_test.definition.request.path_valid_info'));
}
callback();
};
return {
export default {
name: "MsAddCompleteHttpApi",
components: {MsJsr233Processor, MsResponseText, MsApiRequestForm, MsInputTag, MsSelectTree},
data() {
let validateURL = (rule, value, callback) => {
if (!this.httpForm.path.startsWith("/") || this.httpForm.path.match(/\s/) != null) {
callback(this.$t('api_test.definition.request.path_valid_info'));
}
callback();
};
return {
rule: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
@ -147,6 +135,11 @@ export default {
currentModule: {},
reqOptions: REQ_METHOD,
options: API_STATUS,
moduleObj: {
id: 'id',
label: 'name',
},
}
},
props: {moduleOptions: {}, request: {}, response: {}, basisData: {}, syncTabs: Array},
@ -190,7 +183,6 @@ export default {
});
},
setParameter() {
this.httpForm.modulePath = this.getPath(this.httpForm.moduleId);
this.request.path = this.httpForm.path;
this.request.method = this.httpForm.method;
this.httpForm.request.useEnvironment = undefined;
@ -211,15 +203,6 @@ export default {
createModules() {
this.$emit("createRootModelInTree");
},
getPath(id) {
if (id === null) {
return null;
}
let path = this.moduleOptions.filter(function (item) {
return item.id === id ? item.path : "";
});
return path[0].path;
},
urlChange() {
if (!this.httpForm.path || this.httpForm.path.indexOf('?') === -1) return;
let url = this.getURL(this.addProtocol(this.httpForm.path));
@ -248,6 +231,10 @@ export default {
this.$error(this.$t('api_test.request.url_invalid'), 2000);
}
},
setModule(id,data) {
this.httpForm.moduleId = id;
this.httpForm.modulePath = data.path;
},
},
created() {
@ -255,7 +242,6 @@ export default {
if (!this.basisData.environmentId) {
this.basisData.environmentId = "";
}
this.httpForm = JSON.parse(JSON.stringify(this.basisData));
}
}

View File

@ -5,7 +5,7 @@
<el-row>
<el-col :span="12">
<el-form-item :label="$t('commons.name')" prop="name">
<!-- <el-input class="ms-http-input" size="small" v-model="basicForm.name"/>-->
<!-- <el-input class="ms-http-input" size="small" v-model="basicForm.name"/>-->
<el-input v-model="basicForm.name" class="ms-http-input" size="small">
<el-select v-model="basicForm.method" slot="prepend" style="width: 100px" size="small" @change="methodChange">
<el-option v-for="item in methodTypes" :key="item" :label="item" :value="item"/>
@ -16,20 +16,22 @@
</el-col>
<el-col :span="12">
<el-form-item :label="$t('test_track.module.module')" prop="moduleId">
<el-select class="ms-http-input" size="small" v-model="basicForm.moduleId" style="width: 100%" @change="reload">
<div v-if="moduleOptions.length>0">
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</div>
<div v-else>
<el-option :key="0" :value="''">
<div style="margin-left: 40px">
<span style="font-size: 14px;color: #606266;font-weight: 48.93">{{ $t('api_test.definition.select_comp.no_data') }},
</span>
<el-link type="primary" @click="createModules">{{ $t('api_test.definition.select_comp.add_data') }}</el-link>
</div>
</el-option>
</div>
</el-select>
<!--<el-select class="ms-http-input" size="small" v-model="basicForm.moduleId" style="width: 100%" @change="reload">-->
<!--<div v-if="moduleOptions.length>0">-->
<!--<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>-->
<!--</div>-->
<!--<div v-else>-->
<!--<el-option :key="0" :value="''">-->
<!--<div style="margin-left: 40px">-->
<!--<span style="font-size: 14px;color: #606266;font-weight: 48.93">{{ $t('api_test.definition.select_comp.no_data') }},-->
<!--</span>-->
<!--<el-link type="primary" @click="createModules">{{ $t('api_test.definition.select_comp.add_data') }}</el-link>-->
<!--</div>-->
<!--</el-option>-->
<!--</div>-->
<!--</el-select>-->
<ms-select-tree size="small" :data="moduleOptions" :defaultKey="basicForm.moduleId" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
</el-form-item>
</el-col>
</el-row>
@ -79,77 +81,87 @@
</template>
<script>
import {API_STATUS} from "../../model/JsonData";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import {API_STATUS} from "../../model/JsonData";
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import MsSelectTree from "../../../../common/select-tree/SelectTree";
export default {
name: "MsTcpBasicApi",
components: {MsInputTag},
props: {
currentProtocol: {
type: String,
default: "HTTP"
},
moduleOptions: Array,
methodTypes: Array,
basisData: {},
},
created() {
this.getMaintainerOptions();
this.basicForm = this.basisData;
if(this.basicForm.protocol == null){
this.basicForm.protocol = "TCP";
}
},
data() {
return {
basicForm: {},
httpVisible: false,
currentModule: {},
maintainerOptions: [],
loading: false,
rule: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
],
userId: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
moduleId: [{required: true, message: this.$t('test_track.case.input_module'), trigger: 'change'}],
status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
export default {
name: "MsTcpBasicApi",
components: {MsInputTag, MsSelectTree},
props: {
currentProtocol: {
type: String,
default: "HTTP"
},
moduleOptions: Array,
methodTypes: Array,
basisData: {},
},
created() {
this.getMaintainerOptions();
this.basicForm = this.basisData;
if (this.basicForm.protocol == null) {
this.basicForm.protocol = "TCP";
}
},
data() {
return {
basicForm: {},
httpVisible: false,
currentModule: {},
maintainerOptions: [],
loading: false,
rule: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
],
userId: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
moduleId: [{required: true, message: this.$t('test_track.case.input_module'), trigger: 'change'}],
status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
},
value: API_STATUS[0].id,
options: API_STATUS,
moduleObj: {
id: 'id',
label: 'name',
},
}
},
methods: {
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data;
});
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
validate() {
this.$refs['basicForm'].validate((valid) => {
if (valid) {
this.$emit('callback');
}
})
},
createModules() {
this.$emit("createRootModelInTree");
},
methodChange() {
this.$emit("changeApiProtocol", this.basicForm.method);
},
setModule(id,data) {
this.basisData.modulePath = data.path;
this.basisData.moduleId = id;
},
value: API_STATUS[0].id,
options: API_STATUS,
}
},
methods: {
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
this.maintainerOptions = response.data;
});
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
validate() {
this.$refs['basicForm'].validate((valid) => {
if (valid) {
this.$emit('callback');
}
})
},
createModules() {
this.$emit("createRootModelInTree");
},
methodChange() {
this.$emit("changeApiProtocol",this.basicForm.method);
},
}
}
</script>
<style scoped>

View File

@ -178,6 +178,10 @@
<div v-else-if="apiInfo.requestBodyParamType == 'JSON-SCHEMA'" style="margin-left: 10px">
<ms-json-code-edit :body="apiInfo.jsonSchemaBody" ref="jsonCodeEdit"/>
</div>
<!-- <div v-else-if="apiInfo.requestBodyParamType == 'XML'" style="margin-left: 10px">-->
<!-- <ms-json-code-edit :body="apiInfo.jsonSchemaBody" ref="jsonCodeEdit"/>-->
<!-- <editor v-model="formatData" :lang="mode" @init="editorInit" :theme="theme" :height="height"/>-->
<!-- </div>-->
<div v-else class="showDataDiv">
<br/>
<p style="margin: 0px 20px;"
@ -423,7 +427,12 @@ export default {
formatRowData(dataType, data) {
var returnData = data;
if (data) {
returnData = data.replace(/\n/g, '<br>');
if(dataType === 'XML'){
returnData = "<xmp>"+returnData+"</xmp>";
}else{
returnData = data.replace(/\n/g, '<br>');
}
}
return returnData;
},
@ -643,7 +652,7 @@ export default {
if(lastIndex < this.currentApiIndexInApiShowArray){
//
if(this.needAsyncSelect){
// if(this.needAsyncSelect){
//apiShowArray 2
// apiStepIndex-1- 2 < apiInfoArray
let dataIndex = this.apiStepIndex -3;
@ -657,11 +666,11 @@ export default {
if(this.apiShowArray.length > (this.currentApiIndexInApiShowArray+3)){
this.apiShowArray.pop();
}
}
// }
this.apiStepIndex --;
}else if(lastIndex > this.currentApiIndexInApiShowArray){
//
if(this.needAsyncSelect){
// if(this.needAsyncSelect){
//apiShowArray 2
// apiStepIndex+1+ 2 < apiInfoArray
let dataIndex = this.apiStepIndex +3;
@ -678,7 +687,7 @@ export default {
let itemHeight = this.$refs.apiDocInfoDivItem[0].offsetHeight+10;
this.$refs.apiDocInfoDiv.scrollTop = (apiDocDivScrollTop-itemHeight);
}
}
// }
this.apiStepIndex ++;
}
}

View File

@ -46,13 +46,15 @@
icon: 'el-icon-delete',
func: this.deleteEnvironment
}
]
],
selectEnvironmentId: ''
}
},
methods: {
open: function (projectId) {
open: function (projectId, envId) {
this.visible = true;
this.projectId = projectId;
this.selectEnvironmentId = envId;
this.getEnvironments();
listenGoBack(this.close);
},
@ -114,7 +116,16 @@
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
this.environments = response.data;
if (this.environments.length > 0) {
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
if (this.selectEnvironmentId) {
const index = this.environments.findIndex(e => e.id === this.selectEnvironmentId);
if (index !== -1) {
this.$refs.environmentItems.itemSelected(index, this.environments[index]);
} else {
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
}
} else {
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
}
} else {
let item = new Environment({
projectId: this.projectId

View File

@ -25,12 +25,10 @@
<el-row>
<el-col :span="11">
<el-form-item :label="$t('commons.import_module')" prop="moduleId">
<el-select size="small" v-model="formData.moduleId" class="project-select" clearable>
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</el-select>
<ms-select-tree size="small" :data="moduleOptions" :defaultKey="formData.moduleId" @getValue="setModule" :obj="moduleObj" clearable checkStrictly/>
</el-form-item>
<el-form-item v-if="!isScenarioModel&&showImportModel" :label="$t('commons.import_mode')" prop="modeId">
<el-select size="small" v-model="formData.modeId" class="project-select" clearable>
<el-select size="small" v-model="formData.modeId" clearable style="width: 100%">
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
@ -59,7 +57,7 @@
<el-switch
v-model="swaggerSynchronization"
@click.native="scheduleEdit"
>
>
</el-switch>
<span style="color: #6C317C;cursor: pointer;font-weight: bold;margin-left: 10px" @click="scheduleEditByText">{{$t('api_test.api_import.timing_synchronization')}}</span>
</el-form-item>
@ -98,337 +96,338 @@
</template>
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import ScheduleImport from "@/business/components/api/definition/components/import/ImportScheduleEdit";
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import ScheduleImport from "@/business/components/api/definition/components/import/ImportScheduleEdit";
import MsSelectTree from "../../../../common/select-tree/SelectTree";
export default {
name: "ApiImport",
components: {ScheduleImport, MsDialogFooter},
props: {
saved: {
type: Boolean,
default: true,
},
moduleOptions: {},
propotal:String,
model: {
type: String,
default: 'definition'
}
},
data() {
return {
visible: false,
swaggerUrlEnable: false,
swaggerSynchronization: false,
showEnvironmentSelect: true,
modeOptions: [{
id: 'fullCoverage',
name: this.$t('commons.cover')
export default {
name: "ApiImport",
components: {ScheduleImport, MsDialogFooter, MsSelectTree},
props: {
saved: {
type: Boolean,
default: true,
},
{
id: 'incrementalMerge',
name: this.$t('commons.not_cover')
}],
protocol: "",
platforms: [
{
name: 'MeterSphere',
value: 'Metersphere',
tip: this.$t('api_test.api_import.ms_tip'),
exportTip: this.$t('api_test.api_import.ms_export_tip'),
moduleOptions: Array,
propotal: String,
model: {
type: String,
default: 'definition'
}
},
data() {
return {
visible: false,
swaggerUrlEnable: false,
swaggerSynchronization: false,
showEnvironmentSelect: true,
moduleObj: {
id: 'id',
label: 'name',
},
modeOptions: [{
id: 'fullCoverage',
name: this.$t('commons.cover')
},
{
id: 'incrementalMerge',
name: this.$t('commons.not_cover')
}],
protocol: "",
platforms: [
{
name: 'MeterSphere',
value: 'Metersphere',
tip: this.$t('api_test.api_import.ms_tip'),
exportTip: this.$t('api_test.api_import.ms_export_tip'),
suffixes: new Set(['json'])
},
],
postmanPlanform: {
name: 'Postman',
value: 'Postman',
tip: this.$t('api_test.api_import.postman_tip'),
exportTip: this.$t('api_test.api_import.post_export_tip'),
suffixes: new Set(['json'])
},
],
postmanPlanform:{
name: 'Postman',
value: 'Postman',
tip: this.$t('api_test.api_import.postman_tip'),
exportTip: this.$t('api_test.api_import.post_export_tip'),
suffixes: new Set(['json'])
},
swaggerPlanform:{
name: 'Swagger',
value: 'Swagger2',
tip: this.$t('api_test.api_import.swagger_tip'),
exportTip: this.$t('api_test.api_import.swagger_export_tip'),
suffixes: new Set(['json'])
},
harPlanform:{
name: 'HAR',
value: 'Har',
tip: this.$t('api_test.api_import.har_tip'),
exportTip: this.$t('api_test.api_import.har_export_tip'),
suffixes: new Set(['har'])
},
esbPlanform : {
name: 'ESB',
value: 'ESB',
tip: this.$t('api_test.api_import.esb_tip'),
exportTip: this.$t('api_test.api_import.esb_export_tip'),
suffixes: new Set(['xlsx','xls'])
},
selectedPlatform: {},
selectedPlatformValue: 'Metersphere',
result: {},
projects: [],
environments: [],
useEnvironment: false,
formData: {
file: undefined,
swaggerUrl: '',
modeId: this.$t('commons.not_cover'),
moduleId: '',
},
rules: {
modeId: [
{required: true, message: this.$t('commons.please_select_import_mode'), trigger: 'change'},
],
moduleId: [
{required: true, message: this.$t('commons.please_select_import_module'), trigger: 'change'},
],
},
currentModule: {},
fileList: []
}
},
activated() {
this.selectedPlatform = this.platforms[0];
},
created() {
this.platforms.push(this.postmanPlanform);
this.platforms.push(this.swaggerPlanform);
this.platforms.push(this.harPlanform);
},
watch: {
selectedPlatformValue() {
for (let i in this.platforms) {
if (this.platforms[i].value === this.selectedPlatformValue) {
this.selectedPlatform = this.platforms[i];
break;
}
swaggerPlanform: {
name: 'Swagger',
value: 'Swagger2',
tip: this.$t('api_test.api_import.swagger_tip'),
exportTip: this.$t('api_test.api_import.swagger_export_tip'),
suffixes: new Set(['json'])
},
harPlanform: {
name: 'HAR',
value: 'Har',
tip: this.$t('api_test.api_import.har_tip'),
exportTip: this.$t('api_test.api_import.har_export_tip'),
suffixes: new Set(['har'])
},
esbPlanform: {
name: 'ESB',
value: 'ESB',
tip: this.$t('api_test.api_import.esb_tip'),
exportTip: this.$t('api_test.api_import.esb_export_tip'),
suffixes: new Set(['xlsx', 'xls'])
},
selectedPlatform: {},
selectedPlatformValue: 'Metersphere',
result: {},
projects: [],
environments: [],
useEnvironment: false,
formData: {
file: undefined,
swaggerUrl: '',
modeId: this.$t('commons.not_cover'),
moduleId: '',
},
rules: {
modeId: [
{required: true, message: this.$t('commons.please_select_import_mode'), trigger: 'change'},
],
},
currentModule: {},
fileList: []
}
},
propotal(){
let postmanIndex = this.platforms.indexOf(this.postmanPlanform);
let swaggerPlanformIndex = this.platforms.indexOf(this.swaggerPlanform);
let harPlanformIndex = this.platforms.indexOf(this.harPlanform);
let esbPlanformIndex = this.platforms.indexOf(this.esbPlanform);
if(postmanIndex>=0){
this.platforms.splice(this.platforms.indexOf(this.postmanPlanform),1);
}
if(swaggerPlanformIndex>=0){
this.platforms.splice(this.platforms.indexOf(this.swaggerPlanform),1);
}
if(harPlanformIndex>=0){
this.platforms.splice(this.platforms.indexOf(this.harPlanform),1);
}
if(esbPlanformIndex>=0){
this.platforms.splice(this.platforms.indexOf(this.esbPlanform),1);
}
if(this.propotal === 'TCP'){
this.platforms.push(this.esbPlanform);
return true;
}else if(this.propotal === 'HTTP'){
this.platforms.push(this.postmanPlanform);
this.platforms.push(this.swaggerPlanform);
this.platforms.push(this.harPlanform);
return false;
}
}
},
computed: {
isSwagger2() {
return this.selectedPlatformValue === 'Swagger2';
activated() {
this.selectedPlatform = this.platforms[0];
},
showImportModel() {
return this.selectedPlatformValue != 'Har' && this.selectedPlatformValue != 'ESB';
created() {
this.platforms.push(this.postmanPlanform);
this.platforms.push(this.swaggerPlanform);
this.platforms.push(this.harPlanform);
},
showTemplate() {
return this.selectedPlatformValue === 'ESB';
},
isScenarioModel() {
return this.model === 'scenario';
},
projectId() {
return this.$store.state.projectId
},
},
methods: {
scheduleEdit() {
if (!this.formData.swaggerUrl) {
this.$warning(this.$t('commons.please_fill_path'));
this.swaggerSynchronization = !this.swaggerSynchronization
} else {
if (this.swaggerSynchronization) {
this.$refs.scheduleEdit.open(this.buildParam());
}
}
},
scheduleEditByText(){
this.$refs.scheduleEdit.open(this.buildParam());
},
open(module) {
this.currentModule = module;
this.visible = true;
listenGoBack(this.close);
},
upload(file) {
this.formData.file = file.file;
},
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
handleRemove(file, fileList) {
this.formData.file = undefined;
},
downloadTemplate(){
if(this.selectedPlatformValue == "ESB"){
this.$fileDownload('/api/definition/export/esbExcelTemplate');
}
},
uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (this.selectedPlatform.suffixes && !this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
return false;
}
return true;
},
save() {
this.$refs.form.validate(valid => {
if (valid) {
if ((this.selectedPlatformValue != 'Swagger2' || (this.selectedPlatformValue == 'Swagger2' && !this.swaggerUrlEnable)) && !this.formData.file) {
this.$warning(this.$t('commons.please_upload'));
return;
watch: {
selectedPlatformValue() {
for (let i in this.platforms) {
if (this.platforms[i].value === this.selectedPlatformValue) {
this.selectedPlatform = this.platforms[i];
break;
}
let url = '/api/definition/import';
if (this.isScenarioModel) {
url = '/api/automation/import';
}
let param = this.buildParam();
this.result = this.$fileUpload(url, param.file, null, this.buildParam(), response => {
let res = response.data;
this.$success(this.$t('test_track.case.import.success'));
this.visible = false;
this.$emit('refresh', res);
});
} else {
}
},
propotal() {
let postmanIndex = this.platforms.indexOf(this.postmanPlanform);
let swaggerPlanformIndex = this.platforms.indexOf(this.swaggerPlanform);
let harPlanformIndex = this.platforms.indexOf(this.harPlanform);
let esbPlanformIndex = this.platforms.indexOf(this.esbPlanform);
if (postmanIndex >= 0) {
this.platforms.splice(this.platforms.indexOf(this.postmanPlanform), 1);
}
if (swaggerPlanformIndex >= 0) {
this.platforms.splice(this.platforms.indexOf(this.swaggerPlanform), 1);
}
if (harPlanformIndex >= 0) {
this.platforms.splice(this.platforms.indexOf(this.harPlanform), 1);
}
if (esbPlanformIndex >= 0) {
this.platforms.splice(this.platforms.indexOf(this.esbPlanform), 1);
}
if (this.propotal === 'TCP') {
this.platforms.push(this.esbPlanform);
return true;
} else if (this.propotal === 'HTTP') {
this.platforms.push(this.postmanPlanform);
this.platforms.push(this.swaggerPlanform);
this.platforms.push(this.harPlanform);
return false;
}
});
}
},
buildParam() {
let param = {};
Object.assign(param, this.formData);
param.platform = this.selectedPlatformValue;
param.saved = this.saved;
param.model = this.model;
if (this.currentModule) {
param.moduleId = this.formData.moduleId
this.moduleOptions.filter(item => {
if (item.id === this.formData.moduleId) {
param.modulePath = item.path
computed: {
isSwagger2() {
return this.selectedPlatformValue === 'Swagger2';
},
showImportModel() {
return this.selectedPlatformValue != 'Har' && this.selectedPlatformValue != 'ESB';
},
showTemplate() {
return this.selectedPlatformValue === 'ESB';
},
isScenarioModel() {
return this.model === 'scenario';
},
projectId() {
return this.$store.state.projectId
},
},
methods: {
scheduleEdit() {
if (!this.formData.swaggerUrl) {
this.$warning(this.$t('commons.please_fill_path'));
this.swaggerSynchronization = !this.swaggerSynchronization
} else {
if (this.swaggerSynchronization) {
this.$refs.scheduleEdit.open(this.buildParam());
}
})
param.modeId = this.formData.modeId
}
},
scheduleEditByText() {
this.$refs.scheduleEdit.open(this.buildParam());
},
open(module) {
this.currentModule = module;
this.visible = true;
listenGoBack(this.close);
},
upload(file) {
this.formData.file = file.file;
},
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
handleRemove(file, fileList) {
this.formData.file = undefined;
},
downloadTemplate() {
if (this.selectedPlatformValue == "ESB") {
this.$fileDownload('/api/definition/export/esbExcelTemplate');
}
},
uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (this.selectedPlatform.suffixes && !this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
return false;
}
return true;
},
save() {
this.$refs.form.validate(valid => {
if (valid) {
if ((this.selectedPlatformValue != 'Swagger2' || (this.selectedPlatformValue == 'Swagger2' && !this.swaggerUrlEnable)) && !this.formData.file) {
this.$warning(this.$t('commons.please_upload'));
return;
}
let url = '/api/definition/import';
if (this.isScenarioModel) {
url = '/api/automation/import';
}
let param = this.buildParam();
this.result = this.$fileUpload(url, param.file, null, this.buildParam(), response => {
let res = response.data;
this.$success(this.$t('test_track.case.import.success'));
this.visible = false;
this.$emit('refresh', res);
});
} else {
return false;
}
});
},
setModule(id, data) {
this.formData.moduleId = id;
this.formData.modulePath = data.path;
},
buildParam() {
let param = {};
Object.assign(param, this.formData);
param.platform = this.selectedPlatformValue;
param.saved = this.saved;
param.model = this.model;
if (this.currentModule) {
param.moduleId = this.formData.moduleId
param.modeId = this.formData.modeId
}
param.projectId = this.projectId;
if (!this.swaggerUrlEnable) {
param.swaggerUrl = undefined;
}
return param;
},
close() {
this.formData = {
file: undefined,
swaggerUrl: ''
};
this.fileList = [];
removeGoBackListener(this.close);
this.visible = false;
}
param.projectId = this.projectId;
if (!this.swaggerUrlEnable) {
param.swaggerUrl = undefined;
}
return param;
},
close() {
this.formData = {
file: undefined,
swaggerUrl: ''
};
this.fileList = [];
removeGoBackListener(this.close);
this.visible = false;
}
}
}
</script>
<style scoped>
.api-import >>> .el-dialog {
min-width: 750px;
}
.api-import >>> .el-dialog {
min-width: 750px;
}
.format-tip {
background: #EDEDED;
}
.format-tip {
background: #EDEDED;
}
.api-upload {
text-align: center;
margin: auto 0;
}
.api-upload {
text-align: center;
margin: auto 0;
}
.api-upload >>> .el-upload {
width: 100%;
max-width: 350px;
}
.api-upload >>> .el-upload {
width: 100%;
max-width: 350px;
}
.api-upload >>> .el-upload-dragger {
width: 100%;
}
.api-upload >>> .el-upload-dragger {
width: 100%;
}
.el-radio-group {
margin: 10px 0;
}
.el-radio-group {
margin: 10px 0;
}
.el-radio {
margin-right: 20px;
}
.el-radio {
margin-right: 20px;
}
.header-bar, .format-tip, .el-form {
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
.header-bar, .format-tip, .el-form {
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
.header-bar {
padding: 10px 30px;
}
.header-bar {
padding: 10px 30px;
}
.api-import >>> .el-dialog__body {
padding: 15px 25px;
}
.api-import >>> .el-dialog__body {
padding: 15px 25px;
}
.operate-button {
float: right;
}
.operate-button {
float: right;
}
.save-button {
margin-left: 10px;
}
.save-button {
margin-left: 10px;
}
.el-form {
padding: 30px 10px;
}
.el-form {
padding: 30px 10px;
}
.dialog-footer {
float: right;
}
.dialog-footer {
float: right;
}
.swagger-url-disable {
margin-top: 10px;
.swagger-url-disable {
margin-top: 10px;
margin-left: 80px;
}
margin-left: 80px;
}
.el-divider {
height: 200px;
}
.el-divider {
height: 200px;
}
</style>

View File

@ -235,7 +235,7 @@
import {
_handleSelect,
_handleSelectAll, buildBatchParam, getLabel,
getSelectDataCounts, getSystemLabel, initCondition,
getSelectDataCounts, initCondition,
setUnSelectIds, toggleAllSelection
} from "@/common/js/tableUtils";
import {_filter, _sort} from "@/common/js/tableUtils";
@ -380,16 +380,16 @@
} else {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
}
this.getSystemLabel(this.type)
this.initTable();
this.getMaintainerOptions();
getLabel(this, API_LIST);
},
watch: {
selectNodeIds() {
initCondition(this.condition,false);
this.initTable();
},
currentProtocol() {
initCondition(this.condition,false);
this.initTable();
},
trashEnable() {
@ -399,21 +399,12 @@
} else {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
}
initCondition(this.condition,false);
this.initTable();
}
},
methods: {
getSystemLabel(type) {
let param = {}
param.type = type
this.$post('/system/system/header', param, response => {
if (response.data != null) {
this.tableLabel = eval(response.data.props);
}
})
},
customHeader() {
getLabel(this, API_LIST);
this.$refs.headerCustom.open(this.tableLabel)
},
handleBatchMove() {
@ -421,7 +412,7 @@
},
initTable() {
this.selectRows = new Set();
initCondition(this.condition);
initCondition(this.condition,this.condition.selectAll);
this.selectDataCounts = 0;
this.condition.moduleIds = this.selectNodeIds;
this.condition.projectId = this.projectId;
@ -464,13 +455,42 @@
}
})
if (this.$refs.apiDefinitionTable) {
this.$refs.apiDefinitionTable.doLayout()
setTimeout(() => {
this.$refs.apiDefinitionTable.doLayout();
this.result.loading = false;
}, 500)
}
// nexttick:
this.$nextTick(function(){
this.checkTableRowIsSelect();
})
});
}
getLabel(this, API_LIST);
},
checkTableRowIsSelect(){
//
if(this.condition.selectAll){
let unSelectIds = this.condition.unSelectIds;
this.tableData.forEach(row=>{
if(unSelectIds.indexOf(row.id)<0){
this.$refs.apiDefinitionTable.toggleRowSelection(row,true);
//selectRows
if (!this.selectRows.has(row)) {
this.$set(row, "showMore", true);
this.selectRows.add(row);
}
}else{
//selectRow
if (this.selectRows.has(row)) {
this.$set(row, "showMore", false);
this.selectRows.delete(row);
}
}
})
}
},
genProtocalFilter(protocalType) {
if (protocalType === "HTTP") {
this.methodFilters = [

View File

@ -20,6 +20,7 @@
:condition="condition"
:current-module="currentModule"
:is-read-only="isReadOnly"
:moduleOptions="extendTreeNodes"
@exportAPI="exportAPI"
@saveAsEdit="saveAsEdit"
@refreshTable="$emit('refreshTable')"
@ -34,25 +35,25 @@
</template>
<script>
import MsAddBasisApi from "../basis/AddBasisApi";
import SelectMenu from "../../../../track/common/SelectMenu";
import {OPTIONS} from "../../model/JsonData";
import ApiImport from "../import/ApiImport";
import MsNodeTree from "../../../../track/common/NodeTree";
import ApiModuleHeader from "./ApiModuleHeader";
import {buildNodePath} from "../../model/NodeTree";
import MsAddBasisApi from "../basis/AddBasisApi";
import SelectMenu from "../../../../track/common/SelectMenu";
import {OPTIONS} from "../../model/JsonData";
import ApiImport from "../import/ApiImport";
import MsNodeTree from "../../../../track/common/NodeTree";
import ApiModuleHeader from "./ApiModuleHeader";
import {buildNodePath, buildTree} from "../../model/NodeTree";
export default {
name: 'MsApiModule',
components: {
ApiModuleHeader,
MsNodeTree,
MsAddBasisApi,
SelectMenu,
ApiImport
},
data() {
return {
export default {
name: 'MsApiModule',
components: {
ApiModuleHeader,
MsNodeTree,
MsAddBasisApi,
SelectMenu,
ApiImport
},
data() {
return {
result: {},
condition: {
protocol: OPTIONS[0].value,
@ -61,6 +62,7 @@ export default {
},
data: [],
currentModule: {},
extendTreeNodes: [],
}
},
props: {
@ -129,11 +131,17 @@ export default {
this.result = this.$get(url, response => {
if (response.data != undefined && response.data != null) {
this.data = response.data;
let moduleOptions = [];
this.data.forEach(node => {
buildNodePath(node, {path: ''}, moduleOptions);
this.extendTreeNodes = [];
this.extendTreeNodes.unshift({
"id": "root",
"name": this.$t('commons.module_title'),
"level": 0,
"children": this.data,
});
this.$emit('setModuleOptions', moduleOptions);
this.extendTreeNodes.forEach(node => {
buildTree(node, {path: ''});
});
this.$emit('setModuleOptions', this.extendTreeNodes);
this.$emit('setNodeTree', this.data);
if (this.$refs.nodeTree) {
this.$refs.nodeTree.filter(this.condition.filterText);

View File

@ -1,46 +1,23 @@
<template>
<div>
<el-select class="protocol-select" size="small" v-model="condition.protocol">
<el-option
v-for="item in options"
:key="item.value"
:name="item.name"
:value="item.value"
:disabled="item.disabled">
</el-option>
</el-select>
<el-input class="filter-input" :class="{'read-only': isReadOnly}" :placeholder="$t('test_track.module.search')" v-model="condition.filterText"
size="small">
<template v-slot:append>
<el-dropdown v-if="!isReadOnly" size="small" split-button type="primary" class="ms-api-button" @click="handleCommand('add-api')"
v-tester
@command="handleCommand"
:hide-on-click='false'
trigger="click">
<el-button icon="el-icon-folder-add" @click="addApi"></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="add-api">{{ $t('api_test.definition.request.title') }}</el-dropdown-item>
<el-dropdown-item command="debug">{{ $t('api_test.definition.request.fast_debug') }}</el-dropdown-item>
<el-dropdown-item command="import">{{ $t('api_test.api_import.label') }}</el-dropdown-item>
<el-dropdown-item command="export">
<el-dropdown placement="right-start" @command="chooseExportType">
<span>
{{ $t('report.export') }} <i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<template>
<el-dropdown-item command="export-MS">{{ $t('report.export_to_ms_format') }}</el-dropdown-item>
<el-dropdown-item command="export-Swagger" v-show="condition.protocol=='HTTP'">
{{ $t('report.export_to_swagger3_format') }}
</el-dropdown-item>
</template>
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-input>
<el-row>
<el-col class="protocol-col" :span="9">
<el-select class="protocol-select" size="small" v-model="condition.protocol">
<el-option
v-for="item in options"
:key="item.value"
:name="item.name"
:value="item.value"
:disabled="item.disabled">
</el-option>
</el-select>
</el-col>
<el-col :span="15">
<ms-search-bar
:condition="condition"
:commands="operators"/>
</el-col>
</el-row>
<module-trash-button v-if="!isReadOnly" :condition="condition" :exe="enableTrash"/>
@ -54,127 +31,128 @@
</template>
<script>
import {OPTIONS} from "../../model/JsonData";
import MsAddBasisApi from "../basis/AddBasisApi";
import ApiImport from "../import/ApiImport";
import ModuleTrashButton from "./ModuleTrashButton";
import {buildNodePath} from "@/business/components/api/definition/model/NodeTree";
import TemplateComponent from "../../../../track/plan/view/comonents/report/TemplateComponent/TemplateComponent";
export default {
name: "ApiModuleHeader",
components: {TemplateComponent, ModuleTrashButton, ApiImport, MsAddBasisApi},
data() {
return {
options: OPTIONS,
moduleOptions: {}
}
},
props: {
condition: {
type: Object,
default() {
return {}
}
},
currentModule: {
type: Object,
default() {
return {}
}
},
isReadOnly: {
type: Boolean,
default() {
return false
}
},
},
computed: {
projectId() {
return this.$store.state.projectId
},
},
methods: {
handleCommand(e) {
switch (e) {
case "debug":
this.$emit('debug');
break;
case "add-api":
this.addApi();
break;
case "add-module":
break;
case "import":
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));
return;
import {OPTIONS} from "../../model/JsonData";
import MsAddBasisApi from "../basis/AddBasisApi";
import ApiImport from "../import/ApiImport";
import ModuleTrashButton from "./ModuleTrashButton";
import {buildNodePath} from "@/business/components/api/definition/model/NodeTree";
import TemplateComponent from "../../../../track/plan/view/comonents/report/TemplateComponent/TemplateComponent";
import MsSearchBar from "@/business/components/common/components/search/MsSearchBar";
export default {
name: "ApiModuleHeader",
components: {MsSearchBar, TemplateComponent, ModuleTrashButton, ApiImport, MsAddBasisApi},
data() {
return {
options: OPTIONS,
operators: [
{
label: this.$t('api_test.definition.request.title'),
callback: this.addApi
},
{
label: this.$t('api_test.definition.request.fast_debug'),
callback: () => {this.$emit('debug')}
},
{
label: this.$t('api_test.api_import.label'),
callback: this.handleImport
},
{
label: this.$t('report.export'),
children: [
{
label: this.$t('report.export_to_ms_format') ,
callback: () => {
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));
return;
}
this.$emit('exportAPI', 'MS');
}
},
{
label: this.$t('report.export_to_swagger3_format'),
callback: () => {
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));
return;
}
this.$emit('exportAPI', 'Swagger');
}
}
]
}
this.protocol = "HTTP";
this.result = this.$get("/api/module/list/" + this.projectId + "/" + this.condition.protocol, response => {
if (response.data != undefined && response.data != null) {
this.data = response.data;
let moduleOptions = [];
this.data.forEach(node => {
buildNodePath(node, {path: ''}, moduleOptions);
});
this.moduleOptions = moduleOptions
}
this.$refs.apiImport.open(this.moduleOptions);
});
break;
]
}
},
chooseExportType(e) {
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));
return;
props: {
condition: {
type: Object,
default() {
return {}
}
},
moduleOptions: Array,
currentModule: {
type: Object,
default() {
return {}
}
},
isReadOnly: {
type: Boolean,
default() {
return false
}
},
},
computed: {
projectId() {
return this.$store.state.projectId
},
},
methods: {
handleImport() {
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));
return;
}
this.protocol = "HTTP";
this.$refs.apiImport.open(this.moduleOptions);
},
addApi() {
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));
return;
}
this.$refs.basisApi.open(this.currentModule);
},
saveAsEdit(data) {
this.$emit('saveAsEdit', data);
},
refresh() {
this.$emit('refresh');
},
enableTrash() {
this.condition.trashEnable = true;
}
switch (e) {
case "export-MS":
this.$emit('exportAPI', 'MS');
break;
case "export-Swagger":
this.$emit('exportAPI', 'Swagger');
break;
}
},
addApi() {
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));
return;
}
this.$refs.basisApi.open(this.currentModule);
},
saveAsEdit(data) {
this.$emit('saveAsEdit', data);
},
refresh() {
this.$emit('refresh');
},
enableTrash() {
this.condition.trashEnable = true;
}
}
}
</script>
<style scoped>
.protocol-select {
width: 92px;
height: 30px;
}
.read-only {
width: 150px !important;
}
.filter-input {
width: 174px;
padding-left: 3px;
}
.protocol-select {
width: 92px;
height: 30px;
}
.protocol-col {
min-width: 93px;
}
.read-only {
width: 150px !important;
}
.filter-input {
width: 174px;
padding-left: 3px;
}
</style>

View File

@ -9,4 +9,16 @@ export function buildNodePath(node, option, moduleOptions) {
}
}
}
// 构建树
export function buildTree(node, option) {
option.id = node.id;
option.name = node.name;
option.path = option.path + '/' + node.name;
node.path = option.path;
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
buildTree(node.children[i], {path: option.path});
}
}
}

View File

@ -46,13 +46,15 @@
icon: 'el-icon-delete',
func: this.deleteEnvironment
}
]
],
selectEnvironmentId: ''
}
},
methods: {
open: function (projectId) {
open: function (projectId, envId) {
this.visible = true;
this.projectId = projectId;
this.selectEnvironmentId = envId;
this.getEnvironments();
listenGoBack(this.close);
},
@ -114,7 +116,16 @@
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
this.environments = response.data;
if (this.environments.length > 0) {
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
if (this.selectEnvironmentId) {
const index = this.environments.findIndex(e => e.id === this.selectEnvironmentId);
if (index !== -1) {
this.$refs.environmentItems.itemSelected(index, this.environments[index]);
} else {
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
}
} else {
this.$refs.environmentItems.itemSelected(0, this.environments[0]);
}
} else {
let item = new Environment({
projectId: this.projectId

View File

@ -155,7 +155,7 @@ export default {
width: 100%;
background: white;
height: 100vh;
z-index: 999999;
z-index: 2;
}
.full-screen >>> .minder-container {

View File

@ -0,0 +1,78 @@
<template>
<el-input class="ms-search-bar" :placeholder="$t('test_track.module.search')" v-model="condition.filterText" size="small">
<template v-slot:append>
<el-dropdown>
<el-button type="primary">
<span class="tip-font">{{ $t('commons.more_operator') }}</span>
<i class="el-icon-arrow-down el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item, index) in commands" :key="index" @click.native.stop="click(item)">
<span class="tip-font" v-if="!item.children">
{{ item.label }}
</span>
<el-dropdown placement="right-start" v-else>
<span class="tip-font">
{{ item.label }}
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<template>
<el-dropdown-item v-for="(child, index) in item.children" :key="index" @click.native.stop="click(child)">
<span class="tip-font">
{{child.label}}
</span>
</el-dropdown-item>
</template>
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-input>
</template>
<script>
export default {
name: "MsSearchBar",
props: {
condition: {
type: Object,
default() {
return {}
}
},
commands: {
type: Array,
default() {
return [
{
label: this.$t('api_test.api_import.label'),
callback: () => {}
}
]
}
}
},
methods: {
click(item) {
if (item.callback) {
item.callback();
}
}
}
}
</script>
<style scoped>
.tip-font {
font-size: 13px;
}
.el-dropdown .el-button {
padding: 8px;
}
</style>

View File

@ -52,6 +52,10 @@ export default {
this.defaultCheckedKeys.push(i.id)
}
)
if(this.type==='api_list'||this.type==='api_case_list'||this.type==='api_scenario_list'||this.type==='test_plan_function_test_case'
||this.type==='test_plan_api_case'||this.type==='test_plan_load_case'||this.type==='test_plan_scenario_case'){
this.fieldSelected=items
}
},
saveHeader() {
let param = {

View File

@ -81,7 +81,6 @@ export const Test_Case_Review_Case_List = [
{id: 'name', label: i18n.t('commons.name')},
{id: 'priority', label: i18n.t('test_track.case.priority')},
{id: 'type', label: i18n.t('test_track.case.type')},
{id: 'method', label: i18n.t('test_track.case.method')},
{id: 'nodePath', label: i18n.t('test_track.case.module')},
{id: 'projectName', label: i18n.t('test_track.review.review_project')},
{id: 'reviewerName', label: i18n.t('test_track.review.reviewer')},
@ -107,7 +106,7 @@ export const Test_Plan_Function_Test_Case = [
//测试计划-api用例
export const Test_Plan_Api_Case = [
{id: 'num', label: i18n.t('commons.id')},
{id: 'name', label: i18n.t('commons.name')},
{id: 'name', label: i18n.t('api_test.definition.api_name')},
{id: 'priority', label: i18n.t('test_track.case.priority')},
{id: 'path', label: i18n.t('api_test.definition.api_path')},
{id: 'createUser', label: '创建人'},
@ -124,12 +123,12 @@ export const Test_Plan_Load_Case = [
{id: 'createTime', label: i18n.t('commons.create_time')},
{id: 'status', label: i18n.t('commons.status')},
{id: 'caseStatus', label: i18n.t('test_track.plan.load_case.execution_status')},
{id: 'loadReportId', label: i18n.t('test_track.plan.load_case.view_report')},
{id: 'loadReportId', label: i18n.t('test_track.plan.load_case.report')},
]
//测试计划-场景用例
export const Test_Plan_Scenario_Case = [
{id: 'num', label: i18n.t('commons.id')},
{id: 'name', label: i18n.t('commons.name')},
{id: 'name', label: i18n.t('api_test.automation.scenario_name')},
{id: 'level', label: i18n.t('api_test.automation.case_level')},
{id: 'tagNames', label: i18n.t('api_test.automation.tag')},
{id: 'userId', label: i18n.t('api_test.automation.creator')},

View File

@ -152,10 +152,13 @@
return JSON.stringify(this.data).indexOf(this.obj.children) !== -1 ? this.data : this.switchTree();
},
},
mounted() {
this.init();
},
methods: {
outsideClick(e) {
e.stopPropagation();
this.isShowSelect=false;
this.isShowSelect = false;
},
init() {
if (this.defaultKey != undefined && this.defaultKey.length > 0) {
@ -233,7 +236,9 @@
setKey(thisKey) {
this.$refs.tree.setCurrentKey(thisKey);
let node = this.$refs.tree.getNode(thisKey);
this.setData(node.data);
if (node && node.data) {
this.setData(node.data);
}
},
//
setData(data) {
@ -284,7 +289,7 @@
},
//
popoverHide() {
this.$emit('getValue', this.returnDataKeys, this.returnDatas);
this.$emit('getValue', this.returnDataKeys, this.returnDatas ? this.returnDatas : {});
},
//
clearSelectedNodes() {
@ -340,10 +345,13 @@
// select
this.$refs.select.blur();
},
treeData() {//tree
this.$nextTick(() => {
this.init();
})
treeData: {//tree
handler: function () {
this.$nextTick(() => {
this.init();
})
},
deep: true
},
filterText(val) {
this.$nextTick(() => {

View File

@ -0,0 +1,40 @@
<template>
<ms-container>
<ms-main-container>
<el-card>
</el-card>
</ms-main-container>
</ms-container>
</template>
<script>
import MsContainer from "@/business/components/common/components/MsContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
export default {
name: "PerformanceReportCompare",
components: {MsMainContainer, MsContainer},
mounted() {
let reportId = this.$route.path.split('/')[4];
console.log(reportId);
let items = localStorage.getItem("compareReportIds");
console.log(JSON.parse(items));
},
computed: {
isReadOnly() {
return !checkoutTestManagerOrTestUser();
}
},
data() {
return {}
},
methods: {}
}
</script>
<style scoped>
</style>

View File

@ -28,10 +28,6 @@
<el-button :disabled="isReadOnly" type="warning" plain size="mini" @click="downloadJtl()">
{{ $t('report.downloadJtl') }}
</el-button>
<!--<el-button :disabled="isReadOnly" type="warning" plain size="mini">-->
<!--{{$t('report.compare')}}-->
<!--</el-button>-->
</el-row>
</el-col>
<el-col :span="8">
@ -83,6 +79,7 @@
</div>
</el-dialog>
</ms-main-container>
<same-test-reports ref="compareReports"/>
</ms-container>
</template>
@ -99,11 +96,13 @@ import {checkoutTestManagerOrTestUser, exportPdf} from "@/common/js/utils";
import html2canvas from 'html2canvas';
import MsPerformanceReportExport from "./PerformanceReportExport";
import {Message} from "element-ui";
import SameTestReports from "@/business/components/performance/report/components/SameTestReports";
export default {
name: "PerformanceReportView",
components: {
SameTestReports,
MsPerformanceReportExport,
MsReportErrorLog,
MsReportLogDetails,
@ -312,6 +311,9 @@ export default {
Message.error({message: JSON.parse(data).message || e.message, showClose: true});
});
});
},
compareReports() {
this.$refs.compareReports.open(this.report);
}
},
created() {

View File

@ -25,37 +25,32 @@
<el-table-column
prop="name"
:label="$t('commons.name')"
width="150"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="testName"
:label="$t('report.test_name')"
width="150"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="projectName"
:label="$t('report.project_name')"
width="150"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="userName"
:label="$t('report.user_name')"
width="150"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="createTime"
sortable
width="250"
:label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="triggerMode" width="150" :label="'触发方式'" column-key="triggerMode"
<el-table-column prop="triggerMode" width="150" :label="$t('test_track.report.list.trigger_mode')" column-key="triggerMode"
:filters="triggerFilters">
<template v-slot:default="scope">
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
@ -85,6 +80,7 @@
:total="total"/>
</el-card>
</ms-main-container>
<same-test-reports ref="compareReports"/>
</ms-container>
</template>
@ -101,11 +97,14 @@ import MsTableHeader from "../../common/components/MsTableHeader";
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
import ShowMoreBtn from "../../track/case/components/ShowMoreBtn";
import {_filter, _sort} from "@/common/js/tableUtils";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import SameTestReports from "@/business/components/performance/report/components/SameTestReports";
export default {
name: "PerformanceTestReportList",
components: {
SameTestReports,
MsDialogFooter,
MsTableHeader,
ReportTriggerModeItem,
MsTableOperatorButton,
@ -200,6 +199,9 @@ export default {
}
});
},
handleDiff(report) {
this.$refs.compareReports.open(report);
},
_handleDeleteNoMsg(report) {
this.result = this.$post(this.deletePath + report.id, {}, () => {
this.initTableData();

View File

@ -1,24 +1,32 @@
<template>
<div v-loading="result.loading" class="pressure-config-container">
<el-row>
<el-col :span="10">
<el-col :span="12">
<el-collapse v-model="activeNames" accordion>
<el-collapse-item :name="index"
v-for="(threadGroup, index) in threadGroups.filter(th=>th.enabled === 'true' && th.deleted=='false')"
:key="index">
<template slot="title">
<div style="padding-right: 10px">
{{ threadGroup.attributes.testname }}
</div>
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'DURATION'">
{{ $t('load_test.thread_num') }}{{ threadGroup.threadNumber }},
{{ $t('load_test.duration') }}: {{ threadGroup.duration }} {{ getUnitLabel(threadGroup) }}
</el-tag>
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'ITERATION'">
{{ $t('load_test.thread_num') }} {{ threadGroup.threadNumber }},
{{ $t('load_test.iterate_num') }} {{ threadGroup.iterateNum }}
</el-tag>
<el-row>
<el-col :span="14">
<el-tooltip :content="threadGroup.attributes.testname" placement="top">
<div style="padding-right:20px; font-size: 16px;" class="wordwrap">
{{ threadGroup.attributes.testname }}
</div>
</el-tooltip>
</el-col>
<el-col :span="10">
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'DURATION'">
{{ $t('load_test.thread_num') }}{{ threadGroup.threadNumber }},
{{ $t('load_test.duration') }}: {{ threadGroup.duration }} {{ getUnitLabel(threadGroup) }}
</el-tag>
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'ITERATION'">
{{ $t('load_test.thread_num') }} {{ threadGroup.threadNumber }},
{{ $t('load_test.iterate_num') }} {{ threadGroup.iterateNum }}
</el-tag>
</el-col>
</el-row>
</template>
<el-form :inline="true">
<el-form-item :label="$t('load_test.thread_num')">
@ -134,7 +142,7 @@
</el-collapse-item>
</el-collapse>
</el-col>
<el-col :span="14">
<el-col :span="12">
<ms-chart class="chart-container" ref="chart1" :options="options" :autoresize="true"></ms-chart>
</el-col>
</el-row>
@ -578,4 +586,9 @@ export default {
.title {
margin-left: 60px;
}
.wordwrap {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -37,7 +37,7 @@
<el-table-column
prop="ko"
label="KO"
label="FAIL"
align="center"
/>

View File

@ -0,0 +1,117 @@
<template>
<el-dialog :close-on-click-modal="false"
:destroy-on-close="true"
:title="$t('已完成的测试报告')" width="60%"
:visible.sync="loadReportVisible">
<el-table v-loading="reportLoadingResult.loading"
class="basic-config"
:data="compareReports"
@select-all="handleSelectAll"
@select="handleSelectionChange">
<el-table-column type="selection"/>
<el-table-column
prop="name"
:label="$t('commons.name')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="userName"
:label="$t('report.user_name')"
show-overflow-tooltip>
</el-table-column>
<el-table-column prop="triggerMode"
:label="$t('test_track.report.list.trigger_mode')">
<template v-slot:default="scope">
<report-trigger-mode-item :trigger-mode="scope.row.triggerMode"/>
</template>
</el-table-column>
<el-table-column
:label="$t('commons.create_time')">
<template v-slot:default="scope">
<i class="el-icon-time"/>
<span class="last-modified">{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="getCompareReports" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleCompare"/>
</template>
</el-dialog>
</template>
<script>
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import ReportTriggerModeItem from "@/business/components/common/tableItem/ReportTriggerModeItem";
export default {
name: "SameTestReports",
components: {ReportTriggerModeItem, MsDialogFooter, MsTablePagination},
data() {
return {
loadReportVisible: false,
reportLoadingResult: {},
compareReports: [],
currentPage: 1,
pageSize: 10,
total: 0,
selectIds: new Set,
}
},
methods: {
open(report) {
this.getCompareReports(report.testId);
this.loadReportVisible = true;
},
close() {
this.loadReportVisible = false;
},
getCompareReports(testId) {
let condition = {
testId: testId,
filters: {status: ["Completed"]}
};
this.reportLoadingResult = this.$post('/performance/report/list/all/' + this.currentPage + "/" + this.pageSize, condition, res => {
let data = res.data;
this.total = data.itemCount;
this.compareReports = data.listObject;
})
},
handleCompare() {
let reportIds = [...this.selectIds];
localStorage.setItem("compareReportIds", JSON.stringify(reportIds));
this.close();
this.$router.push({path: '/performance/report/compare/' + reportIds[0]});
},
handleSelectAll(selection) {
if (selection.length > 0) {
this.compareReports.forEach(item => {
this.selectIds.add(item.id);
});
} else {
this.compareReports.forEach(item => {
if (this.selectIds.has(item.id)) {
this.selectIds.delete(item.id);
}
});
}
},
handleSelectionChange(selection, row) {
if (this.selectIds.has(row.id)) {
this.selectIds.delete(row.id);
} else {
this.selectIds.add(row.id);
}
},
}
}
</script>
<style scoped>
</style>

View File

@ -1,5 +1,3 @@
import MsProject from "@/business/components/settings/project/MsProject";
const PerformanceTest = () => import('@/business/components/performance/PerformanceTest')
const PerformanceTestHome = () => import('@/business/components/performance/home/PerformanceTestHome')
const EditPerformanceTest = () => import('@/business/components/performance/test/EditPerformanceTest')
@ -7,6 +5,7 @@ const PerformanceTestList = () => import('@/business/components/performance/test
const PerformanceTestReportList = () => import('@/business/components/performance/report/PerformanceTestReportList')
const PerformanceChart = () => import('@/business/components/performance/report/components/PerformanceChart')
const PerformanceReportView = () => import('@/business/components/performance/report/PerformanceReportView')
const PerformanceReportCompare = () => import('@/business/components/performance/report/PerformanceReportCompare')
export default {
path: "/performance",
@ -62,6 +61,11 @@ export default {
path: "report/view/:reportId",
name: "perReportView",
component: PerformanceReportView
}
},
{
path: "report/compare/:reportId",
name: "ReportCompare",
component: PerformanceReportCompare,
},
]
}

View File

@ -157,7 +157,7 @@ export default {
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
if (this.tableData.filter(f => f.name === row.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
this.$error(this.$t('load_test.delete_file') + ', name: ' + row.name);
this.selectIds.clear();
return;
}
@ -216,7 +216,7 @@ export default {
}
if (this.tableData.filter(f => f.name === file.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
this.$error(this.$t('load_test.delete_file') + ', name: ' + file.name);
return false;
}
},

View File

@ -121,7 +121,7 @@ export default {
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
if (this.tableData.filter(f => f.name === row.name + ".jmx").length > 0) {
this.$error(this.$t('load_test.delete_file'));
this.$error(this.$t('load_test.delete_file') + ', name: ' + row.name);
return;
}
}

View File

@ -4,7 +4,7 @@
<el-col>
<el-form :inline="true">
<el-form-item :label="$t('load_test.select_resource_pool')">
<el-select v-model="resourcePool" :disabled="isReadOnly" size="mini">
<el-select v-model="resourcePool" :disabled="isReadOnly" size="mini" @change="resourcePoolChange">
<el-option
v-for="item in resourcePools"
:key="item.id"
@ -23,17 +23,25 @@
v-for="(threadGroup, index) in threadGroups.filter(th=>th.enabled === 'true' && th.deleted=='false')"
:key="index">
<template slot="title">
<div style="padding-right: 20px; font-size: 16px;">
{{ threadGroup.attributes.testname }}
</div>
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'DURATION'">
{{ $t('load_test.thread_num') }}{{ threadGroup.threadNumber }},
{{ $t('load_test.duration') }}: {{ threadGroup.duration }} {{ getUnitLabel(threadGroup) }}
</el-tag>
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'ITERATION'">
{{ $t('load_test.thread_num') }} {{ threadGroup.threadNumber }},
{{$t('load_test.iterate_num')}} {{threadGroup.iterateNum}}
</el-tag>
<el-row>
<el-col :span="14">
<el-tooltip :content="threadGroup.attributes.testname" placement="top">
<div style="padding-right:20px; font-size: 16px;" class="wordwrap">
{{ threadGroup.attributes.testname }}
</div>
</el-tooltip>
</el-col>
<el-col :span="10">
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'DURATION'">
{{ $t('load_test.thread_num') }}{{ threadGroup.threadNumber }},
{{ $t('load_test.duration') }}: {{ threadGroup.duration }} {{ getUnitLabel(threadGroup) }}
</el-tag>
<el-tag type="primary" size="mini" v-if="threadGroup.threadType === 'ITERATION'">
{{ $t('load_test.thread_num') }} {{ threadGroup.threadNumber }},
{{ $t('load_test.iterate_num') }} {{ threadGroup.iterateNum }}
</el-tag>
</el-col>
</el-row>
</template>
<el-form :inline="true">
<el-form-item :label="$t('load_test.thread_num')">
@ -42,6 +50,7 @@
v-model="threadGroup.threadNumber"
@change="calculateTotalChart(threadGroup)"
:min="resourcePoolResourceLength"
:max="maxThreadNumbers"
size="mini"/>
</el-form-item>
<br>
@ -109,6 +118,7 @@
<el-input-number
:disabled="isReadOnly"
:min="1"
:max="threadGroup.duration"
v-model="threadGroup.rampUpTime"
@change="calculateTotalChart(threadGroup)"
size="mini"/>
@ -219,7 +229,8 @@ export default {
resourcePools: [],
activeNames: ["0"],
threadGroups: [],
resourcePoolResourceLength: 1
resourcePoolResourceLength: 1,
maxThreadNumbers: 5000,
}
},
mounted() {
@ -336,6 +347,22 @@ export default {
});
}
},
resourcePoolChange() {
let result = this.resourcePools.filter(p => p.id === this.resourcePool);
if (result.length === 1) {
let threadNumber = 0;
result[0].resources.forEach(resource => {
threadNumber += JSON.parse(resource.configuration).maxConcurrency;
})
this.maxThreadNumbers = threadNumber;
this.threadGroups.forEach(tg => {
if (tg.threadNumber > threadNumber) {
this.$set(tg, "threadNumber", threadNumber);
}
})
this.calculateTotalChart();
}
},
calculateTotalChart() {
let handler = this;
if (handler.duration < handler.rampUpTime) {
@ -344,6 +371,11 @@ export default {
if (handler.rampUpTime < handler.step) {
handler.step = handler.rampUpTime;
}
// 线
let resourcePool = this.resourcePools.filter(v => v.id === this.resourcePool)[0];
if (resourcePool) {
this.resourcePoolResourceLength = resourcePool.resources.length;
}
let color = ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'];
handler.options = {
color: color,
@ -649,7 +681,7 @@ export default {
border-bottom: 1px solid #DCDFE6;
}
/deep/ .el-collapse-item__content{
/deep/ .el-collapse-item__content {
padding-left: 10px;
padding-bottom: 5px;
border-left-width: 8px;
@ -682,4 +714,10 @@ export default {
.title {
margin-left: 60px;
}
.wordwrap {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@ -216,8 +216,36 @@
})
}
this.total = data.itemCount;
this.$nextTick(function(){
this.checkTableRowIsSelect();
});
})
},
checkTableRowIsSelect(){
//
if(this.condition.selectAll){
let unSelectIds = this.condition.unSelectIds;
this.tableData.forEach(row=>{
if(unSelectIds.indexOf(row.id)<0){
this.$refs.userTable.toggleRowSelection(row,true);
//selectRows
if (!this.selectRows.has(row)) {
this.$set(row, "showMore", true);
this.selectRows.add(row);
}
}else{
//selectRow
if (this.selectRows.has(row)) {
this.$set(row, "showMore", false);
this.selectRows.delete(row);
}
}
})
}
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},

View File

@ -598,7 +598,7 @@ export default {
return;
}
this.selectRows = new Set();
this.condition.selectAll = false;
// this.condition.selectAll = false;
this.result = this.$post(this.buildPagePath(this.queryPath), this.condition, response => {
let data = response.data;
this.total = data.itemCount;
@ -615,8 +615,38 @@ export default {
});
}
}
this.$nextTick(function(){
this.checkTableRowIsSelect();
});
})
},
checkTableRowIsSelect(){
//
if(this.condition.selectAll){
let unSelectIds = this.condition.unSelectIds;
this.tableData.forEach(row=>{
if(unSelectIds.indexOf(row.id)<0){
this.$refs.userTable.toggleRowSelection(row,true);
//selectRows
if (!this.selectRows.has(row)) {
this.$set(row, "showMore", true);
this.selectRows.add(row);
}
}else{
//selectRow
if (this.selectRows.has(row)) {
this.$set(row, "showMore", false);
this.selectRows.delete(row);
}
}
})
}
},
handleClose() {
this.form = {roles: [{id: ''}]};
this.btnAddRole = false;

Some files were not shown because too many files have changed in this diff Show More