diff --git a/backend/pom.xml b/backend/pom.xml index 55853f97ac..be89a74d2e 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -17,7 +17,7 @@ UTF-8 1.6.0 1.8 - 5.2.1 + 5.4.1 1.1.3 2.7.8 20.1.0 @@ -276,18 +276,11 @@ spring-boot-starter-data-ldap - - - io.swagger - swagger-parser - 1.0.51 - - - + io.swagger.parser.v3 swagger-parser - 2.0.18 + 2.0.22 @@ -674,4 +667,4 @@ - \ No newline at end of file + diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java index b1141201f5..02f882e070 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiAutomationController.java @@ -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 ids) { apiAutomationService.removeToGc(ids); } + @PostMapping("/removeToGcByBatch") + public void removeToGcByBatch(@RequestBody ApiScenarioBatchRequest request) { + apiAutomationService.removeToGcByBatch(request); + } + @PostMapping("/reduction") public void reduction(@RequestBody List ids) { apiAutomationService.reduction(ids); diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java b/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java index 3affea3472..9a3fe96e37 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java @@ -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 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> 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); + } } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/EsbDataStruct.java b/backend/src/main/java/io/metersphere/api/dto/automation/EsbDataStruct.java index 49f30fdfcd..c15ddce336 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/EsbDataStruct.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/EsbDataStruct.java @@ -29,6 +29,7 @@ public class EsbDataStruct { private boolean required; private String description; private List 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) { diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java index 914ef1eef0..852b3b9755 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java @@ -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 { @@ -132,12 +134,68 @@ public class MsJmeterParser extends ApiImportAbstractParser { 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() {{ + this.add(new KeyValue()); + }}); + samplerProxy.setArguments(new ArrayList() {{ + 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 keyValues = new LinkedList<>(); for (HTTPFileArg arg : source.getHTTPFiles()) { @@ -156,13 +214,15 @@ public class MsJmeterParser extends ApiImportAbstractParser { 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 keyValues = new LinkedList<>(); source.getArguments().getArgumentsAsMap().forEach((k, v) -> { @@ -173,11 +233,12 @@ public class MsJmeterParser extends ApiImportAbstractParser { 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"); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/MsDefinitionParser.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/MsDefinitionParser.java index db0a5bf459..ce76eddb03 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/MsDefinitionParser.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/MsDefinitionParser.java @@ -58,14 +58,16 @@ public class MsDefinitionParser extends MsAbstractParser { private ApiDefinitionImport parseMsFormat(String testStr, ApiTestImportRequest importRequest) { ApiDefinitionImport apiDefinitionImport = JSON.parseObject(testStr, ApiDefinitionImport.class); Map> caseMap = new HashMap<>(); - apiDefinitionImport.getCases().forEach(item -> { - List 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 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); }); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger2Parser.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger2Parser.java index c51b92ac47..a830b1d656 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger2Parser.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/Swagger2Parser.java @@ -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()))); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java index 7a3ec79943..aa9e8d7a9f 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsScenario.java @@ -114,23 +114,26 @@ public class MsScenario extends MsTestElement { // 设置共享cookie config.setEnableCookieShare(enableCookieShare); Map 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); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java index 5fa9333809..a7563bf58a 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java @@ -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()); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsIfController.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsIfController.java index 61867a6079..753e1685ba 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsIfController.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/controller/MsIfController.java @@ -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; diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java index 907e0170fb..2d88dde7f5 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java @@ -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 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; } - } + diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java index d1b8fe7b55..4ae022bfec 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java @@ -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; + private List backEsbDataStruct; + @Override public void toHashTree(HashTree tree, List hashTree, ParameterConfig config) { // 非导出操作,且不是启用状态则跳过执行 diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java index f3f484b747..4ae0e7d279 100644 --- a/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java @@ -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); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index c916f6f956..fa14afa7fa 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -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 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; diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 4b5b664562..36e51b9489 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -90,6 +90,8 @@ public class ApiAutomationService { @Resource @Lazy private TestPlanScenarioCaseService testPlanScenarioCaseService; + @Resource + private EsbApiParamService esbApiParamService; public List 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 bodyUploadIds = request.getBodyUploadIds(); @@ -205,6 +210,9 @@ public class ApiAutomationService { List 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 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()); + } } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java index 9be4f831ac..e2631f175c 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionExecResultService.java @@ -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); }); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java index c984f0880e..c9d1c62f6f 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java @@ -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 sameRequest, ApiDefinitionMapper batchMapper, ApiDefinitionWithBLOBs apiDefinition, ApiTestCaseMapper apiTestCaseMapper, ApiTestImportRequest apiTestImportRequest, List 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 exiteModelList = esbApiParamsMapper.selectByExampleWithBLOBs(example); - if(exiteModelList.isEmpty()){ + if (exiteModelList.isEmpty()) { esbApiParamsMapper.insert(model); - }else{ + } else { model.setId(exiteModelList.get(0).getId()); esbApiParamsMapper.updateByPrimaryKeyWithBLOBs(model); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java b/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java index 01ab101153..5a161efdc6 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDocumentService.java @@ -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 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> 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 previewObjMap = new LinkedHashMap<>(); - for (int i = 0; i < kvsArr.size(); i++) { - JSONObject kv = kvsArr.getJSONObject(i); - if (kv.containsKey("description") && kv.containsKey("files")) { - Map 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 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> bodyParamList = new ArrayList<>(); + JSONArray kvsArr = bodyObj.getJSONArray("binary"); - previewObjMap.put(String.valueOf(name), String.valueOf(value)); + Map previewObjMap = new LinkedHashMap<>(); + for (int i = 0; i < kvsArr.size(); i++) { + JSONObject kv = kvsArr.getJSONObject(i); + if (kv.containsKey("description") && kv.containsKey("files")) { + Map 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> 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 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> 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 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); diff --git a/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java b/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java index adb9a5f556..43064ccb39 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiScenarioReportService.java @@ -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(); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java index 12730c5308..aa7f0724a4 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java @@ -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 { diff --git a/backend/src/main/java/io/metersphere/api/service/EsbApiParamService.java b/backend/src/main/java/io/metersphere/api/service/EsbApiParamService.java index 06ae6dd45f..60e557d4bb 100644 --- a/backend/src/main/java/io/metersphere/api/service/EsbApiParamService.java +++ b/backend/src/main/java/io/metersphere/api/service/EsbApiParamService.java @@ -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 genKeyValueListByDataStruct(MsTCPSampler tcpSampler, List dataStructRequestList) { + List keyValueList = new ArrayList<>(); + String sendRequest = tcpSampler.getRequest(); + String paramRegexStr = "\\$\\{([^}]*)\\}"; + try { + if (StringUtils.isNotEmpty(sendRequest)) { + List 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 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 keyValueList = this.genKeyValueListByDataStruct(tcpSampler, tcpSampler.getEsbDataStruct()); + tcpSampler.setParameters(keyValueList); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + public void deleteByResourceIdIn(List apiIds) { EsbApiParamsExample example = new EsbApiParamsExample(); example.createCriteria().andResourceIdIn(apiIds); esbApiParamsMapper.deleteByExample(example); } + public void checkScenarioRequests(SaveApiScenarioRequest request) { + if(request.getScenarioDefinition() != null ){ + List hashTreeList = request.getScenarioDefinition().getHashTree(); + for (MsTestElement testElement :hashTreeList) { + if(testElement instanceof MsTCPSampler){ + this.handleEsbRequest((MsTCPSampler)testElement); + } + } + } + } } diff --git a/backend/src/main/java/io/metersphere/api/service/HistoricalDataUpgradeService.java b/backend/src/main/java/io/metersphere/api/service/HistoricalDataUpgradeService.java index a9e25176e5..ea15c56062 100644 --- a/backend/src/main/java/io/metersphere/api/service/HistoricalDataUpgradeService.java +++ b/backend/src/main/java/io/metersphere/api/service/HistoricalDataUpgradeService.java @@ -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 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 listSteps = new LinkedList<>(); List 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 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); } diff --git a/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java b/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java index 8c58c01094..49fe510c41 100644 --- a/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java +++ b/backend/src/main/java/io/metersphere/base/domain/TestCaseWithBLOBs.java @@ -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; } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.java index 8fd7e161be..d38d6be6ab 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.java @@ -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 countByProjectIdGroupByExecuteResult(String projectId); List selectLastReportByIds(@Param("scenarioIdList") List ids); + + ApiScenarioReport selectPreviousReportByScenarioId(@Param("scenarioId") String scenarioId, @Param("nowId") String nowId); + } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.xml index 5a552d03e3..93463a6929 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioReportMapper.xml @@ -216,4 +216,9 @@ ) orderData ON orderData.id = report.id; + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportMapper.xml index d89c770d7c..de1170833c 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportMapper.xml @@ -91,6 +91,9 @@ AND project.id = #{reportRequest.projectId,jdbcType=VARCHAR} + + AND ltr.test_id = #{reportRequest.testId,jdbcType=VARCHAR} + diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml index b23321eba5..6930ce6025 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml @@ -274,7 +274,7 @@ #{value} - + and test_case.review_status in #{value} diff --git a/backend/src/main/java/io/metersphere/commons/user/SessionUser.java b/backend/src/main/java/io/metersphere/commons/user/SessionUser.java index 25c4785ff3..04cd5e0ded 100644 --- a/backend/src/main/java/io/metersphere/commons/user/SessionUser.java +++ b/backend/src/main/java/io/metersphere/commons/user/SessionUser.java @@ -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 infos = Arrays.asList(user.getId(), RandomStringUtils.random(6), "" + System.currentTimeMillis()); + List infos = Arrays.asList(user.getId(), RandomStringUtils.randomAlphabetic(6), SessionUtils.getSessionId(), "" + System.currentTimeMillis()); sessionUser.csrfToken = CodingUtil.aesEncrypt(StringUtils.join(infos, "|"), secret, iv); return sessionUser; } diff --git a/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java b/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java index b7ba547f1c..636053ef08 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java @@ -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 sessions = sessionManager.getSessionDAO().getActiveSessions(); diff --git a/backend/src/main/java/io/metersphere/config/KafkaProperties.java b/backend/src/main/java/io/metersphere/config/KafkaProperties.java index 59c24022af..d158b64fcc 100644 --- a/backend/src/main/java/io/metersphere/config/KafkaProperties.java +++ b/backend/src/main/java/io/metersphere/config/KafkaProperties.java @@ -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; diff --git a/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java b/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java index d4cb6ce786..b47c168904 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java +++ b/backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java @@ -9,5 +9,7 @@ public class ExcelResponse { private Boolean success; private List> errList; + private Boolean isUpdated; //是否有更新过用例 + } diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java index 161486e0fc..099c9b462f 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java @@ -8,6 +8,8 @@ import lombok.Setter; @Setter public class TestCaseExcelData { + @ExcelIgnore + private Integer num; @ExcelIgnore private String name; @ExcelIgnore diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java index 218a59e01e..6cc4b5fca4 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataCn.java @@ -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("用例名称") diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java index d60c24fa2a..aeea86ca0f 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataTw.java @@ -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("用例名稱") diff --git a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java index 6916c3a5ec..c56260b523 100644 --- a/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java +++ b/backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelDataUs.java @@ -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") diff --git a/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java b/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java index 9397a20ef9..ad588dc8cd 100644 --- a/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java +++ b/backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java @@ -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 { private String projectId; + protected List updateList = new ArrayList<>(); //存储待更新用例的集合 + + protected boolean isUpdated = false; //判断是否更新过用例,将会传给前端 + Set testCaseNames; Set userIds; + public boolean isUpdated() { + return isUpdated; + } + public TestCaseDataListener(Class clazz, String projectId, Set testCaseNames, Set userIds) { this.clazz = clazz; this.testCaseService = (TestCaseService) CommonBeanFactory.getBean("testCaseService"); @@ -39,12 +51,15 @@ public class TestCaseDataListener extends EasyExcelListener { 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 { // 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 { @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 result = list.stream() + .map(item -> this.convert2TestCase(item)) + .collect(Collectors.toList()); + testCaseService.saveImportData(result, projectId); + } - List result = list.stream() - .map(item -> this.convert2TestCase(item)) - .collect(Collectors.toList()); - - testCaseService.saveImportData(result, projectId); + if (!(updateList.size() == 0)) { + List 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 { 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 { 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(); + } + } } diff --git a/backend/src/main/java/io/metersphere/performance/controller/request/ReportRequest.java b/backend/src/main/java/io/metersphere/performance/controller/request/ReportRequest.java index fa6b697f59..dac96b1de8 100644 --- a/backend/src/main/java/io/metersphere/performance/controller/request/ReportRequest.java +++ b/backend/src/main/java/io/metersphere/performance/controller/request/ReportRequest.java @@ -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 orders; private Map> filters; diff --git a/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java b/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java index e7f6fa41e3..3adbc2cb47 100644 --- a/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java +++ b/backend/src/main/java/io/metersphere/performance/engine/docker/DockerTestEngine.java @@ -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."); } }); } diff --git a/backend/src/main/java/io/metersphere/security/CsrfFilter.java b/backend/src/main/java/io/metersphere/security/CsrfFilter.java index 59442dc61d..56b2e74a0c 100644 --- a/backend/src/main/java/io/metersphere/security/CsrfFilter.java +++ b/backend/src/main/java/io/metersphere/security/CsrfFilter.java @@ -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."); + } } } diff --git a/backend/src/main/java/io/metersphere/service/NodeResourcePoolService.java b/backend/src/main/java/io/metersphere/service/NodeResourcePoolService.java index 2531d644c7..190cf81486 100644 --- a/backend/src/main/java/io/metersphere/service/NodeResourcePoolService.java +++ b/backend/src/main/java/io/metersphere/service/NodeResourcePoolService.java @@ -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 entity = restTemplateWithTimeOut.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), String.class); - return HttpStatus.OK.equals(entity.getStatusCode()); + ResponseEntity 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) { diff --git a/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java b/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java index b359daf3af..1747f0a3c5 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestCaseController.java @@ -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 files) { - testCaseService.edit(request, files); + public String editTestCase(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file") List files) { + return testCaseService.edit(request, files); } @PostMapping("/delete/{testCaseId}") diff --git a/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java b/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java index 35c79d3b2c..68736be502 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestPlanController.java @@ -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}") diff --git a/backend/src/main/java/io/metersphere/track/controller/TestPlanReportController.java b/backend/src/main/java/io/metersphere/track/controller/TestPlanReportController.java index 9c60aea017..aa72e6f0cd 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestPlanReportController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestPlanReportController.java @@ -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"; } diff --git a/backend/src/main/java/io/metersphere/track/request/report/TestPlanReportSaveRequest.java b/backend/src/main/java/io/metersphere/track/request/report/TestPlanReportSaveRequest.java new file mode 100644 index 0000000000..54c60f37ef --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/request/report/TestPlanReportSaveRequest.java @@ -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; + } +} diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java index 3a6d74178e..3582052a02 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java @@ -331,20 +331,6 @@ public class TestCaseReviewService { } public void testReviewRelevance(ReviewRelevanceRequest request) { - String reviewId = request.getReviewId(); - List 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 testCaseIds = request.getTestCaseIds(); if (testCaseIds.isEmpty()) { diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java index 0829f73e25..1ed153236f 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java @@ -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 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 testCases, String projectId) { + Map nodePathMap = testCaseNodeService.createNodeByTestCases(testCases, projectId); + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class); + + /* + 获取用例的“网页上所显示id”与“数据库ID”映射。 + */ + List nums = testCases.stream() + .map(TestCase::getNum) + .collect(Collectors.toList()); + TestCaseExample example = new TestCaseExample(); + example.createCriteria().andNumIn(nums) + .andProjectIdEqualTo(projectId); + List testCasesList = testCaseMapper.selectByExample(example); + Map 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); diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java index df789f41ad..ad98f6fb27 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java @@ -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(); + + } } diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java index 6a1f028330..3d044fcb22 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanReportService.java @@ -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 apiCaseIdList = testPlanApiCaseMapper.selectByExample(apiExample) - .stream().map(TestPlanApiCase::getApiCaseId).collect(Collectors.toList()); - TestPlanApiScenarioExample example = new TestPlanApiScenarioExample(); - example.createCriteria().andTestPlanIdEqualTo(planId); - List scenarioIdList = testPlanScenarioCaseMapper.selectByExample(example) - .stream().map(TestPlanApiScenario::getApiScenarioId).collect(Collectors.toList()); - - LoadCaseRequest loadCaseRequest = new LoadCaseRequest(); - loadCaseRequest.setTestPlanId(planId); - loadCaseRequest.setProjectId(testPlan.getProjectId()); - List 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 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 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 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 testPlanReportIdList, String runMode,String triggerMode) { + public synchronized void updateReport(List 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 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 = 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 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 performaneReportIDList,String triggerMode) { + public void updatePerformanceInfo(TestPlanReport testPlanReport, List performaneReportIDList, String triggerMode) { TestPlanReportDataExample example = new TestPlanReportDataExample(); example.createCriteria().andTestPlanReportIdEqualTo(testPlanReport.getId()); List reportDataList = testPlanReportDataMapper.selectByExampleWithBLOBs(example); @@ -462,36 +474,36 @@ public class TestPlanReportService { List updatePerformaneReportIDList = new ArrayList<>(performaneReportIDList); executorService.submit(() -> { //错误数据检查集合。 如果错误数据出现超过20次,则取消该条数据的检查 - Map errorDataCheckMap = new HashMap<>(); - while (performaneReportIDList.size()>0) { + Map errorDataCheckMap = new HashMap<>(); + while (performaneReportIDList.size() > 0) { List 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 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 testPlanReportIdList) { diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java index e7b0f49a3d..7d9c4047ac 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java @@ -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> testPlanScenarioIdMap = request.getTestPlanScenarioIDMap(); + for (Map.Entry> entry : testPlanScenarioIdMap.entrySet()) { - String testPlanID = entry.getKey(); Map planScenarioIdMap = entry.getValue(); List 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 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> 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 performaneReportIDList = new ArrayList<>(); for (Map.Entry 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 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> 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); + } } } diff --git a/frontend/src/business/components/api/automation/ApiAutomation.vue b/frontend/src/business/components/api/automation/ApiAutomation.vue index 4548ab4f3a..11dc10b0f9 100644 --- a/frontend/src/business/components/api/automation/ApiAutomation.vue +++ b/frontend/src/business/components/api/automation/ApiAutomation.vue @@ -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'); diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index e71ae9f2fb..a3b7e1afe8 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -69,7 +69,8 @@ diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue index 34d27cba63..fc3266b5bc 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioModule.vue @@ -16,32 +16,9 @@ ref="nodeTree"> @@ -52,7 +29,7 @@ @refresh="refresh" ref="basisScenario"/> - + @@ -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); diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index d5c143a998..8978fd056a 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -22,9 +22,7 @@ - - - + @@ -201,7 +199,8 @@ @closePage="close" @unFullScreen="unFullScreen" @showAllBtn="showAllBtn" @runDebug="runDebug" @setProjectEnvMap="setProjectEnvMap" @showScenarioParameters="showScenarioParameters" @setCookieShare="setCookieShare" ref="maximizeHeader"/> - + @@ -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) { // 如果自身没有ID并且场景有ID则赋值场景ID,否则赋值当前项目ID 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, diff --git a/frontend/src/business/components/api/automation/scenario/EnvPopover.vue b/frontend/src/business/components/api/automation/scenario/EnvPopover.vue index f17b968995..bc7296c026 100644 --- a/frontend/src/business/components/api/automation/scenario/EnvPopover.vue +++ b/frontend/src/business/components/api/automation/scenario/EnvPopover.vue @@ -3,6 +3,7 @@ v-model="visible" placement="bottom" width="400" + :disabled="isReadOnly" @show="showPopover" trigger="click"> - + {{ $t('api_test.environment.environment_config') }}
@@ -246,6 +246,9 @@ }, active(item) { item.active = !item.active; + if (this.node) { + this.node.expanded = item.active; + } this.reload(); }, changeRadio() { diff --git a/frontend/src/business/components/api/automation/scenario/component/StepExtendBtns.vue b/frontend/src/business/components/api/automation/scenario/component/StepExtendBtns.vue index d028acef6b..273121e660 100644 --- a/frontend/src/business/components/api/automation/scenario/component/StepExtendBtns.vue +++ b/frontend/src/business/components/api/automation/scenario/component/StepExtendBtns.vue @@ -5,7 +5,7 @@ - 复制步骤 + 删除步骤 查看场景变量 打开场景 @@ -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(); diff --git a/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue b/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue index 65559c659d..bd670d739f 100644 --- a/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/maximize/MaximizeScenario.vue @@ -50,39 +50,41 @@
- - - - -
-
- + +
+ + + +
+
+ +
@@ -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); } diff --git a/frontend/src/business/components/api/definition/ApiDefinition.vue b/frontend/src/business/components/api/definition/ApiDefinition.vue index 374014bcca..fdf74a00a1 100644 --- a/frontend/src/business/components/api/definition/ApiDefinition.vue +++ b/frontend/src/business/components/api/definition/ApiDefinition.vue @@ -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() { diff --git a/frontend/src/business/components/api/definition/components/ApiConfig.vue b/frontend/src/business/components/api/definition/components/ApiConfig.vue index b3adcaf920..ed57810030 100644 --- a/frontend/src/business/components/api/definition/components/ApiConfig.vue +++ b/frontend/src/business/components/api/definition/components/ApiConfig.vue @@ -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); diff --git a/frontend/src/business/components/api/definition/components/Run.vue b/frontend/src/business/components/api/definition/components/Run.vue index 4245075e55..734419b84f 100644 --- a/frontend/src/business/components/api/definition/components/Run.vue +++ b/frontend/src/business/components/api/definition/components/Run.vue @@ -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; diff --git a/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue b/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue index af12c60f6b..59e5afaa62 100644 --- a/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue +++ b/frontend/src/business/components/api/definition/components/case/ApiCaseItem.vue @@ -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, () => { diff --git a/frontend/src/business/components/api/definition/components/case/MsEnvironmentSelect.vue b/frontend/src/business/components/api/definition/components/case/MsEnvironmentSelect.vue index cdc1046818..9c3ca59f9f 100644 --- a/frontend/src/business/components/api/definition/components/case/MsEnvironmentSelect.vue +++ b/frontend/src/business/components/api/definition/components/case/MsEnvironmentSelect.vue @@ -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) { diff --git a/frontend/src/business/components/api/definition/components/complete/BasisApi.vue b/frontend/src/business/components/api/definition/components/complete/BasisApi.vue index b9f96ddcf1..dece21bd2b 100644 --- a/frontend/src/business/components/api/definition/components/complete/BasisApi.vue +++ b/frontend/src/business/components/api/definition/components/complete/BasisApi.vue @@ -10,20 +10,7 @@ - -
- -
-
- -
- {{ $t('api_test.definition.select_comp.no_data') }}, - - {{ $t('api_test.definition.select_comp.add_data') }} -
-
-
-
+
@@ -73,70 +60,79 @@ diff --git a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue index 53f89a01de..444e701fde 100644 --- a/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue +++ b/frontend/src/business/components/api/definition/components/list/ApiCaseSimpleList.vue @@ -1,9 +1,11 @@ diff --git a/frontend/src/business/components/api/definition/model/NodeTree.js b/frontend/src/business/components/api/definition/model/NodeTree.js index ab714f0a0d..694972537a 100644 --- a/frontend/src/business/components/api/definition/model/NodeTree.js +++ b/frontend/src/business/components/api/definition/model/NodeTree.js @@ -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}); + } + } +} diff --git a/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue b/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue index c534d2bd60..3c22c4b94f 100644 --- a/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue +++ b/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue @@ -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 diff --git a/frontend/src/business/components/common/components/MsModuleMinder.vue b/frontend/src/business/components/common/components/MsModuleMinder.vue index e13ea1f363..f0b4d03f19 100644 --- a/frontend/src/business/components/common/components/MsModuleMinder.vue +++ b/frontend/src/business/components/common/components/MsModuleMinder.vue @@ -155,7 +155,7 @@ export default { width: 100%; background: white; height: 100vh; - z-index: 999999; + z-index: 2; } .full-screen >>> .minder-container { diff --git a/frontend/src/business/components/common/components/search/MsSearchBar.vue b/frontend/src/business/components/common/components/search/MsSearchBar.vue new file mode 100644 index 0000000000..43188da64f --- /dev/null +++ b/frontend/src/business/components/common/components/search/MsSearchBar.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/frontend/src/business/components/common/head/HeaderCustom.vue b/frontend/src/business/components/common/head/HeaderCustom.vue index 99bb68f0ae..bd8f9ad28a 100644 --- a/frontend/src/business/components/common/head/HeaderCustom.vue +++ b/frontend/src/business/components/common/head/HeaderCustom.vue @@ -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 = { diff --git a/frontend/src/business/components/common/model/JsonData.js b/frontend/src/business/components/common/model/JsonData.js index 57c5bc5b9a..f064c52d91 100644 --- a/frontend/src/business/components/common/model/JsonData.js +++ b/frontend/src/business/components/common/model/JsonData.js @@ -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')}, diff --git a/frontend/src/business/components/common/select-tree/SelectTree.vue b/frontend/src/business/components/common/select-tree/SelectTree.vue index 945c3bd35d..903273af67 100644 --- a/frontend/src/business/components/common/select-tree/SelectTree.vue +++ b/frontend/src/business/components/common/select-tree/SelectTree.vue @@ -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(() => { diff --git a/frontend/src/business/components/performance/report/PerformanceReportCompare.vue b/frontend/src/business/components/performance/report/PerformanceReportCompare.vue new file mode 100644 index 0000000000..3193c0c973 --- /dev/null +++ b/frontend/src/business/components/performance/report/PerformanceReportCompare.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/src/business/components/performance/report/PerformanceReportView.vue b/frontend/src/business/components/performance/report/PerformanceReportView.vue index 13a512d91e..9758cddeed 100644 --- a/frontend/src/business/components/performance/report/PerformanceReportView.vue +++ b/frontend/src/business/components/performance/report/PerformanceReportView.vue @@ -28,10 +28,6 @@ {{ $t('report.downloadJtl') }} - - - - @@ -83,6 +79,7 @@
+ @@ -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() { diff --git a/frontend/src/business/components/performance/report/PerformanceTestReportList.vue b/frontend/src/business/components/performance/report/PerformanceTestReportList.vue index 02e4a052df..9f5faffcc8 100644 --- a/frontend/src/business/components/performance/report/PerformanceTestReportList.vue +++ b/frontend/src/business/components/performance/report/PerformanceTestReportList.vue @@ -25,37 +25,32 @@ - @@ -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(); diff --git a/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue b/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue index ee9ebb5d74..769fb87c8c 100644 --- a/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue +++ b/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue @@ -1,24 +1,32 @@ - + @@ -161,7 +161,7 @@ import TestPlanApiCaseResult from "./TestPlanApiCaseResult"; import TestPlan from "../../../../../api/definition/components/jmeter/components/test-plan"; import ThreadGroup from "../../../../../api/definition/components/jmeter/components/thread-group"; import {TEST_PLAN_API_CASE, WORKSPACE_ID} from "@/common/js/constants"; -import {_filter, _sort, getLabel, getSystemLabel} from "@/common/js/tableUtils"; +import {_filter, _sort, getLabel} from "@/common/js/tableUtils"; import HeaderCustom from "@/business/components/common/head/HeaderCustom"; import {Test_Plan_Api_Case} from "@/business/components/common/model/JsonData"; import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate"; @@ -268,7 +268,6 @@ export default { created: function () { this.getMaintainerOptions(); this.initTable(); - getSystemLabel(this, this.type) }, activated() { @@ -304,8 +303,6 @@ export default { }, methods: { customHeader() { - getLabel(this, TEST_PLAN_API_CASE); - this.$refs.headerCustom.open(this.tableLabel) }, getMaintainerOptions() { @@ -524,6 +521,9 @@ export default { }); }, autoCheckStatus() { // 检查执行结果,自动更新计划状态 + if (!this.planId) { + return; + } this.$post('/test/plan/autoCheck/' + this.planId, (response) => { }); }, diff --git a/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue b/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue index f997aba38f..e223cdaefa 100644 --- a/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiScenarioList.vue @@ -109,7 +109,7 @@ import MsTableMoreBtn from "../../../../../api/automation/scenario/TableMoreBtn" import MsScenarioExtendButtons from "@/business/components/api/automation/scenario/ScenarioExtendBtns"; import MsTestPlanList from "../../../../../api/automation/scenario/testplan/TestPlanList"; import TestPlanScenarioListHeader from "./TestPlanScenarioListHeader"; -import {_handleSelect, _handleSelectAll, getLabel, getSystemLabel} from "../../../../../../../common/js/tableUtils"; +import {_handleSelect, _handleSelectAll, getLabel} from "../../../../../../../common/js/tableUtils"; import MsTableOperatorButton from "../../../../../common/components/MsTableOperatorButton"; import HeaderCustom from "@/business/components/common/head/HeaderCustom"; import {TEST_CASE_LIST, TEST_PLAN_SCENARIO_CASE} from "@/common/js/constants"; @@ -188,7 +188,6 @@ export default { }, created() { this.search(); - getSystemLabel(this, this.type) }, watch: { @@ -201,8 +200,6 @@ export default { }, methods: { customHeader() { - getLabel(this, TEST_PLAN_SCENARIO_CASE); - this.$refs.headerCustom.open(this.tableLabel) }, search() { diff --git a/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue b/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue index b3e25ac688..0404e4db21 100644 --- a/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseList.vue @@ -296,7 +296,7 @@ import BatchEdit from "../../../../case/components/BatchEdit"; import ClassicEditor from "@ckeditor/ckeditor5-build-classic"; import {hub} from "@/business/components/track/plan/event-bus"; import MsTag from "@/business/components/common/components/MsTag"; -import {_filter, _sort, getLabel, getSystemLabel} from "@/common/js/tableUtils"; +import {_filter, _sort, getLabel} from "@/common/js/tableUtils"; import HeaderCustom from "@/business/components/common/head/HeaderCustom"; import {Test_Plan_Function_Test_Case} from "@/business/components/common/model/JsonData"; import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate"; @@ -424,12 +424,8 @@ export default { beforeDestroy() { hub.$off("openFailureTestCase"); }, - created() { - getSystemLabel(this, this.type) - }, methods: { customHeader() { - getLabel(this, TEST_PLAN_FUNCTION_TEST_CASE); this.$refs.headerCustom.open(this.tableLabel) }, @@ -477,13 +473,19 @@ export default { } this.selectRows.clear(); if (this.$refs.table) { - this.$refs.table.doLayout() + setTimeout(() => { + this.$refs.table.doLayout(); + this.result.loading = false; + }, 500) } }); } getLabel(this, TEST_PLAN_FUNCTION_TEST_CASE); }, autoCheckStatus() { + if (!this.planId) { + return; + } this.$post('/test/plan/autoCheck/' + this.planId, (response) => { }); }, diff --git a/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue b/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue index a6ecaf0e0b..d292a02dd6 100644 --- a/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/load/TestPlanLoadCaseList.vue @@ -100,7 +100,7 @@ - + @@ -131,7 +131,7 @@ import MsTablePagination from "@/business/components/common/pagination/TablePagi import MsPerformanceTestStatus from "@/business/components/performance/test/PerformanceTestStatus"; import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton"; import LoadCaseReport from "@/business/components/track/plan/view/comonents/load/LoadCaseReport"; -import {_filter, _sort, getLabel, getSystemLabel} from "@/common/js/tableUtils"; +import {_filter, _sort, getLabel} from "@/common/js/tableUtils"; import HeaderCustom from "@/business/components/common/head/HeaderCustom"; import {TEST_CASE_LIST, TEST_PLAN_LOAD_CASE} from "@/common/js/constants"; import {Test_Plan_Load_Case, Track_Test_Case} from "@/business/components/common/model/JsonData"; @@ -198,7 +198,7 @@ export default { created() { this.initTable(); this.refreshStatus(); - getSystemLabel(this, this.type) + }, watch: { @@ -211,7 +211,6 @@ export default { }, methods: { customHeader() { - getLabel(this, TEST_PLAN_LOAD_CASE); this.$refs.headerCustom.open(this.tableLabel) }, initTable() { @@ -251,6 +250,9 @@ export default { }, autoCheckStatus() { + if (!this.planId) { + return; + } this.$post('/test/plan/autoCheck/' + this.planId, (response) => { }); }, diff --git a/frontend/src/business/components/track/review/components/TestCaseReviewList.vue b/frontend/src/business/components/track/review/components/TestCaseReviewList.vue index 4fb47c4b4a..9ebc55dc1b 100644 --- a/frontend/src/business/components/track/review/components/TestCaseReviewList.vue +++ b/frontend/src/business/components/track/review/components/TestCaseReviewList.vue @@ -122,7 +122,7 @@ import { checkoutTestManagerOrTestUser, getCurrentWorkspaceId } from "../../../../../common/js/utils"; -import {_filter, _sort, getLabel, getSystemLabel} from "@/common/js/tableUtils"; +import {_filter, _sort, getLabel} from "@/common/js/tableUtils"; import PlanStatusTableItem from "../../common/tableItems/plan/PlanStatusTableItem"; import {Test_Case_Review} from "@/business/components/common/model/JsonData"; import {TEST_CASE_LIST, TEST_CASE_REVIEW_LIST} from "@/common/js/constants"; @@ -173,7 +173,6 @@ export default { }, created() { this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser(); - getSystemLabel(this, this.type) this.initTableData(); }, computed: { @@ -183,7 +182,6 @@ export default { }, methods: { customHeader() { - getLabel(this, TEST_CASE_REVIEW_LIST); this.$refs.headerCustom.open(this.tableLabel) }, diff --git a/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue b/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue index 93d56b4f63..b369020f08 100644 --- a/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue +++ b/frontend/src/business/components/track/review/view/components/TestReviewTestCaseList.vue @@ -202,7 +202,7 @@ import { } from "../../../../../../common/js/constants"; import TestReviewTestCaseEdit from "./TestReviewTestCaseEdit"; import ReviewStatus from "@/business/components/track/case/components/ReviewStatus"; -import {_filter, _sort, getLabel, getSystemLabel} from "@/common/js/tableUtils"; +import {_filter, _sort, getLabel} from "@/common/js/tableUtils"; import HeaderCustom from "@/business/components/common/head/HeaderCustom"; import {Test_Case_Review_Case_List, Track_Test_Case} from "@/business/components/common/model/JsonData"; import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate"; @@ -296,12 +296,8 @@ export default { this.refreshTableAndReview(); this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser(); }, - created() { - getSystemLabel(this, this.type) - }, methods: { customHeader() { - getLabel(this, TEST_CASE_REVIEW_CASE_LIST); this.$refs.headerCustom.open(this.tableLabel) }, initTableData() { diff --git a/frontend/src/common/js/tableUtils.js b/frontend/src/common/js/tableUtils.js index 1980090164..4d5c536279 100644 --- a/frontend/src/common/js/tableUtils.js +++ b/frontend/src/common/js/tableUtils.js @@ -43,7 +43,6 @@ export function setUnSelectIds(tableData, condition, selectRows) { condition.unSelectIds = allIDs.filter(function (val) { return ids.indexOf(val) === -1 }); - } export function getSelectDataCounts(condition, total, selectRows) { @@ -102,9 +101,11 @@ export function _sort(column, condition) { } } -export function initCondition(condition) { - condition.selectAll = false; - condition.unSelectIds = []; +export function initCondition(condition,isSelectAll) { + if(!isSelectAll){ + condition.selectAll = false; + condition.unSelectIds = []; + } } export function getLabel(vueObj, type) { @@ -114,15 +115,14 @@ export function getLabel(vueObj, type) { vueObj.result = vueObj.$post('/system/header/info', param, response => { if (response.data != null) { vueObj.tableLabel = eval(response.data.props); - } - }) -} -export function getSystemLabel(vueObj, type) { - let param = {} - param.type=type - vueObj.result = vueObj.$post('/system/system/header',param, response => { - if (response.data != null) { - vueObj.tableLabel = eval(response.data.props); + }else{ + let param = {} + param.type=type + vueObj.result = vueObj.$post('/system/system/header',param, response => { + if (response.data != null) { + vueObj.tableLabel = eval(response.data.props); + } + }) } }) } diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index a563e26c77..94837b728c 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -5,6 +5,7 @@ export default { pass_rate: 'Pass rate', execution_times: 'Execution times', cover: 'Cover', + module_title: 'Default module', not_cover: 'Not Cover', import: 'Import', import_success: 'Import success', @@ -145,6 +146,7 @@ export default { auth_redirect_tip: 'Jump to the authentication source page for authentication', tag_tip: "Enter Enter to Add Label", node_name_tip: "The name cannot contain'\\'", + more_operator: "More operator", table: { select_tip: "Item {0} data is selected" }, @@ -280,7 +282,7 @@ export default { none: 'None Organization', select: 'Select Organization', service_integration: 'Service integration', - defect_manage: 'Defect management platform', + defect_manage: 'Project management platform', message_settings: 'Message settings', message: { jenkins_task_notification: 'Jenkins task notification', @@ -541,7 +543,10 @@ export default { threadgroup_at_least_one: 'At least one ThreadGroup is enabled', load_api_automation_jmx: 'Import API automation scenario', project_file_exist: "The file already exists in the project, please import it directly", - project_file_update_type_error: 'Updated file types must be consistent' + project_file_update_type_error: 'Updated file types must be consistent', + report: { + diff: "Compare" + }, }, api_test: { creator: "Creator", diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 8c5bcc9afc..251d71ac48 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -5,6 +5,7 @@ export default { pass_rate: '通过率', execution_times: '执行次数', cover: '覆盖', + module_title: '默认模块', not_cover: '不覆盖', import: '导入', import_success: '导入成功', @@ -146,6 +147,7 @@ export default { auth_redirect_tip: '即将跳转到认证源页面进行认证', tag_tip: "输入回车添加标签", node_name_tip: "名称不能包含'\\'", + more_operator: "更多操作", table: { select_tip: "已选中 {0} 条数据" }, @@ -281,7 +283,7 @@ export default { select: '选择组织', delete_warning: '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?', service_integration: '服务集成', - defect_manage: '缺陷管理平台', + defect_manage: '项目管理平台', message_settings: '消息设置', message: { jenkins_task_notification: 'Jenkins接口调用任务通知', @@ -438,7 +440,7 @@ export default { export: '导出', export_to_ms_format: '导出 MeterSphere 格式', export_to_swagger3_format: '导出 Swagger3.0 格式', - compare: '比较', + compare: '报告对比', generation_error: '报告生成错误, 无法查看, 请检查日志详情!', being_generated: '报告正在生成中...', delete_confirm: '确认删除报告: ', @@ -540,7 +542,10 @@ export default { threadgroup_at_least_one: '至少启用一个线程组', load_api_automation_jmx: '引用接口自动化场景', project_file_exist: "项目中已存在该文件,请直接引用", - project_file_update_type_error: '更新的文件类型必须一致' + report: { + diff: "对比" + }, + project_file_update_type_error: '更新的文件类型必须一致', }, api_test: { creator: "创建人", @@ -1193,7 +1198,7 @@ export default { batch_delete_case: '批量删除', batch_unlink: '批量取消关联', project_name: '所属项目', - status: '评审状态', + status: '评审结果', status_prepare: '未评审', status_pass: '通过', status_un_pass: '未通过', diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 61aaa62a43..e9f8dddcd4 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -5,6 +5,7 @@ export default { pass_rate: '通過率', execution_times: '執行次數', cover: '覆蓋', + module_title: '默認模塊', not_cover: '不覆蓋', import: '導入', import_success: '導入成功', @@ -146,6 +147,7 @@ export default { auth_redirect_tip: '即將跳轉到認證源頁面進行認證', tag_tip: "輸入回車添加標簽", node_name_tip: "名稱不能包含'\\'", + more_operator: "更多操作", table: { select_tip: "已选中 {0} 条数据" }, @@ -281,7 +283,7 @@ export default { select: '選擇組織', delete_warning: '刪除該組織將同步刪除該組織下所有相關工作空間和相關工作空間下的所有項目,以及項目中的所有用例、接口測試、性能測試等,確定要刪除嗎?', service_integration: '服務集成', - defect_manage: '缺陷管理平臺', + defect_manage: '項目管理平臺', message_settings: '消息設置', message: { jenkins_task_notification: 'Jenkins接口調用任務通知', @@ -438,7 +440,7 @@ export default { export: '導出', export_to_ms_format: '導出 MeterSphere 格式', export_to_swagger3_format: '導出 Swagger3.0 格式', - compare: '比較', + compare: '報告比較', generation_error: '報告生成錯誤, 無法查看, 請檢查日誌詳情!', being_generated: '報告正在生成中...', delete_confirm: '確認刪除報告: ', @@ -540,7 +542,10 @@ export default { threadgroup_at_least_one: '至少啟用一個線程組', load_api_automation_jmx: '引用接口自動化場景', project_file_exist: "項目中已存在該文件,請直接引用", - project_file_update_type_error: '更新的文件類型必須一致' + project_file_update_type_error: '更新的文件類型必須一致', + report: { + diff: "對比" + }, }, api_test: { creator: "創建人",